From c054b27f628f23fecc505b9e09e78721738f3c4e Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Wed, 16 Sep 2026 13:52:58 -0400 Subject: fix: shutdown admission, recovery identity/order, rollback journal retention, pickup merge --- internal/game/shutdown_admission_test.go | 55 ++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 internal/game/shutdown_admission_test.go (limited to 'internal/game/shutdown_admission_test.go') diff --git a/internal/game/shutdown_admission_test.go b/internal/game/shutdown_admission_test.go new file mode 100644 index 0000000..6aa4be5 --- /dev/null +++ b/internal/game/shutdown_admission_test.go @@ -0,0 +1,55 @@ +package game + +// Shutdown-admission regression: once shutdown has begun, session input must +// never mutate game state — not during the drain window, and especially not +// after the owner has stopped (the transports keep draining until they +// close, so an accepted line would execute concurrently with the shutdown +// sequence or after the final-save barrier). + +import ( + "testing" + + "thehouseoficarus/internal/net" +) + +func TestShutdownRejectsSessionInput(t *testing.T) { + h := newHarness(t) + h.seedAccount("alice", "pw", false) + h.seedCharacter("alice", "alice") + sess, _ := h.connect("alice") + sess.State = net.StateGame + h.g.setShuttingDown() + h.g.stopOwner() + + // The network listeners are closed only AFTER the owner stops, so + // connections can still submit lines through the production entry + // point. Every one of them must be rejected. + h.g.HandleSessionOnOwner(sess, "look") + + // A durable mutation gives an unambiguous admission check: description + // input would rewrite the character. + sess.State = net.StateChangeDesc + h.g.HandleSessionOnOwner(sess, "Input after final save") + if sess.Player.Description == "Input after final save" { + t.Error("session input still mutates character after shutdown/owner stop") + } +} + +func TestShutdownRejectsInputDuringDrain(t *testing.T) { + h := newHarness(t) + h.seedAccount("alice", "pw", false) + h.seedCharacter("alice", "alice") + sess, _ := h.connect("alice") + sess.State = net.StateGame + h.g.setShuttingDown() + + // During the drain window the owner is still alive, but new input is + // already rejected: only work accepted before shutdown executes. + h.g.HandleSessionOnOwner(sess, "look") + sess.State = net.StateChangeDesc + h.g.HandleSessionOnOwner(sess, "Input during drain") + if sess.Player.Description == "Input during drain" { + t.Error("session input accepted after shutdown began") + } + h.barrier() +} -- cgit v1.2.3