# REBNO v1.1 Requirements — Map Groundwork

**Milestone:** v1.1 Map Groundwork
**Date opened:** 2026-05-18
**Total reqs:** 18 (MAP-01..16 + HYG-01..02)

Mirrored in canonical form (`REQ-<PREFIX>-<NUM>`) at `traceable-reqs.toml`.

---

## Map Authoring (MAP)

### Foundation + Loader

- [ ] **MAP-01** — Phaser-side LDtk loader proven on hand-authored `TestBed_001.ldtk` (20×20 BORDERED, 880×800 px per 44×40 extracted constant). Loader routes all entity placements through `apps/client/src/render/legacy-origin.ts`; never reads LDtk `__pivot` for runtime placement. Unit test asserts pivot discard. v1.0 synthetic MVP room code path removed (single source of room truth = LDtk).
- [x] **MAP-02** — Convention ADRs locked before any `.ldtk` file authored:
  - `gridSize` convention (recommended: 4 for IntGrid/Tile floor layers, 1 for Entity layers — accommodates 44×40 non-square pitch)
  - BNCentral GridVania chunk grid (recommended: 4×4 or 2×2 chunks of ≤2000×1600 px each)
  - `tilesetSourceHash` custom-field convention (asserted at loader init as hard error)
  - LDtk version pin in `tools/ldtk-version.txt`

### Conversion (per content category)

- [ ] **MAP-03** — Static (non-interactive, unanimated) floor tiles convertible from `extracted/client-5-8/rooms/*` → `.ldtk`. Tiles preserve 44×40 pitch + atlas tileset references.
- [ ] **MAP-04** — Animated (non-interactive, animated) floor tiles convertible with frame-timing metadata preserved as typed LDtk custom fields (frame indices + per-frame duration). Phaser runtime plays animation by tile species lookup.
- [ ] **MAP-05** — Tiles-with-metadata convertible:
  - DYNAMIC neighbor-rule tiles (Lone/Top/Bottom/Bridge/Corner/Surrounded variants) authored via LDtk auto-tile rules
  - MOVER tiles preserve `dxspeed/dyspeed` from `creationCode` into typed tile metadata
- [ ] **MAP-06** — Interactive tiles convertible with typed fields per type:
  - HEXPORTER: direction enum, station-only flag
  - HEXSLING: enabled-directions enum-set
  - HEXBRIDGE: per-player activation state (runtime, not authored)
  - WARP: destination `EntityRef`, dead-end flag, requires-WrpPlugn flag
  - SPECTRAL GATE: requires-MskPlugn flag
- [ ] **MAP-07** — Generic entities (no per-instance properties) convertible. Object-class identity preserved via `objectId` mapping table.
- [ ] **MAP-08** — Entities-with-metadata convertible — per-instance properties extracted from `creationCode` into typed LDtk custom fields per entity-type. **Initial typed coverage (v1.1):** NPC dialogue lines, platform starting direction + travel distance, teleporter name, messageboard ID. Unmapped entity types: `creationCode` preserved verbatim in opaque `__legacyCreationCode` field + flagged in `conversion-report.json` for follow-up. Lint rule forbids `eval()` of any `__legacyCreationCode` field.

### Converter Tooling

- [ ] **MAP-09** — `tools/room-converter` extended with `ldtk-import` subcommand emitting signed `rooms/<id>/<rev>.{json,sig}` from `.ldtk` sources (reuses existing Ed25519 signing). Per-zone `conversion-report.json` sidecar listing:
  - (a) non-cell-aligned source instances with rounding deltas
  - (b) unmapped entity types needing metadata schema
  - (c) per-objectId mapping decisions
  - (d) tileset hash + LDtk version stamp
  Deterministic round-trip: re-running the importer produces byte-stable `.ldtk` (sort by `instanceId`, mint LDtk UIDs from `hash(roomId, instanceId)`).

### Deploy Pipeline

- [ ] **MAP-10** — `/data/maps/current` symlink-swap deploy via **generalized** `scripts/client-release.sh --kind {client,maps}` (NOT a forked script). New `apps/server/src/maps-dir.ts` mirrors `static-assets.ts` env resolver. `MAPS_DIR=/data/maps/current` wired in `fly.staging.toml`. Atomic `mv -T` swap. Last N=5 releases retention. `/health/maps` endpoint returning hash per zone. `docs/deploy/MAPS-DEPLOY.md` runbook.
- [ ] **MAP-11** — Vite HMR plugin watching `.ldtk` files → Phaser scene reload (dev workflow). Uses `chokidar@4` pinned to match Vite 8 transitive dep.

### In-Game Minimap

- [ ] **MAP-12** — `MinimapHUD` overlay container in GameScene (NOT a separate Phaser Scene; NOT a second camera — would conflict with ADR 0008 Scale.NONE invariant). RenderTexture geometry layer baked ONCE per layout swap (zero per-frame cost for geometry). Local + remote player markers via Colyseus state event subscriptions (never per-frame `forEach`). M-key toggle + `localStorage` persistence. Main camera `ignore()`s the minimap container. Dev-mode perf assert: minimap update path <0.5 ms steady-state.
- [ ] **MAP-13** *(P2 — promote to P1 if scope allows)* — Multi-zone overworld minimap reading LDtk multi-worlds `worlds[].levels[].worldX/worldY` adjacency. Headline LDtk differentiator over Tiled. Phase 11 plan gates promotion on prior-phase margin.

### Runtime Metadata Processors

- [ ] **MAP-16** — Phaser-side runtime metadata processors per entity-type. Initial coverage matches MAP-08:
  - NPC dialogue renderer
  - Platform direction/distance handler
  - Teleporter name lookup
  - Messageboard ID lookup
  Typed schemas in `packages/map-loader` shared with converter for round-trip validity (single source of truth).

### Workflow + UAT

- [ ] **MAP-14** — One real imported zone end-to-end: `Online_Lobby` recommended (smallest world room); `Prairie_Flats` acceptable backup. **NEVER start with BNCentral** (would trip chunking + gridSize + auto-tile + loader simultaneously, making failure non-diagnostic). HUMAN-UAT pass: two real accounts (`dunsen_uat` + `rebbie_uat`), two browsers, movement + chat survive the LDtk-loaded room. Mirrors CLI-08 round at `1f6073e`. Milestone-exit gate.
- [x] **MAP-15** — `pnpm preflight` script substituting missing CI: LDtk version check, atlas hash assertion, conversion regression test, `pnpm trace:check`. Returns non-zero on any failure. Documented as gate before any `flyctl deploy`.

---

## Hygiene Carry-Overs (HYG)

- [x] **HYG-01** — `tooling/no-inline-origin.ts` D-63 audit script. Forbids inline origin math outside `apps/client/src/render/legacy-origin.ts`. Wired into `pnpm preflight`. Catches Pitfall #1 (D-63 bypass) mechanically. Carried from cancelled 06.4-15 plan.
- [x] **HYG-02** — `pnpm trace:check` placeholder sweep. `REQ-SRV-XX` / `REQ-X` illustrative placeholders cleaned from Phase 04 + 06.4 docs. Confirm clean before adding new REQ-MAP-XX / REQ-HYG-XX tags.

---

## Out of Scope (v1.1)

Explicitly excluded with reasoning:

- **Arbitrary GML interpreter at runtime** — never `eval()` `__legacyCreationCode`; lint rule enforces. Belongs in v2+ as typed metadata coverage grows.
- **Fully typed metadata coverage for ALL legacy entity types** — long-tail (rare/one-off entity types). v1.1 covers common known shapes per MAP-08. Unmapped types preserve opaque archive + flagged in conversion-report for v1.2+ extension.
- **Fog-of-war / visited-area persistence on minimap** — requires new schema + Litestream replication + explicit gameplay rule decision. v2+.
- **Bidirectional `.ldtk` → `extracted/` round-trip** — creates competing sources of truth. `.ldtk` is the new authoritative content; `extracted/` is frozen archive.
- **Live in-game map editing** — reimplements LDtk inside the game.
- **Server-side per-tick map mutation API** — competes with file-based git review.
- **Database-backed `.ldtk` storage** — opaque to LDtk editor on re-open; file-based is canonical.
- **Click-to-fast-travel from minimap** — implicates server-side travel rules; out of scope for map-tooling milestone.
- **Map hot-push to connected clients without reload** — conflicts with client-authoritative-movement carve-out (Hard Rule 1 06.7).
- **Auto-deploy on `.ldtk` commit** — GH Actions storage exhausted; v1.1 substitute = `pnpm preflight` + manual `flyctl deploy`.
- **Replicating GameMaker per-instance `depth` as LDtk `F_Int`** — wedges GM mental model into LDtk; use layer stacking instead.
- **Phase 06.8 anti-cheat fall-reset** — separate milestone; `packages/map-loader` will export `deriveWalkable()` ready for 06.8 wire-up.
- **CI restoration** — operator-local `flyctl deploy` continues; revisit when GH Actions storage available.

---

## Traceability

Every v1.1 requirement maps to exactly one phase. Coverage: **18/18** ✓

| Phase | Maps to REQ-IDs |
|-------|------------------|
| 7 Workflow Smoke + Convention Locks | MAP-02, MAP-15, HYG-01, HYG-02 |
| 8 Map-Loader + Phaser LdtkLoader + TestBed_001 | MAP-01 |
| 9 Room-Converter ldtk-import + Runtime Metadata Layer | MAP-03, MAP-04, MAP-05, MAP-06, MAP-07, MAP-08, MAP-09, MAP-16 |
| 10 Map-Deploy Pipeline | MAP-10, MAP-11 |
| 11 MinimapHUD | MAP-12, MAP-13 |
| 12 First Real Imported Zone + Milestone-Exit HUMAN-UAT | MAP-14 |

### Per-requirement status

| REQ | Phase | Status |
|-----|-------|--------|
| MAP-01 | 8 | Pending |
| MAP-02 | 7 | Complete |
| MAP-03 | 9 | Pending |
| MAP-04 | 9 | Pending |
| MAP-05 | 9 | Pending |
| MAP-06 | 9 | Pending |
| MAP-07 | 9 | Pending |
| MAP-08 | 9 | Pending |
| MAP-09 | 9 | Pending |
| MAP-10 | 10 | Pending |
| MAP-11 | 10 | Pending |
| MAP-12 | 11 | Pending |
| MAP-13 | 11 | Pending (P2 — promote on Phase 11 margin) |
| MAP-14 | 12 | Pending |
| MAP-15 | 7 | Complete |
| MAP-16 | 9 | Pending |
| HYG-01 | 7 | Complete |
| HYG-02 | 7 | Complete |
