From 9898d42f2d4ef61b5cbec7e212914d1495e04772 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Fri, 17 Jul 2026 20:15:57 -0400 Subject: feat: add deterministic order to mob/player tick processing, document how tick processing works --- internal/world/mob.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'internal/world/mob.go') diff --git a/internal/world/mob.go b/internal/world/mob.go index fa0cdbf..f33b2f9 100644 --- a/internal/world/mob.go +++ b/internal/world/mob.go @@ -5,6 +5,7 @@ import ( "math/rand" "os" "path/filepath" + "sort" "strings" "sync" @@ -476,6 +477,7 @@ func (s *MobStore) AllInstances() []*MobInstance { for _, inst := range s.instances { out = append(out, inst) } + sort.SliceStable(out, func(i, j int) bool { return out[i].InstanceID < out[j].InstanceID }) return out } @@ -499,6 +501,20 @@ func (s *MobStore) MobsInRoom(roomID int) []*MobInstance { out = append(out, inst) } } + sort.SliceStable(out, func(i, j int) bool { return out[i].InstanceID < out[j].InstanceID }) + return out +} + +// sortedInstancesLocked returns every mob instance sorted by InstanceID. It +// must be called with s.mu held; callers iterate the returned slice instead of +// ranging s.instances directly so per-tick processing order is stable across +// Go's randomized map iteration. +func (s *MobStore) sortedInstancesLocked() []*MobInstance { + out := make([]*MobInstance, 0, len(s.instances)) + for _, inst := range s.instances { + out = append(out, inst) + } + sort.SliceStable(out, func(i, j int) bool { return out[i].InstanceID < out[j].InstanceID }) return out } @@ -506,7 +522,7 @@ func (s *MobStore) Tick() { s.mu.Lock() defer s.mu.Unlock() - for _, inst := range s.instances { + for _, inst := range s.sortedInstancesLocked() { if inst.Shop != nil { s.tickShopLocked(inst) } -- cgit v1.2.3