# MapleWeb v83 browser port blueprint

Status: active

Last updated: 2026-08-21 (America/Los_Angeles)

Requested deployment entrypoint: `http://99.26.147.74/` via Docker host port 80

Security qualification: public account credentials and game traffic cannot be
accepted over plaintext HTTP. Port 80 may serve a health/redirect entrypoint,
but the playable production origin requires a trusted HTTPS hostname and WSS.

Source workspace: repository root

This is the controlling document for the project. Future work starts here, updates the evidence ledger and feature matrix, executes the next unblocked gate, and records what changed before stopping. A stage is never marked complete because code exists; it is complete only when its acceptance evidence is stored in the repository.

## 1. Outcome and definition of done

MapleWeb is complete only when all of the following are true:

1. A user can open the production URL in a supported desktop browser, be redirected from port 80 to a trusted HTTPS origin, register a game account, log in, complete the server-configured PIN/PIC/TOS/gender flows, create a character, select a world/channel, enter the game, disconnect, and reconnect without administrative database work. The current public configuration disables account PIN while retaining PIC.
2. Every GMS v83 feature and every Cosmic-required packet in the project feature catalogue is either:
   - implemented, state-applied, rendered, interactive, fixture-tested, and live-server/browser-tested; or
   - proven not to belong to GMS v83/Cosmic and removed from the catalogue with evidence.
3. Assets are legally supplied by the operator, validated against a signed manifest, never committed to source control, and delivered without corrupt or cross-version nodes.
4. The public transport is HTTPS/WSS only. The browser cannot turn the WebSocket bridge into an arbitrary TCP proxy. Application services are reachable only on the private Docker network, and credentials/secrets never appear in the client bundle or repository.
5. Build, protocol, API, browser, security, load, backup, restore, and rollback checks pass from a clean checkout.
6. The deployed AGPL application prominently offers the exact corresponding source and preserves upstream notices.
7. Production is deployed as pinned Docker images with health checks, persistent-data backups, a rehearsed rollback, host-port and router/firewall validation, and a passing post-deploy smoke suite.

There are no silent waivers for “all features.” A gap remains open until its matrix row and packet-level evidence say otherwise.

## 2. Evidence-based baseline

### 2.1 Authoritative components

| Component | Pinned baseline | Role | Boundary |
|---|---:|---|---|
| `nmnsnv/maplestory-wasm` | `bc0234fe7c7f53322453e7bdd79564d9aca4cd8b` | Browser client foundation | Client and web transport source of truth |
| `P0nk/Cosmic` | `fec53bc7714dc0f1ae3f50b2986cdf2727e0912a` | GMS v83 server emulator | Protocol/gameplay authority; consume pinned upstream without ad-hoc behavior changes |
| `P0nk/Cosmic-client` | `6b7328b1593d34a4b134fe6b8a6d20119e526030` | Native v83/Cosmic file inventory | Binary/content compatibility reference only; no source implementation and no automatic LFS asset ingestion |
| `ryantpayton/MapleStory-Client` | `cbb0fe27cf9683a12eca0a569121d8033cdc2a4d` | AGPL HeavenClient/JourneyClient lineage | Attributed client reference only; reconcile v229.2 data assumptions against Cosmic v83 and WASM |
| `docs/ms-network-protocol.md` | repository baseline | v83 wire catalogue | Packet acceptance reference |
| local Moonlight | `4c6c86ee2e63d30651098206726af414aeef53a7` plus preserved local edits | Read-only behavioral/operations reference | GMS v214.1; no v214 protocol, IDs, data, auth tokens, or server code enter MapleWeb |

Moonlight is a Java 17 Swordie v214.1 server with a native WinForms launcher and injected DLL. Its packet protocol, handshake, launcher API, WZ/DAT data, modern jobs, and database schema are incompatible with v83. Reusable ideas are limited to state diagrams, invariants, content-manifest checks, resumable delivery concepts, and QA scenarios re-derived against v83.

### 2.2 Client completeness baseline

At the pinned client baseline:

- 45 client-to-server opcodes are declared.
- 82 server-to-client opcodes are named.
- 55 inbound opcodes have concrete handlers.
- 21 inbound opcodes are registered as `NullHandler` no-ops.
- The protocol reference contains approximately 139 client-to-server and 101 server-to-client opcode sections.
- Registration, password/ID recovery, many status-bar actions, quests, trade, cash shop, buddy, guild, alliance, family, messenger, merchants, pets, mounts, Maker, Monster Book, many skills, many field objects, and multiple login subflows are absent or partial.
- No project-owned unit, protocol-fixture, integration, browser, security, or gameplay regression suite exists.
- `assets/` and built WASM outputs are absent from the baseline checkout.

These numbers are discovery indicators, not percentages. A declared opcode is not complete until its full state/render/interaction lifecycle is tested.

### 2.3 Asset baseline and provenance gate

The Moonlight workspace's GMS v214 assets are not production-compatible evidence for a GMS v83 content set. The production input requirement remains:

- every required GMS v83 WZ file except `UI.wz`;
- a compatible post-Chaos `UI.wz` (upstream recommends v153+);
- operator confirmation of the right to use and host the converted data.

The project must not commit or redistribute proprietary game assets as source artifacts. Operator-authorized local WZ/NX inputs and derived delivery data stay ignored and read-only once staged.

The operator has explicitly authorized the web client to consume compatible NX
files. NX is therefore the required runtime data source for maps, characters,
items, mobs, NPCs, skills, sound, and UI; the read-only rule prevents accidental
mutation, not runtime use. No files were written under the protected `assets/`
directory. On 2026-08-21, the operator-supplied Cosmic custom WZ set in
`Cosmic-client-main/cosmic-wz` was combined with missing original-v83 files
recovered from the official v83 installer, then converted into an ignored
16-file `nxFiles/` runtime set with a size/SHA-256 manifest. File magic,
manifest delivery, asset WebSocket delivery, and browser client initialization
pass. Stage 7 remains open until representative maps, content IDs, UI behavior,
and multi-client gameplay are proven.

The first public login probe found one concrete UI-version incompatibility:
original-v83 `UI.nx` has no post-Chaos `Basic.img/Notice6/box` tile. The client
now detects incomplete Notice6 frames and uses a self-rendered, clickable
Yes/No/OK fallback. This prevents zero-height tile arithmetic and keeps Cosmic's
Terms and gender prompts usable without modifying asset files.

The pinned Cosmic-client repository advertises original-v83 and Cosmic-custom
WZ inputs, but contains LFS pointers rather than open client source. Its LFS
budget was exhausted during the audit, and its availability would not establish
redistribution rights. ADR 0004 therefore keeps it outside automated builds and
outside the source tree's asset pipeline.

### 2.4 Production-host baseline

Read-only preflight on 2026-08-20 found:

- The new production target is the current Windows/Docker host, not the earlier Plesk server.
- Docker is installed and active; unrelated containers already publish ports 8777-8780 and must remain untouched.
- Host TCP port 80 is currently free.
- The host LAN address is `10.0.0.57/24`; requested public address `99.26.147.74` is not assigned to a local interface and is therefore a NAT/router edge.
- External TCP 80 reachability requires a router/firewall rule forwarding to `10.0.0.57:80` plus verification from outside the LAN.
- A trusted hostname/certificate or equivalent TLS terminator is still required for registration and WSS. Plaintext credentials over the public-IP HTTP URL are not an acceptable production mode.
- A temporary Cloudflare Quick Tunnel provided trusted HTTPS/WSS on 2026-08-21 for public smoke testing. Its generated URL and uptime are ephemeral and do not prove the requested NAT rule or satisfy the permanent Stage 9 origin gate.

No deployment secrets are stored in this document or repository.

## 3. Target architecture

```mermaid
flowchart LR
    B["Desktop browser: portal + WASM client"]
    R["Public NAT/router: 99.26.147.74:80/443"]
    N["Docker edge proxy: host ports 80/443"]
    S["Versioned static JS/WASM/NX assets"]
    A["Account registration API (loopback)"]
    W["Restricted WS-to-TCP bridge (loopback)"]
    G["Pinned Cosmic v83 server"]
    D["MySQL 8 database"]

    B --> R --> N
    N --> S
    N --> A
    N --> W
    W --> G
    A --> D
    G --> D
```

Production rules:

- The Docker edge proxy serves HTML, JS, WASM, source offer, and NX assets. It supplies byte ranges, correct MIME types, isolation headers, CSP, cache policy, and WebSocket upgrades.
- Only the edge proxy publishes host ports. The account API, game bridge, asset service, Cosmic, and database use an internal Docker network and are never published directly.
- The requested deployment uses the root path (`/`). Runtime URLs remain base-path aware so staging can still run below a prefix.
- Host port 80 redirects to the trusted HTTPS origin before any credential or game flow. Registration and the WASM launch control remain disabled if the page is not a secure context.
- The game bridge connects only to the configured Cosmic host and an explicit login/channel-port allowlist. A browser-supplied host is never trusted.
- Cosmic remains the v83 game authority. The registration API is a separate least-privilege account provisioning adapter for the existing Cosmic schema, not a new game protocol.
- Automatic in-game account creation is disabled in production once the registration API is verified, so validation and rate limiting have one entry point.
- Passwords are hashed in a Cosmic-compatible bcrypt format and never logged, returned, stored in browser storage, or embedded in WASM.

## 4. Source and compatibility boundaries

1. Never modify, move, regenerate, or commit anything under `assets/`.
2. Do not copy Moonlight v214 packet code, opcodes, crypto, tokens, WZ/XML/DAT data, SQL schema, job IDs, content scripts, launcher code, DLL hooks, or native process logic.
3. Moonlight-derived behavior must be documented as an invariant/test scenario and independently implemented against v83 documentation.
4. Preserve AGPL notices. Any publicly deployed modified client/server must expose exact corresponding source under the AGPL network-use requirement.
5. Do not expose `.git`, build caches, raw WZ inputs, database dumps, environment files, logs, or server source through the static document root.
6. Do not use the development `web/server.py` as a public origin server.
7. Never deploy the unhardened baseline proxy; a release may use only the restricted build whose allowlist/origin/limit suite passes.
8. Do not execute or redistribute Cosmic-client binaries or ingest its LFS WZ payload automatically; follow ADR 0004 and the Stage 7 provenance gate.

## 5. Stage map

### Stage 0 — Discovery, scope lock, and blueprint

Status: complete (2026-08-19)

Deliverables:

- This blueprint, the feature matrix, architecture decisions, provenance rules, host preflight, risk register, and status journal.
- Pinned upstream commit identifiers and a reproducible way to retrieve dependencies.
- Generated opcode inventory that compares the client, protocol reference, and pinned Cosmic enums/handlers.

Exit gate:

- Every major feature domain has a matrix row.
- Every known blocker has an owner and a next action.
- No secret or proprietary asset is tracked.
- Architecture and version boundaries are unambiguous.

### Stage 1 — Reproducible foundation

Status: complete (2026-08-20)

Deliverables:

- Project-local, pinned Emscripten 4.0.21/CMake build bootstrap for Windows and Linux without relying on an unpinned global toolchain.
- Pinned Cosmic retrieval/build mechanism and clean database migration path.
- Dependency lock/checksum manifest and software bill of materials.
- Build commands with sane job detection, deterministic output locations, and zero warnings.
- Baseline native/static checks plus WASM compile in CI-equivalent conditions.

Exit gate:

- A clean checkout can build `JourneyClient.js` and `JourneyClient.wasm` twice with documented inputs.
- Cosmic unit tests and package build pass at the pinned commit.
- Build outputs contain no absolute developer paths or secrets.

### Stage 2 — Production web shell and transport

Status: in progress

Deliverables:

- Accessible root-path production portal plus `/mapleweb/` staging-prefix coverage, with game canvas, loading, errors, retry, fullscreen, audio unlock, compatibility checks, source/license link, and registration entry point.
- Base-path-aware runtime configuration.
- Explicit `GameWebSocketURL` and `AssetsURL` support, using WSS under HTTPS.
- Restricted game bridge with host/port allowlist, origin checks, connection/time/frame limits, structured logs without packet credentials, ping/pong, and graceful shutdown.
- Hardened asset delivery with manifest validation, bounded requests, cache eviction, corruption recovery, and nginx byte ranges.

Exit gate:

- Static, config, WASM, source offer, and asset URLs work at `/` and below the `/mapleweb/` staging prefix.
- Mixed-content testing is clean.
- Attempts to target arbitrary TCP hosts/ports fail.
- WebSocket sessions survive the configured idle window and recover from a forced disconnect.
- Security headers and CSP pass automated assertions.

Progress evidence (2026-08-20):

- Added a digest-pinned nginx edge and Python runtime Compose topology. Only
  nginx publishes ports; registration, game bridge, and asset streaming share
  its network namespace and bind to loopback, while an external private backend
  network provides Cosmic/MySQL reachability.
- A repeatable random-loopback Docker rehearsal passes HTTP 308, HTTPS static
  serving, isolation/CSP headers, entry/config and immutable-build cache policy,
  WASM MIME plus 206 ranges, dotfile denial, WSS upgrade through the restricted
  bridge, TCP echo, CSRF registration into disposable Cosmic-schema MySQL,
  column-limited INSERT grants, cost-12 bcrypt verification, exact two-port
  publication, and unrelated-container cleanup.
- The rehearsal certificate is intentionally ephemeral and untrusted. A trusted
  public hostname/certificate, root-path browser UI registration, asset-stream
  test, and external NAT probe remain open Stage 2 evidence.

### Stage 3 — Registration and complete login state machine

Status: complete (2026-08-20)

Deliverables:

- Registration API with server-side username/password validation, Cosmic-compatible bcrypt, unique-account handling, generic duplicate/error responses, per-IP and per-identity rate limits, CSRF/origin enforcement, audit events without secrets, health/readiness endpoints, and least-privilege DB credentials.
- Registration UI is intentionally two-field: username and password only. It
  does not ask for a PIN, PIC, password confirmation, or community-server
  acknowledgement; PIN/PIC are distinct optional Cosmic login/character
  security states and are never registration inputs.
- Browser registration UI and working in-game Register button integration.
- Standard v83 login, TOS, gender, server-configurable PIN/PIC, world/channel, character create/delete/select, relog, and recovery/error states without hardcoded PIN/PIC values. The public deployment uses Cosmic's supported `ENABLE_PIN: false` setting.
- Protocol/API fixture suite plus a disposable-database end-to-end test.

Exit gate:

- Fresh account: register → accept required terms/gender and enabled secondary-security steps → create character → enter world. Public QA must prove that PIN-disabled deployments skip the PIN prompt.
- Existing account: correct login, wrong password, banned, already logged in, duplicate registration, rate limit, reconnect, and logout paths behave correctly.
- Password material is absent from logs, browser storage, telemetry, URLs, and error responses.

Progress evidence (2026-08-20):

- The portal supplies a stable random browser identity to the WASM runtime;
  character select, PIC registration, and PIC selection now serialize that
  identity instead of a shared hard-coded MAC/host value.
- Native warning-as-error fixtures assert the complete byte layout of all three
  identity-bearing packets; world/channel, name, create, and delete requests;
  and the TOS, gender, PIN, account parser, connection-generation, and Cosmic
  delete-response state mappings.
- A pure Cosmic response parser now owns character list/name/create/delete,
  channel redirect, and relog validation. Exact fixtures require full packet
  consumption and reject truncation, invalid PIC/slot values, malformed redirect
  padding, and trailing relog data. The character-list status byte is no longer
  misused as the selected channel, and `RELOG_RESPONSE` is registered.
- The same bounded parser now owns `LOGIN_STATUS`: exact success and failure
  fixtures cover wrong credentials, missing accounts, already-online, overload,
  TOS, permanent/temporary bans, reserved fields, truncation, and trailing data.
  Ban FILETIME data is consumed and selects the appropriate blocked/temporary
  notice instead of being silently discarded.
- The packaged form passed a real-browser same-origin CSRF/API flow backed by
  the digest-pinned disposable MySQL schema and INSERT-only identity. The
  harness verified bcrypt and browser secret clearing; 41 web/transport tests
  now cover its static and reverse-proxy boundary.
- A loopback-only, disposable live-Cosmic rehearsal now starts the pinned JAR
  and WZ data against digest-pinned MySQL, waits for Cosmic's own listener-ready
  event, and drives the production client cipher/parser over real TCP. It proves
  the exact v83 handshake, fresh-account/TOS reason 23 with cost-12 bcrypt,
  TOS acceptance, gender selection, PIN registration and returning PIN check,
  wrong-password reason 4, world/character lists, name validation, character
  creation, PIC registration, channel redirect, a fresh channel handshake, and
  `PLAYER_LOGGEDIN` through the initial `SET_FIELD`. MySQL publishes no port;
  both game listeners are randomly bound to loopback and cleanup preserves
  unrelated containers.
- World and character selection now expose the v83 `Common/BtExit` action and
  send `RELOG`. A successful response resets the first-login state and replaces
  the selection UI with a newly constructed login form instead of making the
  invalid unauthenticated world-list request. The portal announces `Sign in
  required`, returns focus to the canvas, and never persists or replays the
  password. The packaged portal loaded cleanly in the in-app browser, while
  the event contract is covered by the web suite; warning-as-error native
  fixtures, the warning-free WASM build, and the live Cosmic recovery matrix
  all pass.
- Stage 3 is complete. Trusted-certificate HTTPS/WSS remains a Stage 2
  deployment gate and is not inferred from the loopback browser harness.

Recovery evidence (2026-08-20):

- The returning live session submits an incorrect PIN, requires Cosmic's retry
  mode, then succeeds with the registered PIN. A later session submits an
  incorrect PIC, consumes `CHECK_SPW_RESULT`, retries correctly, and receives a
  bounded channel redirect.
- Disposable account mutations prove both permanent and future-dated temporary
  ban packets. This exposed a sentinel mismatch: Cosmic `getPermBan` uses
  `getTime(-1)`/`DEFAULT_TIME` (`150842304000000000`), not its item-expiry
  `PERMANENT` constant. Runtime classification and deterministic fixtures now
  use the actual wire value.
- The relog path has an exact `RELOG (0x1C)` request packet, live encrypted
  Cosmic response proof, selection-screen exit controls, and a fresh-login UI
  transition. Cosmic correctly does not serve worlds until the user enters
  credentials again.
- The live fixture cancels an existing-account PIN prompt, closes from the
  client as the runtime does, verifies Cosmic released `loggedin`, reconnects,
  and completes PIN/world/channel entry. A separate isolated `loggedin=2`
  fixture proves the complete already-online reason-7 response.

### Stage 4 — Core playable v83 slice

Status: in progress

Domains:

- set-field/full-character parsing;
- maps, portals, ladders/ropes, swim/ice physics, movement, channel/map changes;
- local/remote character state;
- inventory/equipment/use/scroll/gather/sort;
- AP/SP/keymap/macros;
- melee/ranged/magic damage, death/revive, mob control, drops/reactors;
- NPC dialogue, shops, storage;
- Explorer, Cygnus, and Aran beginner-through-advancement smoke paths.

Exit gate:

- Packet fixtures consume their entire body and assert no over/under-read.
- A new character can progress through representative combat, loot, inventory, NPC, shop, storage, skill, portal, death, and relog scenarios.
- Authoritative channel stats win over stale login-server data.
- Fragmented and coalesced WebSocket packet tests pass with bounded packet lengths.

Progress evidence (2026-08-20):

- Extracted a bounded Cosmic v83 `SET_FIELD` envelope parser. Exact fixtures
  cover initial channel entry, ordinary/chasing map changes, reserved fields,
  channel/map/character bounds, truncation, and trailing bytes.
- Initial character parsing now consumes the skill marker, character
  terminator, and server timestamp; collection counts are bounded, and the
  complete Cosmic New Year card record is consumed instead of left as a TODO.
- Channel-authoritative stats are retained and applied after the character
  body and tail validate, replacing the stale login-server snapshot before map
  selection and stat recalculation. The isolated live probe now validates the
  real Cosmic initial envelope and selected character ID.
- Inventory, skill, cooldown, quest, teleport, and monster-book data now decode
  into an off-stage `Player`; `Stage` receives that object only after the full
  body, character terminator, timestamp, and trailing-data checks pass. Invalid
  linked-name, monster-book, and New Year boolean markers fail closed instead
  of silently becoming false.
- Added an `InitialCharacterData` transfer object. Stats, buddy/link metadata,
  inventory, skills, cooldowns, quests, rings, teleport rocks, monster book,
  New Year cards, area info, and server time now populate this off-stage object;
  the runtime `Player` is constructed only after complete packet validation.
  This isolation is a packet-test boundary and does not replace NX-backed
  rendering or content lookup during gameplay.
- Moved the body decoder into a fixture-callable helper with no UI, `Stage`,
  graphics, or NX-backed appearance initialization. An eighth exact native
  fixture covers stats, linked name, inventory metadata and a use item, skill,
  cooldown, started/completed quests, every ring family, teleport rocks,
  monster book, New Year cards, area info, terminator, and server time. It also
  rejects invalid booleans, truncation, and trailing bytes.
- Extended the exact fixture through equip, ordinary item, pet, and fourth-job
  skill records. Item type and cash flags are strict, Cosmic's complete 5000xxx
  pet family is recognized, and equip item EXP is retained as its actual
  32-bit field instead of being split and truncated to 16 bits.
- Corrected Cosmic's shared Cash-inventory terminator/SkillInfo marker boundary.
  The isolated encrypted live matrix now decodes the entire real initial
  `SET_FIELD`, verifies the authoritative character name, and requires zero
  unread bytes.
- Replaced the permissive inbound movement parser with Cosmic's complete v83
  command table (`0`–`22`). Absolute, relative, teleport/skill, and jump-down
  payloads now consume exact lengths; both footholds are retained; metadata
  commands are consumed without snapping actors to `(0,0)`; empty, negative,
  unknown, truncated, and trailing lists fail closed. A ninth warning-as-error
  native fixture covers every command and exact outbound `MOVE_PLAYER` bytes.
- Added bounded remote-player and mob movement envelope parsers around the
  fragment list. Character/mob IDs, reserved fields, and Cosmic boolean markers
  now fail closed; mob activity, skill, option, and starting-position metadata
  are retained. A tenth warning-as-error native fixture covers complete,
  malformed, truncated, and trailing envelopes and the exact outbound
  `MOVE_LIFE` body, including Cosmic's 13 opaque pre-position bytes.
- Replaced the `MOVE_MONSTER_RESPONSE` no-op with a bounded parser and per-mob
  request/acknowledgement state. Controlled movement IDs advance only after the
  matching response; retries reuse the outstanding ID; wrong/duplicate IDs do
  not mutate MP or proposed-skill state; control transfer resets the sequence.
  `PLAYER_MAP_TRANSFER (0xCF)` is now named correctly and is emitted only after
  the asynchronous NX-backed map load completes. The isolated encrypted Cosmic
  matrix enters a 52-mob map, completes that transition, receives real control,
  sends `MOVE_LIFE`, and validates Cosmic's exact `0xF0` object/movement echo.
  The generated inventory advances to 61 concrete inbound handlers and 20
  explicit no-ops.
- Extracted the monster spawn/controller/kill envelopes from NX-backed map
  construction. The parser consumes Cosmic's ordinary, status-bearing,
  effect-15, linked-parent, fake-mob, make-real, short release/resume, and
  duplicate-animation kill forms before any `Stage` mutation. An eleventh
  warning-as-error native fixture covers those exact layouts and rejects bad
  IDs, modes, controller markers, status masks, origin footholds, item effects,
  truncation, mismatched kill animations, and unambiguous trailing data. The
  isolated encrypted Cosmic matrix now parses a real full controller spawn to
  zero remaining bytes before using its position for the proven movement/ack
  round trip.
- Added strict `APPLY_MONSTER_STATUS`, `CANCEL_MONSTER_STATUS`,
  `DAMAGE_MONSTER`, and `SHOW_MONSTER_HP` envelopes at the pinned Cosmic
  layouts. Spawn-time and incremental masks now persist in a per-mob status
  state, cancellations clear only their selected bits, positive server-authored
  damage renders through the combat damage-number layer, and HP percentages
  reject values above 100 instead of wrapping through a signed byte. A twelfth
  warning-as-error native fixture covers player and mob-skill sources,
  reflection counters, cancellation, damage, healing HP metadata, and malformed
  reserved/mask/duration/count/trailer variants. The generated inventory now
  reports 64 concrete inbound handlers, 20 explicit no-ops, and 217 missing
  server-to-client opcodes.
- Extracted strict drop and reactor lifecycle envelopes. Drop parsing now
  distinguishes animated spawns, ownership updates, ordinary/pet pickups, and
  delayed exploded-meso removal; validates owner/type/boolean/expiration
  boundaries; and replaces same-OID ownership state instead of merely
  reactivating stale metadata. Reactor spawn/hit/destroy now consume Cosmic's
  reserved fields exactly, preserve hit stance and state, queue updates that
  precede NX-backed construction, animate state transitions from Reactor NX,
  and replace reused object IDs safely. A thirteenth warning-as-error fixture
  covers every pinned variant and malformed/truncated/trailing cases.
  `REACTOR_HIT` becomes the 65th concrete inbound handler, leaving 216 missing.
- Extracted strict ordinary spawn, controller assignment/release, standalone
  removal, and scriptable-NPC envelopes. Ordinary spawn now consumes Cosmic's
  required twentieth byte; raw movement bounds and minimap visibility persist
  on the runtime NPC, and hidden entries are omitted from the minimap;
  release changes controller ownership without hiding the object; hard removal
  cancels queued construction; reused object IDs replace stale templates; and
  pending NPC commands are drained on field clear. The server's scriptable set
  is retained as metadata in a replacement registry across map changes,
  including names, and reset on a fresh account login. A fourteenth
  warning-as-error fixture
  covers exact normal/PlayerNPC bounds, both controller modes, removal, empty
  and populated script declarations, fixed validity fields, duplicates,
  truncation, and trailers. The isolated Cosmic probe requires real ordinary
  and controller NPC spawns plus the configured `Rescue Gaga!` declaration.
  The generated inventory now reports 67 concrete inbound handlers, 19
  explicit no-ops, and 215 missing server-to-client opcodes.
  NPC action/movement/speech synchronization and PlayerNPC appearance packet
  `0x51` remain explicit adjacent work, as does rendering the retained script
  name as a quest/script indicator.
- Added a field-scoped affected-area pool and strict Cosmic mist lifecycle.
  `SPAWN_MIST (0x111)` now consumes the exact 39-byte kind/owner/skill/level/
  delay/rectangle/reserved layout, while `REMOVE_MIST (0x112)` consumes the
  exact four-byte object ID. The parser rejects unsupported kinds, invalid
  fixed delays, degenerate bounds, reserved-field corruption, truncation, and
  trailers before mutating `Stage`. Spawn replaces the same object ID, removal
  cancels pending NX construction, and field clear drains both active and
  pending areas. Mob and player sources resolve their distinct Skill NX paths;
  grouped tile variants and direct-frame tiles loop through the existing
  animation runtime, render only the visible rectangle, and remain alive until
  the authoritative server removal. A fifteenth warning-as-error fixture
  covers all four pinned kinds and malformed envelopes, and the warning-free
  release WASM build passes. The generated inventory now reports 69 concrete
  inbound handlers, 19 explicit no-ops, and 213 missing server-to-client
  opcodes. Representative v83 NX/browser/live rendering remains required
  before this lifecycle can advance beyond `fixture-tested`.
- Replaced the three field-obstacle no-ops (`0x8B`–`0x8D`) with strict
  single, full-snapshot, and empty-reset parsers. Counts are bounded by the
  remaining packet body, modes are restricted to Cosmic's observed 0/1/2
  states, and every list validates before map state changes. Map `Obj` now
  preserves its optional NX `name`; a full status list first resets all named
  objects, then applies each server state across all map layers. Mode 0 resets
  and stops at the first frame, mode 1 animates, and mode 2 is retained as the
  server's toggle state without inventing reverse playback. A sixteenth
  warning-as-error fixture covers all modes, empty/populated lists, reset,
  malformed counts/modes, truncation, and trailers. The warning-free release
  WASM build passes. The generated inventory now reports 72 concrete inbound
  handlers, 16 explicit no-ops, and 213 missing server-to-client opcodes.
  Reverse/toggle playback and representative named-object live/browser
  evidence remain adjacent work.
- Replaced the shifted `SPAWN_PLAYER (0xA0)` handler with one complete
  off-stage Cosmic DTO and made `REMOVE_PLAYER_FROM_MAP (0xA1)` exact. Foreign
  buff parsing now distinguishes morph's short value from combo's byte even
  when the upper mask is zero, verifies the fixed padding and repeated random
  keys, and retains both mask words. Look, chocolate/effect/chair, position,
  up to three embedded pets, mount state, player-shop/minigame announce boxes,
  chalkboard, crush/friendship/marriage looks, New Year card IDs, fixed tail,
  and team all consume before `Stage` mutation. Embedded pets no longer read a
  nonexistent slot byte and preserve their 64-bit wire unique ID through
  `CharSpawn`, `Char`, and `PetLook`. Pending spawns are keyed: removal cancels
  construction, same-ID spawn replaces stale runtime state, and map clear
  drains the queue. A seventeenth warning-as-error fixture covers ordinary,
  morph, combo, pet, shop, minigame, ring/card/chalkboard, malformed-mask,
  fourth-pet, truncation, trailer, and exact removal variants. The warning-free
  release WASM build passes. Two-client live/browser presence and visual-tail
  consumers remain required.
- Completed the separate inbound pet event family at Cosmic's pinned layouts.
  `SPAWN_PET (0xA8)` now validates summon/dismiss variants before character
  lookup, so an unknown owner cannot leave the packet unread. `MOVE_PET
  (0xAA)` wraps the standard movement parser; chat, name change, exception
  list, command response, and food response (`0xAB`–`0xAE`) enforce exact
  slots, booleans, fixed markers, counts, and trailers. Runtime pets retain
  full-width unique IDs, apply absolute/relative movement, render chat bubbles,
  rebuild name labels, and atomically replace pickup exceptions. Command IDs
  and success are retained without guessing the missing NX command-action
  mapping. An eighteenth warning-as-error fixture covers every envelope and
  malformed slot/mode/marker/count/type/truncation/trailer cases. The
  warning-free release WASM build passes. The generated inventory now reports
  77 concrete inbound handlers, 16 explicit no-ops, and 208 missing
  server-to-client opcodes. Two-client pet behavior, pickup filtering, command
  animation, mount, feed, and autopot remain open.
- Completed the bounded Mystic Door field lifecycle. `SPAWN_PORTAL (0x43)`
  now distinguishes its exact 12-byte active route from the exact eight-byte
  double-sentinel removal; mixed sentinels, bad map IDs, and trailers fail
  closed. `SPAWN_DOOR (0x113)` and `REMOVE_DOOR (0x114)` validate strict
  booleans, fixed markers, positive owners, and complete 9/5-byte bodies
  before field mutation. Doors are keyed by owner, same-owner spawns replace,
  removal cancels pending construction, and map clear drains door and route
  state. The shared v83 `MapHelper.img/portal/game/tp` animation renders both
  map portal type 6 and dynamic field doors. Pressing Up at a field door sends
  the exact `USE_DOOR (0x85)` owner plus field-to-town byte behind a cooldown.
  A nineteenth warning-as-error fixture covers active/removal routes,
  spawn/remove, sentinels, IDs, markers, truncation/trailers, and exact outbound
  bytes; the warning-free release WASM build passes. The generated inventory
  now reports 80 concrete inbound handlers, 16 explicit no-ops, and 205
  missing server-to-client opcodes. Compatible NX/browser/live evidence and
  town-to-field owner selection from party-door metadata remain open.
- Completed all six pinned inbound summon envelopes (`0xAF`–`0xB4`). The
  24-byte spawn validates its v83 marker, skill level, fixed short, Cosmic's
  actual movement types 0/1/3, booleans, and complete body; remove, movement,
  attack target lists, damage, and Beholder skill stances likewise validate
  fixed markers/counts and consume fully before field mutation. Summons are
  keyed by object ID and correlated with owner IDs; same-OID replacement,
  remove-before-construction, queued movement/attack/damage/skill ordering,
  animated/instant removal, and map-clear cleanup are implemented. Runtime
  art resolves the source-authored `Skill/<job>.img/skill/<id>/summon` states
  (`summoned`, `stand`, `fly`/`move`, `attack1`, `skillN`, `hit`, `die`). It
  applies authoritative movement, attack direction, Beholder stance 5–8,
  summon damage numbers, and target damage numbers without guessing local
  damage. A twentieth warning-as-error fixture covers every envelope and bad
  marker/level/movement type/bool/mode/count/target/damage/truncation/trailer
  cases; the warning-free release WASM build passes. The generated inventory
  now reports 86 concrete inbound handlers, 16 explicit no-ops, and 199
  missing server-to-client opcodes. Representative NX/browser/two-client
  evidence and the owner-side C2S movement/targeting/attack/damage controller
  remain open; inbound rendering is not mislabeled as autonomous summon play.
- Completed the source-authored town-to-field half of Mystic Door interaction.
  The six party-status door records are retained alongside their aligned owner
  IDs, town/target map IDs, and validated town coordinates. This session-level
  registry survives ordinary field clears, is replaced atomically by each
  complete party status, and resets on party creation/disband/self-leave or a
  new character session. Pressing Up inside the retained town portal bounds
  now dispatches `USE_DOOR` with the matched owner and `townToField=1`; the
  existing dynamic field door dispatches the reverse byte. This avoids
  guessing party history/portal slots. The warning-free release WASM build
  passes; a two-map party live/browser scenario remains required before the
  bidirectional interaction can advance beyond fixture-tested.
- Added the exact Cosmic-facing client summon serializers without conflating
  them with autonomous control. `MOVE_SUMMON (0xAF)` writes object ID, start
  position, count, and a standard movement fragment. `SUMMON_ATTACK (0xB0)`
  writes object ID, tick, direction/count, both eight-byte metadata regions,
  target positions/delay, and damage with a checked byte-sized target count.
  `DAMAGE_SUMMON (0xB1)` writes object ID, the v83 `-1` attack marker, damage,
  and attacker template ID. A twenty-first warning-as-error native fixture
  asserts their exact byte sizes, opcodes, offsets, markers, and payloads.
  The generated client-to-server inventory advances from 47 to 50 named
  opcodes and from 131 to 128 missing. The warning-free release WASM build
  passes; the next slice must make
  local summon simulation call these serializers rather than claim that the
  serializers alone create working summon AI.
- Remaining gate: load compatible NX content and prove representative portal
  movement/map transition after the fully validated initial field.

### Stage 5 — Social and economy systems

Status: pending

Domains:

- whisper/general/party/buddy/guild/alliance/family/messenger/spouse chat;
- buddy, party, guild, alliance, family, BBS, memo, marriage/wedding;
- player trade, Duey, player shops, hired merchants, Owl;
- Cash Shop, MTS, coupons, gifting, name/world transfers;
- fame, rankings, storage and cash inventory invariants.

Exit gate:

- Two- and multi-client browser scenarios pass for every social state transition.
- Economy actions are transactional under disconnect/retry and do not duplicate or lose items/currency.
- Every corresponding status-bar/key action opens a functional UI or explains a deliberate v83 limitation.

### Stage 6 — Remaining v83 gameplay/content systems

Status: pending

Domains:

- all v83 skills, buffs, debuffs, summons, dragons, doors, mist, monster statuses, pet/mount behavior, and autopot;
- quest log/actions/progress/expiry;
- Maker, skill books, hammer, Vega, item pets/rings, Teleport Rock, Monster Book;
- minigames, Carnival, PQs, expeditions, Dojo, events, field obstacles/effects, scripted NPCs;
- GM/admin packets needed by supported operations.

Exit gate:

- Every protocol catalogue row is lifecycle-complete and fixture-tested.
- Every feature-domain scenario passes against the pinned Cosmic server.
- No client-owned TODO/no-op remains for an in-scope v83 feature.

### Stage 7 — Asset/content pipeline

Status: pending; production input dependency identified

Deliverables:

- Legally supplied, version-confirmed WZ input set.
- Pinned WZ-to-NX converter and non-destructive conversion script.
- Canonical 17-file manifest: the 16 v83 content files (including `Morph.nx`) plus the isolated `LoginTheme.nx` compatibility overlay, with size, SHA-256, schema/node probes, and client/server content compatibility probes.
- Immutable versioned publishing layout and rollback manifest.
- Browser storage quota, cache eviction, partial-download, corruption, and cold/warm-load tests.

Exit gate:

- All 17 required NX files pass node probes and representative map/item/mob/NPC/skill/sound/UI lookups.
- Content IDs used by the server resolve to compatible client data.
- Cold and warm startup targets pass without unbounded browser memory growth.
- Operator provenance confirmation is recorded outside the public repository.

### Stage 8 — Quality, security, resilience, and performance

Status: pending

Deliverables:

- Unit, fuzz/property, packet-fixture, API integration, browser automation, multi-client gameplay, load, soak, and recovery suites.
- Threat model for registration, database, WebSocket bridge, static assets, browser storage, admin surfaces, supply chain, and deployment.
- Resource budgets for WASM memory, JS cache, IndexedDB, asset bandwidth, server connections, CPU, and database pools.
- Backup/restore rehearsal, log rotation, metrics, alerts, runbooks, and incident/rollback procedures.

Exit gate:

- No open critical/high security finding.
- Registration/proxy abuse, malformed frames, oversized packets, slow clients, reconnect storms, and dependency failure tests pass.
- A representative multi-client soak completes with bounded memory/handles and no economy/state corruption.
- Database backup is restored into a clean environment and gameplay state is verified.

### Stage 9 — Production deployment and sign-off

Status: pending

Deliverables:

- Versioned Docker images, immutable release manifest, Compose production override, internal-only service network, Docker secrets/environment provisioning, persistent-volume backup, health checks, and atomic image-tag rollback.
- HTTPS/WSS configuration with COOP/COEP/CORP, CSP, `nosniff`, WASM MIME, range support, immutable versioned assets, non-cacheable HTML/config, WebSocket upgrade, no buffering, and long timeouts.
- Post-deploy smoke and synthetic monitoring.

Exit gate:

- All prior stage gates and every matrix row are signed off.
- Production 200/index, WASM MIME, 206 range, headers, 101 upgrade, >60-second session, registration, login, character entry, gameplay smoke, reconnect, source offer, backup, and rollback checks pass.
- External probes confirm `99.26.147.74:80` reaches only the edge container and redirects to trusted HTTPS; no internal service/database port is public.

Deployment happens only after this gate. “The page loads” is not project completion.

## 6. Test evidence model

Every feature moves through these states, in order:

1. `catalogued` — protocol/mechanic identified.
2. `parsed` — all packet fields are bounds-checked and consumed.
3. `state-applied` — authoritative game state changes correctly.
4. `rendered` — visual/audio result is correct where applicable.
5. `interactive` — user can initiate and cancel the flow with keyboard/mouse.
6. `fixture-tested` — deterministic packet/API tests pass.
7. `live-tested` — passes against pinned Cosmic with disposable data.
8. `browser-tested` — passes in the supported production-like browser stack.
9. `verified` — regression evidence is stored and the matrix owner signs off.

Tests must cover happy paths, rejected actions, disconnect boundaries, duplicates/retries, malformed input, and state persistence.

## 7. Security invariants

- WSS/HTTPS is mandatory outside loopback development.
- Only nginx is Internet-facing for MapleWeb. Internal services bind to `127.0.0.1`.
- Browser messages select an allowed game port, never an arbitrary host.
- Packet payloads, passwords, PINs, PICs, cookies, tokens, and DB strings are redacted from logs.
- Packet lengths, WebSocket frames, asset chunk sizes/ranges, request bodies, connection counts, and timeouts are bounded.
- Registration returns generic authentication/account-existence errors and is rate-limited.
- DB users are separated: Cosmic owns gameplay tables; the registration adapter receives only the minimum account permissions.
- Static delivery cannot traverse outside the release root or expose dotfiles/source caches.
- Runtime secrets come from Docker secrets or host-restricted environment files, never from `config.json` or JavaScript.
- Dependencies are pinned and checksummed; deployment uses exact built artifacts.

## 8. Risk register

| Risk | Severity | Current control | Closure condition |
|---|---|---|---|
| No production-compatible v83 asset set is present | Critical | Code work continues without copying v214 data | Operator supplies/proves compatible assets and Stage 7 passes |
| Baseline client is far from full v83 feature coverage | Critical | Packet/domain matrix and milestone gates | All rows reach `verified` |
| Baseline proxy is an open TCP pivot | Critical | Never deploy baseline proxy | Allowlist/origin/limit tests pass |
| Public proprietary NX delivery may lack distribution rights | Critical | Assets ignored and provenance gate | Operator records lawful hosting basis |
| Public-IP port 80 would expose credentials/game traffic | Critical | Port 80 redirect only; secure-context launch/registration gate | Trusted HTTPS hostname and WSS Stage 2 tests pass |
| NAT/router rule for `99.26.147.74` is absent or targets another host | High | Record LAN target `10.0.0.57`; no deployment assumption | External port probe and router/firewall evidence pass |
| Mixed-content/root-path failures in Docker edge proxy | High | Same-origin configurable URLs | Stage 2 HTTPS/WSS tests pass |
| WebSocket/TCP fragmentation can corrupt packets | High | Planned buffered framing + limits | Fragment/coalesce/fuzz tests pass |
| Unbounded LazyFS caches can exhaust browser memory/storage | High | Planned budgets/LRU/quota recovery | Cold/warm soak targets pass |
| Cosmic is an emulator, not proof of canonical completeness | High | Canonical v83 + Cosmic catalogues | Catalogue reconciliation signed off |
| Compose could accidentally publish an internal service/database port | Critical | Edge-only `ports`; internal services use `expose`/private network | External port inventory proves only 80/443 are added |
| AGPL source-offer failure | High | Source link and release manifest required | Exact deployed source is publicly retrievable |
| Port-80 deployment could disturb unrelated containers | High | Exact port inventory and Compose project isolation | Unrelated container/port proof plus rollback rehearsal passes |

## 9. Future-self operating protocol

At the start of every work session:

1. Read this file, `docs/FEATURE_MATRIX.md`, and `PROJECT_STATUS.md`.
2. Run `git status --short --branch`; preserve unrelated/user changes.
3. Confirm the pinned upstream commits and current stage.
4. Select the smallest unblocked gate that produces testable evidence.
5. Update tests and the matrix in the same change as implementation.

Before ending a session:

1. Run the checks proportional to the change.
2. Record commands/results in `PROJECT_STATUS.md` without secrets.
3. Update stage/matrix status only when evidence exists.
4. Write the exact next action and any blocker.
5. Do not deploy an incomplete stage or replace production manually.

## 10. Immediate execution order

1. Continue the resumable public checklist in `docs/QA_LOG.md` from release `public-test-qa-stage-c4`: implement and browser-prove the missing quest log, then test shop/storage, item use/equip, audio across a map change, reconnect, and two-client presence. PIN-free login, blocked-name notice/Cancel behavior, selected-world layout, historical fresh-account PIN reconnect, shell authentication synchronization, world-selection relog, two-character navigation/deletion, initial-map loading, walking/jumping, Heena dialogue, one map transition, fullscreen HUD, key configuration, party UI, core character windows, world map, and chat are already browser-proven and should not be repeated unless affected by a change.
2. Replace the temporary Quick Tunnel with a durable trusted hostname/TLS path and prove the `99.26.147.74` router/firewall forwarding separately.
3. Close feature domains in Stages 4–6 with packet, state, render, interaction, live-server, and browser evidence.
4. Finish the Stage 7 version, schema, content-ID, representative-gameplay, and browser-delivery gates against the operator-authorized staged NX set; Moonlight v214 data is not a v83 substitute.
5. Rehearse the isolated Docker deployment, NAT/firewall, backup, rollback, and external probes before Stage 9 sign-off.
