testsAndMisc/docs/superpowers/evidence/reverse-survivors-status-effects-2026-08-09.json
Krzysztof kuhy Rudnicki 43b63ac266 reverse_survivors: survivor status effects (slow, suppress, bleed)
The warden mires, the artillery stifles, the leech unknits. All three need
contact, so a survivor that kites cleanly takes none of them — debuffs
punish positional failure rather than taxing existence.

isOn/factor are written as arithmetic rather than comparisons:
Math.min(1, Math.ceil(t)) is 1 while a timer is live and 0 once it expires,
so consumers multiply instead of branching. The whole status module
therefore contributes zero branches, which is what makes the 100% gate
affordable for a system this cross-cutting. Statuses are applied
unconditionally too; a 0-duration spec is a provable no-op.

applyStatus uses Math.max, so re-applying refreshes rather than stacks.
That caps a horde's total lockdown at one duration by construction — a
structural guarantee, not a tuned number.

123 tests, 100% coverage. Branches 117 -> 119: the draw halo gate and the
snapshot chip filter. The status logic itself adds none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFyXexNjMQG9V7K8de17fc
2026-08-09 23:08:51 +02:00

39 lines
3.2 KiB
JSON

{
"intent": "Give the survivor three status debuffs - slow, suppress (fire rate) and bleed (regen block) - so the new ranged and fast units have a lever beyond raw damage.",
"scope": [
"reverse_survivors/src/core/status.ts (new leaf module) and status.test.ts",
"reverse_survivors/src/core/types.ts (StatusKind, STATUS_POWER/LABELS, per-spec debuff fields, Survivor.statuses, Projectile payload)",
"reverse_survivors/src/core/sim.ts (tick and consume), snapshot.ts (chips), src/ui/Hud.tsx, src/render/draw.ts (halo), src/styles.css",
"Non-goal: director powers and difficulty tiers ship as their own commits"
],
"changes": [
"isOn/factor are arithmetic (Math.min(1, Math.ceil(t))) rather than comparisons, so the whole status system contributes ZERO branches; consumers multiply by the result instead of branching on it.",
"applyStatus uses Math.max, making refresh-not-stack a structural guarantee: a horde can never chain-lock the survivor past one status duration, regardless of how many enemies land hits.",
"Statuses are applied unconditionally at contact and projectile-hit sites; a 0-duration spec makes the call a provable no-op, which is cheaper than a guard branch under the 100% gate.",
"Projectiles carry their own debuff payload so a hit needs no lookup of a possibly-dead firer.",
"Statuses tick at the top of survivorStep, before they are read, so a status applied on tick N is felt from N+1 and an inert application is never observable.",
"Suppression scales the reload rather than the countdown, keeping the remaining timer linear in dt so replays and assertions stay exact.",
"The draw.ts halo is gated so an unafflicted survivor renders exactly as before, keeping the existing BASE draw-call counts valid."
],
"verification": [
{
"command": "npm run check (typecheck + eslint --max-warnings 0 + vitest --coverage)",
"result": "pass",
"evidence": "123 tests passed. Coverage 100% on all four metrics: statements 446/446, branches 119/119, functions 84/84, lines 429/429. Branch count rose 117 -> 119, accounted for by the halo gate and the snapshot chip filter; the status module itself adds none."
},
{
"command": "targeted assertions in sim.test.ts",
"result": "pass",
"evidence": "Warden contact slows movement by exactly STATUS_POWER.slow against an unslowed control run; artillery shells lengthen the next reload to 0.55 * 1.75; a leech contact blocks regen and it resumes on expiry; two simultaneous warden contacts do not exceed the 2.0s cap; a plain rusher leaves all three timers at 0."
}
],
"risks": [
"Bleed is a no-op unless the survivor has bought the regen upgrade, since it starts at regen 0. This is intended - the Leech counters a specific build - and matches the README description.",
"All three debuffs require contact, so a survivor that kites cleanly takes none of them. This is deliberate: debuffs punish positional failure rather than taxing existence."
],
"rollback": [
"git revert the commit; src/core/status.ts and status.test.ts are new files and disappear with it.",
"After rollback run `npm run check` in reverse_survivors/ and confirm the suite passes at 100% coverage."
]
}