# CLAUDE.md — REBNO Project Instructions

## Project

**REBNO** — ground-up rebuild of *BN Online* (a late-1990s/early-2000s GameMaker 5.3a multiplayer game) on a modern web stack. Target: Chrome users walk around and chat in real time, then full feature parity with the original.

**Core Value:** Original BN Online players can open Chrome, log in with their migrated account, walk around, and chat with each other in real time — bug-free.

## Source-of-Truth Files

Everything depends on two files in `legacy/open-source-release/`:

- `BN Online Client 5-8.gmd` — latest client revision
- `BN Online Master 5-4.gmd` — latest server revision

Older revisions in `legacy/source-archive/` and `legacy/servers/` are reference-only. Do not target them as primary inputs.

## Repo Layout

| Path | Purpose |
|---|---|
| `decomp/wiki/` | 17-doc reverse-engineering knowledge base for GameMaker 5.3a — read these before reasoning about extraction, the `.gmd` format, the 39dll wire protocol, or `.bno`/`.bnb`/`.bnu` save data |
| `legacy/open-source-release/` | Authoritative source `.gmd` files (the two listed above) |
| `legacy/source-archive/`, `legacy/servers/` | Older revisions — reference only |
| `legacy/unity-project/`, `legacy/maya-project/` | Prior abandoned remake attempts and original Maya assets — informative, not authoritative |
| `legacy/audio/`, `legacy/open-source-release-extras/` | Original audio + assorted artifacts |
| `.planning/` | GSD workflow state — see below |
| `.planning/codebase/` | 7 docs mapping the existing brownfield codebase |
| `.planning/research/` | 5 docs of project-level research (STACK, FEATURES, ARCHITECTURE, PITFALLS, SUMMARY) |

## Tech Stack (decided)

- **Server**: Node 22 + TypeScript + Colyseus 0.17 + `ws` + Better-Auth + argon2id + better-sqlite3 + Litestream
- **Client**: TypeScript + Vite — engine = Phaser 3 (preferred) OR PixiJS (fallback). Final choice locked by ADR at end of Phase 2.
- **Transport**: WebSocket binary frames; authoritative server; clients send intent
- **Persistence**: SQLite + Litestream → Tigris bucket on Fly.io. Postgres only if Phase 3 surfaces a relational requirement.
- **Hosting**: Fly.io single machine, persistent volume, <50 CCU target
- **Deploy**: **Operator-local builds + direct `flyctl deploy`** as of 06.7-09 (2026-05-17). GitHub Actions storage quota exhausted; the prior `.github/workflows/deploy-staging.yml` full-path AND fast-path workflows are out of service. Playwright post-deploy smoke is dropped. Full procedure: [`docs/deploy/LOCAL-DEPLOY.md`](docs/deploy/LOCAL-DEPLOY.md). [doc->REQ-DEP-04]
- **Monorepo**: pnpm workspaces. Shared packages: `protocol` (wire types + codec) and `game-logic` (pure deterministic simulation, runs in Node + browser identically).

See `.planning/research/STACK.md` for version pins, alternatives matrix, and Fly.io specifics.

## Extracted Constants (load-bearing — never assume otherwise)

| Constant | Value | Source of truth |
|---|---|---|
| Floor tile dimensions | **44 × 40 px** (W × H) | `extracted/client-5-8/sprites/0023-Tile1/meta.json` |
| Room tick rate | 30 steps/sec | `extracted/client-5-8/rooms/*/meta.json` `speed: 30` |
| View / camera viewport | 640 × 480 px | `extracted/client-5-8/rooms/*/meta.json` `views[0].viewW/viewH` |
| BNCentral room dimensions | 8000 × 6400 px | `extracted/client-5-8/rooms/0058-BNCentral/meta.json` |
| MVP synthetic room (20×20 tiles) | 880 × 800 px | derived: 20·44 × 20·40 |

**NEVER hard-code 32×32 or any other tile size.** GameMaker's editor defaults are misleading; BNO's tile pitch is 44×40. Every renderer, collision grid, asset-pipeline atlas slicer, and test-room generator must read these from extracted `meta.json` or use the constants above. See [`docs/extracted-engine/scene-room-model.md`](docs/extracted-engine/scene-room-model.md) "Canonical floor tile" for full derivation.

## Coordinate Conventions (D-63 — Plan 06.4)

The legacy GameMaker engine and our Phaser renderer use different sprite-origin
conventions. Player-attached effects (TeleIn, TeleOut, HexportIn/Out, ncol*,
jokershell, watching) must convert between them.

| Engine | Navi sprite origin | Effect placement |
|--------|---------------------|------------------|
| Legacy GM (5.3a) | `(0, 0)` = top-left (head) | effect drawn at legacy `(x, y)` = Navi top-left |
| Phaser (REBNO)   | `(0.5, 1)` = bottom-center (feet) | sprite.x = Navi center, sprite.y = visible feet line (NOT sprite-rect bottom) |

**Convention shift pin:**
- `legacy_x = phaser_x - NAVI_WIDTH_PX/2 = phaser_x - 18`
- `legacy_y = phaser_y - NAVI_VISIBLE_FEET_Y = phaser_y - 46`

`NAVI_VISIBLE_FEET_Y = 46` is the bbox-bottom of NaviStandD, NOT 48 (sprite-rect
height). The two-pixel difference (rows 46-47 in the sprite rect) is the
transparent foot-shadow region below the visible feet — including those rows
in the conversion places effects two pixels too low (operator UAT 2026-05-14).

**D-55c round-2 (06.4 round-2 UAT 2026-05-15 on commit 63b022f) effect-nudge:**
`EFFECT_NUDGE_X_PX = -1`, `EFFECT_NUDGE_Y_PX = +2` are added inside
`phaserOriginForLegacyPlayerAttached`. Phaser origin convention: increasing
originX shifts the rendered sprite LEFT (`sprite-left = sprite.x - originX * width`);
increasing originY shifts the rendered sprite UP. Cycle-5 set
`EFFECT_NUDGE_X_PX = +1` (originX = 34/64) to correct a 1 px right deviation
on staging 5fbb357; round-2 UAT on commit 63b022f reported TeleIn 2 px too
LEFT — i.e., cycle-5 over-corrected. To shift the sprite 2 px RIGHT we
DECREASE originX-numerator by 2: net `EFFECT_NUDGE_X_PX = -1`, originX = 32/64
= 0.5. `EFFECT_NUDGE_Y_PX` stays at +2 (vertical good per operator).
Round-2 TeleIn-variant origins are `[32/64, 90/100] = [0.5, 0.9]`. The bare
`NAVI_WIDTH_PX = 36` / `NAVI_VISIBLE_FEET_Y = 46` constants stay anchored to
the extracted meta.json — drift detection against
`extracted/client-5-8/sprites/0000-NaviStandD/meta.json` continues to assert
those source-of-truth values.

**Canonical entry:** `apps/client/src/render/legacy-origin.ts` exports
`phaserOriginForLegacyPlayerAttached({legacyOriginX, legacyOriginY, width, height}) → [originX, originY]`.
All player-attached effect ports MUST use this helper. Inline origin math in
`apps/client/src/render/` is rejected by code review (HARD gate 5).

**Source of truth:** `extracted/client-5-8/sprites/0000-NaviStandD/meta.json`
(width=36, height=48, bboxBottom=46). The atlas-mvp.json frame metadata is
asserted against these constants by the unit test
`apps/client/src/__test__/legacy-origin.test.ts` (drift-detection — fails CI
if atlas regeneration produces different Navi metrics).

[doc->REQ-CLI-04] [doc->REQ-CLI-08]

## Hard Rules

1. **Server-authoritative (with narrow movement carve-out).** Clients send intent for everything EXCEPT player movement. Server emits state. Never trust client identity, chat origin, inventory, room transitions, or persistence — these are server-authoritative. **Movement (position, velocity, facing, anim_state) is CLIENT-AUTHORITATIVE as of Phase 06.7 — server stores client-reported state and broadcasts to peers.** Anti-cheat (illegal-position fall trigger) is deferred to Phase 06.8. [doc->REQ-SRV-04]
2. **No faithful port of plaintext passwords.** Original `localList.txt` plaintext archive is a security incident, not a feature. argon2id from packet 1.
3. **No faithful port of "run clipboard as superuser" admin.** Original RCE-as-a-feature admin model is replaced by a separate authenticated web UI in Phase 7.
4. **`.bno` / `.bnb` / `.bnu` parsing requires extracted GML first.** These formats only exist as the call order of `file_bin_*` ops in the project's own GML. Do NOT hex-inspect; extract Phase 1, derive Phase 3, parse Phase 4+.
5. **39dll wire protocol = call order.** Same principle. Reverse it from `write*`/`read*` sequences in extracted Master GML, not from packet captures.
6. **Extract → document → rewrite, in that order.** Phases 1-3 produce schemas; Phases 4+ consume them. No new TypeScript before Phase 4.
7. **Modern decompilers (UTMT, Altar.NET) cannot read GM 5.3a.** Use era-appropriate tools: GM Decompiler v2.1, GMD-Recovery, LateralGM, in a WinXP VM. See `decomp/wiki/13-modern-tool-incompat.md`.
8. **Repo stays private through Phase 7.** BNO is Capcom-derived (Mega Man Battle Network). Legal/IP path deferred. Verify `legacy/` is not pushed publicly until cleared.

## GSD Workflow

This project uses the GSD (Get Shit Done) phased-execution system. Workflow state lives in `.planning/`:

- `PROJECT.md` — project context, requirements, constraints, decisions (read first)
- `REQUIREMENTS.md` — 49 v1 requirements with REQ-IDs, traceability table
- `ROADMAP.md` — 7 phases with goals, requirement mapping, success criteria
- `STATE.md` — current phase + status (read to know where work is)
- `config.json` — workflow preferences (mode=yolo, granularity=standard, parallelization=on, model_profile=quality, research=on, plan_check=on, verifier=on)
- `research/` — project-level research outputs (STACK, FEATURES, ARCHITECTURE, PITFALLS, SUMMARY)
- `codebase/` — codebase map (STACK, INTEGRATIONS, ARCHITECTURE, STRUCTURE, CONVENTIONS, TESTING, CONCERNS)

### Workflow Discipline

- **Per-phase flow**: `/gsd-discuss-phase N` → `/gsd-plan-phase N` → `/gsd-execute-phase N` → `/gsd-verify-work N`
- **Atomic commits**: Each phase commits its artifacts immediately. Use `gsd-sdk query commit "msg" <files>`.
- **Phases 1-3 do NOT parallelize with Phase 4+.** Within a phase, parallel plan execution is fine.
- **Hard milestone**: CLI-08 in Phase 6 — two players move + chat over deployed server. Entire rebuild gates here.
- **Research-flagged phases** (1, 3, 7) need deeper per-phase research at planning time.

## Phase Roadmap (high level)

1. **Extraction** — both `.gmd` → diffable per-resource trees (8 reqs)
2. **Client Engine Documentation** — document client engine + Phaser/Pixi ADR (4 reqs)
3. **Server Documentation & Schemas** — reverse 39dll, save formats, persistence ADR, closed parity checklist (6 reqs)
4. **Server Rebuild (MVP)** — Node + Colyseus authoritative server for movement+chat slice (12 reqs)
5. **Deploy** — Fly.io + Litestream + CI/CD + restore runbook (8 reqs)
6. **Client Rebuild — MVP Gate** — Vite+Phaser/Pixi client, **CLI-08 hard milestone** (10 reqs incl. AST-01)
7. **Full Parity** — closed parity checklist resolved + admin UI + asset pipeline (11 reqs)

See `.planning/ROADMAP.md` for full goals, success criteria, and dependencies.

## Conventions

- **Code**: TypeScript everywhere. Strict mode. Shared types via `packages/protocol`.
- **Docs**: Markdown. Per-asset documentation goes under `docs/extracted-engine/` and `docs/extracted-server/` once Phases 2/3 produce them.
- **ADRs**: `docs/adr/NNNN-title.md` for any decision that locks future work.
- **Commits**: Each plan = one commit. Commit messages reference REQ-IDs where applicable.

## Requirements Traceability (traceable-reqs)

The repo uses [traceable-reqs](https://github.com/BigscreenVR/traceable-reqs) for tag-based requirements tracing. The manifest at `traceable-reqs.toml` is authoritative for the 61 v1 requirement IDs in canonical form `REQ-<PREFIX>-<NUM>` (e.g. `REQ-SRV-03`). Human prose in `.planning/REQUIREMENTS.md` and `ROADMAP.md` may continue to use the bare prefix form (`SRV-03`, `CLI-08`) — these are the same IDs.

### Tagging contract — applies to every plan, every agent

When a plan produces an artifact that satisfies a requirement, embed a tag in the artifact:

| Stage  | Where it lives                                | Tag form                  |
|--------|-----------------------------------------------|---------------------------|
| `doc`  | Markdown — prose, ADRs, runbooks              | `[doc->REQ-CDOC-01]`      |
| `impl` | TS / Rust / C / C++ / Python source comments  | `// [impl->REQ-SRV-03]`   |
| `unit` | Unit-test source comments                     | `// [unit->REQ-SRV-03]`   |
| `int`  | Integration / E2E test source comments        | `// [int->REQ-CLI-08]`    |

Rules:

- Tags live **only in comments** (Rust/TS/C/C++/Python) or **on any line** in Markdown.
- Multiple tags on one line are allowed and counted independently: `// [impl->REQ-SRV-03] [impl->REQ-SRV-04]`.
- A tag pointing at an ID not in `traceable-reqs.toml` is an `undeclared_id` finding — fix the typo or add the requirement.
- A bracketed token containing `->` that doesn't exactly match `[stage->REQ-ID]` is a `parse_error` — no internal spaces.
- Default `required_stages = ["doc"]`. Code-bearing requirements add `impl`/`unit` to their `required_stages` at phase plan time, not before.

### Verifying

```bash
pnpm trace:list              # list reqs and stage status
pnpm trace:check             # exit non-zero if any finding
pnpm trace:check:json        # structured JSON for CI / agents
traceable-reqs trace REQ-SRV-03   # drill down on one req
```

Phase verification (`/gsd-verify-work`) treats `missing_stage` for the **phase's reqs** as a verification failure. CI hard-gate (`pnpm trace:check` blocks merge) is deferred to Phase 5 alongside the GitHub Actions pipeline (DEP-04). Until then, run `pnpm trace:check` locally before claiming a phase complete.

### Adding or modifying requirements

1. Add the human row to `.planning/REQUIREMENTS.md` (bare prefix form).
2. Mirror to `traceable-reqs.toml` as `REQ-<PREFIX>-<NUM>` with the same title.
3. Set `required_stages` only for the stages the requirement genuinely needs — start with `doc` and tighten at plan time.
4. Update `ROADMAP.md` mapping if the phase changes.
5. Run `pnpm trace:check` to confirm the manifest parses and IDs match what's tagged.

## Reference

- Reverse-engineering wiki entry point: `decomp/wiki/00-overview.md`
- Extraction pipeline procedure: `decomp/wiki/15-extraction-pipeline.md`
- Wire-protocol notes: `decomp/wiki/08-39dll-networking.md`
- Save-format notes: `decomp/wiki/16-bno-bnb-notes.md`
- Concerns / risks (read before planning): `.planning/codebase/CONCERNS.md`

---
*Generated: 2026-05-02 at project initialization*
