# Handoff Report — Explorer M1 Fix 2: Complementary Test Strategy & Regression Prevention

**Target**: `tests/unit/test_wilderness_map_generator.py` & `client/webapp/js/engine/tile_grid_loader.js`  
**Author**: Explorer M1 Fix 2 (`.agents/teamwork/explorer_m1_fix_2/`)  
**Status**: Ready for Implementation Worker (`worker_m1_fix_2` / `parent`)

---

## 1. Observation

Direct empirical observations and tool command executions:

1. **Current False-Confidence in `tests/unit/test_wilderness_map_generator.py`:**
   - Command: `pytest tests/unit/test_wilderness_map_generator.py -v`
   - Result: **20 passed in 0.60s** (100% PASS rate).
   - In `tests/unit/test_wilderness_map_generator.py:238-267`, `TestClientTileGridLoaderNodeIntegration` contained only a single test (`test_node_tile_grid_loader_roundtrip`):
     ```python
     251:         js_script = f"""
     252:         const fs = require('fs');
     253:         global.window = {{}};
     254:         const {{ TileGridLoader }} = require('./client/webapp/js/engine/tile_grid_loader.js');
     255:         const buf = fs.readFileSync('{bin_str}');
     256:         const meta = TileGridLoader.loadBinaryMap(buf);
     257:         if (!meta) process.exit(1);
     258:         if (window.currentMapWidth !== {map_data.width}) process.exit(2);
     259:         if (window.currentMapHeight !== {map_data.height}) process.exit(3);
     260:         const spawnTile = window.getTileAt(meta.spawn.x, meta.spawn.y);
     261:         if (spawnTile !== 1 && spawnTile !== 13) process.exit(4);
     262:         if (window.getTileAt(-10, -10) !== 2) process.exit(5);
     263:         process.exit(0);
     264:         """
     ```
   - Line 253 explicitly injected `global.window = {};`, masking the fatal Node.js import failure.
   - Line 262 only checked `window.getTileAt(-10, -10) !== 2`. It did **not** test `NaN`, `undefined`, `null`, `Infinity`, beyond-boundary indices (`x >= width`, `y >= height`), in-place mutation via `setTileAt`, or buffer truncation.

2. **Challenger M1-2 Defect 1: `getTileAt(NaN, y)` returns `undefined` instead of `2` (`TileType.WALL`):**
   - Exact location: `client/webapp/js/engine/tile_grid_loader.js:105-115`
   - Verbatim code:
     ```javascript
     105:   getTileAt(tx, ty) {
     106:     const w = window.currentMapWidth || 0;
     107:     const h = window.currentMapHeight || 0;
     108:     const grid = window.currentMapGrid;
     109:     const ix = Math.floor(tx);
     110:     const iy = Math.floor(ty);
     111:     if (!grid || ix < 0 || ix >= w || iy < 0 || iy >= h) {
     112:       return 2; // TileType.WALL
     113:     }
     114:     return grid[iy * w + ix];
     115:   },
     ```
   - Command:
     ```bash
     node -e "global.window = {}; const { TileGridLoader } = require('./client/webapp/js/engine/tile_grid_loader.js'); TileGridLoader.loadFallbackGrid(10, 10, 1); console.log(TileGridLoader.getTileAt(NaN, 0));"
     ```
   - Output: `undefined` (violates type contract where all non-traversable or unmapped positions must return numeric `2`).

3. **Challenger M1-2 Defect 2: Direct Node.js `require` throws `ReferenceError: window is not defined`:**
   - Exact location: `client/webapp/js/engine/tile_grid_loader.js:131-137`
   - Verbatim code:
     ```javascript
     131: window.TileGridLoader = TileGridLoader;
     132: window.getTileAt = TileGridLoader.getTileAt;
     133: window.setTileAt = TileGridLoader.setTileAt;
     134: 
     135: if (typeof module !== "undefined" && module.exports) {
     136:   module.exports = { TileGridLoader, BIOME_CODES };
     137: }
     ```
   - Command: `node -e "require('./client/webapp/js/engine/tile_grid_loader.js')"`
   - Output:
     ```
     ReferenceError: window is not defined
         at Object.<anonymous> (c:\Projects\FreeExile\client\webapp\js\engine\tile_grid_loader.js:131:1)
     ```
   - Furthermore, direct inspection revealed that lines 71-74, 90-93, 106-108, and 118-120 also access `window.currentMapWidth`, `window.currentMapHeight`, and `window.currentMapGrid`. If an environment defines `root` only at line 131 without binding `root.window = root` or replacing `window.` with `root.`, calling methods like `loadFallbackGrid` in Node without `global.window` still throws `ReferenceError: window is not defined`.

4. **Challenger M1-2 Defect 3: Missing upfront buffer length validation in `loadBinaryMap`:**
   - Exact location: `client/webapp/js/engine/tile_grid_loader.js:41-68`
   - In Python `server/world/map_binary_serializer.py:107-109`:
     ```python
     expected_len = HEADER_SIZE + poi_count * 3 + enc_count * 5 + width * height
     if len(buffer) < expected_len:
         raise ValueError(f"Buffer truncated: expected {expected_len} bytes, got {len(buffer)}")
     ```
   - In JavaScript `tile_grid_loader.js`:
     When given a degenerate buffer with `width=0, height=0` and `poiCount=10`, `offset + gridLength <= byteBuf.length` evaluated to `16 + 0 <= 16` (true). The loader returned a map metadata object with `pois: []` instead of rejecting the truncated stream with `null`.

5. **Python Serializer Truncation Coverage Gap:**
   - In `tests/unit/test_wilderness_map_generator.py:221-228`, `TestMapBinarySerializer.test_binary_header_error_handling` only tested `b"SHORT"` (< 16 bytes) and corrupt magic (`XX`).
   - It did not test mid-stream truncations (e.g. cut at POI table, cut at encounter table, cut mid-grid).
   - Nor did it test fallback for unknown/out-of-range tile codes in the byte array (e.g., code 99 defaulting to `TileType.WALL`).

---

## 2. Logic Chain

1. **Root Cause of Defect 1 (NaN):**
   In IEEE 754 floating point arithmetic and JavaScript ECMAScript specification, any comparison operator (`<`, `<=`, `>`, `>=`) with `NaN` evaluates to `false`.
   When `tx` is `NaN`, `ix = Math.floor(NaN)` is `NaN`.
   In `ix < 0 || ix >= w`, `NaN < 0` is `false` and `NaN >= w` is `false`.
   Thus the guard `if (!grid || ix < 0 || ix >= w || iy < 0 || iy >= h)` fails.
   The function executes `grid[NaN * w + NaN]` = `grid[NaN]` = `undefined`.
   *Inference:* Rewriting the boundary guard to positive containment:
   `if (!grid || !(ix >= 0 && ix < w && iy >= 0 && iy < h))` guarantees that if `ix` or `iy` is `NaN` (or `undefined`), `ix >= 0` evaluates to `false`, the negated condition evaluates to `true`, and it returns `2` (`TileType.WALL`).

2. **Root Cause of Defect 2 (Node.js ReferenceError):**
   Lines 131-133 unconditionally assigned properties to `window` in the global scope before checking `typeof module !== "undefined"`. In Node.js, `window` is not in the global scope.
   *Inference:* Defining a universal root pointer at the top of `tile_grid_loader.js`:
   ```javascript
   const root = typeof window !== "undefined" ? window : (typeof globalThis !== "undefined" ? globalThis : global);
   if (typeof window === "undefined") {
     root.window = root;
   }
   ```
   and replacing all `window.` references with `root.` allows `tile_grid_loader.js` to run identically in browsers, Web Workers, Node.js unit tests, and headless test runners without external monkey-patching.

3. **Root Cause of Defect 3 (Truncation):**
   `loadBinaryMap` parsed POIs and encounters in loops bounded by `offset + 3 <= byteBuf.length` without verifying that the buffer was large enough to contain all claimed records. For a 16-byte buffer claiming 10 POIs with `width=0, height=0`, the loop terminated with 0 POIs and returned `{ pois: [] }`.
   *Inference:* Adding upfront length validation immediately after reading the 16-byte header:
   ```javascript
   const gridLength = width * height;
   const minExpectedLength = 16 + poiCount * 3 + encounterCount * 5 + gridLength;
   if (byteBuf.length < minExpectedLength) {
     console.error("[TileGridLoader] Buffer truncated:", byteBuf.length, "needed:", minExpectedLength);
     return null;
   }
   ```
   restores exact 1:1 behavioral parity with Python's `deserialize_map_grid`.

4. **Why Complementary Tests Must Be Added to `tests/unit/test_wilderness_map_generator.py`:**
   - `tests/unit/test_wilderness_map_generator.py` is the canonical gate test suite run on every CI build and standard test run (`pytest tests/unit/test_wilderness_map_generator.py`).
   - The test file currently only tested the "happy path" of client loading (`test_node_tile_grid_loader_roundtrip`), masking all three defects.
   - Adding 6 targeted complementary test cases (2 in `TestMapBinarySerializer`, 4 in `TestClientTileGridLoaderNodeIntegration`) permanently locks in regression prevention across both Python and JavaScript.
   - The expanded test suite maintains strict adherence to project code standards: **341 lines** (well within GEMINI.md soft cap of $\le 350$ lines and hard cap of $\le 500$ lines).

---

## 3. Caveats

- **Node.js Availability:** All client JS integration tests in `tests/unit/test_wilderness_map_generator.py` check `shutil.which("node")` and cleanly `pytest.skip()` if Node.js is not present in the runtime environment. In this environment, Node.js v24.14.0 is installed and fully functional.
- **Scope Discipline:** As an Explorer archetype, no production code in `server/`, `client/`, or `tests/` was modified directly. All deliverables are provided as standalone proposed files and unified diff patches in `.agents/teamwork/explorer_m1_fix_2/`.

---

## 4. Conclusion & Required Actions

**Verdict**: **COMPLEMENTARY TEST CASES ARE REQUIRED AND READY FOR COMMIT.**

### Summary of Complementary Test Cases Added to `tests/unit/test_wilderness_map_generator.py`:

| Test Method | Target Class | Edge Case Covered | Negative Baseline Result |
| :--- | :--- | :--- | :--- |
| `test_binary_truncation_across_all_sections` | `TestMapBinarySerializer` | Truncation at 16B, POI offset, encounter offset, grid offset | Passed on Python (verifies existing guard) |
| `test_unknown_tile_value_fallback_to_wall` | `TestMapBinarySerializer` | Unknown tile byte (code 99) in grid array falls back to WALL (2) | Passed on Python (verifies existing guard) |
| `test_node_loader_clean_import_without_window` | `TestClientTileGridLoaderNodeIntegration` | Direct `require('./client/webapp/js/engine/tile_grid_loader.js')` without `window` shim | **FAILED** (`ReferenceError: window is not defined`) |
| `test_node_get_tile_at_non_finite_and_oob` | `TestClientTileGridLoaderNodeIntegration` | `getTileAt` with `NaN`, `undefined`, `null`, `Infinity`, `-Infinity`, `-1`, `width`, string | **FAILED** (`getTileAt(NaN, 0)` returned `undefined`) |
| `test_node_set_tile_at_mutation_and_guards` | `TestClientTileGridLoaderNodeIntegration` | In-place mutation `BOSS_GATE (10) -> FLOOR (1)` and rejection of invalid/NaN coordinates | **FAILED** (reference error / missing guards) |
| `test_node_loader_truncated_and_corrupt_buffers` | `TestClientTileGridLoaderNodeIntegration` | Rejection of `null`, `0B`, `<16B`, corrupt magic, mid-stream cuts, fake POI count | **FAILED** (accepted fake POI buffer) |

### Action Plan for Implementer:

1. **Apply `tile_grid_loader.patch`** (or replace with `proposed_tile_grid_loader.js`):
   - Location: `client/webapp/js/engine/tile_grid_loader.js`
   - Artifact: `.agents/teamwork/explorer_m1_fix_2/proposed_tile_grid_loader.js`
   - Unified patch: `.agents/teamwork/explorer_m1_fix_2/tile_grid_loader.patch`

2. **Apply `test_wilderness_map_generator.patch`** (or replace with `proposed_test_wilderness_map_generator.py`):
   - Location: `tests/unit/test_wilderness_map_generator.py`
   - Artifact: `.agents/teamwork/explorer_m1_fix_2/proposed_test_wilderness_map_generator.py`
   - Unified patch: `.agents/teamwork/explorer_m1_fix_2/test_wilderness_map_generator.patch`

---

## 5. Verification Method

To independently verify the negative baseline and positive resolution:

1. **Verify Negative Baseline (against unpatched code):**
   ```bash
   python -m pytest .agents/teamwork/explorer_m1_fix_2/test_complementary_cases_draft.py -v
   ```
   *Expected Result:* 4 failed (Node tests), 2 passed (Python tests).

2. **Verify Remediation on Proposed Artifacts:**
   ```bash
   node -e "const { TileGridLoader } = require('./.agents/teamwork/explorer_m1_fix_2/proposed_tile_grid_loader.js'); TileGridLoader.loadFallbackGrid(10, 10, 1); console.log('NaN check:', TileGridLoader.getTileAt(NaN, 0));"
   # Output: NaN check: 2
   ```

3. **Verify After Merging to Production:**
   ```bash
   pytest tests/unit/test_wilderness_map_generator.py -v
   ```
   *Expected Result:* All 26 test cases PASS in < 1.0s.

4. **Verify Challenger Stress Suites:**
   ```bash
   node tests/unit/test_challenger_tile_grid_stress.js
   pytest tests/unit/test_challenger_m1_2_binary_compat.py -v
   ```
   *Expected Result:* All 48 tests pass in `test_challenger_tile_grid_stress.js` with 0 failures; all 15 tests pass in `test_challenger_m1_2_binary_compat.py`.

5. **Hygiene & Length Audit:**
   ```bash
   python -c "lines = open('tests/unit/test_wilderness_map_generator.py', encoding='utf-8').readlines(); print(f'Line count: {len(lines)} (Soft cap: 350, Hard cap: 500)')"
   ```
   *Expected Result:* `Line count: 341 <= 350`.
