20dd6bc502
ci / verify (push) Successful in 46s
The packs total 3.9GB, almost all of it Helton Yan's SFX shipped as 24-bit / 96kHz studio masters -- ~2MB for a single gunshot. Two guards, because they solve different problems: - .gitignore excludes the contents. The directory and its guard files stay tracked, so a fresh clone has the .gdignore in place before anyone drops packs back in. - assetpacks/.gdignore stops Godot descending into the tree at all. This is the urgent half: tools/check.sh runs --import, which walks all of res://, so the next check would have imported several thousand PNGs and 2,100 of those WAVs, and every run after it. Verified by controlled test rather than assumed -- an unguarded PNG under res:// imports and gets a .import file beside it; with .gdignore neither happens. check.sh stays at 3.3s and .godot/imported at 1.2MB. The trade the .gdignore buys is that nothing in assetpacks/ can be referenced from a scene or script. That is the intended shape: raw source on one side, game-ready files copied into res://assets/ on the other, so adding a huge pack costs the repo and the import step nothing. docs/ASSETS.md records attribution, because gitignoring the packs also gitignores their licence files. Worth flagging: four of the six packs shipped with no licence text at all, and two of the undecided ones are demo versions, which are often more restricted than the paid packs. Only RF Catacombs has explicit terms (public domain, no resale). Those rows need a source URL before anything ships. Also removed __MACOSX and .DS_Store extraction artefacts, and left considering_dont_use_yet/ untouched as the user's own "not chosen" marker.
143 lines
7.3 KiB
Markdown
143 lines
7.3 KiB
Markdown
# 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 | **partial** | `SimBoss.room` exists and bounds curtain patterns, but nothing clamps the boss — every boss is stationary today. Enforcement belongs with boss movement in Stage 5. |
|
||
| **Actor interest management** | **todo** | The known gap. `ServerRuntime._physics_process` still encodes one snapshot and sends it to every peer in the instance, so clients are told about enemies the fog then hides. Terrain is properly withheld; actors are not. **Do this first.** |
|
||
|
||
### The invariant that matters 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.
|
||
|
||
---
|
||
|
||
## 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.
|