modules: waves — the PvE wave director as a library - #8576
Draft
keithharvey wants to merge 1 commit into
Draft
Conversation
Contributor
Integration Test Results20 tests 8 ✅ 16s ⏱️ For more details on these failures, see this check. Results for commit d1563bc. ♻️ This comment has been updated with latest results. |
Member
|
He's touching my babies now aaaaaaaaa |
The base game's PvE wave machinery is two 2,500-line monoliths that are ~70% line-identical to each other, enabled only by a LuaAI team being present, with no way for anything else to ask for a wave. This adds the module they should have been: a generic wave director that a flavor pack configures with plain data. The seam is one table — the WaveSpec — plus optional named hooks. The gadget hosts 1..n directors keyed by name, so the multiplayer mode and a mission's scripted pressure are the same machinery at different intensities. The split is the hybrid rule, and it maps onto directories. lib/ is rewritten, pure and spec'd: the anger clocks, the archetype wheel, squad composition, dynamic difficulty and the endless reloop, boss lifecycle, the cadence brain, and the director that ties them together. spring/ is ported leaf code — placement cascades, the spawn drain, squad AI, creep structures — kept close to the original so a playtester recognises its behaviour, with every file-level global it used to read threaded through explicit state instead. Two disciplines carry through. The spec is immutable after Start: everything the monolith mutated on its config lives in a mutable params copy inside the director's state, which is the pile a savegame writes. And the gadget is a dumb executor — the director returns orders, including already-composed waves, so the whole lifecycle runs under busted against a fake map and a seeded RNG. Composition changes one thing on purpose: the monolith drew a random candidate and re-rolled up to a thousand times until one landed in the anger bracket. That is rejection sampling with a silent failure mode. This filters first and draws once, which is distribution-equivalent because buckets carry an entry once per weight point. Placing a burrow is the part that decides whether a director runs at all, and it took three fixes to get right: A director's origin is a preference, not a cage. Placement widens its box when the named corner has no ground to stand on — the original refused, which is defensible on a symmetric multiplayer map and useless to a mission that names a corner by map fraction and finds lava there. A last resort that still refuses is not a last resort. The fallback dropped the sight test but went on demanding a clear flat circle of 1.5 burrows, which is spacing, not fit — so on a small or crowded map every attempt failed including the one whose job was to succeed. It now asks for the footprint, and sweeps DETERMINISTICALLY outward from the middle of the box through the placement module before falling back to the random probe. The probe can miss a spot that exists; the sweep cannot, and it picks the same spot on every client. And a wet sample is a sample. The surface test ran once, after the probe had already chosen, and refusing it threw the spot away and ended the cadence — so on a map like Shallow Straits, where most shoreline is neither land nor sea, nearly every cadence placed nothing. It is per sample now, and the warning counts what refused rather than blaming crowding by default. A callin is never unhooked from inside a callin. The handler defers its list updates while iterating but nils the method field immediately, so a director that stopped during GameFrame took the whole gadget with it on the next frame. The hooks install once and idle on an empty host list. Nothing is live yet. No spec is registered, so no callin is hooked and scav_spawner_defense.lua still owns the scavengers game.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The base game's PvE wave machinery is two ~2,500-line monoliths about 70% line-identical to each other, enabled only when a LuaAI team is present, with no way for anything else to ask for a wave. This is the module they should have been: a generic wave director configured with plain data.
The seam is one table, the
WaveSpec. The gadget hosts 1..n directors keyed by name, so a multiplayer mode and a mission's scripted pressure are the same machinery at different intensities.lib/is rewritten, pure and spec'd;spring/is ported leaf code kept close to the original so a playtester recognises its behaviour. The spec is immutable afterStart, with everything the monolith mutated moved into a params copy in the director's state — the pile a savegame writes. The director returns orders rather than acting, so the whole lifecycle runs under busted against a fake map and a seeded RNG.One deliberate behaviour change: to compose a wave the monolith re-rolled a random candidate up to a thousand times until one landed in the anger bracket. This filters first and draws once, distribution-equivalent because buckets carry an entry once per weight point.
Burrow placement goes through #8632, so a spawner lands in the same spot on every client, and a mission's named origin keeps its direction instead of widening until it covers the map.
Nothing is live yet — no spec registered, no callin hooked,
scav_spawner_defense.luastill owns the scavengers game. #8577 switches it over.