Unlock menu after failed join; remove contact damage; cover clean disconnects
ci / verify (push) Successful in 45s
ci / verify (push) Successful in 45s
A failed connection left the connect buttons disabled forever. _show_menu() returned early when a menu already existed, so the set_busy(false) that re-enables them never ran on the way back from _abort_connect -- the player was locked out of both joining and hosting with no way out but a restart. Also guards against a second attempt stacking on an in-flight one. Contact damage is gone as a concept: nothing in this game hurts you by touching it, because every threat should be a bullet you can see and dodge. The Stalker walked at you and dealt contact damage and nothing else, so it now carries a point-blank shotgun instead -- five pellets, 62 degrees, 18-tick lifetime for about 78px of reach, so it still has to close and leaves nothing lingering. tests/unit/test_content.gd asserts every hostile enemy has an emitter, so a new enemy cannot quietly reintroduce the mechanic. The menu's "Disconnect" DOES get the anti-cheese protection -- verified, not assumed. It calls Net.shutdown(), the socket closes, and the server takes the same linkdead path as a SIGKILL, because the channel is keyed on the socket closing rather than on how it closed. Made permanent: a --leave-after bot flag and two smoke assertions covering the polite exit alongside the hard kill, so a future "clean leave" message that skipped the channel would fail a test. The menu now says so out loud in a dungeon -- the mechanic only reads as fair if the cost is known before clicking. 103 tests, 12 smoke assertions; check.sh, test.sh and smoke.sh all pass.
This commit is contained in:
@@ -104,6 +104,39 @@ Two defences now:
|
||||
`tools/diag_prediction.gd` injects a +14 tick drift mid-run and asserts the gap
|
||||
returns to normal; it exits non-zero if it does not.
|
||||
|
||||
### Every exit runs the same channel
|
||||
|
||||
There is deliberately no "I am leaving cleanly" message. The escape channel is
|
||||
keyed on the socket closing, so all four ways out converge on it:
|
||||
|
||||
| Route | Path |
|
||||
| --- | --- |
|
||||
| Hold F | `BTN_ESCAPE` -> channel |
|
||||
| Menu -> "Return to hub" | synthesises a held `BTN_ESCAPE` -> channel |
|
||||
| Menu -> "Disconnect to menu" | `Net.shutdown()` -> socket close -> `linkdead` -> channel |
|
||||
| Killing the process / pulling the cable | socket close -> `linkdead` -> channel |
|
||||
|
||||
The last two are the same server-side code path, and `tools/smoke.sh` asserts
|
||||
both: one bot is SIGKILLed mid-dungeon, another calls the same `Net.shutdown()`
|
||||
the menu button calls. Adding a clean-leave message that skipped the channel
|
||||
would break that test, which is the point of having it.
|
||||
|
||||
The menu says so out loud when you are in a dungeon -- the mechanic only reads
|
||||
as fair if the player knows the cost before clicking.
|
||||
|
||||
## No contact damage
|
||||
|
||||
Nothing hurts you by touching it. Every threat is a bullet you can see and
|
||||
dodge, which is the contract the genre runs on; an enemy that damages you for
|
||||
occupying the same space is an unavoidable, unreadable hit.
|
||||
|
||||
The Stalker used to be exactly that -- it walked at you and dealt contact
|
||||
damage. It now carries a point-blank shotgun instead: five pellets, 62 degrees,
|
||||
and an 18-tick lifetime that gives it about 78px of reach, so it still has to
|
||||
close the distance and still leaves nothing lingering in the arena.
|
||||
`tests/unit/test_content.gd` asserts that every hostile enemy has at least one
|
||||
emitter, so a new enemy cannot quietly reintroduce the mechanic.
|
||||
|
||||
## No invulnerability frames
|
||||
|
||||
A hit grants no immunity — every bullet that touches you deals its damage. In a
|
||||
|
||||
Reference in New Issue
Block a user