package game import ( "testing" "thehouseoficarus/internal/behavior" "thehouseoficarus/internal/player" "thehouseoficarus/internal/world" ) func spawnTestMob(g *Game, defID string, roomID int) *world.MobInstance { def, err := g.MobStore.LoadDef(defID) if err != nil { panic("spawnTestMob: " + err.Error()) } cfg := &behavior.SpawnMobConfig{ID: defID} return g.MobStore.SpawnTransient(def, cfg, roomID, "") } func patchAggroRand(val float64) func() { orig := rollAggroRand rollAggroRand = func() float64 { return val } return func() { rollAggroRand = orig } } func TestAggroInlineRollFiresOnArrival(t *testing.T) { defer patchAggroRand(0.0)() g := escapeTestGame() spawnTestMob(g, "skeleton", 100) p := player.New("agro_fire") p.RoomID = 100 sess := escapeTestSession(p) g.checkAggro(sess) if g.Combat.Get(p.Name) == nil { t.Error("aggro should have started combat on inline roll") } } func TestAggroPerTickRetrySubscriber(t *testing.T) { t.Run("miss_then_retry_fires", func(t *testing.T) { defer patchAggroRand(0.5)() g := escapeTestGame() spawnTestMob(g, "skeleton", 200) p := player.New("agro_retry") p.RoomID = 200 sess := escapeTestSession(p) g.checkAggro(sess) if g.Combat.Get(p.Name) != nil { t.Fatal("inline roll should have missed (val=0.5)") } rollAggroRand = func() float64 { return 0.0 } g.Ticks.Tick() if g.Combat.Get(p.Name) == nil { t.Error("per-tick retry subscriber should have started combat") } }) t.Run("miss_then_ineligible_unsubscribes", func(t *testing.T) { defer patchAggroRand(0.5)() g := escapeTestGame() spawnTestMob(g, "skeleton", 300) p := player.New("agro_leave") p.RoomID = 300 sess := escapeTestSession(p) g.checkAggro(sess) if g.Combat.Get(p.Name) != nil { t.Fatal("inline roll should have missed (val=0.5)") } p.RoomID = 999 g.Ticks.Tick() if g.Combat.Get(p.Name) != nil { t.Error("retry should have self-unsubscribed, no combat started") } }) }