From 3d90a18c15ca54f262cfa61edba4866c91f29802 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Fri, 17 Jul 2026 19:21:01 -0400 Subject: refactor: deterministic per-tick subscriber ordering (now fires in same order registered) --- internal/engine/tick_test.go | 62 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 internal/engine/tick_test.go (limited to 'internal/engine/tick_test.go') diff --git a/internal/engine/tick_test.go b/internal/engine/tick_test.go new file mode 100644 index 0000000..520ef19 --- /dev/null +++ b/internal/engine/tick_test.go @@ -0,0 +1,62 @@ +package engine + +import ( + "sync" + "testing" +) + +// TestProcessTickFiresInSubscriptionOrder asserts the engine invokes due +// tick subscribers in ascending subscription-ID order, i.e. the order in +// which they registered. This is the determinism contract that callers +// (notably the game's master subscriber at cmd/thoi/main.go, which subscribes +// first as ID 1 and must fire before combat/aggro/respawn per-tick callbacks) +// increasingly rely on. +func TestProcessTickFiresInSubscriptionOrder(t *testing.T) { + e := New() + var mu sync.Mutex + var order []int + + // Three interval-1 subscribers. Each appends its own index. All return + // true to stay subscribed (we Unsubscribe at the end). + id1 := e.Subscribe(1, func() bool { + mu.Lock() + order = append(order, 1) + mu.Unlock() + return true + }) + id2 := e.Subscribe(1, func() bool { + mu.Lock() + order = append(order, 2) + mu.Unlock() + return true + }) + id3 := e.Subscribe(1, func() bool { + mu.Lock() + order = append(order, 3) + mu.Unlock() + return true + }) + + if id1 >= id2 || id2 >= id3 { + t.Fatalf("ID monotonicity broken: got %d, %d, %d", id1, id2, id3) + } + + // processTick is synchronous: it invokes every due interval-1 subscriber + // inline in turn before returning. So `order` is fully populated here. + e.processTick() + + mu.Lock() + got := append([]int(nil), order...) + mu.Unlock() + + e.Unsubscribe(id1) + e.Unsubscribe(id2) + e.Unsubscribe(id3) + + if len(got) != 3 { + t.Fatalf("expected 3 subscriber firings, got %d: %v", len(got), got) + } + if got[0] != 1 || got[1] != 2 || got[2] != 3 { + t.Errorf("subscribers fired out of registration order: got %v, want [1 2 3]", got) + } +} -- cgit v1.2.3