# Status and roadmap **Read this first when picking up work.** It maps every feature in the design brief to its current state and the files that implement it, so you can find the relevant code without re-reading the whole project. Art and audio source packs, and their licence status, are recorded in [ASSETS.md](ASSETS.md) — the packs themselves are not in git. Design decisions that are already settled — and the reasoning behind them — live in [DECISIONS.md](DECISIONS.md). Check there before asking the user something that may already have an answer. Legend: **done** · **partial** (works, with a stated gap) · **todo** (not started) --- ## Stage 1 — World and exploration · *done* | Feature | State | Where | | --- | --- | --- | | Tile grid: movement / bullet / sight blocking | done | [src/sim/map_grid.gd](../src/sim/map_grid.gd) | | Dungeon generation from (seed, depth) | done | [src/sim/map_gen.gd](../src/sim/map_gen.gd) | | Hand-authored boss arenas and hub | done | [src/content/rooms.gd](../src/content/rooms.gd) | | Walls, pillars, pits, barricades | done | `MapGrid.Kind` + the three `BLOCKS_*` tables | | Enemies placed per room at creation | done | `Instance._populate()` | | Scrolling camera | done | [src/view/game_scene.gd](../src/view/game_scene.gd) | | Hard fog of war | done | `WorldView._draw_terrain` / `_visible` | | Per-peer map streaming (anti map-hack) | done | `ServerRuntime._stream_map` | | Aggro: range **and** line of sight | done | `SimWorld._aggro_target` | | Cursor-to-world aiming under a scrolling camera | done | `ClientRuntime.screen_to_world` | | Boss confined to its room | done | `SimWorld._step_boss` clamps to `SimBoss.room`. A no-op while bosses are stationary — established now so Stage 5's movement cannot quietly break it. | | Actor interest management | done | `NetCodec.encode_snapshot(world, countdown, for_peer)`; per-peer encode in `ServerRuntime`. Bullet spawns filtered separately, see below. | ### The invariants that matter most here **Maps are streamed per peer, and the generation seed is never sent.** See [NETCODE.md](NETCODE.md#maps-are-streamed-never-sent). `MAP_STREAM_RADIUS` must stay wider than `FOG_VIEW_RADIUS`, or the client predicts movement against terrain it does not have. **Three radii, deliberately different, and each has a floor it must respect:** | Radius | Value | Must exceed | Why | | --- | --- | --- | --- | | `FOG_VIEW_RADIUS` | 460 | — | What the player can see. | | `ACTOR_INTEREST_RADIUS` | 800 | fog radius | Enemies beyond it are never sent. Above the fog radius so nothing pops in at the edge of sight. | | `MAP_STREAM_RADIUS` | 900 | fog radius | Client predicts movement and simulates bullets against terrain it cannot see. | | `BULLET_INTEREST_RADIUS` | 2200 | longest bullet travel + fog radius | A bullet is announced once at spawn. Withhold one that later flies into view and it becomes invisible damage. `test_interest.gd` computes the floor from real content, so a faster bullet fails a test instead. | Fog is a *rendering* rule and defends nothing on its own — a modified client draws whatever it holds. The defence is what the server declines to send. --- ## Art and audio · *first pass done* Placeholders are gone: terrain, actors and bullets are sprites, and four sounds play off server events. Enough to prove the pipeline, not a finished look. | Feature | State | Where | | --- | --- | --- | | Atlas/sound table in one place | done | [src/view/art.gd](../src/view/art.gd) | | Terrain, actors, boss from the 0x72 atlas | done | `WorldView._draw_tile` / `_draw_sprite` | | Animated bullet sprites, one MultiMesh per kind | done | [src/view/bullet_renderer.gd](../src/view/bullet_renderer.gd) | | SFX pool driven by server events | done | [src/view/sfx.gd](../src/view/sfx.gd) | | Rects validated without a display | done | `tests/unit/test_art.gd` | | Impact/death VFX animation | todo | `Art.IMPACT` is loaded and validated but nothing plays it yet | | Directional sprites, hit flashes, screen shake | todo | | | Audio buses and a volume setting | todo | Everything plays on Master at hardcoded dB | | **In-game credits screen** | **todo** | Not cosmetic: the SFX are CC BY 4.0 and attribution is a licence *requirement*. [CREDITS.md](../CREDITS.md) is not reachable by a player. | | Replace the two non-redistributable packs | todo | Bullet and FX art is local-only and non-commercial. CC0 replacements would let them into the repo and unblock a commercial release. See [ASSETS.md](ASSETS.md). | ## Stage 2 — Characters, persistence, levels · *todo, next* Depends on nothing in Stage 1 except a place to stand. Blocked only on the identity layer, which is decided but unbuilt. | Feature | Notes | | --- | --- | | Identity abstraction | Shaped like Steamworks so it swaps out cleanly: opaque ticket from client → server validates → stable 64-bit account id (a SteamID64 stand-in). Local dev provider persists a generated id in `user://`. See [DECISIONS.md](DECISIONS.md#identity). | | Character store | Server-side, keyed by account id. Needs a schema and a file format; nothing persistent exists in the project today. | | Up to 5 characters, random colour each | Colour is a placeholder for a later cosmetic system. | | Last-played character auto-selected on join | | | Permadeath → mark inactive, never delete | Archival/troubleshooting. Confirm whether the 5-character cap counts only *active* characters. | | Death flow: pick another character or create one | Replaces today's "return to hub" button, which currently just revives you. | | Levels 1–15, +10 max HP per level | Confirm whether level 1 is 100 HP (→ 240 at cap) or 110. | | XP from kills, bosses worth much more | First full dungeon should give slightly more than one level. | Current behaviour to replace: `SimPlayer` has no identity beyond a peer id; `ServerRuntime.peer_names` is client-supplied and trusted for display only. --- ## Stage 3 — Upgrades · *todo* | Feature | Notes | | --- | --- | | Lobby NPC, 3 random choices per level gained | Confirm whether unclaimed level-ups queue. | | Every upgrade also grants +5% damage, additive | Displayed on the choice alongside its specific effects. | | Upgrade list | split shot, glass cannon, spread, sniper, doubleshot (rarer), poison (rare), eraser (legendary) | | Rarity weights | **Unspecified.** Needs numbers. | | Damage stacking formula | **Unspecified.** Sniper is explicitly multiplicative; the rest read as additive. Order needs pinning down before any of it is built. | | "Chosen upgrades" screen | | Damage is currently the constant `SimConfig.PLAYER_BULLET_DAMAGE`; upgrades turn it into a per-player computed stat, so `SimWorld._fire_player_shot` grows a stats block. Several upgrades (split, spread, doubleshot) change how many bullets spawn per shot, so they belong in the same place. --- ## Stage 4 — Inventory and loot · *todo* | Feature | Notes | | --- | --- | | Small always-on-screen inventory | Slot count unspecified. | | Health potions: rare from trash, guaranteed from bosses | | | World-shared loot | Player-instanced loot planned later. | | A unique, useless food item from bosses | Exists specifically to test player-instanced loot. Confirm whether it should be instanced *now* or just marked for it. | | Dropping items so others can pick them up | | --- ## Stage 5 — Boss features and new bosses · *todo* | Feature | State | | --- | --- | | Stationary phases | done — every current phase | | Roaming / chasing within the boss room | todo; needs `SimBoss.room` clamping (see Stage 1 partial) | | Phases that move to preset locations | todo | | Attacks spawned at a distance with a telegraph indicator | todo — new event type plus a renderer, and it must survive fog | | More bosses | partial — `Rooms.choir_vault()` is authored but has no `BossDef` yet | The boss format itself is proven: `tests/unit/test_boss.gd` builds a boss from scratch and asserts the simulation needs no changes to run it. Movement will be the first thing that format has not covered, so expect `BossPhase` to gain a movement field rather than `SimWorld` gaining a branch. --- ## Deliberate omissions Not oversights — each was considered and rejected for now, with the reasoning in [NETCODE.md](NETCODE.md) or [DECISIONS.md](DECISIONS.md): - **Lag compensation.** Rewinding to a shooter's view means a player who dodged still gets hit; wrong trade for this genre. - **Snapshot delta compression.** Fine at current actor counts. - **DTLS / encryption.** `ENetMultiplayerPeer` supports it. Required before any public server, not before then. - **Pattern-level bullet replication.** A real bandwidth win that couples the client to emitter behaviour. - **`MultiplayerSynchronizer` / `MultiplayerSpawner`.** Right tools, wrong shape for a bullet hell — see [ARCHITECTURE.md](ARCHITECTURE.md). --- ## Open questions for the user Genuinely unspecified; do not guess at these, they change the design: 1. Damage stacking order — additive pool then multiplicative, or something else? 2. Rarity weights for the four upgrade tiers. 3. Split shot geometry: ±22.5° from the original heading, or 45° to each side? 4. Poison: do applications stack, or refresh a single DoT? 5. Eraser: does it delete *enemy bullets* it passes through? 6. Inventory slot count. 7. Do unclaimed level-ups queue at the NPC? 8. Does the 5-character cap count only active characters? 9. Glass cannon's −50% health: of base HP, or of levelled max HP? 10. What advances dungeon depth? `--depth` is a dev flag; nothing raises it in play.