Issue #1 · · Voyager #037

Mechanics Notes #1: How Level Spawns Are Built

Why can 3 points produce one Conehead or three Basic Zombies? Work through 0.17q spawn budgets, lanes, and wave timing with examples from level 1.

Restart the same level a few times, and a Conehead may appear where a group of Basic Zombies appeared before. Where does that difference come from? And when does the game decide which lane they will enter?

To answer those questions, we first need to understand how a wave is put together. We will start with a small example from level 1, then introduce authored spawns, lane selection, and refresh timing. You can follow from the beginning without knowing the level-file format; readers familiar with point pools may find the later exceptions useful. The rules here describe 0.17q's standard spawn system, checked against its level files and program logic. Zombies created by bosses, controllers, and preset entities need their own explanations.

Chance takes root: a three-pip die branches into one Conehead and three Basic Zombie faces.
Chance takes root: a three-pip die branches into one Conehead and three Basic Zombie faces.

Start with three points

Suppose the game has 3 points available to fill a wave. Its only choices are a Basic Zombie costing 1 point and a Conehead Zombie costing 3. Both are unlocked, and both have the same draw weight. We will call the available points the “budget” and the points needed to generate one zombie its “cost.”

Before calculating anything, consider what this wave could contain:

  • One Conehead Zombie.
  • Three Basic Zombies.
  • Either of the above.

If you chose the third answer, you have found the key to this example. But there is still something to explain: why can equal weights produce such different numbers?

Follow the two possibilities separately. If the first draw picks a Conehead, it spends all 3 points at once, leaving just that one zombie. If it picks a Basic, 2 points remain. A Conehead is now too expensive, so the next two draws can only pick Basics. The result is three Basic Zombies.

The same 3-point budget produces one Conehead above or three Basics below. Each dot represents 1 point. These are alternative outcomes, not simultaneous spawns. The teaching figures in this article are diagrams, not gameplay screenshots.
The same 3-point budget produces one Conehead above or three Basics below. Each dot represents 1 point. These are alternative outcomes, not simultaneous spawns. The teaching figures in this article are diagrams, not gameplay screenshots.

This is the situation on wave 4 of level 1 at MEDIUM difficulty. Neither outcome spends more points. The same allowance is simply spent on different zombies.

Equal weights do not mean equal counts. Weight affects the result of a draw; cost affects which candidates can take part afterward. Let us examine that rule more closely.

How the point pool spends its budget

Check the remaining points after every draw

The list of candidates in our example is the point pool, written as ZombiePoints.ZombieList in the level file. Each entry can set three main parameters:

  • Points: the points deducted when this entry is drawn once—its cost; default 1.
  • Weight: its relative weight in the current draw; default 100.
  • Waves: the first displayed wave that permits ordinary draws, counting from 1. The default 0 allows participation from wave 1.

Before a draw, the game checks which entries are unlocked and which it can afford with the remaining budget. Only candidates that pass both checks take part, using their respective weights. The chosen entry's cost is deducted, and the checks run again with the points left over.

Being drawn does not remove an entry from the pool. It can be selected again as long as the conditions still hold. Ordinary draws end when the budget runs out or no candidate remains eligible.

In our opening example, the Conehead and Basic compete with equal weight only on the first draw. After one Basic is chosen, the Conehead becomes too expensive to take part again. We cannot treat every draw as an even choice between the two. The Conehead is excluded for the current remaining budget, though; it has not been deleted from the level's pool.

Where the budget comes from

We began by giving the game 3 points. How does it arrive at that allowance? An ordinary wave's default base budget is B = i / 3 + 1, where i is a wave index starting at 0. Use 0 for wave 1 and 3 for wave 4. Keep any fraction from the division for now.

Multiply the base budget by the difficulty multiplier M, then convert to integer points. For wave 4 of level 1 on MEDIUM:

  • Calculate the base budget: 3 / 3 + 1 = 2.
  • Apply the difficulty multiplier: 2 × 1.5 = 3.
  • The result is already an integer, leaving 3 points for the wave.

The five default multipliers are 1, 1.5, 2, 2.5, 3. An author can provide one shared Multiplier or a five-element array with a value for each difficulty. If ZombiePoints.Points is an array whose length exactly matches the total Waves, its entries replace the base budget for each wave. The difficulty multiplier still applies afterward.

The name Points can be confusing here. Inside a pool entry, it is the cost of one zombie. As a per-wave array under ZombiePoints, it supplies the base budgets. Check which level of the file you are reading first.

Spawn points are an internal allowance, separate from sun, Glory, and zombie HP. A Conehead costing 3 does not mean it has three times a Basic Zombie's durability.

One detail remains in the integer conversion. The program first adds a tiny 0.000001, then discards the fractional part; a positive budget above that epsilon but below 1 becomes 1 point. This method is enough for most hand calculations. A result very close to a boundary also requires attention to single-precision arithmetic.

Where the author's chosen zombies go

A point pool explains random additions, but consider another common arrangement: the author wants a Conehead in a particular wave and specifies lane 3. That record can go straight into the top-level ZombieList, without waiting for the pool to draw one.

We now have two lists with the same name. The top-level ZombieList stores authored records for each wave; ZombiePoints.ZombieList stores candidates for random additions. A level can use both. Set the random additions aside for a moment, and look at how the authored list becomes separate waves.

Splitting the list into waves

Suppose we write “Basic, Basic, wave separator, Conehead, end marker,” in that order. How many waves does that make? The two Basics belong to wave 1. After the separator, the Conehead belongs to wave 2.

In a level file, the wave separator is called NULLZOMBIE and the end marker ENDNULLZOMBIE. The game reads the list in order. Each ordinary record specifies one zombie; the first marker advances to the next wave, while the second ends the list.

Now put two separators next to each other. With no ordinary record between them, they leave a wave with no authored zombies. The figure compares these two arrangements:

Read from left to right. N stands for NULLZOMBIE and E for ENDNULLZOMBIE. Above, two Basics followed by N, a Conehead, and E make two waves. Below, adding a second N leaves wave 2 empty and moves the Conehead to wave 3. The empty wave has no authored records; the pool may still add zombies.
Read from left to right. N stands for NULLZOMBIE and E for ENDNULLZOMBIE. Above, two Basics followed by N, a Conehead, and E make two waves. Below, adding a second N leaves wave 2 empty and moves the Conehead to wave 3. The empty wave has no authored records; the pool may still add zombies.

A separator is therefore not a zombie, and an empty authored wave need not be empty in play. Also, two identical records of the same type and lane still mean two zombies. They do not merge into one. The site's x2 marker preserves that quantity.

Using one list across difficulties

Suppose the author wants our Conehead to appear only on HARD and above. They can add LowDif: 30. That 30 comes from the numeric difficulty values: EASY = 10, MEDIUM = 20, HARD = 30, IMPOSSIBLE = 40, and UNBALANCED = 50.

The comparison is straightforward. Keep the record if the current difficulty value is at least LowDif; otherwise, skip it. Omitting LowDif gives a threshold of 0, so all five difficulties pass this check.

What if we raise the threshold from 30 to 44? Can IMPOSSIBLE still include the zombie? Although 44 is closer to 40, it is not assigned to that tier. Since 40 < 44, only UNBALANCED's value of 50 passes.

Column headings 10, 20, 30, 40, and 50 represent the five difficulties. With a threshold of 30, the final three columns keep the Conehead. Raise it to 44, and only 50 keeps it. A dash means this record is filtered out, not that the whole wave is empty.
Column headings 10, 20, 30, 40, and 50 represent the five difficulties. With a threshold of 30, the final three columns keep the Conehead. Raise it to 44, and only 50 keeps it. A dash means this record is filtered out, not that the whole wave is empty.

LowDif compares numbers; it does not choose the nearest difficulty tier. The Dif rating in the level profile is a separate value and does not enter this comparison.

Repeating part of the table

The authored list can also specify a section to reuse. EndlessWave sets its starting point, again counting from 0. Level 3 has ten authored waves, and its EndlessWave: 7 points to wave 8.

After wave 10, reading returns to the records for wave 8. When the site expands Waves: 15, displayed waves 11–15 reuse authored waves 8, 9, 10, 8, and 9. The reading position returns to an earlier section, while the displayed wave number continues to increase.

Level 3's authored-wave expansion. The upper bracket marks source waves 8, 9, and 10. In each column below, the upper number is the displayed wave and the lower number is the authored wave it reuses. Waves 11–15 map to 8, 9, 10, 8, and 9. Only authored records repeat here; this does not mean the pool copies its earlier results.
Level 3's authored-wave expansion. The upper bracket marks source waves 8, 9, and 10. In each column below, the upper number is the displayed wave and the lower number is the authored wave it reuses. Waves 11–15 map to 8, 9, 10, 8, and 9. Only authored records repeat here; this does not mean the pool copies its earlier results.

Here, 15 only bounds the page's expansion. It does not establish that the looping level must end on wave 15 in play. That still depends on the game's runtime completion conditions.

Add three special rules

We now know how authored records are split into waves and how a pool spends its allowance. A few more rules let us follow the construction order: the game adds authored records, handles automatic flag groups, applies final-wave protection, deducts authored costs, and finally uses the remaining budget for ordinary draws.

This happens wave by wave when the level is entered. In particular, pool types, quantities, and initialization lanes are generated while building the table at level start. A wave that happens to be difficult for your formation is not evidence that the pool just inspected your plants and drew a counter. Soul lane selection does read plant state, as we will see later, but that is a separate step.

Large-wave budgets and flag groups

Large waves usually bring more company. The default budget formula makes a large-wave adjustment on every tenth wave and the final wave: multiply the base budget by 2.5, then calculate the extra Basic Zombie count N. To obtain N, discard the fraction from i × M / 3 and constrain the result to 2–8.

Including one Flag Zombie gives a group size of C = N + 1. If the enlarged base budget is at least 1.5 × C, subtract C before applying the difficulty multiplier and integer conversion. We can think of this as reserving points for the flag group.

Reserving points and actually creating a group need separate attention, however. Generating one Flag Zombie and N Basics also requires ZombiePoints.Flag: true, which is off by default. The reservation branch itself does not check Flag, so a deduction alone does not establish that an automatic group will appear.

A valid per-wave Points array overrides the base budget. There is another easy source of confusion: an author may write a single Flag Zombie into the authored list. That is an authored record and does not enable automatic groups. When we return to level 1, we will work through the large-wave numbers in full.

Authored records can consume the budget too

Suppose a wave has 5 points and the author has separately placed a Conehead in it. With no other special handling, the pool still has those 5 points to spend. Writing one authored zombie does not automatically remove one random addition.

To charge authored spawns to the budget, enable SubtractMode, which is off by default. If the Conehead record's deduction cost is 3, for example, 2 points remain for ordinary draws.

The cost has its own lookup order. Use an explicit Points value on the authored record first. Otherwise, look for the corresponding type's price in the pool; if none is available, use 0. Only authored records in this wave that pass the difficulty filter enter the deduction. The allowance left for random additions therefore depends on their costs, not simply on how many authored zombies there are.

The final wave can include an undrawn entry

Consider a different case. The pool has included a Conehead with nonzero weight throughout the level, but earlier draws happened never to pick it. Can it remain absent on the final wave? Protected, enabled by default, handles this situation.

It checks each pool entry's history, inserts each nonzero-weight entry with no previous pool generation once, and deducts its cost. For a small example, suppose 2 points remain at this check, the only entry needing protection is a 3-point Conehead, and all lanes are available. Despite being unaffordable, the Conehead is inserted, taking the budget to 2 - 3 = -1.

Final-wave protection checks this pool entry's generation history. Above, n = 0 means no earlier Conehead generation from this entry: protection adds one, deducts 3 from 2, and leaves −1. Below, n = 1 means it was generated earlier, so protection adds none and leaves 2 points. These are alternative cases, not consecutive steps.
Final-wave protection checks this pool entry's generation history. Above, n = 0 means no earlier Conehead generation from this entry: protection adds one, deducts 3 from 2, and leaves −1. Below, n = 1 means it was generated earlier, so protection adds none and leaves 2 points. These are alternative cases, not consecutive steps.

Why can an unaffordable zombie be inserted? Protection runs before ordinary unlock and budget checks, and before authored deductions. Neither a locked entry nor an insufficient budget rules out a protected insertion.

Pay particular attention to “from this pool entry.” If an earlier Conehead came from the authored list, it does not count as a pool draw. Conversely, an entry previously generated from the pool does not receive another protected spawn on the final wave. Protection tracks pool-entry history, not which zombies the player has seen on the lawn.

Choosing an entrance lane

Fixed and random lanes

So far, we have asked which zombies appear. Now consider where they enter. Which lane does Row: 2 specify? Lane 3, because row numbers start at 0. On a typical five-lane lawn, the top lane is numbered 0 and the bottom 4.

If a record omits Row or uses Row: -1, the game waits until that zombie spawns to choose a lane. It randomly selects from currently available lanes that are not gated by a lane cooldown. If all available lanes are cooling down, it picks the one with the lowest cooldown.

Fixed lanes are not unconditional either. An invalid, unavailable, or cooldown-gated lane falls back to the same random path. A fixed-lane label tells us what the record specifies; the actual spawn must still pass runtime checks.

The level also has a top-level Row array describing lane states, where state 2 means unavailable. This differs from Row: 2 on a zombie record: one describes a lane's state, while the other specifies a destination for a zombie.

Why initialization lanes look more even

Watch several zombies generated by the pool, and their lanes may seem reasonably spread out. This comes from the smooth selector used during initialization. The pool and automatic flag groups track how many selections have passed since each lane's last two picks, then adjust weights to favor lanes picked less recently.

Think of it as a running record of lane choices. It is not cleared at the next wave, and authored fixed lanes update it too. Earlier records can therefore affect a later wave's lane distribution.

Smooth selection does not require every lane to take a turn before one can repeat. A lane picked just now can be chosen again, and five zombies need not occupy one lane each. Type draws and smooth lane draws also share the same level-local random stream, so changing earlier candidates or records can change later results.

What Soul selection measures

Row: -2 is a different case. It follows ordinary random selection on EASY and MEDIUM; HARD, IMPOSSIBLE, and UNBALANCED enable Soul scoring. At the start of each wave, the game calculates a plant-layout snapshot, then selects the available lane with the lowest score. Ties go to the topmost lane.

Use an easy formation to calculate. All five lanes are available; lanes 1, 3, and 5 each contain one full-health Wall-nut; lanes 2 and 4 are empty, with no other plants anywhere. A Wall-nut contributes 5 × current HP / maximum HP using integer arithmetic, or 5 at full health. The five scores are therefore 5, 0, 5, 0, and 5.

Lanes 2 and 4 share the minimum. Which one wins? The topmost-tie rule selects lane 2.

Soul selection on HARD and above: lane numbers are on the left and scores on the right. Full-health Wall-nuts score 5 in lanes 1, 3, and 5; lanes 2 and 4 both score 0. The gold outline marks the selected lane 2, while the dashed outline marks the equally scored but lower lane 4.
Soul selection on HARD and above: lane numbers are on the left and scores on the right. Full-health Wall-nuts score 5 in lanes 1, 3, and 5; lanes 2 and 4 both score 0. The gold outline marks the selected lane 2, while the dashed outline marks the equally scored but lower lane 4.

Now change the situation slightly. A zombie from this wave has already entered lane 2. Does the next Soul record switch to lane 4? In this example, it does not. The score uses the plant snapshot from the start of the wave, and the new zombie has not changed that snapshot. Several Soul records can consequently choose the same lane in succession.

The full score also considers adjacent-lane support from the Dd gene of Peashooters and Peanuts, but it does not include every plant's damage, crowd control, sun production, and Active Skill options. A “weak Soul lane” only means the lane with the lowest score under this calculation. It need not be the lane you find hardest to defend.

Does faster clearing bring the next wave sooner?

We know what a wave contains and where its zombies enter. One question remains: when does the next wave arrive? WaveManager helps control this. InitTime sets the first wait, Counter sets the interval counter, and Factor helps calculate the early-refresh threshold. Its Flag subsection controls the interval leading into a large wave.

First, follow the early-refresh check. After a wave is generated, the game records an initial aggregate for its eligible zombies, then multiplies that value by Factor to obtain a threshold. Once more than 200 counter units have elapsed, with more than 100 remaining, reaching or falling below the threshold changes the remaining wait to 100.

We can use a purely numeric example. Suppose the initial aggregate is 100 and the factor is 0.25, giving a threshold of 25. A counter that started at 2500 has run for 300 units, leaving 2200. If the remaining aggregate is now 20, all three conditions hold, and the counter changes from 2200 to 100.

A numeric early-refresh example: 100 × 0.25 gives a threshold of 25. Elapsed units are 300, above 200; remaining units are 2200, above 100; and the remaining aggregate, 20, does not exceed 25. With all three conditions met, the remaining counter changes from 2200 to 100. The aggregate is not a zombie count, and counter units are not milliseconds.
A numeric early-refresh example: 100 × 0.25 gives a threshold of 25. Elapsed units are 300, above 200; remaining units are 2200, above 100; and the remaining aggregate, 20, does not exceed 25. With all three conditions met, the remaining counter changes from 2200 to 100. The aggregate is not a zombie count, and counter units are not milliseconds.

If only 150 units had elapsed, an aggregate of 20 would not trigger this branch at that moment. Early refresh depends on both clearing progress and counter conditions, not simply on the zombies left on screen.

The 0.17q defaults are an initial wait of 1800 logic-update units, an ordinary counter of 2500–3100 with factor 0.25, and a large-wave counter of 4000 with factor 0. Levels can override these: level 48, for example, sets both ordinary and large-wave factors to 0.5–0.65. These units cannot be read directly as milliseconds or rendered frames.

Two qualifications matter. First, the aggregate checks wave membership and entity state, so a count of every zombie on the lawn cannot replace it. Second, triggering the condition only shortens the remaining wait to 100; it does not immediately spawn the next wave. Large-wave warnings and additional counters can also affect the actual spawn time. The site does not currently simulate this live timing process.

Return to level 1 and work it through

We now have enough rules to read a simple level. Return to level 1: it has ten waves, no top-level authored list, and automatic flag groups enabled. Basics and Coneheads cost 1 and 3 points, both have weight 4000, and Coneheads unlock on wave 2.

On MEDIUM, the multiplier is 1.5. What is the earliest wave on which a Conehead can participate in ordinary draws? Work out the first four budgets:

  • Wave 1: 1 × 1.5 converts to 1 point.
  • Wave 2: (1 / 3 + 1) × 1.5 gives 2 points.
  • Wave 3: the calculation gives 2.5, still 2 after discarding the fraction.
  • Wave 4: (3 / 3 + 1) × 1.5 = 3, finally reaching 3 points.
The first four waves of level 1 on MEDIUM. Each column shows the wave number above and its budget as dots: 1, 2, 2, and 3. Faded Coneheads with dashes are ineligible for ordinary draws. Only the outlined Conehead on wave 4 becomes eligible; the figure does not promise that the draw will select it.
The first four waves of level 1 on MEDIUM. Each column shows the wave number above and its budget as dots: 1, 2, 2, and 3. Faded Coneheads with dashes are ineligible for ordinary draws. Only the outlined Conehead on wave 4 becomes eligible; the figure does not promise that the draw will select it.

The answer is wave 4. Wave 2 unlocks the Conehead, but its 2 points cannot cover a cost of 3; wave 3 is still short. Unlocking permits a draw, while the budget must make it affordable. Even after both conditions hold, whether a Conehead appears still depends on the draw itself.

Now consider wave 10. With i = 9, the base budget is 9 / 3 + 1 = 4, enlarged to 10 for the large wave. The extra Basic count is 9 × 1.5 / 3 = 4.5, truncated to 4, giving five zombies including the Flag Zombie.

Since 10 ≥ 1.5 × 5, subtract 5 from the base budget, multiply by 1.5, and convert to 7 integer points. These 7 points are available for subsequent pool processing. The automatic group separately contains one Flag Zombie and four Basics. Keep the two parts separate so that neither the group nor its reservation gets counted incorrectly.

Open the level 1 MEDIUM seed example and expand the full table to check your calculation. In the site's current model, seed 123456 produces one Conehead in lane 3 on wave 4. On wave 10, the pool adds four Basics and one Conehead alongside the automatic group, spending exactly 7 points.

One small exercise remains: can wave 2 draw a Conehead on UNBALANCED? This level's multiplier array is 1, 1.5, 2, 2.5, 4, so the highest setting overrides the default 3. Try the calculation before comparing your answer below.

At four times the base budget, wave 2 has 5 points. The Conehead is both unlocked and affordable, so it can participate. Its eligibility moves from wave 4 to wave 2 because the higher multiplier meets its cost earlier, not because its unlock wave changed.

What to know when checking a wave table

Choose the difficulty first, then distinguish known counts, candidate types, and lanes chosen at spawn time. A question mark means the pool quantity has not yet been resolved by initialization simulation. “Eligible lanes 1/2/3/4/5” lists possible destinations for one record; it does not mean one zombie in every lane.

Enabling the simulated table uses the entered unsigned 32-bit integer to initialize the site's reconstructed MT19937. Under the same implementation, the same level, difficulty, and seed produce the same result. We can return to a draw and check its cost and the candidates left afterward. However, this seed serves a different purpose from the 12-character I, Zombie Endless share code, and the site does not reconstruct how the game derives its upstream seed. Entering a number therefore does not identify the roster from a particular gameplay session.

Two reconstruction differences remain. The original program leaves lane weights uninitialized before the first smooth draw; the site normalizes them to zero. Native code updates authored fixed-lane history before difficulty filtering, whereas the current simulation records only fixed lanes active at the selected difficulty. Levels mixing such records may produce different lanes. Spawn-time cooldowns, random selection, Soul snapshots, and wave timing are also outside this initialization simulation.

Official pages analyze only the base level{id}.json. Files such as level34_1.json are related sublevels or variants and cannot be merged into the base table; DIY Levels analyzes the currently selected sublevel. “No standard wave table” means there is no ordinary roster to expand. Controllers, bosses, and preset entities may still create zombies, requiring a look at the level's other arrangements.