claudeandClaude Opus 5 ef28970654 Close three holes an unmodified client could walk through
One account is now one session. Two peers signed in as the same account
each got their own player built from the SAME character record, so both
held a copy of one bag and whichever wrote last won: drop the single
potion from both copies and two potions hit the floor. Reconciling two
live mirrors of one record cannot be made to work, so the handshake
refuses the second connection -- the newcomer, not the incumbent, or
claiming an account would be a way to kick anyone off the server.

A muzzle offset is a teleport and the bullet pool never sees it: it
samples geometry once per tick. Forward that is 19px, but Doubleshot
adds up to 45px sideways, wider than a 32px wall plus the 6px hitbox
holding you off it. Six Doubleshots, stand flush against a wall, fire
along it, and live shots came out the far side. Shots now start from the
shooter when geometry stands in the way, which keeps the count honest.

Bullet-spawn events carry the firing actor's exact position, and they
were filtered by a flat 2900-unit radius against an 800-unit actor cull.
A depth-1 map's diagonal is about 2521, so that radius culled nothing:
every enemy that fired was pinpointed for every player, through fog, on
terrain the client was deliberately not streamed. The test is now
per bullet -- can THIS one reach ground you can see -- which keeps the
guarantee the wide radius existed for and drops the radar.

Found while fixing the above: a rejection never reached the client. The
RPC is queued and only leaves on the frame's flush, so kicking in the
same call threw the reason away, and _on_server_disconnected overwrote
last_error anyway. Both were true of protocol mismatch too, which is the
one message a player most needs. Smoke asserts the reason arrives.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 17:04:05 +02:00
2026-09-03 16:03:57 +02:00
2026-09-03 16:03:57 +02:00
2026-09-07 16:20:10 +02:00
2026-09-03 16:03:57 +02:00
2026-09-07 16:20:10 +02:00
2026-09-03 16:03:57 +02:00
2026-09-07 16:20:10 +02:00

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

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.

S
Description
No description provided
Readme
2.4 MiB
0 Stars 1 Watchers 0 Forks
Languages
GDScript 98.6%
Shell 1.2%
Python 0.2%