aboutsummaryrefslogtreecommitdiff
path: root/internal/engine/tick.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/engine/tick.go')
-rw-r--r--internal/engine/tick.go21
1 files changed, 20 insertions, 1 deletions
diff --git a/internal/engine/tick.go b/internal/engine/tick.go
index ff1bace..251373c 100644
--- a/internal/engine/tick.go
+++ b/internal/engine/tick.go
@@ -2,6 +2,7 @@ package engine
import (
"math/rand"
+ "sort"
"sync"
"time"
)
@@ -85,6 +86,17 @@ func (e *Engine) Stop() {
}
}
+// processTick fires every due subscriber for the current engine tick.
+//
+// Subscribers are invoked in ascending subscription-ID order, i.e. the order in
+// which they were registered with Subscribe. IDs are allocated monotonically
+// (e.nextID++), so subscription order is a stable, deterministic sequence:
+// the bootstrap master subscriber (cmd/thoi/main.go) subscribes at startup as
+// ID 1 and therefore always fires first each tick, followed by each
+// subsequently-registered tick (combat rounds, aggro rolls, scheduled respawns,
+// etc.) in the order they registered. This guarantees predictable per-tick
+// ordering across runs — critical for features whose behaviour depends on the
+// relative ordering of combat resolution vs. movement vs. aggro checks.
func (e *Engine) processTick() {
e.mu.Lock()
snapshot := make(map[uint64]*subscriber, len(e.subscribers))
@@ -93,7 +105,14 @@ func (e *Engine) processTick() {
}
e.mu.Unlock()
- for id, sub := range snapshot {
+ ids := make([]uint64, 0, len(snapshot))
+ for id := range snapshot {
+ ids = append(ids, id)
+ }
+ sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
+
+ for _, id := range ids {
+ sub := snapshot[id]
sub.ticks++
if sub.ticks >= sub.interval {
sub.ticks = 0