# Parity feature inventory — Phase 3 plan 05

Working draft / audit trail. The canonical artifact is
`docs/extracted-server/parity-checklist.json`. This file documents the
methodology (per Phase 3 RESEARCH §Pattern 5) and the source-walk that fed
each row into the checklist.

Per D-18, every legacy server feature gets a row classified
disposition ∈ {`in-phase-6`, `in-phase-7`, `deferred-stage-8`,
`rejected-with-reason`}.

## Source walk inputs

1. `docs/extracted-server/protocol.json` — every opcode row (22 s2c rows)
2. `docs/extracted-server/save-formats.json` — every format/grammar row (9 formats)
3. `docs/extracted-server/SUBSYSTEM-MAP.json` — 9 narrative subsystems (+ `misc` partition catch-all)
4. `docs/extracted-server/{account-auth,chat,persistence,world-simulation,room-management,message-board,client-server-bridge,packet-protocol,admin-anti-port}.md` — every H2 heading
5. `docs/extracted-engine/{client-networking,save-load,...}.md` — H2 sections that imply server work
6. `legacy/open-source-release/,ServerCommands.txt` — 6 admin keybinds + chat-line "type+send"
7. `legacy/servers/enlyzeam-current/Ctrl+O Codes.txt` — 2 GML snippets (stash/teleport, hexport-out anim)
8. `.planning/research/FEATURES.md` — feature-landscape inventory + parity-inferred items
9. `.planning/research/PITFALLS.md` §B1 (server-authoritative) + §C7 (parity scope explosion)

## Disposition rules (from RESEARCH disposition rules + D-19)

- `in-phase-6` (~7 rows; CLI-08 MVP slice): movement, chat-public, login,
  login-response, room-join, room-leave, heartbeat
- `in-phase-7` (~25-35): full chat (whispers/channels/ignore), inventory,
  hexbridges, friends list, message board, areas, account recovery,
  multi-room transitions, modernized admin web UI
- `deferred-stage-8` (~5-10): legacy alt-login detection, legacy
  Superweird import, QoL items
- `rejected-with-reason` (~10-15): client-trusted positions/scores/origin
  (B1), Ctrl+E clipboard-RCE, Ctrl+Q kick-via-clipboard, Ctrl+9
  reload-accounts, ,run shell-out, take-a-break.exe execution, plaintext
  password storage as canonical, opcode 12 mod-execute wire path

## Row sources by inventory walk

### From SDOC-01 server subsystems

#### account-auth.md

- `## Login flow (server side, opcode 0)` → row `login` (mvp:true,
  in-phase-6, opcodes=[0]); cite `0359-server_receive.gml` case 0
- s2c login response (opcode 8) → row `login-response` (mvp:true,
  in-phase-6, opcodes=[8])
- `## User-init lifecycle` → row `user-init-lifecycle` (mvp:false,
  in-phase-7) — Phase 4 wraps this in Colyseus `onJoin`/`onLeave`
- `## Session restore from localList.txt` → row `legacy-userlist-import`
  (mvp:false, in-phase-7) — drives SRV-09/10/11 migration; references
  `localList.txt` save-field
- `## Plaintext credentials staging path` → covered by
  `legacy-userlist-import`. The plaintext-storage-as-canonical pattern
  (NOT migration staging) → row `plaintext-passwords-canonical`
  (rejected-with-reason: "CLAUDE.md hard rule #2 — argon2id from packet 1")
- `## Logout` → row `logout` (mvp:false, in-phase-7) — driven by socket
  EOF, originating in opcode 5 broadcast

#### chat.md

- `## Chat-line opcode (4 c2s)` → row `chat-public` (mvp:true,
  in-phase-6, opcodes=[4])
- `## Server-operator command parsing` — splits per-backtick command:
  - `` `goto ocs `` → row `cmd-goto-ocs` (deferred-stage-8 — operator
    nav, irrelevant when admin lives in web UI)
  - `` `run *.exe `` (`take-a-break.exe`) → row `cmd-run-shell-out`
    (rejected-with-reason: "RCE-as-feature CLAUDE.md hard rule #3")
  - `` `end session `` → row `cmd-end-session-broadcast`
    (rejected-with-reason: "Broadcast executable GML over opcode 12
    wire — RCE")
  - `` `command `` → row `cmd-help-text` (deferred-stage-8 — admin help
    is a typed UI surface)
  - `` `clear screen `` → row `cmd-clear-scrollback` (deferred-stage-8)
  - `` `maint `` → row `cmd-maintmode-toggle` (in-phase-7 — modernized
    `maintenance-mode` admin endpoint)
  - `` `servermsg `` (and `` `servermsg <text>``) → row `cmd-servermsg`
    (in-phase-7 — modernized as `set-motd` admin endpoint)
  - `` `clientver `` (and `` `clientver <num>``) → row `cmd-clientver`
    (in-phase-7 — modernized as `set-min-client-version` admin endpoint)
- `## Chat scrollback (server-local)` → row `chat-scrollback-operator`
  (deferred-stage-8 — operator UI lives in web UI)
- `## Area scoping` → row `area-scoping` (in-phase-7 — Phase 6 ships
  per-room scope, Phase 7 layers per-area on top)

#### persistence.md

- `## Per-event write cadence` rows derived per file:
  - `User_Area.bnu` writes → row `user-area-persistence` (in-phase-7)
  - `User_Inv.bnu` writes → row `user-inventory-persistence` (in-phase-7)
  - `User_News.bnu` writes → row `user-news-persistence` (in-phase-7)
  - `User_Hxb.bnu` writes → row `user-hexbridge-persistence` (in-phase-7)
  - `MB_Log.bnb` writes → row `mb-log-persistence` (in-phase-7)
  - `MSettings.bno` writes → row `server-settings-persistence` (in-phase-7)
- `## Scheduled (alarm-driven) write cadence` → row
  `scheduled-save-cadence` (rejected-with-reason: "Per-event SQLite tx
  replaces 30s alarm; crash-loss surface eliminated"; modernized:
  per-tx WAL replication)
- `## Crash-loss surface` documents the gap; rejection captured above

#### world-simulation.md

- `## The tick is the GameMaker step event` → row `tick-loop`
  (mvp:false, in-phase-6 — Colyseus simulation tick replaces it; covered
  by `movement` + `heartbeat`)
- `## Tilemap collision (server-authoritative)` → row
  `tilemap-collision` (in-phase-7 — Phase 6 ships room-bounds clamp
  only)
- `## Per-player state arrays` → row `player-state-broadcast`
  (mvp:false, in-phase-7 — Colyseus state replication subsumes)
- Client-trusted x/y broadcast (opcode 3 inbound) → row
  `client-trusted-position` (rejected-with-reason: "PITFALLS B1
  server-authoritative; server validates delta_t * max_speed and
  collision")
- Client-trusted sprite broadcast (opcode 2 inbound) → row
  `client-trusted-sprite-index` (rejected-with-reason: "PITFALLS B1
  client could spoof animation/state — server tracks intent only")

#### room-management.md

- `## Room change opcode (1 c2s)` → row `room-join` (mvp:true,
  in-phase-6, opcodes=[1])
- `## Area-change vs room-change` → row `area-change-event`
  (in-phase-7 — covered by `area-scoping`)
- `## Persistence flags per area` → row `area-persistence-flags`
  (in-phase-7)
- room-leave (paired w/ room-join when player leaves) → row
  `room-leave` (mvp:true, in-phase-6, opcodes=[1,5] — re-uses room
  change wire, also driven by socket disconnect via opcode 5 logoff)

#### message-board.md

- `## State shape` + `## Topic / reply lifecycle` → 5 distinct rows:
  - `mb-list-boards` (in-phase-7, opcodes=[13])
  - `mb-list-replies` (in-phase-7, opcodes=[14])
  - `mb-summary` (in-phase-7, opcodes=[18])
  - `mb-newmsg-flags` (in-phase-7, opcodes=[19])
  - `mb-post-topic` (in-phase-7) — c2s side absent from current
    protocol.json (s2c-only); cite via `0365-mb_backup.gml`
  - `mb-post-reply` (in-phase-7) — same
- `## Per-board / per-user persistence` covered by `mb-log-persistence`
  + `user-news-persistence`

#### packet-protocol.md / client-server-bridge.md

- `## CLI-08 MVP slice` cross-walks login/room/movement/chat — covered
- s2c-only opcodes pid-assign/udp-port-assign → row `pid-assignment`
  (in-phase-7 — Colyseus session-id replaces; no wire opcode in v1)
- Online-list broadcast (opcode 9) → row `online-list` (in-phase-7)
- Room snapshot (opcode 11) → row `room-snapshot` (mvp:false,
  in-phase-7 — Colyseus room state replication subsumes; Phase 6
  derives from `room-join`)
- Mod-execute (opcode 12) → row `opcode-12-mod-execute`
  (rejected-with-reason: "Wire path for broadcasting executable GML to
  clients; CLAUDE.md hard rule #3"; modernized: typed events only)
- duo-request-relay (15), duo-state-broadcast (22), duo-confirm-relay
  (23) → row `duo-relay-system` (in-phase-7) — interaction between two
  players (trade/duel?); not in MVP
- area-broadcast (16) → row `area-broadcast` (in-phase-7 — covered by
  `area-scoping`, separate wire row tracks the opcode)
- broadcast-login (0), broadcast-logoff (5), broadcast-room (1),
  broadcast-sprite (2) → covered by `login`, `logout`, `room-join`,
  `client-trusted-sprite-index`
- opcode-24-s2c, opcode-26-s2c → row `unknown-opcode-24`,
  `unknown-opcode-26` (deferred-stage-8 — empty-payload opcodes;
  semantics not yet recovered)

#### admin-anti-port.md (every row REJECTED)

- Ctrl+E → row `admin-ctrl-e-clipboard-rce` (rejected-with-reason
  per CLAUDE.md hard rule #3; modernized: _none — surface deleted_)
- Ctrl+Q → row `admin-ctrl-q-clipboard-kick` (rejected; modernized:
  `view-audit-log` + `kick`)
- Ctrl+9 → row `admin-ctrl-9-clipboard-snippets` (rejected; modernized:
  `kick`/`mute`/typed admin actions)
- Ctrl+??? → row `admin-ctrl-question-target-rce` (rejected; modernized:
  `assign-role`/`ban`)
- Ctrl+M → row `admin-ctrl-m-mb-view` (rejected; modernized:
  `mb-moderate`)
- Stash/restore location (Ctrl+O Codes.txt) → row
  `admin-stash-location-snippet` (rejected; modernized: _admin self-
  teleport UI form, not wire intent_)
- Hexport-out animation (Ctrl+O Codes.txt) → row `admin-hexport-anim`
  (rejected; modernized: `null` — cosmetic-bypass not in v1)
- Old Account Updater binaries → row `admin-old-account-updater-binary`
  (rejected; modernized: `account-recover`)
- `` `end session `` chat command → covered by
  `cmd-end-session-broadcast`

### From genre table-stakes (FEATURES.md MVP-Critical)

- Heartbeat (NEW; not in protocol.json — Phase 4 SRV adds) → row
  `heartbeat` (mvp:true, in-phase-6, opcodes=[] — synthesized;
  originating_gml cites `0349-operations.gml` step loop poll cadence)
- Movement-broadcast (opcode 3 s2c) → row `movement` (mvp:true,
  in-phase-6, opcodes=[3])
- Disconnect/reconnect handling → row `reconnect-grace-window`
  (in-phase-7)
- Player nameplate → row `player-nameplate` (in-phase-7 — Phase 6
  may carry this informally)

### From genre differentiators / parity-critical (FEATURES.md)

- Whisper / DM → row `whisper-private-chat` (in-phase-7) — NOT in
  original (chat is broadcast-only); rejected? No — modernized addition
  per FEATURES.md scope. Disposition: `in-phase-7`.
- Friends list / presence → `friends-list` (in-phase-7)
- Ignore/block list → `ignore-list` (in-phase-7)
- Account-level settings → `account-settings` (in-phase-7)
- Account recovery (email reset) → `account-recovery-email`
  (in-phase-7)
- Profanity filter / chat moderation → `chat-moderation` (in-phase-7)
- Chat history retention → `chat-history-buffer` (in-phase-7)

### From CDOC-01 client subsystems (server-side touchpoints only)

- Save-load (client-networking handshake) → covered by `login` /
  `login-response` rows
- Audio/rendering/animation/collision/scene-room are client-only →
  not parity-checklist rows (no server feature)

### PITFALLS B1 driven explicit rejected rows

- `client-trusted-position` (B1; rejected) — covered above
- `client-trusted-sprite-index` (B1; rejected) — covered above
- `client-supplied-chat-origin` → row `client-supplied-chat-origin`
  (rejected: "PITFALLS B1; server tags chat sender from socket auth")
- `client-trusted-room-id` → folded into `room-join` (Phase 6 must
  validate; not a separate row)
- `client-trusted-pid-assignment` → row `client-trusted-pid`
  (rejected: "Original assigns pid via opcode 6; rebuild uses session
  cookies tied to argon2id-verified accounts")

### Misc legacy quirks

- `dabypass` (Disconnected Alley account-bypass flag) → row
  `dabypass-flag` (deferred-stage-8 — niche per-account flag; restore
  if Phase 7 PAR enumerates rooms requiring it)
- `Saber Mage` / `Vance Serori` hardcoded alt-login exemptions → row
  `legacy-alt-login-exemption` (deferred-stage-8 per RESEARCH
  disposition rules)
- Superweird import / Phase 1 informational A7 → row
  `legacy-superweird-import` (deferred-stage-8)

## Aggregate

Target counts (per RESEARCH §Pattern 5; ±20% tolerance):

- in-phase-6: 7 (movement, chat-public, login, login-response,
  room-join, room-leave, heartbeat)
- in-phase-7: ~30
- deferred-stage-8: ~8
- rejected-with-reason: ~13

Total target: ≥40 rows.

## Methodology cite

`.planning/phases/03-server-documentation-schemas/03-RESEARCH.md`
§Pattern 5 — feature inventory must walk SDOC-01 + CDOC-01 H2 headings,
opcode rows, save-format rows, and every legacy admin keybind. Every
admin keybind becomes a `rejected-with-reason` row with a
`modernized_replacement` field per D-20.
