test: cover tests.streaksCurrentIsZeroAndNilOnAnEmptyHistory

This commit is contained in:
dualfroz committed 2026-09-11 23:12:01 +02:00
1 parent fac05c737f
commit 8e7c57dcbf
1 file changed
+173
+173
View File
@@ -10163,6 +10163,179 @@ function tests.exportCompendiumCsvListsEverySpeciesSeenOrNot()
ok(contains(csv, "false,0"), "an undiscovered species still reports as unseen with a zero tally") ok(contains(csv, "false,0"), "an undiscovered species still reports as unseen with a zero tally")
end end
function tests.streaksCurrentIsZeroAndNilOnAnEmptyHistory()
local length, outcome = Streaks.current(Streaks.new())
eq(length, 0, "an empty history has a streak length of zero")
eq(outcome, nil, "an empty history has no outcome to report")
end
function tests.streaksCurrentCountsOnlyTheUnbrokenRunAtTheEnd()
local history = Streaks.new()
Streaks.record(history, true)
Streaks.record(history, true)
Streaks.record(history, false)
Streaks.record(history, false)
Streaks.record(history, false)
local length, outcome = Streaks.current(history)
eq(length, 3, "three losses in a row at the tail end give a streak of three")
eq(outcome, false, "the current streak's outcome is a loss")
Streaks.record(history, true)
length, outcome = Streaks.current(history)
eq(length, 1, "a single win right after a loss streak is itself a streak of exactly one")
eq(outcome, true, "the fresh win flips the current outcome to a win")
end
function tests.streaksRecordTrimsHistoryPastItsCap()
local history = Streaks.new()
for i = 1, Streaks.HISTORY_CAP + 10 do
Streaks.record(history, i % 2 == 0)
end
eq(#history, Streaks.HISTORY_CAP, "history never grows past its fixed cap")
end
function tests.streaksRecordDropsTheOldestEntryNotTheNewestOnceOverCap()
local history = Streaks.new()
for i = 1, Streaks.HISTORY_CAP do
Streaks.record(history, false)
end
eq(#history, Streaks.HISTORY_CAP, "filling exactly to the cap keeps every entry")
Streaks.record(history, true)
eq(#history, Streaks.HISTORY_CAP, "one more push still respects the same cap")
eq(history[#history], true, "the newest push is the one that survives at the tail")
eq(history[1], false, "the oldest entry was the one dropped to make room")
end
function tests.streaksBestWinStreakFindsTheLongestWinRunAnywhereNotJustTheActiveOne()
local history = Streaks.new()
for _, won in ipairs({ true, true, true, false, true, false, true, true }) do
Streaks.record(history, won)
end
eq(Streaks.bestWinStreak(history), 3, "the best win streak is the earlier run of three, not the trailing two")
local currentLength, currentOutcome = Streaks.current(history)
eq(currentLength, 2, "the currently active streak is only two long")
eq(currentOutcome, true, "the currently active streak is a winning one")
end
function tests.streaksBestWinStreakIsZeroWithNoWinsAtAll()
local history = Streaks.new()
Streaks.record(history, false)
Streaks.record(history, false)
eq(Streaks.bestWinStreak(history), 0, "a history of nothing but losses has a best win streak of zero")
end
function tests.metaRecordRunFeedsStreaksAndRunHistorySurvivesASaveLoadRoundTrip()
local fresh = Meta.load("/tmp/warren_meta_does_not_exist_71234.sav")
eq(#fresh.runHistory, 0, "a freshly defaulted meta state has an empty run history")
local meta = Meta.new()
Meta.recordRun(meta, { deepestDepth = 1, kills = 0, bossKills = 0, questsCompleted = 0, artifactsFound = 0, won = true })
Meta.recordRun(meta, { deepestDepth = 1, kills = 0, bossKills = 0, questsCompleted = 0, artifactsFound = 0, won = false })
eq(#meta.runHistory, 2, "each recorded run appends exactly one entry to the history")
local currentLength, currentOutcome = Streaks.current(meta.runHistory)
eq(currentLength, 1, "the most recent recorded run is a fresh streak of one")
eq(currentOutcome, false, "the most recent recorded run was a loss")
local path = os.tmpname()
Meta.save(meta, path)
local restored = Meta.load(path)
os.remove(path)
eq(#restored.runHistory, 2, "run history survives a save/load round trip")
eq(restored.runHistory[1], true, "the oldest entry keeps its own outcome")
eq(restored.runHistory[2], false, "the newest entry keeps its own outcome")
end
function tests.metaRecordRunUnlocksComebackOnlyAfterALossThenAWin()
local meta = Meta.new()
Meta.recordRun(meta, { deepestDepth = 1, kills = 0, bossKills = 0, questsCompleted = 0, artifactsFound = 0, won = false })
eq(meta.achievements.comeback, nil, "a lone loss cannot unlock a comeback")
Meta.recordRun(meta, { deepestDepth = 1, kills = 0, bossKills = 0, questsCompleted = 0, artifactsFound = 0, won = true })
eq(meta.achievements.comeback, true, "winning right after a loss unlocks the comeback")
end
function tests.metaRecordRunDoesNotUnlockComebackOnAFirstEverWinOrAWinAfterAWin()
local isolated = Meta.new()
Meta.recordRun(isolated, { deepestDepth = 1, kills = 0, bossKills = 0, questsCompleted = 0, artifactsFound = 0, won = true })
eq(isolated.achievements.comeback, nil, "a first-ever run that wins has no prior loss to come back from")
local streak = Meta.new()
Meta.recordRun(streak, { deepestDepth = 1, kills = 0, bossKills = 0, questsCompleted = 0, artifactsFound = 0, won = true })
Meta.recordRun(streak, { deepestDepth = 1, kills = 0, bossKills = 0, questsCompleted = 0, artifactsFound = 0, won = true })
eq(streak.achievements.comeback, nil, "a win right after another win is not a comeback")
end
-- Nemesis (nemesis.lua): a cross-floor hunter born mid-run from a plain
-- monster that was actually hunting the player at the stairs, escalating
-- every time it follows without being killed first.
function tests.nemesisIsEligibleRequiresAHuntingHostileAndExcludesEverySpecialCase()
local orc = Monsters.create(Monsters.byKey.orc, 1, 5, 5, 1)
ok(not Nemesis.isEligible(orc), "a monster that has not noticed the player yet is not eligible")
orc.awareness = Stealth.HUNTING
ok(Nemesis.isEligible(orc), "a hunting monster under a self-directed ai is eligible")
local wretch = Monsters.create(Monsters.byKey.wretch, 2, 5, 5, 1)
wretch.awareness = Stealth.HUNTING
ok(not Nemesis.isEligible(wretch), "a permanent flee species never gives chase down a stairwell")
local shopkeeper = Monsters.create(Monsters.byKey.shopkeeper, 3, 5, 5, 4)
shopkeeper.awareness = Stealth.HUNTING
ok(not Nemesis.isEligible(shopkeeper), "the shopkeeper's vend ai is never eligible")
local boss = Bosses.create(Bosses.byKey.lich, 4, 5, 5)
boss.awareness = Stealth.HUNTING
ok(not Nemesis.isEligible(boss), "a boss already has its own escalation path")
local elite = Monsters.create(Monsters.byKey.orc, 5, 5, 5, 3)
elite.awareness, elite.elite = Stealth.HUNTING, true
ok(not Nemesis.isEligible(elite), "an elite already has its own escalation path")
local mimic = Mimics.reveal(6, 5, 5, 3)
mimic.awareness = Stealth.HUNTING
ok(not Nemesis.isEligible(mimic), "a revealed mimic already has its own escalation path")
local shade = Graves.reveal(7, 5, 5, {})
shade.awareness = Stealth.HUNTING
ok(not Nemesis.isEligible(shade), "a grave shade already has its own escalation path")
local dead = Monsters.create(Monsters.byKey.rat, 8, 5, 5, 1)
dead.awareness, dead.hp = Stealth.HUNTING, 0
ok(not Nemesis.isEligible(dead), "a dead monster cannot give chase")
local already = Monsters.create(Monsters.byKey.rat, 9, 5, 5, 1)
already.awareness, already.nemesis = Stealth.HUNTING, true
ok(not Nemesis.isEligible(already), "an existing nemesis cannot spawn a second one")
end
function tests.nemesisPickHunterReturnsTheNearestEligibleHunterBreakingTiesByListOrder()
local player = Entity.new({ x = 5, y = 5, glyph = "@", name = "you", kind = "player", hp = 20, attack = 5, defense = 1 })
local level = arena(21, 11)
local far = Monsters.create(Monsters.byKey.orc, 1, 15, 5, 1)
far.awareness = Stealth.HUNTING
local dormant = Monsters.create(Monsters.byKey.orc, 2, 6, 5, 1)
local near = Monsters.create(Monsters.byKey.orc, 3, 7, 5, 1)
near.awareness = Stealth.HUNTING
level.entities = { far, dormant, near }
eq(Nemesis.pickHunter(level, player), near, "the nearest eligible hunter wins over a dormant closer monster and a farther one")
local tie = Monsters.create(Monsters.byKey.orc, 4, 3, 5, 1)
tie.awareness = Stealth.HUNTING
level.entities = { near, tie }
eq(Nemesis.pickHunter(level, player), near, "an equidistant tie is broken by earlier list order")
level.entities = { dormant }
eq(Nemesis.pickHunter(level, player), nil, "no eligible hunter on the floor returns nil")
end
function tests.nemesisBeginFromCapturesSpeciesAndHpFractionAtGrudgeOne()
local orc = Monsters.create(Monsters.byKey.orc, 1, 5, 5, 1)
orc.hp = orc.maxHp // 2
local pursuit = Nemesis.beginFrom(orc)
eq(pursuit.species, "orc", "the pursuit remembers which species is following")
eq(pursuit.grudge, 1, "a freshly begun pursuit starts at grudge one")
eq(pursuit.hpFraction, 0.5, "the pursuit captures the hunter's hp fraction at the moment it gave chase")
end
function tests.nemesisEscalateRaisesGrudgeAndHealsTowardFullBothCapped()
local pursuit = { species = "orc", grudge = 1, hpFraction = 0.3 }
end end
local order = { local order = {