Stage 1: tile maps, walls, fog of war, aggro, scrolling camera
ci / verify (push) Successful in 46s

Replaces the fixed centred arena with per-world tile geometry, which is the
foundation the remaining features sit on.

- MapGrid: tile grid with three independent flags -- blocks movement, bullets,
  sight -- so a pit stops feet but not bullets or eyes, and a barricade stops
  feet and bullets but not eyes. Circle collision with per-axis sliding, and
  Bresenham line of sight shared by fog and aggro.
- MapGen: rooms and corridors generated from (seed, depth), with hand-authored
  boss arenas from Rooms stamped in first so a corridor can never carve through
  a designed fight. Reachability from spawn to boss is asserted over 40 seeds --
  "usually connected" is the failure mode that ruins one run in twenty.
- Dungeons are populated at creation, per room, instead of gating on waves. You
  explore and choose your fights; the run ends when the boss dies, not when the
  map is swept. Boss rooms have no lock, so walking out is always available.
- Aggro: enemies need range AND line of sight, so a dungeon stays quiet until
  engaged and cover actually protects.
- Hard fog, scrolling camera, and terrain rendering.

Maps are streamed per peer in chunks around that peer's player, and the seed is
deliberately NOT sent -- a client holding it could regenerate the whole dungeon,
which is a map hack for free. Stream radius (900u) is wider than view radius
(460u) because the client predicts movement against walls and simulates bullets
that die on them; the accepted cost is a cheater seeing a little further than
the fog, never the floor plan.

Partial map knowledge means wall deaths must be announced rather than derived.
A test caught the subtle half of that: out-of-bounds tiles read as WALL by
design, so checking geometry before bounds reported every bullet leaving the map
as a wall kill.

128 tests (was 103); check.sh, test.sh and smoke.sh pass. 0.26 ms/tick with 4
players and a boss, ~65x headroom.
This commit is contained in:
2026-09-03 20:49:27 +02:00
parent e1f9fa8096
commit 7af439341d
34 changed files with 1522 additions and 196 deletions
+12 -1
View File
@@ -24,7 +24,8 @@ Everything after `--` goes to `GameOpts.parse()`:
| `--bot` | Scripted input instead of the keyboard. Implies `--join`. |
| `--host`, `--port`, `--name` | Connection details. |
| `--autoquit N` | Quit after N physics ticks. |
| `--boss-rush` | Server-side: new dungeons open straight onto the boss. |
| `--boss-rush` | Server-side: dungeons spawn the boss and no trash. |
| `--depth N` | Server-side: depth of new dungeons, which drives map size. |
| `--verbose` / `--quiet` | Log level. |
A change is done when `check.sh`, `test.sh` and — if it touched networking,
@@ -60,6 +61,9 @@ and `tests/integration/test_replica_parity.gd` pin this down.
| --- | --- |
| `src/sim/` | The whole game as plain RefCounted objects. No nodes, no physics server, no rendering. |
| `src/sim/patterns/` | Bullet emitters — the authoring surface for every enemy and boss. |
| `src/sim/map_grid.gd` | Tile grid: collision, line of sight, chunk streaming. |
| `src/sim/map_gen.gd` | Dungeon generation; `build()` is the only entry point. |
| `src/content/rooms.gd` | Hand-authored room stamps (hub, boss arenas) as text. |
| `src/content/content.gd` | All enemies and bosses, defined in code. Source of truth. |
| `src/net/` | Codec, `ServerRuntime`, `ClientRuntime`. |
| `src/instances/` | Lobby hub and dungeon runs. |
@@ -116,6 +120,13 @@ ticks in milliseconds with no SceneTree.
dead zone where the server rejects everything and the client never notices —
the ship and the authoritative position separate permanently. Pinned by
`tests/unit/test_input_lead.gd`.
- **Never send the map, or its seed.** Geometry is streamed per peer in chunks
around that peer's player (`ServerRuntime._stream_map`). The seed would let
any client regenerate the whole dungeon. `MAP_STREAM_RADIUS` must stay wider
than `FOG_VIEW_RADIUS`, or prediction runs on terrain the client lacks.
- **Bullet speed must stay under one tile per tick.** Wall collision samples
position once per tick, so anything faster tunnels. Pinned by
`test_bullet_speeds_stay_below_the_tunnelling_threshold`.
- **No contact damage.** Every enemy threatens through bullets only; touching
one is harmless. `tests/unit/test_content.gd` enforces that every hostile has
an emitter.