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) } }