Skip to content

Commit 4e8d2b7

Browse files
authored
The floor rules count the time each beat covered, up to one second (#1613)
The dungeon's floor rules now count the time each beat actually covered, up to one second, instead of taking every beat as 0.25 s. A slow frame no longer slows them, and a long stall costs at most one second of a rule's effect (#1613). ## What changed - `game/Source/Cataclysm/Dungeon/CataclysmDungeonGameMode.h` and `.cpp`, the dungeon game mode: `Tick` records `BeatSeconds`, the time the beat covered, before the wave-check accumulator resets, and clamps it to 1.0 s. The 79 uses of the fixed 0.25 s in the floor rules read it instead. The waves are unchanged. - `game/Source/Cataclysm/Tests/CataclysmDungeonModifierEffectsTests.cpp`: three tests -- Mortal Decay's rate is the same at four and at two frames a second, a three-second frame costs one second, and Death's Embrace's clock counts the same at both frame rates. - `docs/DECISIONS.md`: the entry and window w77's record. ## Evidence: window w77, at 3f6b0af on development c120310 - Build: 72 actions, 62 files compiled. - The three new tests alone: 3 performed, 3 succeeded. Printed: "a frame of one second cost 5.100 and a frame of three cost 5.100"; "Mortal Decay over one second cost 5.100 at four frames a second and 5.100 at two"; "Death's Embrace cut 0.0 then 10.0 at four frames a second, 0.0 then 10.0 at two". - Whole Unreal suite: 4347 performed, 4347 succeeded, 0 failed (registered 4347; 4347 declared in the tree, gap 0). Memory: peak 8,086 MB, final 7,777 MB. - Python: "7022 passed, 8 skipped"; JUnit tests=7030, failures=0, errors=0, skipped=8. ruff: "All checks passed!". - Proof P1 (`BeatSeconds` set back to 0.25 s): PROVED. With the break in, `ABeatCountsItsOwnTimeSoMortalDecaySapsItsRateAtTwoFramesASecond` failed; restored, it passed. - Proof P2 (the one-second limit removed): PROVED. With the break in, `ABeatCountsAtMostOneSecondSoALongFrameCostsOneSecondOfMortalDecay` failed; restored, it passed. After the tested commit, only `docs/DECISIONS.md` changed (the record). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent c120310 commit 4e8d2b7

4 files changed

Lines changed: 423 additions & 102 deletions

File tree

‎docs/DECISIONS.md‎

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,101 @@
22

33
Decisions made outside the Google Drive documents, newest first.
44

5+
## 2026-10-28 — The floor rules count the time each beat covered, up to one second, so a slow frame no longer slows them (#1613)
6+
7+
**Affects:** `CataclysmDungeonGameMode.h` and `.cpp` (a new field `BeatSeconds`, a new constant `LongestBeatSeconds`,
8+
one line in `Tick`, and 79 uses of `SecondsBetweenWaveChecks` replaced by `BeatSeconds`), and three new tests in
9+
`CataclysmDungeonModifierEffectsTests.cpp`. Fixes [#1613](https://github.com/sdubois777/Cataclysm/issues/1613).
10+
11+
### SAID FIRST
12+
13+
- **BUILT AND RUN: window w77 below passed every registered figure, and both proofs were PROVED.**
14+
- **IT WAS WIDER THAN THE ISSUE SAID.** #1613 named two rules and said the fault did not bite at a playable frame
15+
rate. At `060470dfb` about fifty rules took every beat to be a quarter of a second, and setting the accumulator to
16+
zero drops the part of the last frame past the interval at every frame rate: at thirty frames a second a beat
17+
covers eight frames, 0.267 seconds, so every timed rule ran about six per cent slow. That is arithmetic from the
18+
code, not a measurement.
19+
20+
### What it was
21+
22+
`Tick` adds each frame's time to `SinceWaveCheckSeconds` and, when that reaches `SecondsBetweenWaveChecks` (0.25 s),
23+
runs one beat and sets it back to zero, so that a long frame brings in one wave and not four. The rules the beat runs
24+
then took the beat to be 0.25 s. Mortal Decay, for one, took `Maximum x Rate / 100 x 0.25` a beat: at two frames a
25+
second one beat a frame, half its stated rate; at four, exact; at thirty, 94%.
26+
27+
### What changed
28+
29+
`Tick` records the accumulator's value in `BeatSeconds` before setting it to zero, at most `LongestBeatSeconds`, and
30+
the rules measure time by it. The wave arrival is unchanged.
31+
32+
**THE LIMIT, 1.0 SECOND, IS A JUDGEMENT**, ruled by the coordinating session on 2026-10-28: engines clamp a frame's
33+
time for the same reason, so that a load or a breakpoint does not deal thirty seconds of a floor rule in one beat. With
34+
it every rule is exact down to one frame a second, and runs slow below that as before.
35+
36+
**THE 79 REPLACEMENTS**, every use of `SecondsBetweenWaveChecks` in the file but the comparison in `Tick` and a comment
37+
naming the constant, listed and read before the script ran. Each means the time that passed this beat; none is a count
38+
of beats, a cadence written as a number of beats, or a delay that must stay 0.25. The script asserts it changed 79.
39+
40+
| Kind | Uses | Rules |
41+
|---|---|---|
42+
| A rate times the time | 6 | Forced March, Mortal Decay, Suffering Aura (health and mana, through `SufferingAuraLossFor`), Necrotic Ground's creature regeneration (through `NecroticGroundRegenPerBeat`), Singularity Wells' pull |
43+
| A countdown taken down by the time | 12 | Floor obstacle warnings, Insanity Bursts' lock and madness (2), Mind-Shattering Illusions' slow, Reality Rifts' rest and gift (2), Shadowy Enemies' fire, Battlefield Relics' three buffs (3), Grim Totems' embrace, Infernal Rain's falls |
44+
| A clock or cadence the time is added to | 61 | The Reaper, the Corrupted Stalker, Plague Convergence (3), Echoes of the Past, Wings of the Host (2), Necrotic Bloom, the floor's clear time, Trial of Endurance, Portal Unleashing, Plaguebearer, Morale Break (2), Raw Sewage (2), Gated Shortcuts, floor quakes (2), Warzone Control Points (2), Swarm of Locusts (3), Abyssal Rifts, Infested Hoard, Infection Bloom (2), Funereal Procession (3), Insanity Bursts (2), Mind-Shattering Illusions, Carrion Feast, Infested Veins (2), War Banner (2), Sanctioned Passage, Blood Price, Divine Wrath, Infernal Rain, Singularity Wells, Edict of Silence, Edict of Vigil, Dirge Resonance, Artillery Strike (2), Hallowed Groundfall, Grasping Tentacles, Death's Embrace, Judgment Zones (2), Anti-Magic Zones, Blood Altar, Necrotic Ground (2), Ravenous Hoard, Grave Tide |
45+
46+
Every one of those rules is called once, from `StepFloorRulesThatChange` or, for the clear time, from `Tick` after
47+
it, so `BeatSeconds` is always the beat that is running. `PopulateFloor` also sets the accumulator to zero on a new
48+
floor; that only restarts the count there. Five comments that said a beat is a quarter of a second, and the comment in
49+
`DeathsEmbraceCutsHealingAndTheStairsClearIt`, now say what the code does; no assertion changed.
50+
51+
**A PAUSED GAME STILL COUNTS NOTHING.** The comments on Death's Embrace, Edict of Silence and Edict of Vigil count by
52+
the beat so that a paused game is not counted, and that holds: `Tick` does not run while the game is paused, and
53+
`BeatSeconds` is built from the frames `Tick` is given.
54+
55+
### How it is checked
56+
57+
All three run the step at a low frame rate against a control at four frames a second, the rate every other test uses.
58+
59+
| Test | Kind | What it asserts |
60+
|---|---|---|
61+
| `ABeatCountsItsOwnTimeSoMortalDecaySapsItsRateAtTwoFramesASecond` | rate | one second of Mortal Decay at its ceiling costs one second's rate in four frames of 0.25 (CONTROL) and in two of 0.5 |
62+
| `ABeatCountsAtMostOneSecondSoALongFrameCostsOneSecondOfMortalDecay` | the limit | a frame of 1 s costs one second's rate (CONTROL), and a frame of 3 s costs the same, not three |
63+
| `ABeatCountsItsOwnTimeSoDeathsEmbraceStacksAtTenSecondsAtTwoFramesASecond` | clock | Death's Embrace's first stack arrives on the fortieth frame of 0.25 (CONTROL) and on the twentieth of 0.5, both ten seconds, and not a frame earlier |
64+
65+
Each prints a line beginning "BEAT SECONDS". The six existing tests that tick two or four beats' time at once are
66+
wave tests in `CataclysmDungeonGameModeTests.cpp`; the whole suite settles them.
67+
68+
### Registered before the run
69+
70+
| Figure | Registered |
71+
|---|---|
72+
| The three tests alone | 3 / 3 |
73+
| Whole Unreal suite | 4347 / 0: `development` at `c120310b7` (#2576) measured 4344, and three tests are added; the branch moved onto it as `fix/beat-rules-count-real-time-1` |
74+
| Python | of record, with JUnit, and ruff |
75+
| The "BEAT SECONDS" lines | quoted as printed |
76+
| P1, `BeatSeconds` set to `SecondsBetweenWaveChecks` in `Tick` | the rate test fails: two frames of 0.5 cost half |
77+
| P2, the limit removed | the limit test fails: a frame of 3 s costs three seconds |
78+
79+
### Run
80+
81+
**Window w77 (lock `dungeon-w77`, `fix/beat-rules-count-real-time-1` at `3f6b0afff`, on `c120310b7`, 2026-10-28)
82+
passed.** The lock was taken at 16:03 on a free-memory reading immediately before it of 11,399 MB, with the other five
83+
conditions met: both of `development`'s runs at `c120310b7` succeeded, none in progress or queued, the lock free, no
84+
build or editor process, no open pull request. The lock was released before anything was written here. The move onto
85+
`c120310b7` met only the top of this file (patch ids before and after `284df7f8c371`).
86+
87+
| Step | Registered | Printed |
88+
|---|---|---|
89+
| Build | | `Build: Succeeded - 72 actions, 62 files compiled` |
90+
| The three tests alone | 3 / 3 | `3 tests performed, 3 succeeded, 0 failed` |
91+
| Whole Unreal suite | 4347 / 0 | `4347 tests performed, 4347 succeeded, 0 failed`; declared 4347, gap 0. The six multi-beat wave tests passed in it |
92+
| Memory | quoted | `final_used_virtual_mb=7777`, peak 8,086 |
93+
| Python (JUnit) | 7030 or more | 7030 tests, 0 failures, 0 errors, 8 skipped ("7022 passed, 8 skipped"); ruff `All checks passed!` |
94+
| The "BEAT SECONDS" lines | quoted as printed | "Mortal Decay over one second cost 5.100 at four frames a second and 5.100 at two; one second's rate is 5.100"; "a frame of one second cost 5.100 and a frame of three cost 5.100; one second's rate is 5.100"; "Death's Embrace cut 0.0 then 10.0 at four frames a second, 0.0 then 10.0 at two" |
95+
| P1, `BeatSeconds` set to `SecondsBetweenWaveChecks` | the rate test fails: two frames of 0.5 cost half | "1 tests performed, 0 succeeded, 1 failed: ABeatCountsItsOwnTimeSoMortalDecaySapsItsRateAtTwoFramesASecond", printing "5.100 at four frames a second and 2.550 at two"; restored "1 tests performed, 1 succeeded, 0 failed". **PROVED** |
96+
| P2, the limit removed | the limit test fails: a frame of 3 s costs three seconds | "1 tests performed, 0 succeeded, 1 failed: ABeatCountsAtMostOneSecondSoALongFrameCostsOneSecondOfMortalDecay", printing "a frame of three cost 15.300"; restored "1 tests performed, 1 succeeded, 0 failed". **PROVED** |
97+
98+
---
99+
5100
## 2026-10-28 — No row carries a scope label: 48 Scope.* tags removed, and a test refuses one (#1620)
6101

7102
**Affects:** 48 cells of `docs/All_Things_Cataclysm.xlsx` (the "Enchantments" sheet's two tag columns, D and I, and

0 commit comments

Comments
 (0)