Prompts named the default keys whatever was bound. Every action is rebindable, but the HUD said "WASD move", "LMB fire", "hold F" and "1-4 use", the quartermaster said "E talk" and the upgrade screen "Close (E)". All of them now go through Settings.short_label(), and a test rebinds fire, escape, interact and a slot key and checks each prompt follows. The HUD's top-left was a developer's status line -- name, instance id, health as text, frame rate -- with the boss bar drawn over its right end and the level-up notice sharing a row with the escape bar. It is now name and level, where you are, health with its numbers, experience, escape and level-ups, stacked by a running cursor so nothing has a fixed row to collide on. Instance and fps moved behind F1, which is who they were for. Arrival protection is a centred banner rather than a note beside the health bar, because it is the one moment the rules change. A hit used to tint the whole screen red, which in a bullet hell dims the bullets at exactly the moment you are among them. It is an edge now, and the same edge pulses below a third of your health. New: a message feed above the inventory for what the server just confirmed -- items taken, used and dropped, levels gained -- and party health top-right in a dungeon, where the hub shows who is online. The pickup prompt now also names the dungeon under your feet when you stand on its ring, in the same order the server resolves that key: loot first, then the entrance. The boss bar used to announce the boss to anyone within the actor interest radius -- which passes through walls -- so a player in the entrance room was told the Warden was near. It waits until the fog has actually lit the boss once, then stays for the fight. Permadeath was announced only by the roster screen appearing. The client records its own death from the server's event and the roster says who fell, at what level, and where. The connect screen lost a name field that did nothing for a person -- it only named the characters bots create -- gained a proper panel, remembers the last server, and shows the protocol number a mismatch refusal will quote. And screenshot.tscn ran as the player's real account against their real character store, walking their character into dungeons for pictures -- with health now carried between runs, a stray hit would have stuck. It has its own store and account now, like every diagnostic, and a seeded character. The real store was checked: nothing it carries changed. Co-Authored-By: Claude Opus 5.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 to pick up loot and to use the hub's portals and quartermaster, 1–4 use an inventory slot, shift+1–4 drop one, hold F for one second to escape a dungeon, Esc menu, F1 hitbox overlay. Every one of those is rebindable in Settings.
What is here
| Twin-stick bullet hell | Server-simulated bullet field, ~300 concurrent bullets at 0.28 ms/tick — around 60x headroom against the 60 Hz budget. |
| Authoritative multiplayer | Clients send input and nothing else. Hits, damage, death, loot, upgrades and instance transfers are all server decisions. Prediction and reconciliation for the local player. |
| Enemies | Five readable movements (static, drift, orbit, approach, strafe) composed with bullet emitters. Nothing deals contact damage; every threat is a bullet you can see. |
| Two bosses | The Warden of the Fold stands still through four phases. The Cantor of the Vault walks a circuit, chases, orbits, and marks the floor before it strikes. Both are pure data. |
| Three dungeons | Two real runs, one per boss, plus a Proving Grounds: the same content at a fraction of the health and ten times the drop rate, for testing by hand. |
| Characters | Up to five living per account, permadeath, levels 1–15, experience shared across the party undivided. |
| Inventory and loot | Four slots, always on screen. Loot is either world-shared or instanced per player, and the instanced kind is filtered on the wire rather than hidden in the client. |
| Upgrades | Seven, drawn three at a time and spent at a hub NPC. Damage, fire rate, extra projectiles, splitting, poison, and a legendary that deletes enemy bullets. |
| Emergency escape | A one-second channel back to the hub that damage does not interrupt — because if it did, quitting the process would be the better escape. Dropping your connection runs the same channel. |
| Settings | Rebindable controls and volumes, and an in-game credits screen. |
Develop
tools/check.sh # parse-check every script ~5s
tools/test.sh # 468 GUT tests, headless ~4s
tools/smoke.sh # server + 4 bot clients over ENet ~40s
Read CLAUDE.md first — it is short, and the Godot CLI gotchas in it will otherwise cost an afternoon.
Docs
- docs/ROADMAP.md — what is built, what is next, where each feature lives
- docs/DECISIONS.md — settled design decisions and their reasoning
- docs/ARCHITECTURE.md — code map and the node-free simulation
- docs/NETCODE.md — replication model, hit validation, known gaps
- docs/WORKFLOW.md — agent-assisted Godot development, and the verification traps this project has actually hit
- docs/ASSETS.md — asset packs and their licence findings
- CREDITS.md — third-party attribution
- CLAUDE.md — commands, invariants, Godot CLI gotchas
Not ready for the internet
LocalAuthProvider lets any client claim any account id. It exists to have the
same shape as Steamworks so swapping is one class, and it must be replaced
before this is reachable from anywhere untrusted. There is no transport
encryption either. See docs/ROADMAP.md.