Waves, victory, and progression
Separate rules for advancing waves, ending a battle, and continuing a run.
10 min read
In this chapter
- 6.1Record health after the actions finish
- 6.2A large HP report can reach its integer limit
- 6.3The deadline can move forward
- 6.4Automatic next-wave uses a direct request
- 6.5A tap during the warning leaves a different state behind
- 6.6Initial approach speed is a separate option
- 6.7Completion is an AND over its registered predicates
- 6.8Loss and continuation inspect different state again
The wave clock asks whether to advance the schedule. Victory asks whether every registered completion condition passes. The loss module asks whether a qualifying zombie crossed its boundary. These questions inspect different state, so the same survivor can be irrelevant to the next-wave health threshold and still prevent victory.
Record health after the actions finish
At wave start, Wave invokes every action before recording m_current. The query filters entities by m_spawned and a virtual state predicate. Its base health implementation converts body plus helmet HP to an integer when entity flag mask 0x2 is set, and returns zero otherwise.
Let be this initial reported current-wave total, and the signed 32-bit conversion detailed below. For a sampled fraction , the stored integer threshold is
for the constructor baseline. Later checks query remaining health again. Neither point cost nor accumulated player damage substitutes for that query.11Post-action health snapshotWave start invokes all action callbacks before querying eligible current-wave health and storing m_currentWaveTotalHealth. The query filters the spawn-wave marker and excluded states and obtains virtual health values. Later checks compare newly queried health with the stored integer threshold. Placement rejection precedes the initial query; deaths after that query can lower remaining health without rewriting the snapshot.0x101ce76d80x102082abc0x101ce7ce00x1019b09c00x10199dfc80x101988e70
For example, 8,000 eligible HP plus a 2,000-HP newcomer rejected during placement gives , and gives . It does not first record 10,000 HP and then credit rejection as 2,000 damage. If a counted zombie disappears afterward, the later remaining-health total can fall even without a plant attack.
The base current-wave state filter
The base virtual predicate excludes these registered states. Subclasses can override the predicate. This filter is distinct from the victory scan below.
A large HP report can reach its integer limit
The executable is ARM64, but this path combines float32 arithmetic with signed 32-bit integer reports. The base getter adds body and helmet HP, then converts that sum to an integer. The wave query converts each reported contribution back to float32, adds the contributions in order, and converts the accumulator to an integer again.
For positive HP, truncates toward zero within range and saturates at above it. The conversion does not wrap a large positive report into a negative value. The stored health fields remain separate from this capped report.22Float32 accumulation and signed int32 reportingThe base getter converts the float32 body-plus-helmet sum with FCVTZS W0. The wave query converts each signed contribution to float32, adds it to a float32 accumulator, and converts the final sum with FCVTZS W0. Positive out-of-range conversion saturates at 2147483647 under non-trapping FP behavior. The threshold uses the capped initial report. ARM64 arithmetic checks cover conversion boundaries, sequential rounding, and fractions 0.7, 0.8, and 0.85.0x10199dfc80x102082abc0x101ce76d80x101ce7298
Once the initial query saturates, the stored baseline stops growing with the uncapped accumulator. These thresholds include the native float32 rounding:
For a constructed starting accumulator of 10,000,000,000 HP and , the remaining report must fall to about 1,717,986,944. That represents roughly 82.82% of the starting amount disappearing, rather than 20%. While the remaining report is also saturated, it is still above the smaller threshold. The ordinary timer can advance the wave earlier; saturation does not disable that timer.
The progress calculation also uses the reported current/initial health ratio. While both reports are saturated, the ratio remains 1 and the health-derived part of within-wave progress remains zero. If a single base getter reaches the same limit, only its reported contribution is capped; this query does not reduce its stored body or helmet HP.
Rounding can matter before saturation. A constructed sequence of 79 contributions of 27,000,000 produces 2,133,002,496 in the checked float32 accumulation. With 80 such contributions, the accumulator is 2,160,002,560 and the returned total is 2,147,483,647. The construction view now follows these reporting conversions for its eligible ordinary-action subtotal.
The Endless gameplay reference gives a level-10 health multiplier of 100,000, making base HP 270 become 27,000,000. A wave’s HP depends on its resulting entities and their strength. The final scheduled wave has no additional ordinary batch after it.
The deadline can move forward
Let be the last-wave time, the selected interval, and initially . The ordinary interval is 20 plus a floating draw in 0–5 game seconds, or 35 seconds when the following wave is large.
When the remaining-health test succeeds, the normal path updates the deadline as
Subject to the normal state/mode guards, its advance test then requires
Reaching the health threshold early therefore makes the deadline due, but does not remove this path’s four-second minimum. Even still passes through its timing guards.
If the next wave is large, the normal path first enters the announcement state and adds Huge to the existing deadline. The constructor delay is 5 seconds. It does not start an unrelated five-second timer from the announcement entry time. With and health reaching the threshold at , the first normal gate opens at 4, but the shifted deadline is . With no early health trigger, the 35-second deadline becomes 40.33Timed advancement and large-wave delayThe normal timed-state update checks the next deadline and the minimum interval since the last wave, together with its mode guards. The health path can move the deadline earlier. The large-wave announcement state adds HugeWaveDelay to the existing deadline. Constructor timings are baselines; the WaveManager properties also attempt a DefaultWaveManagerProperties lookup.0x101ce73800x101ce7ce00x101ce6fe0
Startup has its own countdown: constructor values are 12 seconds normally and 5 for conveyor mode. A level/perk helper can modify the normal startup value. These are game-clock quantities, separate from the later intervals and from zombie movement pace.
Automatic next-wave uses a direct request
HasSkip reacts when requested button visibility changes from false to true. The normal timed state requests visibility when
A health-triggered earlier can therefore make the button available early. If the preference and boundary guards allow it, the Board sends the same request as a manual next-wave action. The ordinary request calls the advance function directly; it does not rerun the normal deadline, health, or four-second test.
For a current player-facing wave number , regular flag spacing , and a wave already started, the automatic boundary guard is
The handler also suppresses requests before the first wave. Its boundary expression does not independently inspect the final-wave marker. At level 81, and : current wave 4 is guarded; current wave 12 before a short final wave 13 is not guarded by this expression.
Turning the option off leaves normal progression active. Turning it on adds these manual-style requests. Because the trigger is a visibility transition, changing the preference while requested visibility remains true is not itself another such event.44Automatic manual-style requestsThe false-to-true next-wave visibility event checks HasSkipNextWave and the boundary guards, then invokes the manual request handler. That ordinary WaveManager branch calls the advance function directly. The normal visibility condition uses nextWaveTime minus half the spawn interval. The automatic guard uses the regular FlagWaveInterval; it does not separately inspect a final-wave marker here.0x101581d5c0x101ce70940x101ce6e8c
A tap during the warning leaves a different state behind
The pre-spawn huge-wave warning can precede a regular flag wave or the final wave. The ordinary timed update has already requested the next-wave button before it enters announcement state 2, and entering that state does not hide the button. A successful tap reaches the direct advance handler, which rejects only finished state 8. It starts the announced wave immediately and hides the button.
That warning is separate from the final-wave-start message. The latter is emitted when the final wave actually starts, after advance has requested the button hidden. Seeing a banner alone therefore does not establish that the button is still available.
The difference is a missing reset on the manual path. The announcement timeout advances the wave and then restores normal timing. A manual request calls advance directly and omits that reset. Starting the new wave still replaces its last-wave time and deadline, so the old warning deadline no longer governs it.55Manual advancement omits the announcement-state resetThe ordinary button release reaches a request that rejects only finished state 8 and advances directly. The state-2 timeout additionally calls the reset helper, while the manual path omits it. Wave start writes a fresh deadline without resetting state 2. That state updates actions and progress but omits normal HP acceleration and button visibility. The final-wave-start message is separate. Controlled original-instruction checks cover manual and timed flag/final transitions; external services and event listeners are stubbed.0x101fde6b40x101581c900x101ce6e8c0x101ce60080x101ce75280x101583bf8
For a controlled example, wave 4 is active and the warning deadline is 35. A tap at 31 starts wave 5. With an entered next interval of 22.5, its fresh deadline is 53.5. Even if the new wave clears at 36, state 2 does not run the usual HP acceleration. At 53.5, wave 6 starts and normal state 1 resumes. Letting the original warning expire instead starts wave 5 at 35 and restores state 1 immediately.
For the final wave, retaining state 2 can likewise postpone completion after the required enemies have cleared. At the fresh deadline, exhausting the wave vector writes finished state 8, but the announcement caller then resets it to state 1. A further normal update leaves it finished. There is no extra scheduled wave, and the entity-clear predicate must also pass. The extra update has no fixed duration established by these checks.
The warning tab in the interactive view compares these paths using editable times and a chosen clearance event. It keeps the automatic-next-wave preference off so the manual request can be examined separately. The preference’s regular-boundary guard does not reject a player’s tap.
Initial approach speed is a separate option
HasStart is checked during creation together with the type’s Hasty, spawn-wave marker, and mode gates. An accepted request applies a condition that doubles movement pace before later adjustments. It clears at game-coordinate .
Hasty defaults to true. The current Endless and portal catalog explicitly sets it false for cannon, swashbuckler, west_, and lostcity_. Portal marker also fails the creation path’s wave-marker gate. Later knockback does not, by this path, reapply the initial condition.66The creation-time initial-rush conditionEntity creation checks the profile option, HastyOnStart, wave marker, and a mode exclusion before applying condition 7. The condition tracker doubles the movement-pace factor before later adjustments. Zombie update clears the condition at X <= 792. This creation branch does not reapply it after knockback. The catalog's four explicit false type flags are retained in the reference data.0x1015946a80x1019853a80x101999778
This affects how soon eligible entities reach the lawn and how waves overlap. It does not reroll the pool, alter the point budget, or speed up the global game clock.
Completion is an AND over its registered predicates
For registered callbacks ,
The manager rejects an empty callback vector and stops on the first false result. Two required checks here are the finished Wave and Zombies. The former becomes finished when advancement reaches the end of the installed wave vector; merely starting the final wave is not that transition.
Zombies first scans qualifying Board zombie pointers, without restricting them to the current wave. It then scans the relevant grid-item collection for objects whose blocking predicate returns true. Hence earlier survivors, portal children, and the portal itself can still matter after current-wave health has reached its threshold.77Completion is an AND over registered predicatesThe module-manager aggregate rejects an empty predicate vector and stops at its first false result. WaveManager registers a finished-state check; standard ordinary files also register ZombiesDeadWinCon. The latter scans qualifying zombie pointers without a current-wave restriction, applies explicit exceptions, and then checks grid-item collection 0x30 for blocking virtual predicates.0x1015555140x101ce42500x101b5c264
A manual tap during the final pre-spawn warning can leave the manager waiting for its fresh deadline even after entity clearance. Clearing the lawn alone does not reset the retained announcement state.
Which zombie exceptions does the clear scan apply?
After its entity-flag gate, the native scan has these explicit exceptions. There is no general positive-body-HP requirement replacing them.
Other qualifying zombies can block victory while off the lawn. A death transition, removal dispatch, and erasure from the Board list are also different moments.
A Modern portal’s blocker returns true while its reference resolves. Its final emission leaves another scheduled check; that check requests closure, and the close-animation callback removes the portal. An empty queue therefore does not immediately clear that blocker. Surviving children remain independent blockers after the portal disappears.88The portal remains a blocker until removalGridItemZombiePortal's completion-blocking virtual returns true unconditionally. The ordinary emitter consumes the final queue entry and still advances its next deadline. A later empty-queue check starts the close animation, whose callback requests removal and invalidates the weak identity. Queue exhaustion and removal are separate states.0x1001227340x100121a000x100121e44
The portal view shows this sequence separately from ordinary wave advancement.
Loss and continuation inspect different state again
The ordinary loss module requires CanTrigger and its own state/type/flag gates. In the unshifted branch, its declared Zombie means
A Board flag can shift the threshold by 30, and another mode can add a Y check. A negative spawn-wave marker alone is not an exemption.99The ordinary loss boundaryThe module checks CanTriggerZombieWin, virtual state predicates, entity flags, and additional mode/type conditions before comparing X - 200 with the configured threshold. The article gives the unshifted case; a Board flag can shift that threshold and another mode can add a Y condition. The eligibility helper does not test for a nonnegative spawn-wave marker.0x10153839c0x1015386b4
After victory, the client sends the end-level request and processes the returned progress record before continuing. It does not simply increment local progress and assume success. The ordinary continuation route loads the next file only when the returned stored progress plus one is below 150. With the bundled Boss interval of 5, the sequence through 1–149 includes 120 ordinary levels and 29 Boss boundaries.1010Returned progress and the continuation boundaryThe successful end-level response assigns its returned record before completion UI/events continue. The ordinary victory route then loads the next world file only when stored progress plus one is below 150. The filename helper uses the effective BossInterval. This is the inspected continuation rule, not a universal claim about server admission through other start/skip routes. All standard ordinary files declare CanSaveGameState false.0x101ffc3e00x101d6cf7c0x101c360dc0x101d4a4e8
That record is not a saved battlefield. The ordinary definitions declare CanSave; neither progression nor the separate saved loot schedules establishes restoration of a previous wave, its entities, or its row history.
Notes
The evidence behind each claim: the code path, and the native addresses where it was traced.
- 1
Post-action health snapshot
Wave start invokes all action callbacks before querying eligible current-wave health and storing m_currentWaveTotalHealth. The query filters the spawn-wave marker and excluded states and obtains virtual health values. Later checks compare newly queried health with the stored integer threshold. Placement rejection precedes the initial query; deaths after that query can lower remaining health without rewriting the snapshot.
0x101ce76d80x102082abc0x101ce7ce00x1019b09c00x10199dfc80x101988e70 - 2
Float32 accumulation and signed int32 reporting
The base getter converts the float32 body-plus-helmet sum with FCVTZS W0. The wave query converts each signed contribution to float32, adds it to a float32 accumulator, and converts the final sum with FCVTZS W0. Positive out-of-range conversion saturates at 2147483647 under non-trapping FP behavior. The threshold uses the capped initial report. ARM64 arithmetic checks cover conversion boundaries, sequential rounding, and fractions 0.7, 0.8, and 0.85.
0x10199dfc80x102082abc0x101ce76d80x101ce7298 - 3
Timed advancement and large-wave delay
The normal timed-state update checks the next deadline and the minimum interval since the last wave, together with its mode guards. The health path can move the deadline earlier. The large-wave announcement state adds HugeWaveDelay to the existing deadline. Constructor timings are baselines; the WaveManager properties also attempt a DefaultWaveManagerProperties lookup.
0x101ce73800x101ce7ce00x101ce6fe0 - 4
Automatic manual-style requests
The false-to-true next-wave visibility event checks HasSkipNextWave and the boundary guards, then invokes the manual request handler. That ordinary WaveManager branch calls the advance function directly. The normal visibility condition uses nextWaveTime minus half the spawn interval. The automatic guard uses the regular FlagWaveInterval; it does not separately inspect a final-wave marker here.
0x101581d5c0x101ce70940x101ce6e8c - 5
Manual advancement omits the announcement-state reset
The ordinary button release reaches a request that rejects only finished state 8 and advances directly. The state-2 timeout additionally calls the reset helper, while the manual path omits it. Wave start writes a fresh deadline without resetting state 2. That state updates actions and progress but omits normal HP acceleration and button visibility. The final-wave-start message is separate. Controlled original-instruction checks cover manual and timed flag/final transitions; external services and event listeners are stubbed.
0x101fde6b40x101581c900x101ce6e8c0x101ce60080x101ce75280x101583bf8 - 6
The creation-time initial-rush condition
Entity creation checks the profile option, HastyOnStart, wave marker, and a mode exclusion before applying condition 7. The condition tracker doubles the movement-pace factor before later adjustments. Zombie update clears the condition at X <= 792. This creation branch does not reapply it after knockback. The catalog's four explicit false type flags are retained in the reference data.
0x1015946a80x1019853a80x101999778 - 7
Completion is an AND over registered predicates
The module-manager aggregate rejects an empty predicate vector and stops at its first false result. WaveManager registers a finished-state check; standard ordinary files also register ZombiesDeadWinCon. The latter scans qualifying zombie pointers without a current-wave restriction, applies explicit exceptions, and then checks grid-item collection 0x30 for blocking virtual predicates.
0x1015555140x101ce42500x101b5c264 - 8
The portal remains a blocker until removal
GridItemZombiePortal's completion-blocking virtual returns true unconditionally. The ordinary emitter consumes the final queue entry and still advances its next deadline. A later empty-queue check starts the close animation, whose callback requests removal and invalidates the weak identity. Queue exhaustion and removal are separate states.
0x1001227340x100121a000x100121e44 - 9
The ordinary loss boundary
The module checks CanTriggerZombieWin, virtual state predicates, entity flags, and additional mode/type conditions before comparing X - 200 with the configured threshold. The article gives the unshifted case; a Board flag can shift that threshold and another mode can add a Y condition. The eligibility helper does not test for a nonnegative spawn-wave marker.
0x10153839c0x1015386b4 - 10
Returned progress and the continuation boundary
The successful end-level response assigns its returned record before completion UI/events continue. The ordinary victory route then loads the next world file only when stored progress plus one is below 150. The filename helper uses the effective BossInterval. This is the inspected continuation rule, not a universal claim about server admission through other start/skip routes. All standard ordinary files declare CanSaveGameState false.
0x101ffc3e00x101d6cf7c0x101c360dc0x101d4a4e8