1	# Feature Research
2
3	**Domain:** 2D top-down multiplayer online game (BN Online rebuild) — small-scale (<50 CCU), web-delivered, persistent accounts, casual movement + chat focus
4	**Researched:** 2026-05-01
5	**Confidence:** HIGH for genre table-stakes and movement-sync recommendation; MEDIUM for original-game parity feature inventory (definitive list comes from Stage 2/3 GML extraction); HIGH for anti-feature scope guardrails (already locked in `PROJECT.md` Out of Scope).
6
7	## Scope Note
8
9	The feature landscape is dual-sourced:
10
11	1. **Genre table stakes** — what any 2D persistent multiplayer game needs to feel complete (researched here from current ecosystem patterns).
12	2. **Original-game parity** — what BNO specifically had. This research treats parity inferentially from `decomp/wiki/`, `legacy/servers/*` artifacts, `,ServerCommands.txt`, `Ctrl+O Codes.txt`, and the documented save formats. The authoritative parity inventory will land in Stage 2 (client features) and Stage 3 (server features). Until then, "parity" features below are flagged with `[parity-inferred]` and may shift.
13
14	The MVP gate per `PROJECT.md` Stage 6 is **two players in the same room, see each other move, exchange chat messages**. Everything in this document is sized against that anchor.
15
16	---
17
18	## Feature Landscape
19
20	### Table Stakes (Users Expect These)
21
22	Features missing from this list = product feels broken to a returning BNO player or any modern 2D MMO player. These split into **MVP-critical** (block the Stage 6 gate) and **parity-critical** (block Stage 7 ship).
23
24	#### MVP-Critical (Stage 6 gate)
25
26	| Feature | Why Expected | Complexity | Notes |
27	|---------|--------------|------------|-------|
28	| Account creation (new) | Players need an identity that survives reconnect; anonymous-only is for arcade browser games, not persistent ones | MEDIUM (S) | bcrypt/argon2 hash, email optional for v1 (returning-player UX is username-first per `PROJECT.md` Auth decision), no email verification for MVP. Username uniqueness check. |
29	| Login (existing) | Stage 7 must accept migrated original-BNO usernames | MEDIUM (S) | Two paths: fresh accounts (bcrypt verify) and migrated accounts (transparent re-hash on first successful auth, OR forced reset — see "Account migration" decision section below). |
30	| Session token / connection auth | WebSocket connection must be tied to authenticated identity, not just an open socket | SMALL | Short-lived JWT or opaque token issued at login, presented on WS upgrade or first WS message. Server rejects unauthenticated frames. |
31	| Persistent character state | A player who logs out, then back in, expects to be where they left off (or at a sane spawn) with the same identity | MEDIUM | At minimum: position room + spawn override on login, plus character name. `[parity-inferred]` BNO `.bnu` files persist much more (see Differentiators / parity restoration below). MVP can persist only `{username, last_room, x, y}`. |
32	| Movement sync (other players visible & moving smoothly) | The whole point of multiplayer | MEDIUM | See "Movement-sync recommendation" section below. Recommendation: client-side prediction for local player + interpolation for remote players. |
33	| Server-side movement validation | Without it, anyone with browser devtools teleports / speedhacks / walks through walls | MEDIUM | Authoritative server holds ground-truth position; validates `delta_t * max_speed` per intent message; clamps to walkable tiles. Already locked as architectural decision (`PROJECT.md` Stage 4). |
34	| Basic chat (room/local channel) | Cannot have "multiplayer + chat" MVP without it | SMALL | Server broadcasts chat to all sockets in the same room. No history persistence at MVP. Plain text, length-capped (e.g., 200 chars). |
35	| Room/zone model | BNO had distinct rooms (the GameMaker `room_*` model); modern players expect zones | MEDIUM | Server tracks `players[room_id] -> Set<sessionId>`. Movement and chat scoped to room. Room transitions are explicit messages. Single-process, multi-room. See "Room/zone management" section below. |
36	| Disconnect / reconnect handling | TCP/WS drops happen constantly on real networks; players expect to drop back in without losing 30 minutes | MEDIUM | Server keeps player state in memory for N seconds (e.g., 60s grace) after socket close; reconnect within window resumes session. After grace window, force re-login but state is on disk. Broadcast leave/join only after grace expires for a clean drop. |
37	| Player nameplate / visible identity | Players can't socialize if they can't tell who is who | SMALL | Render username above sprite. Trivial once movement sync works. |
38	| Sane error UX (login failed, server down, kicked) | "Connection lost" with no message = uninstall | SMALL | Client surfaces server-emitted reason codes as user-readable strings. |
39
40	#### Parity-Critical (Stage 7) — Beyond MVP, Required For Ship
41
42	| Feature | Why Expected | Complexity | Notes |
43	|---------|--------------|------------|-------|
44	| Full character persistence | `[parity-inferred]` Original `.bnu` per-user state is rich (HXB, Inv, MB_News are documented dirs in `legacy/servers/*/UserData/`) — inventory, message-board reads, and presumably more | LARGE | Schema definition is **blocked on Stage 3 GML extraction** of `Master 5-4.gmd`. Until then, treat `.bnu` as opaque. Once schema known, define migration script from `.bnu` → SQL/JSON. |
45	| All original rooms / world map | Returning players expect to recognize the world | LARGE | Room layouts extracted in Stage 1, ported to client tilemaps in Stage 6/7. Not a server concern beyond room-id registry. |
46	| All original sprites + animations | Same — recognizability is the parity bar | LARGE | Asset pipeline. Client-side. |
47	| Server-side admin commands (kick, mute, ban) | `[parity-inferred]` `,ServerCommands.txt` documents `Ctrl+E` clipboard exec, `Ctrl+Q` variable inspect, `Ctrl+???` client-side exec | MEDIUM | Modernized: replace clipboard-exec-as-superuser with a vetted command set callable from an admin auth scope. **Do not port the original RCE-by-design model** (flagged in `CONCERNS.md` as wide-open RCE). |
48	| Message board / persistent posting | `[parity-inferred]` `MB_News`, `MB_Log.bnb` exist in every server snapshot — the game had a persistent message board | MEDIUM | Schema TBD pending Stage 3. Database-backed in rebuild (not flat-file). |
49	| Per-user settings persisted | `[parity-inferred]` `Settings.bno` exists; original game had server-side user settings | SMALL once schema known | Same blocker as `.bnu` — needs Stage 3 extraction. |
50	| Server-authoritative game-state events | Whatever the original game did beyond "walk + chat" (combat? trades? NPCs?) belongs server-side | LARGE | Scope unknown until Stage 3. The `Crasher.exe` and `Server Saver.exe` artifacts in `legacy/servers/` hint at server lifecycle features (state snapshot, recovery) that need cataloguing. |
51
52	### Differentiators (Quality / Competitive Advantage)
53
54	These are the "feels modern, not just faithful" upgrades. They map to "modernized UX" in `PROJECT.md` Stage 6.
55
56	| Feature | Value Proposition | Complexity | Notes |
57	|---------|-------------------|------------|-------|
58	| Friends list | Persistent social graph; lets players find each other across sessions instead of yelling in chat | MEDIUM | Add table `friendships(user_a, user_b, status, created_at)`. Symmetric. UI surface: list with online/offline status. **Depends on**: account system (table-stakes). |
59	| Online/offline presence | Without it, the friends list is just a list of strings | SMALL | Server tracks `online_users: Set<userId>`. Friends list query joins against it. Push presence change events to friends. |
60	| Private messages (whisper / DM) | Lets friends talk across rooms; current chat is room-scoped | SMALL once chat works | Same chat-message infra, addressed by recipient userId instead of broadcast. Persist to DB if offline-message support is wanted (next row). |
61	| Offline message delivery | Send a DM to a friend who's offline; they see it on next login | MEDIUM | `messages(from, to, body, sent_at, read_at)` table. Needs a TTL/cap policy (e.g., 100 messages or 30 days) to bound storage. |
62	| Ignore / block list | Lets users self-moderate; reduces moderator load | SMALL | Per-user `blocks(blocker, blocked)` table. Server filters chat/whispers/visibility based on blocker's list. Cheaper than profanity filtering for most user-vs-user friction. |
63	| Player report mechanism | Lets the community surface bad actors without admin attention required for every incident | MEDIUM | Reports persist with a snippet of recent chat / context. Admin queue in admin tool. **Depends on**: chat history retention (even if just a rolling 100-line buffer). |
64	| Moderation tools (mute / kick / ban) for non-server-console admins | The original game required physical/RDP server console access (huge security/scaling problem) | MEDIUM | Web-based admin UI gated on admin role flag. Granularity: per-user mute (chat only), kick (forces disconnect), ban (refuses login by username + optional IP). |
65	| Chat history per room (recent N lines) | Players who join mid-conversation can see context | SMALL | In-memory rolling buffer per room, e.g., last 50 lines, broadcast as a single payload on room enter. Optional DB persistence for moderation review. |
66	| Account recovery (email-based reset) | Without it, every forgotten password is a manual admin task — does not scale even at 50 CCU if churn is high | MEDIUM | Requires email-on-account (optional at signup, but pushed for migrated accounts). SMTP via a transactional provider (Resend/Postmark/SES). One-time signed reset token. |
67	| Account-level settings (audio, keybinds, accessibility) | Modern players expect to rebind keys, mute SFX, adjust UI scale | MEDIUM | Stored per-user; client fetches on login. Most settings are client-only and don't need server validation. |
68	| In-game UI polish (HiDPI, responsive layout, decent typography) | Already a `PROJECT.md` constraint ("Modernized UX (HiDPI, responsive, keyboard + gamepad input)") | MEDIUM | Phaser 3 / Pixi handle DPR; the work is mostly choosing to do it from day one rather than retrofitting. |
69	| Emotes / canned animations | Cheap social signaling; classic 2D MMO staple | SMALL | New packet type `EMOTE`, server broadcasts to room. Client plays sprite animation. **Depends on**: per-character animation system. |
70	| Party / grouping (named groups across rooms) | Lets friends play together when split across rooms | LARGE | Group state, group chat channel, group-leave/join semantics, leadership transfer, persistence across disconnect. **Defer past v1** — friends list + whispers covers the social need at <50 CCU scale. |
71	| Web-native delivery (no install, share-a-URL) | The biggest single UX win over the original; original required Win32 binary + 39dll + manual server connect | SMALL (it's a consequence of the stack choice, not a feature to build) | Already locked in `PROJECT.md` (Chrome desktop, Vite client). Worth calling out because it's a major differentiator vs. the original. |
72
73	### Anti-Features (Out of Scope For v1)
74
75	Most of these are already locked in `PROJECT.md` "Out of Scope". Documented here to surface the *why* and the alternative.
76
77	| Feature | Why Requested | Why Problematic | Alternative |
78	|---------|---------------|-----------------|-------------|
79	| Voice chat | "Modern multiplayer has voice" | Adds WebRTC infra (TURN/STUN, signaling, peer mesh or SFU), moderation burden 10x text (per [getstream voice moderation guide](https://getstream.io/blog/audio-voice-moderation/)), and a privacy/PII surface that doesn't fit a <50 CCU friends-and-test scope. ESRB now audits voice for compliance starting Aug 2025. | Text chat only. Players who want voice use Discord. |
80	| Mobile clients (iOS / Android native) | "Reach more players" | Tripled platform-test surface; each native runtime needs its own input model, UI scaling, and store compliance. Already excluded in `PROJECT.md`. | Chrome desktop only for v1. Mobile browser is best-effort, not blocking. |
81	| ML-based anti-cheat (behavior detection) | "Cheaters ruin online games" | At <50 CCU on a friends-and-test scope, the cost-benefit is wildly inverted — model training data doesn't exist, false-positive rate is unacceptable for a small community, and it doesn't address the actual cheats people will try. Already excluded. | Authoritative server (already locked) + per-input bounds checks + manual ban for confirmed cheaters. |
82	| Monetization (cosmetics, battle pass, ads) | "Need revenue" | Adds payment infra (Stripe + tax), age gates, refund logic, and changes the moral relationship with returning players who paid nothing for the original. | Free, no monetization for v1. Donation link if hosting cost becomes real. |
83	| Modding / user-generated content API | "Long tail content" | Sandboxing user code in the server is a research project; UGC moderation queue is a full-time job; backwards-compat constraints lock the rebuild before parity is even reached. Already excluded. | Defer indefinitely. If demanded post-parity, scope as a separate project. |
84	| 3D / VR | "Modern visuals" | Faithful 2D rebuild is the entire point — going 3D is a different game. Already excluded. | Stay 2D. |
85	| Email verification at signup (MVP) | "Prevent throwaway accounts" | Adds blocking SMTP dependency at MVP; at <50 CCU friend-and-test scope, the abuse vector isn't real yet | Make email optional at signup, required only if user opts into account recovery later. |
86	| Real-time everything (e.g., live presence updates streamed at 60Hz) | "Feels alive" | Wastes bandwidth + server CPU on info no one consumes; chat doesn't need <100ms latency | Push presence/friends-list updates as discrete events, not streams. Movement is the only true real-time channel. |
87	| Server-side replays / spectator mode | "Esports table stakes" | The original is not an esport. Recording infra, storage cost, replay-deterministic-engine constraints all real. | Skip unless concrete demand emerges post-parity. |
88	| Cross-server / federation | "Bigger world" | Not needed below sharding threshold; see "Room/zone management" below. Single Fly machine is the explicit `PROJECT.md` v1 scale. | Single server. Revisit if CCU sustainably exceeds ~200. |
89	| Public account self-service username changes | "Quality-of-life" | Breaks identity continuity for the migrated original-BNO users — the entire "preserve usernames" decision is undone if anyone can rename. Also breaks friends list / message history references. | Display name (cosmetic, changeable) layered on top of immutable username. Defer past v1. |
90	| Aggressive AI profanity filter | "Toxicity is bad" | Matches generated by AI filters at MVP scale produce more false positives than catches; per [WebPurify gaming guide](https://www.webpurify.com/gaming/) and [Greip blog](https://greip.io/blog/From-Toxic-to-Terrific-A-Guide-to-Integrating-Profanity-Filters-in-Online-Gaming-247) effective filters need ongoing wordlist tuning that doesn't justify itself for friends-and-test scope. | Simple wordlist filter (toggleable per-user) + ignore/block list + report mechanism + admin mute. AI-based moderation can be layered in later if community grows. |
91
92	---
93
94	## Movement-Sync Recommendation
95
96	**Recommendation: client-side prediction for the local player + entity interpolation for remote players, with server reconciliation. Authoritative server. NO lockstep.**
97
98	### Rationale for the BNO rebuild's specific shape
99
100	Three constraints drive this:
101
102	1. **Casual 2D top-down movement.** Not a competitive shooter, not an RTS. Sub-frame tick precision is not needed; "remote players are visibly ~100ms in the past" is invisible at this game's pace.
103	2. **<50 CCU on a single Fly.io machine.** Server CPU and bandwidth budgets are generous per-player; we can afford to validate every input.
104	3. **WebSocket transport.** Reliable ordered TCP-like — no UDP for this stack — which removes the need for the more complex sequence-number / packet-loss handling that FPS-grade netcode requires.
105
106	### Why each technique is included
107
108	- **Client-side prediction (local player only).** Without it, the local player feels laggy on every input — keypress → wait for round-trip → see movement. Even at 50–100ms RTT this is unbearable for a top-down game where the player presses an arrow key and expects instant motion. Per [Gabriel Gambetta's reference series](https://www.gabrielgambetta.com/client-side-prediction-server-reconciliation.html) and [KinematicSoup](https://kinematicsoup.com/news/2017/5/30/multiplayerprediction), this is industry-standard for any responsiveness-sensitive multiplayer.
109	- **Server reconciliation.** When the server's authoritative position diverges from the client's prediction (because the server clamped a wall hit, or the player got hit by a server-side event), the client smoothly snaps back. Implementation: client keeps a buffer of recent inputs with monotonic input IDs; server acknowledges with the input ID it last processed and the resulting authoritative position; client replays inputs after that ID against the server-corrected position.
110	- **Entity interpolation (remote players only).** Render remote players ~100–150ms behind the latest received state, interpolating between the two most recent server snapshots. Per [Gambetta's interpolation article](https://www.gabrielgambetta.com/entity-interpolation.html), this trades a small visual delay for completely smooth movement that handles dropped/late updates gracefully. No client-side prediction for remote entities — we don't try to predict where another player will move.
111
112	### Why NOT lockstep
113
114	Lockstep (every client simulates identically from a shared input stream) is great for RTS / fighting games where determinism matters and player count is small + fixed. For an MMO-style room with players joining/leaving, variable latency, and no need for frame-perfect replay, lockstep adds rigid sync requirements without payoff. Skip.
115
116	### Why NOT interpolation-only (no prediction)
117
118	Would work — and is genuinely simpler — *if* the local player's input latency was acceptable. At 80ms RTT on a typical Fly.io edge connection, the local player sees an 80ms delay between keypress and movement on screen. For a top-down WASD/arrow-key game that's noticeable and bad. Adding prediction is ~2 days of work and pays back forever.
119
120	### Tick rate
121
122	- **Server tick: 20 Hz (50ms).** Standard for non-FPS multiplayer. Sufficient for casual movement.
123	- **Client send rate: 20 Hz** (one input/intent message per server tick). Client batches keys-held-this-tick into one message.
124	- **Server broadcast rate: 20 Hz** authoritative positions per room.
125	- **Client render rate: 60 Hz** (or display Hz), interpolating between the two most recent server snapshots.
126
127	At 50 CCU split across rooms with maybe 10 players in the busiest room, broadcast cost = 10 players × 10 visible peers × 20 Hz × ~24 bytes per position update = ~48 KB/s outbound for that room. Trivial.
128
129	### Movement-sync feature dependencies
130
131	- Requires: server-side validation (table stakes), authoritative server model (locked).
132	- Enables: collision detection on server, zone-based culling (don't broadcast players in other rooms).
133	- Enabled by: WebSocket binary frames (locked), shared TS packet types between client and server.
134
135	---
136
137	## Chat Pattern Recommendation
138
139	### Channel model
140
141	Three channels at parity (one at MVP):
142
143	1. **Local / room** — broadcast to all players in the same room. **MVP: this only.**
144	2. **Whisper / DM** — direct user-to-user, addressed by username. Routed even across rooms.
145	3. **System / server** — server-emitted notifications (player joined, kicked, server restart in 5min). One-way, not user-sendable.
146
147	Defer global / world channel past v1 — it's a moderation cost multiplier with little gain at <50 CCU.
148
149	### Rate limiting
150
151	Per `getstream.io` and `WebPurify` chat-moderation references, the right pattern is per-user token bucket:
152
153	- **5 messages per 5 seconds per user**, burst-allowed up to ~10. Slow-mode on top: minimum 1s between messages in the same channel.
154	- Server enforces; client soft-enforces (greys out send button) to avoid surprising the user.
155	- Violation response: silently drop the message, send back a reason code (`RATE_LIMITED`).
156
157	Whisper rate limit is stricter (e.g., 3/10s) because it bypasses room-bound visibility and is a higher abuse vector.
158
159	### Profanity filter
160
161	**MVP: none.** Use ignore/block + report instead.
162
163	**Parity: simple wordlist + per-user toggle.**
164
165	- Server-side wordlist of ~100 common slurs + obvious variants. Substring match with word boundaries.
166	- Per-user toggle in account settings (default ON for room chat, OFF for whispers — whispers are consensual).
167	- Filtered messages render with replacement (`****`), not blocked entirely, so context is preserved.
168	- AI-based filtering deferred — see Anti-Features. Rebuild can integrate WebPurify / GGWP / Greip later if community grows past the friends-and-test scale.
169
170	### History persistence
171
172	- **In-memory rolling buffer per room: last 50 messages.** Sent to client on room enter so they don't see an empty channel.
173	- **DB persistence for whispers if offline delivery is enabled.** TTL 30 days.
174	- **DB persistence for room chat ONLY for moderation review.** Last 24h, accessible to admin tools, auto-purged. Never exposed to players.
175	- **Logs are PII.** Treat per the lessons in `CONCERNS.md`: never commit, encrypt at rest, access-controlled.
176
177	### Chat feature dependencies
178
179	- Requires: account system (sender identity), session auth (prevent impersonation), room model (scoping room channel).
180	- Enables: report mechanism, moderation, whispers, social signals.
181	- Enabled by: rate limiter, profanity wordlist (parity, not MVP).
182
183	---
184
185	## Room/Zone Management Recommendation
186
187	### Architecture
188
189	**Single Node process, multi-room, all rooms in-memory.** No sharding at v1.
190
191	```
192	NodeServer
193	├── RoomRegistry
194	│   ├── Room "town_square"  → { players: Set, tickLoop, chatBuffer }
195	│   ├── Room "shop_area"    → { players: Set, tickLoop, chatBuffer }
196	│   ├── Room "battle_zone"  → { players: Set, tickLoop, chatBuffer }
197	│   └── ...
198	├── SessionRegistry → { sessionId → { userId, ws, currentRoom } }
199	└── DB (SQLite/Postgres, decision deferred per PROJECT.md)
200	```
201
202	Rooms are not separate processes — that's premature complexity for <50 CCU. One Node event loop handles all rooms; the 20Hz tick per room is cheap.
203
204	### Room transitions
205
206	- Player sends `ENTER_ROOM { room_id }` (with a server-side gate: must be reachable from current room, or it's a teleport-class action requiring server consent).
207	- Server: removes from old room's player set, adds to new, broadcasts `PLAYER_LEFT` to old room and `PLAYER_JOINED` to new, sends new room's chat buffer + initial player snapshot to the entering player.
208	- Persistence: `last_room` field updated atomically.
209
210	### Sharding triggers — when to revisit
211
212	The single-process model holds until **one or more** of these is true:
213
214	- **CCU sustainably > ~200.** At this point Node event-loop latency from per-tick broadcast starts being measurable and you want process-per-region.
215	- **Single most-popular room > ~50 concurrent players.** Bandwidth from "everyone-sees-everyone" position broadcasts becomes O(n²) per tick.
216	- **CPU profiling shows the tick loop blocking > 5ms.** Gives the green light for splitting into worker threads or processes.
217	- **Multi-region requirement.** Latency to Asia/EU users from one Fly region becomes a complaint. Solve with regional Fly machines + cross-region presence sync — but that's a v2+ problem.
218
219	None of these are remotely close at v1's <50 CCU friend-and-test scope. Fly.io single machine is the right answer per `PROJECT.md` Constraints.
220
221	### Room/zone feature dependencies
222
223	- Requires: persistence layer (for `last_room`), session registry, account system.
224	- Enables: scoped chat, scoped movement broadcasts, world-map navigation, per-room moderation rules.
225	- Enabled by: server tick architecture, WebSocket session routing.
226
227	---
228
229	## Account Migration UX Recommendation
230
231	The original `localList.txt` / `remoteList.txt` files contain ~298 plaintext (username, password) pairs (per `CONCERNS.md`). These are PII and **must never leave a local-only environment unredacted**. The migration design has to respect that.
232
233	### Recommended approach: **transparent re-hash on first successful login, with a forced reset escape hatch.**
234
235	1. **Pre-migration (offline, local-only):**
236	   - Read `localList.txt` (the more recent "remote" list per the 2021 provenance note).
237	   - For each `(username, password)` row: compute `bcrypt(password)` and store `(username, bcrypt_hash, original_hash_format='plaintext_migrated', migrated_at)` in the new DB.
238	   - The plaintext password is **never written to the new DB** — only the bcrypt hash.
239	   - Source `localList.txt` is then deleted from the migration host (or re-redacted to `[REDACTED]` per `CONCERNS.md` recommendation).
240
241	2. **At first login (returning player):**
242	   - User enters their original username + password.
243	   - Server bcrypt-verifies. Success → log them in, set `migrated_at_login=now()`, optionally surface a one-time toast: "Your account was migrated from BNO classic. We've upgraded the security on your password — no action needed. You can change your password anytime in Settings."
244	   - Failure → standard "wrong password" path. (We are NOT keeping the plaintext, so we cannot help them recover the original password — but they can use email-recovery if they registered an email, or admin-assisted reset.)
245
246	3. **Forced-reset escape hatch:**
247	   - If a returning user logs in successfully but the original password fails our modern strength rule (e.g., < 8 chars, top-1000 common password — many of the original passwords in `CONCERNS.md` examples qualify: `123456`, `harrypotter`), prompt for a new password before allowing in. Do NOT block them — let them choose a new one inline.
248	   - Rationale: many original passwords are weak by current standards. We migrate identity (the username) without forcing migration of weak credentials.
249
250	4. **Email collection (optional, post-MVP):**
251	   - First login does not require email. User can add one later for recovery purposes.
252	   - When they do, send a verification link before the email is usable for recovery.
253
254	### Why this UX wins
255
256	- **No "click this link in your email" gate before they can play.** Many of the original players are 10+ years gone; their old emails may be dead. Forcing email verification before first login orphans them.
257	- **Username preserved** — addresses the `PROJECT.md` "preserve identity continuity for returning players" decision.
258	- **Modernizes the hash silently** — no scary "your password was insecure" lecture, just a silent re-hash. The "we upgraded security" toast is informative, not blame-shifting.
259	- **Weak-password remediation happens at the most teachable moment** (post-login, in-context).
260	- **Survives the migration database being lost.** Bcrypt hashes are derivative; if we lose them we can re-run the migration from a fresh redaction-pass on `localList.txt` if the source artifact is preserved. (The source `localList.txt` files in `legacy/servers/*` are exactly that backup — see `CONCERNS.md` for the inventory and PII handling requirements.)
261
262	### Migration feature dependencies
263
264	- Requires: bcrypt/argon2 infrastructure, DB user table, login flow.
265	- Enables: returning-player re-engagement (the entire reason for the rebuild per `PROJECT.md` Core Value).
266	- Blocked by: nothing technical — but operationally requires a one-time, isolated migration host that handles the plaintext source list.
267
268	---
269
270	## Feature Dependencies
271
272	```
273	Account creation ──┬──> Login ──┬──> Session token
274	                   │            │       │
275	                   │            │       └──> Authenticated WS connection
276	                   │            │              │
277	                   │            │              ├──> Chat (any channel)
278	                   │            │              ├──> Movement (intent → server validate)
279	                   │            │              ├──> Persistent character state
280	                   │            │              └──> Room transitions
281	                   │            │
282	                   │            └──> Account migration (returning users)
283	                   │
284	                   ├──> Account settings (audio, keybinds, etc.)
285	                   └──> Email recovery (optional, post-MVP)
286
287	Movement (server-validated)
288	   ├──requires──> Authoritative server tick loop
289	   ├──requires──> Room model (scope broadcasts)
290	   ├──enables──> Collision (server-side)
291	   └──enables──> Visible remote players (interpolation)
292
293	Chat (room channel)
294	   ├──requires──> Authenticated session
295	   ├──requires──> Room model
296	   ├──requires──> Rate limiter
297	   ├──enables──> Whispers (DMs)
298	   ├──enables──> Reports (uses chat history snapshots)
299	   └──enables──> Mute (admin action on chat)
300
301	Friends list ──requires──> Account system
302	   └──enables──> Online presence + Whisper-from-friends-list
303
304	Moderation
305	   ├──requires──> Account system (target identity)
306	   ├──requires──> Admin role flag (server-side)
307	   ├──requires──> Chat history (for evidence)
308	   └──provides──> Mute / Kick / Ban (each with their own server enforcement path)
309
310	Persistence layer
311	   ├──required-by──> EVERYTHING that survives reconnect
312	   └──blocked-by──> Stage 3 schema decision (SQLite vs Postgres)
313
314	Full character persistence (.bnu parity)
315	   └──blocked-by──> Stage 3 GML extraction (.bnu schema unknown until then)
316	```
317
318	### Dependency notes
319
320	- **Persistent character state (full parity) is blocked on Stage 3.** Until the original `.bnu` schema is extracted, only minimal state (`{username, last_room, x, y}`) can be persisted at MVP. This is enough for the MVP gate but not for Stage 7. **Implication for roadmap: the parity-feature inventory cannot be finalized until Stage 3 completes.**
321	- **Friends list, whispers, reports all require account system + chat infra to be solid.** Chase MVP first, layer these as a "social" milestone after.
322	- **Moderation tools MUST exist before any non-friend user is invited.** The original game's "Ctrl+E run clipboard as superuser" approach is a security nightmare flagged in `CONCERNS.md`. Build modern admin commands as part of the same milestone that opens signups beyond friends.
323	- **Rate limiter is an MVP+1 feature.** At MVP (two friends testing) it's not enforced; first time real users join, it must be on. Don't ship a non-rate-limited chat to the public.
324	- **Email recovery's hard dependency is SMTP infra**, not code. Choose a provider (Resend / Postmark / SES) at the time you decide to build recovery — not earlier.
325
326	---
327
328	## MVP Definition
329
330	### Launch With (v1 — Stage 6 MVP gate)
331
332	The strict reading of `PROJECT.md` Stage 6: "two players join the same room, see each other move, exchange chat messages."
333
334	- [ ] Account creation (new accounts only — migration can wait a beat after MVP if it streamlines the gate)
335	- [ ] Login + session token issuance
336	- [ ] Persistent username (DB-backed, bcrypt hash)
337	- [ ] WebSocket connection authenticated by session token
338	- [ ] Single room (start with one — MVP doesn't need the world map yet)
339	- [ ] Movement: client-side prediction for local + interpolation for remote + server reconciliation + server-side validation
340	- [ ] Chat: room/local channel, server broadcasts to all in room
341	- [ ] Player nameplate visible above sprite
342	- [ ] Disconnect/reconnect handling with grace window
343	- [ ] Sane error messages (login failure, server down, kicked)
344	- [ ] One placeholder sprite, one placeholder room — no asset parity required at MVP
345
346	That's it. **Not** at MVP: friends list, whispers, moderation, full parity rooms, full sprite library, message board, account migration UI, chat history persistence, profanity filter, email recovery.
347
348	### Add After Validation (v1.x — between MVP and full parity ship)
349
350	Triggered by: MVP runs reliably for two test users for a week without intervention.
351
352	- [ ] Account migration flow for returning BNO usernames (bcrypt re-hash on first login)
353	- [ ] Multi-room support with room-transition mechanics
354	- [ ] Player report mechanism + admin queue
355	- [ ] Chat rate limiting (turn it on before any non-tester joins)
356	- [ ] Ignore/block list
357	- [ ] Friends list + online presence
358	- [ ] Whisper / DM
359	- [ ] Wordlist profanity filter (toggleable per-user)
360	- [ ] Modernized admin tools (web UI for kick/mute/ban — replacing the original RCE-style server console)
361	- [ ] Account settings (audio, keybinds, accessibility)
362	- [ ] Account recovery (email-based reset)
363	- [ ] Chat history (rolling buffer per room, sent on enter)
364	- [ ] Session reconnect across browser refresh (grace window, no re-login)
365
366	### Future Consideration (Stage 7 full parity, then beyond)
367
368	Triggered by: Stage 3 GML extraction completes; parity feature inventory becomes definitive.
369
370	- [ ] Full character state persistence per `.bnu` schema (TBD post-Stage 3)
371	- [ ] All original rooms / world map ported
372	- [ ] All original sprites + animations
373	- [ ] Message board (`MB_News`, `MB_Log`) reimplemented against modern DB
374	- [ ] Per-user settings persistence (`Settings.bno` parity, schema TBD post-Stage 3)
375	- [ ] Whatever gameplay-specific features Stage 3 surfaces (combat? trades? NPCs? — currently unknown)
376	- [ ] Offline message delivery (whispers persist for offline recipients)
377	- [ ] Emotes
378	- [ ] Party / grouping (only if friends list + whispers prove insufficient — likely defer)
379
380	### Beyond v1 — Explicitly Out (and staying out)
381
382	Per `PROJECT.md` Out of Scope and the Anti-Features section above: voice chat, mobile native, ML anti-cheat, monetization, modding API, 3D/VR, server replays, federation, AI profanity filtering, public username changes.
383
384	---
385
386	## Feature Prioritization Matrix
387
388	| Feature | User Value | Implementation Cost | Priority |
389	|---------|------------|---------------------|----------|
390	| Account creation + login | HIGH | MEDIUM | P1 |
391	| Session-authenticated WS | HIGH | LOW | P1 |
392	| Movement sync (prediction + interpolation) | HIGH | MEDIUM | P1 |
393	| Server-side movement validation | HIGH | MEDIUM | P1 |
394	| Room/local chat | HIGH | LOW | P1 |
395	| Single room model | HIGH | LOW | P1 |
396	| Disconnect/reconnect handling | HIGH | MEDIUM | P1 |
397	| Persistent username + min character state | HIGH | LOW | P1 |
398	| Player nameplates | MEDIUM | LOW | P1 |
399	| Sane error UX | HIGH | LOW | P1 |
400	| Account migration (returning players) | HIGH | MEDIUM | P2 |
401	| Multi-room + transitions | HIGH | MEDIUM | P2 |
402	| Chat rate limiting | MEDIUM | LOW | P2 (must precede public access) |
403	| Friends list + presence | MEDIUM | MEDIUM | P2 |
404	| Whispers (DMs) | MEDIUM | LOW | P2 |
405	| Ignore/block | MEDIUM | LOW | P2 |
406	| Player report mechanism | MEDIUM | MEDIUM | P2 |
407	| Admin tools (kick/mute/ban) | HIGH (operationally) | MEDIUM | P2 (must precede public access) |
408	| Wordlist profanity filter | MEDIUM | LOW | P2 |
409	| Account recovery (email reset) | MEDIUM | MEDIUM | P2 |
410	| Chat history (rolling buffer) | MEDIUM | LOW | P2 |
411	| Account-level settings | MEDIUM | MEDIUM | P2 |
412	| Full `.bnu` parity | HIGH (for returning players) | LARGE | P3 (blocked on Stage 3) |
413	| All original rooms + sprites | HIGH (parity) | LARGE | P3 |
414	| Message board | MEDIUM | MEDIUM | P3 |
415	| Emotes | LOW | LOW | P3 |
416	| Offline message delivery | LOW | MEDIUM | P3 |
417	| Party / grouping | LOW | LARGE | P3 (defer indefinitely) |
418	| Voice chat | LOW (in this game's scope) | LARGE | OUT |
419	| Mobile native | LOW (in v1) | LARGE | OUT |
420	| ML anti-cheat | LOW | LARGE | OUT |
421	| Monetization | LOW | LARGE | OUT |
422	| Modding API | LOW | LARGE | OUT |
423	| 3D / VR | LOW | LARGE | OUT |
424
425	**Priority key:**
426	- P1: MVP gate (Stage 6)
427	- P2: Required before public access / full parity ship (Stage 7)
428	- P3: Parity-driven, scope confirmed by Stage 3 extraction
429	- OUT: Anti-feature, locked out per `PROJECT.md`
430
431	---
432
433	## Competitor / Reference Analysis
434
435	The "competitors" here are reference points rather than market rivals — this is a faithful rebuild of a specific game, not a market product. References inform the modernization layer.
436
437	| Feature | BN Online (original) | Generic 2D MMO (e.g., Habbo / Tibia / browser-MMO patterns) | Our Approach |
438	|---------|----------------------|----------------------------------------|--------------|
439	| Auth | Plaintext passwords in `localList.txt`; no hashing | bcrypt/argon2, optional 2FA | bcrypt/argon2 with username preservation + transparent re-hash on first login |
440	| Transport | 39dll over raw TCP, no encryption | TLS WebSocket | WSS (WebSocket Secure) — TLS terminated at Fly.io edge |
441	| Movement | `[parity-inferred]` Authoritative server with `writebyte`/`writedouble` move packets per `decomp/wiki/08-39dll-networking.md` | Mix of authoritative + client-prediction depending on game | Authoritative server + client-side prediction + interpolation |
442	| Chat | `[parity-inferred]` Room-broadcast chat, no persistence beyond `MB_Log.bnb` board | Multi-channel (local/global/whisper), AI-moderated, rate-limited | Room channel at MVP; whispers + system + wordlist filter at parity |
443	| Admin | "Ctrl+E run clipboard as superuser" RCE-by-design (`,ServerCommands.txt`) | Web admin panel with role-gated commands | Web admin panel, role-gated, no clipboard exec |
444	| Friends list | None apparent in legacy sources | Standard | Add as differentiator |
445	| Account migration | N/A | N/A | Custom — silent re-hash, weak-password upgrade prompt |
446	| Modding | None | Some games support; most do not | Out of scope |
447	| Hosting | Single home/colo Windows machine | Cloud (AWS/GCP/Fly/Railway) | Fly.io single machine (`PROJECT.md` locked) |
448	| Persistence | Bespoke `.bno`/`.bnu`/`.bnb` flat files | SQL or NoSQL | SQLite + Litestream OR Postgres (deferred per `PROJECT.md`) |
449
450	---
451
452	## Sources
453
454	- `C:\Users\decid\Documents\projects\rebno\.planning\PROJECT.md` — Stages, constraints, Out of Scope, Auth/Hosting decisions (HIGH confidence: project-internal source of truth)
455	- `C:\Users\decid\Documents\projects\rebno\.planning\codebase\CONCERNS.md` — PII handling for `localList.txt`, original RCE-by-design admin model, plaintext credential inventory (HIGH confidence)
456	- `C:\Users\decid\Documents\projects\rebno\decomp\wiki\08-39dll-networking.md` — Original transport pattern, "structure = call order" packet semantics (HIGH confidence)
457	- `C:\Users\decid\Documents\projects\rebno\decomp\wiki\16-bno-bnb-notes.md` — `.bno`/`.bnb`/`.bnu` parsing strategy, Stage 3 dependency (HIGH confidence)
458	- [Gabriel Gambetta — Client-Side Prediction and Server Reconciliation](https://www.gabrielgambetta.com/client-side-prediction-server-reconciliation.html) — Reference for MVP movement netcode (HIGH confidence: industry-standard reference series)
459	- [Gabriel Gambetta — Entity Interpolation](https://www.gabrielgambetta.com/entity-interpolation.html) — Reference for remote-player smoothing
460	- [KinematicSoup — Client-side Prediction for Smooth Multiplayer Gameplay](https://kinematicsoup.com/news/2017/5/30/multiplayerprediction) — Cross-source confirmation
461	- [GetStream — Chat Moderation 101](https://getstream.io/blog/chat-moderation/) — Rate limiting + moderation patterns (MEDIUM confidence: vendor blog, but methodology is industry-standard)
462	- [WebPurify — Moderating Gaming Websites and Apps](https://www.webpurify.com/gaming/) — Profanity filter approach
463	- [Greip — Integrating Profanity Filters in Online Gaming](https://greip.io/blog/From-Toxic-to-Terrific-A-Guide-to-Integrating-Profanity-Filters-in-Online-Gaming-247) — Cross-source on filter limitations at small scale
464	- [GetStream — Audio and Voice Moderation Implementation Guide](https://getstream.io/blog/audio-voice-moderation/) — Rationale for excluding voice from v1
465
466	---
467	*Feature research for: 2D top-down multiplayer online game (BN Online rebuild)*
468	*Researched: 2026-05-01*
469

# Feature Research

**Domain:** 2D top-down multiplayer online game (BN Online rebuild) — small-scale (<50 CCU), web-delivered, persistent accounts, casual movement + chat focus
**Researched:** 2026-05-01
**Confidence:** HIGH for genre table-stakes and movement-sync recommendation; MEDIUM for original-game parity feature inventory (definitive list comes from Stage 2/3 GML extraction); HIGH for anti-feature scope guardrails (already locked in `PROJECT.md` Out of Scope).

## Scope Note

The feature landscape is dual-sourced:

1. **Genre table stakes** — what any 2D persistent multiplayer game needs to feel complete (researched here from current ecosystem patterns).
2. **Original-game parity** — what BNO specifically had. This research treats parity inferentially from `decomp/wiki/`, `legacy/servers/*` artifacts, `,ServerCommands.txt`, `Ctrl+O Codes.txt`, and the documented save formats. The authoritative parity inventory will land in Stage 2 (client features) and Stage 3 (server features). Until then, "parity" features below are flagged with `[parity-inferred]` and may shift.

The MVP gate per `PROJECT.md` Stage 6 is **two players in the same room, see each other move, exchange chat messages**. Everything in this document is sized against that anchor.

---

## Feature Landscape

### Table Stakes (Users Expect These)

Features missing from this list = product feels broken to a returning BNO player or any modern 2D MMO player. These split into **MVP-critical** (block the Stage 6 gate) and **parity-critical** (block Stage 7 ship).

#### MVP-Critical (Stage 6 gate)

| Feature | Why Expected | Complexity | Notes |
|---------|--------------|------------|-------|
| Account creation (new) | Players need an identity that survives reconnect; anonymous-only is for arcade browser games, not persistent ones | MEDIUM (S) | bcrypt/argon2 hash, email optional for v1 (returning-player UX is username-first per `PROJECT.md` Auth decision), no email verification for MVP. Username uniqueness check. |
| Login (existing) | Stage 7 must accept migrated original-BNO usernames | MEDIUM (S) | Two paths: fresh accounts (bcrypt verify) and migrated accounts (transparent re-hash on first successful auth, OR forced reset — see "Account migration" decision section below). |
| Session token / connection auth | WebSocket connection must be tied to authenticated identity, not just an open socket | SMALL | Short-lived JWT or opaque token issued at login, presented on WS upgrade or first WS message. Server rejects unauthenticated frames. |
| Persistent character state | A player who logs out, then back in, expects to be where they left off (or at a sane spawn) with the same identity | MEDIUM | At minimum: position room + spawn override on login, plus character name. `[parity-inferred]` BNO `.bnu` files persist much more (see Differentiators / parity restoration below). MVP can persist only `{username, last_room, x, y}`. |
| Movement sync (other players visible & moving smoothly) | The whole point of multiplayer | MEDIUM | See "Movement-sync recommendation" section below. Recommendation: client-side prediction for local player + interpolation for remote players. |
| Server-side movement validation | Without it, anyone with browser devtools teleports / speedhacks / walks through walls | MEDIUM | Authoritative server holds ground-truth position; validates `delta_t * max_speed` per intent message; clamps to walkable tiles. Already locked as architectural decision (`PROJECT.md` Stage 4). |
| Basic chat (room/local channel) | Cannot have "multiplayer + chat" MVP without it | SMALL | Server broadcasts chat to all sockets in the same room. No history persistence at MVP. Plain text, length-capped (e.g., 200 chars). |
| Room/zone model | BNO had distinct rooms (the GameMaker `room_*` model); modern players expect zones | MEDIUM | Server tracks `players[room_id] -> Set<sessionId>`. Movement and chat scoped to room. Room transitions are explicit messages. Single-process, multi-room. See "Room/zone management" section below. |
| Disconnect / reconnect handling | TCP/WS drops happen constantly on real networks; players expect to drop back in without losing 30 minutes | MEDIUM | Server keeps player state in memory for N seconds (e.g., 60s grace) after socket close; reconnect within window resumes session. After grace window, force re-login but state is on disk. Broadcast leave/join only after grace expires for a clean drop. |
| Player nameplate / visible identity | Players can't socialize if they can't tell who is who | SMALL | Render username above sprite. Trivial once movement sync works. |
| Sane error UX (login failed, server down, kicked) | "Connection lost" with no message = uninstall | SMALL | Client surfaces server-emitted reason codes as user-readable strings. |

#### Parity-Critical (Stage 7) — Beyond MVP, Required For Ship

| Feature | Why Expected | Complexity | Notes |
|---------|--------------|------------|-------|
| Full character persistence | `[parity-inferred]` Original `.bnu` per-user state is rich (HXB, Inv, MB_News are documented dirs in `legacy/servers/*/UserData/`) — inventory, message-board reads, and presumably more | LARGE | Schema definition is **blocked on Stage 3 GML extraction** of `Master 5-4.gmd`. Until then, treat `.bnu` as opaque. Once schema known, define migration script from `.bnu` → SQL/JSON. |
| All original rooms / world map | Returning players expect to recognize the world | LARGE | Room layouts extracted in Stage 1, ported to client tilemaps in Stage 6/7. Not a server concern beyond room-id registry. |
| All original sprites + animations | Same — recognizability is the parity bar | LARGE | Asset pipeline. Client-side. |
| Server-side admin commands (kick, mute, ban) | `[parity-inferred]` `,ServerCommands.txt` documents `Ctrl+E` clipboard exec, `Ctrl+Q` variable inspect, `Ctrl+???` client-side exec | MEDIUM | Modernized: replace clipboard-exec-as-superuser with a vetted command set callable from an admin auth scope. **Do not port the original RCE-by-design model** (flagged in `CONCERNS.md` as wide-open RCE). |
| Message board / persistent posting | `[parity-inferred]` `MB_News`, `MB_Log.bnb` exist in every server snapshot — the game had a persistent message board | MEDIUM | Schema TBD pending Stage 3. Database-backed in rebuild (not flat-file). |
| Per-user settings persisted | `[parity-inferred]` `Settings.bno` exists; original game had server-side user settings | SMALL once schema known | Same blocker as `.bnu` — needs Stage 3 extraction. |
| Server-authoritative game-state events | Whatever the original game did beyond "walk + chat" (combat? trades? NPCs?) belongs server-side | LARGE | Scope unknown until Stage 3. The `Crasher.exe` and `Server Saver.exe` artifacts in `legacy/servers/` hint at server lifecycle features (state snapshot, recovery) that need cataloguing. |

### Differentiators (Quality / Competitive Advantage)

These are the "feels modern, not just faithful" upgrades. They map to "modernized UX" in `PROJECT.md` Stage 6.

| Feature | Value Proposition | Complexity | Notes |
|---------|-------------------|------------|-------|
| Friends list | Persistent social graph; lets players find each other across sessions instead of yelling in chat | MEDIUM | Add table `friendships(user_a, user_b, status, created_at)`. Symmetric. UI surface: list with online/offline status. **Depends on**: account system (table-stakes). |
| Online/offline presence | Without it, the friends list is just a list of strings | SMALL | Server tracks `online_users: Set<userId>`. Friends list query joins against it. Push presence change events to friends. |
| Private messages (whisper / DM) | Lets friends talk across rooms; current chat is room-scoped | SMALL once chat works | Same chat-message infra, addressed by recipient userId instead of broadcast. Persist to DB if offline-message support is wanted (next row). |
| Offline message delivery | Send a DM to a friend who's offline; they see it on next login | MEDIUM | `messages(from, to, body, sent_at, read_at)` table. Needs a TTL/cap policy (e.g., 100 messages or 30 days) to bound storage. |
| Ignore / block list | Lets users self-moderate; reduces moderator load | SMALL | Per-user `blocks(blocker, blocked)` table. Server filters chat/whispers/visibility based on blocker's list. Cheaper than profanity filtering for most user-vs-user friction. |
| Player report mechanism | Lets the community surface bad actors without admin attention required for every incident | MEDIUM | Reports persist with a snippet of recent chat / context. Admin queue in admin tool. **Depends on**: chat history retention (even if just a rolling 100-line buffer). |
| Moderation tools (mute / kick / ban) for non-server-console admins | The original game required physical/RDP server console access (huge security/scaling problem) | MEDIUM | Web-based admin UI gated on admin role flag. Granularity: per-user mute (chat only), kick (forces disconnect), ban (refuses login by username + optional IP). |
| Chat history per room (recent N lines) | Players who join mid-conversation can see context | SMALL | In-memory rolling buffer per room, e.g., last 50 lines, broadcast as a single payload on room enter. Optional DB persistence for moderation review. |
| Account recovery (email-based reset) | Without it, every forgotten password is a manual admin task — does not scale even at 50 CCU if churn is high | MEDIUM | Requires email-on-account (optional at signup, but pushed for migrated accounts). SMTP via a transactional provider (Resend/Postmark/SES). One-time signed reset token. |
| Account-level settings (audio, keybinds, accessibility) | Modern players expect to rebind keys, mute SFX, adjust UI scale | MEDIUM | Stored per-user; client fetches on login. Most settings are client-only and don't need server validation. |
| In-game UI polish (HiDPI, responsive layout, decent typography) | Already a `PROJECT.md` constraint ("Modernized UX (HiDPI, responsive, keyboard + gamepad input)") | MEDIUM | Phaser 3 / Pixi handle DPR; the work is mostly choosing to do it from day one rather than retrofitting. |
| Emotes / canned animations | Cheap social signaling; classic 2D MMO staple | SMALL | New packet type `EMOTE`, server broadcasts to room. Client plays sprite animation. **Depends on**: per-character animation system. |
| Party / grouping (named groups across rooms) | Lets friends play together when split across rooms | LARGE | Group state, group chat channel, group-leave/join semantics, leadership transfer, persistence across disconnect. **Defer past v1** — friends list + whispers covers the social need at <50 CCU scale. |
| Web-native delivery (no install, share-a-URL) | The biggest single UX win over the original; original required Win32 binary + 39dll + manual server connect | SMALL (it's a consequence of the stack choice, not a feature to build) | Already locked in `PROJECT.md` (Chrome desktop, Vite client). Worth calling out because it's a major differentiator vs. the original. |

### Anti-Features (Out of Scope For v1)

Most of these are already locked in `PROJECT.md` "Out of Scope". Documented here to surface the *why* and the alternative.

| Feature | Why Requested | Why Problematic | Alternative |
|---------|---------------|-----------------|-------------|
| Voice chat | "Modern multiplayer has voice" | Adds WebRTC infra (TURN/STUN, signaling, peer mesh or SFU), moderation burden 10x text (per [getstream voice moderation guide](https://getstream.io/blog/audio-voice-moderation/)), and a privacy/PII surface that doesn't fit a <50 CCU friends-and-test scope. ESRB now audits voice for compliance starting Aug 2025. | Text chat only. Players who want voice use Discord. |
| Mobile clients (iOS / Android native) | "Reach more players" | Tripled platform-test surface; each native runtime needs its own input model, UI scaling, and store compliance. Already excluded in `PROJECT.md`. | Chrome desktop only for v1. Mobile browser is best-effort, not blocking. |
| ML-based anti-cheat (behavior detection) | "Cheaters ruin online games" | At <50 CCU on a friends-and-test scope, the cost-benefit is wildly inverted — model training data doesn't exist, false-positive rate is unacceptable for a small community, and it doesn't address the actual cheats people will try. Already excluded. | Authoritative server (already locked) + per-input bounds checks + manual ban for confirmed cheaters. |
| Monetization (cosmetics, battle pass, ads) | "Need revenue" | Adds payment infra (Stripe + tax), age gates, refund logic, and changes the moral relationship with returning players who paid nothing for the original. | Free, no monetization for v1. Donation link if hosting cost becomes real. |
| Modding / user-generated content API | "Long tail content" | Sandboxing user code in the server is a research project; UGC moderation queue is a full-time job; backwards-compat constraints lock the rebuild before parity is even reached. Already excluded. | Defer indefinitely. If demanded post-parity, scope as a separate project. |
| 3D / VR | "Modern visuals" | Faithful 2D rebuild is the entire point — going 3D is a different game. Already excluded. | Stay 2D. |
| Email verification at signup (MVP) | "Prevent throwaway accounts" | Adds blocking SMTP dependency at MVP; at <50 CCU friend-and-test scope, the abuse vector isn't real yet | Make email optional at signup, required only if user opts into account recovery later. |
| Real-time everything (e.g., live presence updates streamed at 60Hz) | "Feels alive" | Wastes bandwidth + server CPU on info no one consumes; chat doesn't need <100ms latency | Push presence/friends-list updates as discrete events, not streams. Movement is the only true real-time channel. |
| Server-side replays / spectator mode | "Esports table stakes" | The original is not an esport. Recording infra, storage cost, replay-deterministic-engine constraints all real. | Skip unless concrete demand emerges post-parity. |
| Cross-server / federation | "Bigger world" | Not needed below sharding threshold; see "Room/zone management" below. Single Fly machine is the explicit `PROJECT.md` v1 scale. | Single server. Revisit if CCU sustainably exceeds ~200. |
| Public account self-service username changes | "Quality-of-life" | Breaks identity continuity for the migrated original-BNO users — the entire "preserve usernames" decision is undone if anyone can rename. Also breaks friends list / message history references. | Display name (cosmetic, changeable) layered on top of immutable username. Defer past v1. |
| Aggressive AI profanity filter | "Toxicity is bad" | Matches generated by AI filters at MVP scale produce more false positives than catches; per [WebPurify gaming guide](https://www.webpurify.com/gaming/) and [Greip blog](https://greip.io/blog/From-Toxic-to-Terrific-A-Guide-to-Integrating-Profanity-Filters-in-Online-Gaming-247) effective filters need ongoing wordlist tuning that doesn't justify itself for friends-and-test scope. | Simple wordlist filter (toggleable per-user) + ignore/block list + report mechanism + admin mute. AI-based moderation can be layered in later if community grows. |

---

## Movement-Sync Recommendation

**Recommendation: client-side prediction for the local player + entity interpolation for remote players, with server reconciliation. Authoritative server. NO lockstep.**

### Rationale for the BNO rebuild's specific shape

Three constraints drive this:

1. **Casual 2D top-down movement.** Not a competitive shooter, not an RTS. Sub-frame tick precision is not needed; "remote players are visibly ~100ms in the past" is invisible at this game's pace.
2. **<50 CCU on a single Fly.io machine.** Server CPU and bandwidth budgets are generous per-player; we can afford to validate every input.
3. **WebSocket transport.** Reliable ordered TCP-like — no UDP for this stack — which removes the need for the more complex sequence-number / packet-loss handling that FPS-grade netcode requires.

### Why each technique is included

- **Client-side prediction (local player only).** Without it, the local player feels laggy on every input — keypress → wait for round-trip → see movement. Even at 50–100ms RTT this is unbearable for a top-down game where the player presses an arrow key and expects instant motion. Per [Gabriel Gambetta's reference series](https://www.gabrielgambetta.com/client-side-prediction-server-reconciliation.html) and [KinematicSoup](https://kinematicsoup.com/news/2017/5/30/multiplayerprediction), this is industry-standard for any responsiveness-sensitive multiplayer.
- **Server reconciliation.** When the server's authoritative position diverges from the client's prediction (because the server clamped a wall hit, or the player got hit by a server-side event), the client smoothly snaps back. Implementation: client keeps a buffer of recent inputs with monotonic input IDs; server acknowledges with the input ID it last processed and the resulting authoritative position; client replays inputs after that ID against the server-corrected position.
- **Entity interpolation (remote players only).** Render remote players ~100–150ms behind the latest received state, interpolating between the two most recent server snapshots. Per [Gambetta's interpolation article](https://www.gabrielgambetta.com/entity-interpolation.html), this trades a small visual delay for completely smooth movement that handles dropped/late updates gracefully. No client-side prediction for remote entities — we don't try to predict where another player will move.

### Why NOT lockstep

Lockstep (every client simulates identically from a shared input stream) is great for RTS / fighting games where determinism matters and player count is small + fixed. For an MMO-style room with players joining/leaving, variable latency, and no need for frame-perfect replay, lockstep adds rigid sync requirements without payoff. Skip.

### Why NOT interpolation-only (no prediction)

Would work — and is genuinely simpler — *if* the local player's input latency was acceptable. At 80ms RTT on a typical Fly.io edge connection, the local player sees an 80ms delay between keypress and movement on screen. For a top-down WASD/arrow-key game that's noticeable and bad. Adding prediction is ~2 days of work and pays back forever.

### Tick rate

- **Server tick: 20 Hz (50ms).** Standard for non-FPS multiplayer. Sufficient for casual movement.
- **Client send rate: 20 Hz** (one input/intent message per server tick). Client batches keys-held-this-tick into one message.
- **Server broadcast rate: 20 Hz** authoritative positions per room.
- **Client render rate: 60 Hz** (or display Hz), interpolating between the two most recent server snapshots.

At 50 CCU split across rooms with maybe 10 players in the busiest room, broadcast cost = 10 players × 10 visible peers × 20 Hz × ~24 bytes per position update = ~48 KB/s outbound for that room. Trivial.

### Movement-sync feature dependencies

- Requires: server-side validation (table stakes), authoritative server model (locked).
- Enables: collision detection on server, zone-based culling (don't broadcast players in other rooms).
- Enabled by: WebSocket binary frames (locked), shared TS packet types between client and server.

---

## Chat Pattern Recommendation

### Channel model

Three channels at parity (one at MVP):

1. **Local / room** — broadcast to all players in the same room. **MVP: this only.**
2. **Whisper / DM** — direct user-to-user, addressed by username. Routed even across rooms.
3. **System / server** — server-emitted notifications (player joined, kicked, server restart in 5min). One-way, not user-sendable.

Defer global / world channel past v1 — it's a moderation cost multiplier with little gain at <50 CCU.

### Rate limiting

Per `getstream.io` and `WebPurify` chat-moderation references, the right pattern is per-user token bucket:

- **5 messages per 5 seconds per user**, burst-allowed up to ~10. Slow-mode on top: minimum 1s between messages in the same channel.
- Server enforces; client soft-enforces (greys out send button) to avoid surprising the user.
- Violation response: silently drop the message, send back a reason code (`RATE_LIMITED`).

Whisper rate limit is stricter (e.g., 3/10s) because it bypasses room-bound visibility and is a higher abuse vector.

### Profanity filter

**MVP: none.** Use ignore/block + report instead.

**Parity: simple wordlist + per-user toggle.**

- Server-side wordlist of ~100 common slurs + obvious variants. Substring match with word boundaries.
- Per-user toggle in account settings (default ON for room chat, OFF for whispers — whispers are consensual).
- Filtered messages render with replacement (`****`), not blocked entirely, so context is preserved.
- AI-based filtering deferred — see Anti-Features. Rebuild can integrate WebPurify / GGWP / Greip later if community grows past the friends-and-test scale.

### History persistence

- **In-memory rolling buffer per room: last 50 messages.** Sent to client on room enter so they don't see an empty channel.
- **DB persistence for whispers if offline delivery is enabled.** TTL 30 days.
- **DB persistence for room chat ONLY for moderation review.** Last 24h, accessible to admin tools, auto-purged. Never exposed to players.
- **Logs are PII.** Treat per the lessons in `CONCERNS.md`: never commit, encrypt at rest, access-controlled.

### Chat feature dependencies

- Requires: account system (sender identity), session auth (prevent impersonation), room model (scoping room channel).
- Enables: report mechanism, moderation, whispers, social signals.
- Enabled by: rate limiter, profanity wordlist (parity, not MVP).

---

## Room/Zone Management Recommendation

### Architecture

**Single Node process, multi-room, all rooms in-memory.** No sharding at v1.

```
NodeServer
├── RoomRegistry
│   ├── Room "town_square"  → { players: Set, tickLoop, chatBuffer }
│   ├── Room "shop_area"    → { players: Set, tickLoop, chatBuffer }
│   ├── Room "battle_zone"  → { players: Set, tickLoop, chatBuffer }
│   └── ...
├── SessionRegistry → { sessionId → { userId, ws, currentRoom } }
└── DB (SQLite/Postgres, decision deferred per PROJECT.md)
```

Rooms are not separate processes — that's premature complexity for <50 CCU. One Node event loop handles all rooms; the 20Hz tick per room is cheap.

### Room transitions

- Player sends `ENTER_ROOM { room_id }` (with a server-side gate: must be reachable from current room, or it's a teleport-class action requiring server consent).
- Server: removes from old room's player set, adds to new, broadcasts `PLAYER_LEFT` to old room and `PLAYER_JOINED` to new, sends new room's chat buffer + initial player snapshot to the entering player.
- Persistence: `last_room` field updated atomically.

### Sharding triggers — when to revisit

The single-process model holds until **one or more** of these is true:

- **CCU sustainably > ~200.** At this point Node event-loop latency from per-tick broadcast starts being measurable and you want process-per-region.
- **Single most-popular room > ~50 concurrent players.** Bandwidth from "everyone-sees-everyone" position broadcasts becomes O(n²) per tick.
- **CPU profiling shows the tick loop blocking > 5ms.** Gives the green light for splitting into worker threads or processes.
- **Multi-region requirement.** Latency to Asia/EU users from one Fly region becomes a complaint. Solve with regional Fly machines + cross-region presence sync — but that's a v2+ problem.

None of these are remotely close at v1's <50 CCU friend-and-test scope. Fly.io single machine is the right answer per `PROJECT.md` Constraints.

### Room/zone feature dependencies

- Requires: persistence layer (for `last_room`), session registry, account system.
- Enables: scoped chat, scoped movement broadcasts, world-map navigation, per-room moderation rules.
- Enabled by: server tick architecture, WebSocket session routing.

---

## Account Migration UX Recommendation

The original `localList.txt` / `remoteList.txt` files contain ~298 plaintext (username, password) pairs (per `CONCERNS.md`). These are PII and **must never leave a local-only environment unredacted**. The migration design has to respect that.

### Recommended approach: **transparent re-hash on first successful login, with a forced reset escape hatch.**

1. **Pre-migration (offline, local-only):**
   - Read `localList.txt` (the more recent "remote" list per the 2021 provenance note).
   - For each `(username, password)` row: compute `bcrypt(password)` and store `(username, bcrypt_hash, original_hash_format='plaintext_migrated', migrated_at)` in the new DB.
   - The plaintext password is **never written to the new DB** — only the bcrypt hash.
   - Source `localList.txt` is then deleted from the migration host (or re-redacted to `[REDACTED]` per `CONCERNS.md` recommendation).

2. **At first login (returning player):**
   - User enters their original username + password.
   - Server bcrypt-verifies. Success → log them in, set `migrated_at_login=now()`, optionally surface a one-time toast: "Your account was migrated from BNO classic. We've upgraded the security on your password — no action needed. You can change your password anytime in Settings."
   - Failure → standard "wrong password" path. (We are NOT keeping the plaintext, so we cannot help them recover the original password — but they can use email-recovery if they registered an email, or admin-assisted reset.)

3. **Forced-reset escape hatch:**
   - If a returning user logs in successfully but the original password fails our modern strength rule (e.g., < 8 chars, top-1000 common password — many of the original passwords in `CONCERNS.md` examples qualify: `123456`, `harrypotter`), prompt for a new password before allowing in. Do NOT block them — let them choose a new one inline.
   - Rationale: many original passwords are weak by current standards. We migrate identity (the username) without forcing migration of weak credentials.

4. **Email collection (optional, post-MVP):**
   - First login does not require email. User can add one later for recovery purposes.
   - When they do, send a verification link before the email is usable for recovery.

### Why this UX wins

- **No "click this link in your email" gate before they can play.** Many of the original players are 10+ years gone; their old emails may be dead. Forcing email verification before first login orphans them.
- **Username preserved** — addresses the `PROJECT.md` "preserve identity continuity for returning players" decision.
- **Modernizes the hash silently** — no scary "your password was insecure" lecture, just a silent re-hash. The "we upgraded security" toast is informative, not blame-shifting.
- **Weak-password remediation happens at the most teachable moment** (post-login, in-context).
- **Survives the migration database being lost.** Bcrypt hashes are derivative; if we lose them we can re-run the migration from a fresh redaction-pass on `localList.txt` if the source artifact is preserved. (The source `localList.txt` files in `legacy/servers/*` are exactly that backup — see `CONCERNS.md` for the inventory and PII handling requirements.)

### Migration feature dependencies

- Requires: bcrypt/argon2 infrastructure, DB user table, login flow.
- Enables: returning-player re-engagement (the entire reason for the rebuild per `PROJECT.md` Core Value).
- Blocked by: nothing technical — but operationally requires a one-time, isolated migration host that handles the plaintext source list.

---

## Feature Dependencies

```
Account creation ──┬──> Login ──┬──> Session token
                   │            │       │
                   │            │       └──> Authenticated WS connection
                   │            │              │
                   │            │              ├──> Chat (any channel)
                   │            │              ├──> Movement (intent → server validate)
                   │            │              ├──> Persistent character state
                   │            │              └──> Room transitions
                   │            │
                   │            └──> Account migration (returning users)
                   │
                   ├──> Account settings (audio, keybinds, etc.)
                   └──> Email recovery (optional, post-MVP)

Movement (server-validated)
   ├──requires──> Authoritative server tick loop
   ├──requires──> Room model (scope broadcasts)
   ├──enables──> Collision (server-side)
   └──enables──> Visible remote players (interpolation)

Chat (room channel)
   ├──requires──> Authenticated session
   ├──requires──> Room model
   ├──requires──> Rate limiter
   ├──enables──> Whispers (DMs)
   ├──enables──> Reports (uses chat history snapshots)
   └──enables──> Mute (admin action on chat)

Friends list ──requires──> Account system
   └──enables──> Online presence + Whisper-from-friends-list

Moderation
   ├──requires──> Account system (target identity)
   ├──requires──> Admin role flag (server-side)
   ├──requires──> Chat history (for evidence)
   └──provides──> Mute / Kick / Ban (each with their own server enforcement path)

Persistence layer
   ├──required-by──> EVERYTHING that survives reconnect
   └──blocked-by──> Stage 3 schema decision (SQLite vs Postgres)

Full character persistence (.bnu parity)
   └──blocked-by──> Stage 3 GML extraction (.bnu schema unknown until then)
```

### Dependency notes

- **Persistent character state (full parity) is blocked on Stage 3.** Until the original `.bnu` schema is extracted, only minimal state (`{username, last_room, x, y}`) can be persisted at MVP. This is enough for the MVP gate but not for Stage 7. **Implication for roadmap: the parity-feature inventory cannot be finalized until Stage 3 completes.**
- **Friends list, whispers, reports all require account system + chat infra to be solid.** Chase MVP first, layer these as a "social" milestone after.
- **Moderation tools MUST exist before any non-friend user is invited.** The original game's "Ctrl+E run clipboard as superuser" approach is a security nightmare flagged in `CONCERNS.md`. Build modern admin commands as part of the same milestone that opens signups beyond friends.
- **Rate limiter is an MVP+1 feature.** At MVP (two friends testing) it's not enforced; first time real users join, it must be on. Don't ship a non-rate-limited chat to the public.
- **Email recovery's hard dependency is SMTP infra**, not code. Choose a provider (Resend / Postmark / SES) at the time you decide to build recovery — not earlier.

---

## MVP Definition

### Launch With (v1 — Stage 6 MVP gate)

The strict reading of `PROJECT.md` Stage 6: "two players join the same room, see each other move, exchange chat messages."

- [ ] Account creation (new accounts only — migration can wait a beat after MVP if it streamlines the gate)
- [ ] Login + session token issuance
- [ ] Persistent username (DB-backed, bcrypt hash)
- [ ] WebSocket connection authenticated by session token
- [ ] Single room (start with one — MVP doesn't need the world map yet)
- [ ] Movement: client-side prediction for local + interpolation for remote + server reconciliation + server-side validation
- [ ] Chat: room/local channel, server broadcasts to all in room
- [ ] Player nameplate visible above sprite
- [ ] Disconnect/reconnect handling with grace window
- [ ] Sane error messages (login failure, server down, kicked)
- [ ] One placeholder sprite, one placeholder room — no asset parity required at MVP

That's it. **Not** at MVP: friends list, whispers, moderation, full parity rooms, full sprite library, message board, account migration UI, chat history persistence, profanity filter, email recovery.

### Add After Validation (v1.x — between MVP and full parity ship)

Triggered by: MVP runs reliably for two test users for a week without intervention.

- [ ] Account migration flow for returning BNO usernames (bcrypt re-hash on first login)
- [ ] Multi-room support with room-transition mechanics
- [ ] Player report mechanism + admin queue
- [ ] Chat rate limiting (turn it on before any non-tester joins)
- [ ] Ignore/block list
- [ ] Friends list + online presence
- [ ] Whisper / DM
- [ ] Wordlist profanity filter (toggleable per-user)
- [ ] Modernized admin tools (web UI for kick/mute/ban — replacing the original RCE-style server console)
- [ ] Account settings (audio, keybinds, accessibility)
- [ ] Account recovery (email-based reset)
- [ ] Chat history (rolling buffer per room, sent on enter)
- [ ] Session reconnect across browser refresh (grace window, no re-login)

### Future Consideration (Stage 7 full parity, then beyond)

Triggered by: Stage 3 GML extraction completes; parity feature inventory becomes definitive.

- [ ] Full character state persistence per `.bnu` schema (TBD post-Stage 3)
- [ ] All original rooms / world map ported
- [ ] All original sprites + animations
- [ ] Message board (`MB_News`, `MB_Log`) reimplemented against modern DB
- [ ] Per-user settings persistence (`Settings.bno` parity, schema TBD post-Stage 3)
- [ ] Whatever gameplay-specific features Stage 3 surfaces (combat? trades? NPCs? — currently unknown)
- [ ] Offline message delivery (whispers persist for offline recipients)
- [ ] Emotes
- [ ] Party / grouping (only if friends list + whispers prove insufficient — likely defer)

### Beyond v1 — Explicitly Out (and staying out)

Per `PROJECT.md` Out of Scope and the Anti-Features section above: voice chat, mobile native, ML anti-cheat, monetization, modding API, 3D/VR, server replays, federation, AI profanity filtering, public username changes.

---

## Feature Prioritization Matrix

| Feature | User Value | Implementation Cost | Priority |
|---------|------------|---------------------|----------|
| Account creation + login | HIGH | MEDIUM | P1 |
| Session-authenticated WS | HIGH | LOW | P1 |
| Movement sync (prediction + interpolation) | HIGH | MEDIUM | P1 |
| Server-side movement validation | HIGH | MEDIUM | P1 |
| Room/local chat | HIGH | LOW | P1 |
| Single room model | HIGH | LOW | P1 |
| Disconnect/reconnect handling | HIGH | MEDIUM | P1 |
| Persistent username + min character state | HIGH | LOW | P1 |
| Player nameplates | MEDIUM | LOW | P1 |
| Sane error UX | HIGH | LOW | P1 |
| Account migration (returning players) | HIGH | MEDIUM | P2 |
| Multi-room + transitions | HIGH | MEDIUM | P2 |
| Chat rate limiting | MEDIUM | LOW | P2 (must precede public access) |
| Friends list + presence | MEDIUM | MEDIUM | P2 |
| Whispers (DMs) | MEDIUM | LOW | P2 |
| Ignore/block | MEDIUM | LOW | P2 |
| Player report mechanism | MEDIUM | MEDIUM | P2 |
| Admin tools (kick/mute/ban) | HIGH (operationally) | MEDIUM | P2 (must precede public access) |
| Wordlist profanity filter | MEDIUM | LOW | P2 |
| Account recovery (email reset) | MEDIUM | MEDIUM | P2 |
| Chat history (rolling buffer) | MEDIUM | LOW | P2 |
| Account-level settings | MEDIUM | MEDIUM | P2 |
| Full `.bnu` parity | HIGH (for returning players) | LARGE | P3 (blocked on Stage 3) |
| All original rooms + sprites | HIGH (parity) | LARGE | P3 |
| Message board | MEDIUM | MEDIUM | P3 |
| Emotes | LOW | LOW | P3 |
| Offline message delivery | LOW | MEDIUM | P3 |
| Party / grouping | LOW | LARGE | P3 (defer indefinitely) |
| Voice chat | LOW (in this game's scope) | LARGE | OUT |
| Mobile native | LOW (in v1) | LARGE | OUT |
| ML anti-cheat | LOW | LARGE | OUT |
| Monetization | LOW | LARGE | OUT |
| Modding API | LOW | LARGE | OUT |
| 3D / VR | LOW | LARGE | OUT |

**Priority key:**
- P1: MVP gate (Stage 6)
- P2: Required before public access / full parity ship (Stage 7)
- P3: Parity-driven, scope confirmed by Stage 3 extraction
- OUT: Anti-feature, locked out per `PROJECT.md`

---

## Competitor / Reference Analysis

The "competitors" here are reference points rather than market rivals — this is a faithful rebuild of a specific game, not a market product. References inform the modernization layer.

| Feature | BN Online (original) | Generic 2D MMO (e.g., Habbo / Tibia / browser-MMO patterns) | Our Approach |
|---------|----------------------|----------------------------------------|--------------|
| Auth | Plaintext passwords in `localList.txt`; no hashing | bcrypt/argon2, optional 2FA | bcrypt/argon2 with username preservation + transparent re-hash on first login |
| Transport | 39dll over raw TCP, no encryption | TLS WebSocket | WSS (WebSocket Secure) — TLS terminated at Fly.io edge |
| Movement | `[parity-inferred]` Authoritative server with `writebyte`/`writedouble` move packets per `decomp/wiki/08-39dll-networking.md` | Mix of authoritative + client-prediction depending on game | Authoritative server + client-side prediction + interpolation |
| Chat | `[parity-inferred]` Room-broadcast chat, no persistence beyond `MB_Log.bnb` board | Multi-channel (local/global/whisper), AI-moderated, rate-limited | Room channel at MVP; whispers + system + wordlist filter at parity |
| Admin | "Ctrl+E run clipboard as superuser" RCE-by-design (`,ServerCommands.txt`) | Web admin panel with role-gated commands | Web admin panel, role-gated, no clipboard exec |
| Friends list | None apparent in legacy sources | Standard | Add as differentiator |
| Account migration | N/A | N/A | Custom — silent re-hash, weak-password upgrade prompt |
| Modding | None | Some games support; most do not | Out of scope |
| Hosting | Single home/colo Windows machine | Cloud (AWS/GCP/Fly/Railway) | Fly.io single machine (`PROJECT.md` locked) |
| Persistence | Bespoke `.bno`/`.bnu`/`.bnb` flat files | SQL or NoSQL | SQLite + Litestream OR Postgres (deferred per `PROJECT.md`) |

---

## Sources

- `C:\Users\decid\Documents\projects\rebno\.planning\PROJECT.md` — Stages, constraints, Out of Scope, Auth/Hosting decisions (HIGH confidence: project-internal source of truth)
- `C:\Users\decid\Documents\projects\rebno\.planning\codebase\CONCERNS.md` — PII handling for `localList.txt`, original RCE-by-design admin model, plaintext credential inventory (HIGH confidence)
- `C:\Users\decid\Documents\projects\rebno\decomp\wiki\08-39dll-networking.md` — Original transport pattern, "structure = call order" packet semantics (HIGH confidence)
- `C:\Users\decid\Documents\projects\rebno\decomp\wiki\16-bno-bnb-notes.md` — `.bno`/`.bnb`/`.bnu` parsing strategy, Stage 3 dependency (HIGH confidence)
- [Gabriel Gambetta — Client-Side Prediction and Server Reconciliation](https://www.gabrielgambetta.com/client-side-prediction-server-reconciliation.html) — Reference for MVP movement netcode (HIGH confidence: industry-standard reference series)
- [Gabriel Gambetta — Entity Interpolation](https://www.gabrielgambetta.com/entity-interpolation.html) — Reference for remote-player smoothing
- [KinematicSoup — Client-side Prediction for Smooth Multiplayer Gameplay](https://kinematicsoup.com/news/2017/5/30/multiplayerprediction) — Cross-source confirmation
- [GetStream — Chat Moderation 101](https://getstream.io/blog/chat-moderation/) — Rate limiting + moderation patterns (MEDIUM confidence: vendor blog, but methodology is industry-standard)
- [WebPurify — Moderating Gaming Websites and Apps](https://www.webpurify.com/gaming/) — Profanity filter approach
- [Greip — Integrating Profanity Filters in Online Gaming](https://greip.io/blog/From-Toxic-to-Terrific-A-Guide-to-Integrating-Profanity-Filters-in-Online-Gaming-247) — Cross-source on filter limitations at small scale
- [GetStream — Audio and Voice Moderation Implementation Guide](https://getstream.io/blog/audio-voice-moderation/) — Rationale for excluding voice from v1

---
*Feature research for: 2D top-down multiplayer online game (BN Online rebuild)*
*Researched: 2026-05-01*


/grill-with-docs to find a mutual understanding around the general ideas, mechanics, and features of battle network online (the legacy project) and what's making it into ReBNO


Grounded. Read PROJECT, STATE, decomp overview, FEATURES research, legacy glossary, and the full `LEGACY_FEATURE_REFERENCE.md` triage.

Key finding: **no game-domain glossary exists yet.** `decomp/wiki/glossary.md` = GameMaker internals only. `FEATURES.md` = generic-MMO + parity scaffolding, and it repeatedly admits the actual *gameplay loop is unknown* ("combat? trades? NPCs? — currently unknown"). The richest game-domain doc is `LEGACY_FEATURE_REFERENCE.md`, but it only covers floor tiles + travel tiles + 2 navi expansions + spectrum data. No `CONTEXT.md` at root.

So this grill = pull BNO's real design out of your head into a canonical glossary + lock what's in-scope for ReBNO. I'll build `CONTEXT.md` as terms resolve.

Walking the tree top-down. Root question first — it gates everything below it.

---

**Q1 — Core game identity / the loop.**

BNO wears a Mega Man Battle Network skin (Navis, "hexport," Capcom-derived). MMBN proper = virus-busting + battle chips + folders. But every artifact I can see points the *other* way:

- Extracted features = traversal + exploration (movers, slippery ice, hidden/glow tiles, hexport, warps, hexbridges)
- Persistence dirs = `HXB`, `Inv`, `MB_News` (message board) — social/customization, **no chip/folder/battle save data**
- Unlocks = navi *expansions* (`WrpPlugn`, `MskPlugn`) + Spectrum Data (sprite customization) — traversal & cosmetic, not combat power
- v1.0 core value = "walk around and chat"

**My recommended answer:** BNO is a **social-exploration chat world** — a persistent hangout where Navis explore puzzle-ish zones (traversal mechanics + hidden secrets), customize their look, and socialize. **No combat system.** The MMBN aesthetic is skin, not mechanics. ReBNO targets that identity; it does NOT build a battle engine.

Is that right — **no combat, social-exploration the core loop**? Or did BNO have combat / minigames / PvP I'm not seeing in the artifacts?


You're mostly correct when it comes to legacy BNO. Exploration is the core focus, with basic social features baked in. The gameplay loop consists of eking out hidden platforms, pathways, and portals, overcoming timing-sensitive movement challenges, and solving puzzles that revolve around various interwoven game mechanics. The rewards are progression-centric: new zones, new areas (subregions), new message boards, collectables, and key items. Key items are primarily navi expansions, which unlock abilities and features which enable deeper progression (very metroidvania-like), or unlocks the usage of spectrum data (navi customization). Collectables include zone data (teleport to a zone), area data (teleport to areas within a zone that you have the zone data for), spectrum data (change avatar cosmetics), and friend data (in-universe friends list). --- all of this applies to legacy BNO, but Rebno has the potential to add new systems and potentially update what's there. Its first and foremost goal, however, is to have full feature parity with BNO with various QOL tweaks afforded by the modern web infrastructure it's built on. It is also intended to eventually run on a variety of platforms. Just being web-based unlocks all desktop operating systems and Android + iOS. I also hope to target more novel platforms like gaming handhelds (Android -- so controller support will become first-class -- and Linux), Discord Activities, and potentially some sort of VR overlay. --- In the distant future, ReBNO may shed the MMBN IP. Most of its art assets are original, barring the avatars, NPC sprites, and "data" item drops. It may become something more akin to CrossCode or Radio The Universe. It may also potentially gain a battle system in time. but both of these ideas are far off. Again, primary focus RN is BNO parity with QoL addtions.


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


ReBNO feature parity and platform expansion


Excellent — that's the core. Capturing now, then next branch.


1	# CONTEXT.md Format
2
3	## Structure
4
5	```md
6	# {Context Name}
7
8	{One or two sentence description of what this context is and why it exists.}
9
10	## Language
11
12	**Order**:
13	{A one or two sentence description of the term}
14	_Avoid_: Purchase, transaction
15
16	**Invoice**:
17	A request for payment sent to a customer after delivery.
18	_Avoid_: Bill, payment request
19
20	**Customer**:
21	A person or organization that places orders.
22	_Avoid_: Client, buyer, account
23	```
24
25	## Rules
26
27	- **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others as aliases to avoid.
28	- **Flag conflicts explicitly.** If a term is used ambiguously, call it out in "Flagged ambiguities" with a clear resolution.
29	- **Keep definitions tight.** One or two sentences max. Define what it IS, not what it does.
30	- **Show relationships.** Use bold term names and express cardinality where obvious.
31	- **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs.
32	- **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine.
33	- **Write an example dialogue.** A conversation between a dev and a domain expert that demonstrates how the terms interact naturally and clarifies boundaries between related concepts.
34
35	## Single vs multi-context repos
36
37	**Single context (most repos):** One `CONTEXT.md` at the repo root.
38
39	**Multiple contexts:** A `CONTEXT-MAP.md` at the repo root lists the contexts, where they live, and how they relate to each other:
40
41	```md
42	# Context Map
43
44	## Contexts
45
46	- [Ordering](./src/ordering/CONTEXT.md) — receives and tracks customer orders
47	- [Billing](./src/billing/CONTEXT.md) — generates invoices and processes payments
48	- [Fulfillment](./src/fulfillment/CONTEXT.md) — manages warehouse picking and shipping
49
50	## Relationships
51
52	- **Ordering → Fulfillment**: Ordering emits `OrderPlaced` events; Fulfillment consumes them to start picking
53	- **Fulfillment → Billing**: Fulfillment emits `ShipmentDispatched` events; Billing consumes them to generate invoices
54	- **Ordering ↔ Billing**: Shared types for `CustomerId` and `Money`
55	```
56
57	The skill infers which structure applies:
58
59	- If `CONTEXT-MAP.md` exists, read it to find contexts
60	- If only a root `CONTEXT.md` exists, single context
61	- If neither exists, create a root `CONTEXT.md` lazily when the first term is resolved
62
63	When multiple contexts exist, infer which one the current topic relates to. If unclear, ask.
64

# CONTEXT.md Format

## Structure

```md
# {Context Name}

{One or two sentence description of what this context is and why it exists.}

## Language

**Order**:
{A one or two sentence description of the term}
_Avoid_: Purchase, transaction

**Invoice**:
A request for payment sent to a customer after delivery.
_Avoid_: Bill, payment request

**Customer**:
A person or organization that places orders.
_Avoid_: Client, buyer, account
```

## Rules

- **Be opinionated.** When multiple words exist for the same concept, pick the best one and list the others as aliases to avoid.
- **Flag conflicts explicitly.** If a term is used ambiguously, call it out in "Flagged ambiguities" with a clear resolution.
- **Keep definitions tight.** One or two sentences max. Define what it IS, not what it does.
- **Show relationships.** Use bold term names and express cardinality where obvious.
- **Only include terms specific to this project's context.** General programming concepts (timeouts, error types, utility patterns) don't belong even if the project uses them extensively. Before adding a term, ask: is this a concept unique to this context, or a general programming concept? Only the former belongs.
- **Group terms under subheadings** when natural clusters emerge. If all terms belong to a single cohesive area, a flat list is fine.
- **Write an example dialogue.** A conversation between a dev and a domain expert that demonstrates how the terms interact naturally and clarifies boundaries between related concepts.

## Single vs multi-context repos

**Single context (most repos):** One `CONTEXT.md` at the repo root.

**Multiple contexts:** A `CONTEXT-MAP.md` at the repo root lists the contexts, where they live, and how they relate to each other:

```md
# Context Map

## Contexts

- [Ordering](./src/ordering/CONTEXT.md) — receives and tracks customer orders
- [Billing](./src/billing/CONTEXT.md) — generates invoices and processes payments
- [Fulfillment](./src/fulfillment/CONTEXT.md) — manages warehouse picking and shipping

## Relationships

- **Ordering → Fulfillment**: Ordering emits `OrderPlaced` events; Fulfillment consumes them to start picking
- **Fulfillment → Billing**: Fulfillment emits `ShipmentDispatched` events; Billing consumes them to generate invoices
- **Ordering ↔ Billing**: Shared types for `CustomerId` and `Money`
```

The skill infers which structure applies:

- If `CONTEXT-MAP.md` exists, read it to find contexts
- If only a root `CONTEXT.md` exists, single context
- If neither exists, create a root `CONTEXT.md` lazily when the first term is resolved

When multiple contexts exist, infer which one the current topic relates to. If unclear, ask.


Single context → root `CONTEXT.md`. Seeding it with what's resolved.


Write

# REBNO Domain Context

The shared language of *BN Online* (legacy game) and *ReBNO* (its rebuild). This glossary
is the source of truth for game-domain terms — what concepts ARE, and the canonical word for
each. It is NOT a spec and holds no implementation detail.

> Status: actively being refined via `/grill-with-docs`. Terms marked _(draft)_ are
> provisional pending confirmation.

## Language

### Game identity

**BN Online** (alias: **BNO**):
The legacy game — a persistent, multiplayer **social-exploration** world built in GameMaker
5.3a, skinned in the Mega Man Battle Network aesthetic. The core loop is exploration, not
combat: uncover hidden platforms/pathways/portals, beat timing-sensitive movement challenges,
and solve puzzles built from interwoven traversal mechanics. **No combat system.**
_Avoid_: "MMBN game", "battle game" (the IP is skin, not mechanics).

**ReBNO**:
The ground-up rebuild of BNO on a modern web stack. Primary goal: **full feature parity with
BNO plus QoL tweaks** afforded by web infrastructure. Not a reskin and not a new game — parity
first. New systems and an eventual IP-shed / battle system are explicitly far-future, out of
current scope.
_Avoid_: "the port" (it is a from-scratch rebuild, not a port).

**Progression** (the reward axis):
Advancement is exploration-driven and Metroidvania-shaped — rewards unlock the ability to reach
more of the world. Reward types: new **Zones**, new **Areas**, new **Message Boards**,
**Collectables**, and **Key Items**.

### World structure _(draft — being refined)_

**Zone**:
A top-level region of the world (e.g. BNCentral, Bahoo, Noiya, Floes of Ghennam, Emptiness'
Hull). Reaching a zone requires its **Zone Data** collectable.

**Area** (alias: **Subregion**) _(draft)_:
A sub-region within a Zone. Reaching an area by teleport requires its **Area Data**, which is
only usable if you already hold the parent Zone's Zone Data.

### Key Items & Collectables

**Key Item**:
A progression-gating reward. Primarily **Navi Expansions**. Metroidvania lock-and-key: a key
item unlocks an ability/feature that opens previously-unreachable progression.

**Navi Expansion** (legacy code aliases: `WrpPlugn`, `MskPlugn`):
A class of Key Item that unlocks a Navi ability or feature. Examples: `WrpPlugn` enables use of
**Warp** tiles; `MskPlugn` enables use of **Spectrum Data** (via Spectral Gates).

**Collectable**:
A gatherable reward. The known taxonomy, all surfaced in-universe as **Data** drops:
- **Zone Data** — unlocks teleport to a Zone.
- **Area Data** — unlocks teleport to an Area within a Zone you already hold Zone Data for.
- **Spectrum Data** — unlocks an avatar cosmetic option (requires `MskPlugn` to apply).
- **Friend Data** — an in-universe friends-list entry.

**Data** _(draft)_:
The in-universe noun for collectable item drops (zone/area/spectrum/friend data). The avatar,
NPC, and "data" drop sprites are the only non-original art (MMBN-derived); the rest of BNO's art
is original.

### Avatar & travel

**Navi** _(draft)_:
The player-controlled avatar. Cosmetics are customized via **Spectrum Data**.

**Hexport**:
A mode of travel through dedicated tiles (Hexporter / Hexsling / Hexbridge). Distinct from Warp
and from Data-teleport.

**Warp**:
Intra-zone teleportation via Warp tiles. Inert without the `WrpPlugn` Navi Expansion.

**Spectrum Data customization** (via **Spectral Gate** tiles):
Equipping Spectrum Data to change the Navi's appearance. Requires `MskPlugn`.

### Social

**Message Board**:
A persistent in-world posting surface (legacy `MB_News` / `MB_Log`). New boards are a
progression reward.

**Friend Data**:
The legacy in-universe friends list (collected as Data). _Flagged: see ambiguities._

## Flagged ambiguities

- **"Friends list"** — legacy **Friend Data** (an in-universe collectable) vs a *modern*
  account-level friends list proposed in `.planning/research/FEATURES.md` as a new
  differentiator. These may be the same concept or two distinct systems. **Unresolved.**

## Example dialogue

_(to be written once core terms are confirmed)_


You're mostly correct when it comes to legacy BNO. Exploration is the core focus, with basic social features baked in. The gameplay loop consists of eking out hidden platforms, pathways, and portals,…


File created successfully at: C:\Users\decid\Documents\projects\rebno\CONTEXT.md (file state is current in your context — no need to Read it back)

# REBNO Domain Context

The shared language of *BN Online* (legacy game) and *ReBNO* (its rebuild). This glossary
is the source of truth for game-domain terms — what concepts ARE, and the canonical word for
each. It is NOT a spec and holds no implementation detail.

> Status: actively being refined via `/grill-with-docs`. Terms marked _(draft)_ are
> provisional pending confirmation.

## Language

### Game identity

**BN Online** (alias: **BNO**):
The legacy game — a persistent, multiplayer **social-exploration** world built in GameMaker
5.3a, skinned in the Mega Man Battle Network aesthetic. The core loop is exploration, not
combat: uncover hidden platforms/pathways/portals, beat timing-sensitive movement challenges,
and solve puzzles built from interwoven traversal mechanics. **No combat system.**
_Avoid_: "MMBN game", "battle game" (the IP is skin, not mechanics).

**ReBNO**:
The ground-up rebuild of BNO on a modern web stack. Primary goal: **full feature parity with
BNO plus QoL tweaks** afforded by web infrastructure. Not a reskin and not a new game — parity
first. New systems and an eventual IP-shed / battle system are explicitly far-future, out of
current scope.
_Avoid_: "the port" (it is a from-scratch rebuild, not a port).

**Progression** (the reward axis):
Advancement is exploration-driven and Metroidvania-shaped — rewards unlock the ability to reach
more of the world. Reward types: new **Zones**, new **Areas**, new **Message Boards**,
**Collectables**, and **Key Items**.

### World structure _(draft — being refined)_

**Zone**:
A top-level region of the world (e.g. BNCentral, Bahoo, Noiya, Floes of Ghennam, Emptiness'
Hull). Reaching a zone requires its **Zone Data** collectable.

**Area** (alias: **Subregion**) _(draft)_:
A sub-region within a Zone. Reaching an area by teleport requires its **Area Data**, which is
only usable if you already hold the parent Zone's Zone Data.

### Key Items & Collectables

**Key Item**:
A progression-gating reward. Primarily **Navi Expansions**. Metroidvania lock-and-key: a key
item unlocks an ability/feature that opens previously-unreachable progression.

**Navi Expansion** (legacy code aliases: `WrpPlugn`, `MskPlugn`):
A class of Key Item that unlocks a Navi ability or feature. Examples: `WrpPlugn` enables use of
**Warp** tiles; `MskPlugn` enables use of **Spectrum Data** (via Spectral Gates).

**Collectable**:
A gatherable reward. The known taxonomy, all surfaced in-universe as **Data** drops:
- **Zone Data** — unlocks teleport to a Zone.
- **Area Data** — unlocks teleport to an Area within a Zone you already hold Zone Data for.
- **Spectrum Data** — unlocks an avatar cosmetic option (requires `MskPlugn` to apply).
- **Friend Data** — an in-universe friends-list entry.

**Data** _(draft)_:
The in-universe noun for collectable item drops (zone/area/spectrum/friend data). The avatar,
NPC, and "data" drop sprites are the only non-original art (MMBN-derived); the rest of BNO's art
is original.

### Avatar & travel

**Navi** _(draft)_:
The player-controlled avatar. Cosmetics are customized via **Spectrum Data**.

**Hexport**:
A mode of travel through dedicated tiles (Hexporter / Hexsling / Hexbridge). Distinct from Warp
and from Data-teleport.

**Warp**:
Intra-zone teleportation via Warp tiles. Inert without the `WrpPlugn` Navi Expansion.

**Spectrum Data customization** (via **Spectral Gate** tiles):
Equipping Spectrum Data to change the Navi's appearance. Requires `MskPlugn`.

### Social

**Message Board**:
A persistent in-world posting surface (legacy `MB_News` / `MB_Log`). New boards are a
progression reward.

**Friend Data**:
The legacy in-universe friends list (collected as Data). _Flagged: see ambiguities._

## Flagged ambiguities

- **"Friends list"** — legacy **Friend Data** (an in-universe collectable) vs a *modern*
  account-level friends list proposed in `.planning/research/FEATURES.md` as a new
  differentiator. These may be the same concept or two distinct systems. **Unresolved.**

## Example dialogue

_(to be written once core terms are confirmed)_