a943aa19f625bf1df4ae3c42e9c82374ace50df6
ci / verify (push) Successful in 47s
Two labelled portals now stand side by side in the hub. The Proving Grounds runs the same generator, the same rooms, the same enemies and the same four-phase Warden -- enemies at a fifth health, the boss at 288 instead of 3600, and trash dropping potions 80% of the time instead of 8%. A manual pass over loot, the inventory, dropping and every boss phase takes a couple of minutes rather than a quarter of an hour. It is multipliers over the shared content rather than a parallel copy: a duplicated Content would drift the first time anything was tuned, and "identical but easier" would quietly stop being true. And it is a portal rather than a launch flag, so the two can be compared back to back without restarting the server -- which is most of the point. Which dungeon you enter is resolved from the player's server-side position, and PORTAL_USED carries the answer. There is deliberately no client message that names a dungeon: one would let any client ask for the generous loot table and bring the results back to the hub. Instance matching compares dungeon ids too, so walking into one entrance can never drop you into the other's run on timing alone. SimWorld.portals replaces portal_pos/portal_enabled, enter_instance carries the portal list and the dungeon id (the client needs the latter to scale the boss bar's ceiling the way the server scaled the boss), and Protocol.VERSION goes to 7. Also pins what happens when two players reach for one item on the same tick: exactly one gets it -- the loop is sequential and the pickup erases the entity before the next player looks. The tie-break is join order rather than distance, which is arbitrary rather than designed, so it is recorded as such. Stale doc fixed while here: MapGen.build() still claimed the client rebuilds the map from the seed, which has not been true since map streaming landed and is the opposite of the rule. check.sh clean, 288 tests, SMOKE PASS (18 assertions, both dungeon kinds opened over a real socket), all three diagnostics green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Transcience
Top-down twin-stick bullet-hell with a dedicated, server-authoritative backend. Godot 4.7, GDScript, no external runtime dependencies.
Play
tools/server.sh # dedicated server on :27015
tools/client.sh --join --name ada # connect a client
Or run one client and press Host and play for a listen server.
Controls — WASD move, mouse aim, LMB fire, E on the ring in the hub to enter a dungeon, hold F for three seconds to escape back to the hub.
Develop
tools/check.sh # parse-check every script ~5s
tools/test.sh # GUT suite, headless ~2s
tools/smoke.sh # server + 2 bot clients, ENet ~35s
godot --headless --path . --script tools/bench.gd
Read CLAUDE.md first — it is short, and the Godot CLI gotchas in it will otherwise cost an afternoon.
What is here
| Twin-stick bullet hell | Server-simulated bullet field, ~350 concurrent bullets at 0.24 ms/tick. |
| Authoritative multiplayer | Clients send input only. Hits, damage, death and instance transfers are server decisions. Prediction + reconciliation for the local player. |
| Enemies | Four readable behaviours (static, drift, orbit, approach, strafe) composed with bullet emitters. |
| Boss | The Warden of the Fold: stationary, four phases, each adding one idea. Defined entirely as data. |
| Lobby hub | Shared persistent instance with a portal into dungeon runs. |
| Emergency escape | Three-second channel back to the hub, cancelled by damage. |
Docs
- docs/ROADMAP.md — what is built, what is next, where each feature lives
- docs/DECISIONS.md — settled design decisions and their reasoning
- CREDITS.md — third-party asset attribution
- CLAUDE.md — commands, invariants, Godot CLI gotchas
- docs/WORKFLOW.md — agent-assisted Godot development, and why the setup looks like this
- docs/ARCHITECTURE.md — code map and the node-free simulation
- docs/NETCODE.md — replication model, hit validation, known gaps
- docs/ROADMAP.md — what is built and what is next
Description
Languages
GDScript
98.6%
Shell
1.1%
Python
0.3%