This document specifies the procedural generation system for Tao Baryon's endless mode. It covers the wave generation algorithm, depth-based scaling functions, enemy pool construction, formation logic, environmental hazard injection, boss insertion, reward curve, and the lore framing that ties infinite difficulty to the game's narrative.
Endless mode unlocks after Stage 30 is cleared. It is a prestige mode — not a progression shortcut. Full specification in 00_amendments.md §Amendment 4 and 03_difficulty_curve.md §Endless Mode Scaling.
Endless mode answers one question: how far can you go?
Every system in endless mode serves that question. The generation algorithm must feel fair at depth 5 and still be generating novel, readable, survivable-but-barely content at depth 500. It must never feel random in a frustrating way — patterns must be learnable even as they scale. And it must be narratively coherent: the deeper the player goes, the more the Apparatus's bleed-through has corrupted the physics of the region.
Three tensions the algorithm must hold:
Depth is the core variable driving all endless generation. It is an integer that increments by 1 each time a wave is fully cleared.
Depth is not wave number. Every wave cleared = +1 depth, including sub-waves and continuation waves within a stage generation event. This means a player who clears a complex multi-wave sequence advances depth faster than one grinding simple singles — rewarding aggression.
The generation system runs a pipeline each time a new wave is needed:
[Depth counter]
│
▼
[Difficulty Budget Calculator]
│ produces: budget_points, density_cap, heat_level
▼
[Enemy Pool Selector]
│ produces: weighted enemy_pool[]
▼
[Formation Template Picker]
│ produces: formation_template
▼
[Wave Assembler]
│ produces: wave_spec (enemy list, positions, timings, bullet patterns)
▼
[Hazard Injector]
│ produces: hazard_list (active during this wave)
▼
[Event Checker]
│ checks: boss_due? | ace_spawn? | boltzmann_event? | bleed_event?
▼
[Wave Executor]
│ runs the wave, collects results
▼
[Reward Calculator]
│ produces: credits, mastery_xp, shard_chance
▼
[Depth++, loop]
Each stage of this pipeline is defined below.
Every wave is assigned a budget expressed in abstract "threat points." Enemy units cost threat points; the assembler fills a wave with enemies until the budget is spent.
base_budget = 20 + (depth × 2.5)
scaling_multiplier = 1.0 + (depth / 200) // asymptotic, softcaps near depth 200
budget = base_budget × scaling_multiplier
budget = clamp(budget, 20, 2400) // hard floor and ceiling
| Depth | Approximate budget |
|---|---|
| 1 | 22 |
| 10 | 47 |
| 25 | 87 |
| 50 | 152 |
| 100 | 278 |
| 200 | 540 |
| 500 | 970 (soft plateau) |
| 1000 | 1380 |
| 2000 | 1980 |
| 5000 | 2390 |
The budget ceiling means depth 5000 is not infinitely harder than depth 1000 — the difficulty ceiling is reached through pattern complexity and hazard density, not purely through budget inflation. A wave at depth 5000 has roughly the same enemy count as depth 1000, but the pattern vocabulary is maximally complex.
Simultaneously, a density cap limits how many enemies can be on screen simultaneously (regardless of budget):
density_cap = clamp(8 + floor(depth / 8), 8, 65)
| Depth | Max simultaneous enemies |
|---|---|
| 1 | 9 |
| 25 | 11 |
| 50 | 14 |
| 100 | 20 |
| 200 | 33 |
| 500 | 65 (cap) |
| 1000+ | 65 (cap holds) |
The density cap prevents the screen from becoming unreadable. At the density ceiling, the algorithm must spawn enemies in waves — clearing previous enemies before introducing new ones — maintaining visual legibility.
Heat level is a categorical variable that drives bullet pattern selection:
heat_level = clamp(floor(depth / 25), 0, 10)
| Depth range | Heat level | Pattern tier |
|---|---|---|
| 0–24 | 0 | Sector 1–2 patterns only |
| 25–49 | 1 | Sector 2–3 patterns |
| 50–74 | 2 | Sector 3 patterns |
| 75–99 | 3 | Sector 3–4 patterns |
| 100–124 | 4 | Sector 4 patterns |
| 125–149 | 5 | Sector 4–5 patterns |
| 150–174 | 6 | Sector 5 patterns |
| 175–199 | 7 | Sector 5 + early bullet hell |
| 200–224 | 8 | Bullet hell tier 1 |
| 225–249 | 9 | Bullet hell tier 2 |
| 250+ | 10 | Maximum: Touhou-tier + |
At each depth, a weighted pool of valid enemy types is constructed. The pool determines which enemies the assembler may select from.
Enemy types unlock progressively by depth:
| Depth gate | Enemies unlocked |
|---|---|
| 0 | G1, G2, S1, K1 (Sector 1 basics) |
| 10 | G3, G4, S2, K2, B1, B2 |
| 20 | G5, K3, B3, Sn1 |
| 30 | Sn2, K4, B4 |
| 50 | B5, Sn3, E1 (Choral Knight elite) |
| 75 | E2 (Inquisitor Hunter elite) |
| 100 | E3 (Geometer Saint elite) — +Boltzmann event trigger |
| 150 | E4 (Apparatus Guardian elite) |
| 200 | Ace-tier variants (all enemy types gain +30% HP, +20% speed) |
| 350 | Ace-tier variants gain second bullet pattern (simultaneous) |
| 500 | Ultra-tier variants (all enemy types gain +60% HP, +40% speed vs. base) |
Once the pool is constructed from unlocked enemy types, each enemy type is assigned a weight based on:
weight[e] = base_weight[e]
× recency_bias(e, last_10_waves) // reduce weight if overused recently
× heat_preference(e, heat_level) // snipers prefer high heat; grunts are flat
× depth_bias(e, depth) // elites increase in weight with depth
Recency bias ensures the player does not fight the same enemy type for 10 consecutive waves. If an enemy appeared in the last 3 waves, its weight is multiplied by 0.4. If in the last 6 waves, 0.7. Otherwise 1.0.
Heat preference maps enemy types to their ideal heat levels:
| Enemy class | Ideal heat | Weight outside ideal |
|---|---|---|
| Grunts (G1–G5) | 0–4 | 0.5× at heat 7+ |
| Strafers (S1–S2) | 2–6 | 0.6× at heat 0, 0.7× at heat 9+ |
| Bombers (B1–B5) | 3–7 | 0.5× at heat 0–1, 0.8× at heat 9+ |
| Snipers (Sn1–Sn3) | 5–9 | 0.4× at heat 0–2 |
| Kamikaze (K1–K4) | 0–5 | 0.5× at heat 8+ |
| Elites (E1–E4) | 4–10 | 0.6× below heat 4 |
Depth bias increases elite weight linearly:
elite_depth_weight = 1.0 + (depth / 100) // E1 at depth 100 has 2× base weight vs depth 0
Enemies do not spawn randomly distributed. They spawn in formations — structured spatial arrangements that create intentional bullet geometry. A formation template defines:
The system maintains a library of templates indexed by enemy count and heat level.
Single-enemy formations (heat 0–3):
Pair formations (heat 0–5):
Trio formations (heat 1–6):
Quad formations (heat 3–8):
Large formations (heat 5–10):
At each wave generation step:
// pick formation size based on budget and density_cap
available_budget = wave_budget_remaining
max_formation_size = min(density_cap - current_on_screen, floor(available_budget / cheapest_enemy_cost))
formation_size = random_from_distribution(1..max_formation_size, biased toward mid-range)
// pick template from heat-appropriate formations of that size
candidates = formation_library.filter(size == formation_size AND min_heat <= heat_level)
template = weighted_random(candidates, prefer: templates not used in last 5 waves)
The assembler takes a formation template and fills it with specific enemies from the weighted pool.
procedure fill_formation(template, enemy_pool, budget_remaining):
slots = template.slot_count
result = []
for slot in slots:
candidates = enemy_pool.filter(cost <= budget_remaining / slots_remaining)
chosen = weighted_random(candidates)
result.append(chosen)
budget_remaining -= chosen.cost
slots_remaining -= 1
return result
Slot cost table (threat points per enemy unit):
| Enemy class | Base cost |
|---|---|
| Grunt G1 | 2 |
| Grunt G2 | 3 |
| Grunt G3 | 4 |
| Grunt G4 | 5 |
| Grunt G5 | 6 |
| Strafer S1 | 5 |
| Strafer S2 | 8 |
| Bomber B1 | 4 |
| Bomber B2 | 6 |
| Bomber B3 | 8 |
| Bomber B4 | 11 |
| Bomber B5 | 14 |
| Sniper Sn1 | 9 |
| Sniper Sn2 | 13 |
| Sniper Sn3 | 18 |
| Kamikaze K1 | 3 |
| Kamikaze K2 | 4 |
| Kamikaze K3 | 6 |
| Kamikaze K4 | 9 |
| Elite E1 | 30 |
| Elite E2 | 38 |
| Elite E3 | 45 |
| Elite E4 | 55 |
Elite enemies consume a large portion of the budget. At low depths, a single elite may nearly exhaust the budget, making it the centerpiece of the wave. At high depths, the budget supports multiple elites simultaneously.
After enemy selection, each enemy's bullet pattern is assigned from the heat-level-appropriate vocabulary:
pattern_pool = bullet_patterns.filter(min_heat_level <= heat_level)
pattern = weighted_random(pattern_pool, prefer: formation_coherence)
Formation coherence preference: when multiple enemies share a formation, the assembler prefers patterns that create complementary geometry — e.g., two snipers with alternating predictive shots create a rhythmic dodge window; two snipers both firing simultaneous waves create visual noise. The assembler enforces a coherence score and re-rolls pattern combinations below threshold 0.4.
Bullet pattern vocabulary by heat level:
| Heat level | Available patterns |
|---|---|
| 0 | Pulse, Spread |
| 1 | + Wave |
| 2 | + Predictive |
| 3 | + Spread-wide (5-shot) |
| 4 | + Spiral (slow) |
| 5 | + Spiral (medium), Mandala (small) |
| 6 | + Mandala (medium), Lensed (single curve) |
| 7 | + Cascading, Lensed (dual) |
| 8 | + Threading, Mandala (large) |
| 9 | + Spiral (fast), Threading (dense) |
| 10 | + Simultaneous multi-pattern (one enemy fires two patterns at once) |
Hazards are environmental obstacles injected independently of the enemy wave. They persist for a duration, then clear.
| Depth | Hazards available |
|---|---|
| 0–24 | None |
| 25–49 | Exotic Matter Clouds (visual, mild projectile curves) |
| 50–99 | + Gravitational Lensing Zones (visual warning of lensed bullets) |
| 100–149 | + Moving Lensing Zones (drift slowly across screen) |
| 150–199 | + Time-Dilated Zones (player and enemies slow inside) |
| 200–299 | + Erupting Exotic Clouds (expand then collapse, 8s cycle) |
| 300–499 | + Cosmic Strings (thin instant-kill lines, slow drift) |
| 500+ | + Spacetime Collapse Zones (region with increasing damage over time) |
hazard_chance = clamp((depth - 20) / 300, 0.0, 0.75)
// at depth 20: 0%, depth 50: 10%, depth 100: 26%, depth 200: 60%, depth 320+: 75%
When a hazard is injected, exactly one hazard type is selected (weighted toward recently-unused types). Multiple hazards can be active simultaneously only at depth 200+, with probability:
double_hazard_chance = clamp((depth - 200) / 400, 0.0, 0.4)
// at depth 200: 0%, depth 400: 50% of hazard-waves have two hazards, depth 600+: 40% cap
Cosmic strings at depth 300+ have a mandatory telegraph: a thin visual indicator appears 2 seconds before the string materializes, giving the player time to clear the zone. The string drifts at a predictable speed. This is lore-justified — the strings are real physics tears, not attacks; they move according to physics, not toward the player.
After the wave is assembled but before it executes, the event checker runs special event triggers.
Every 50 depths, a boss event triggers, replacing the normal wave entirely:
| Depth | Boss event |
|---|---|
| 50 | Reverent Maelin (rematch, S1 boss) — 50% HP vs. campaign version |
| 100 | Final Chorus (rematch, S2 boss) — 70% HP |
| 150 | Inquisitor Cohort (rematch, S3 boss) — 85% HP |
| 200 | Mathematician-Saint Voren (rematch, S4 boss) — 100% HP + Endless Adaptation |
| 250 | Architect-Saint Halen Fragment — a sub-instance of Halen's consciousness, 800 HP (vs 2500 campaign), single-phase, uses Phase 2 patterns |
| 300 | Maelin rematch v2 — 120% HP, new pattern additions |
| 350+ | Boss cycle restarts with +20% HP scaling per 50-depth cycle |
Endless Adaptation (depth 200+ bosses): Bosses in endless mode gain a new passive — each time they lose a phase (70% / 40% threshold), they temporarily gain +25% fire rate and +15% movement speed for 10 seconds. This is the "Endless Corruption" mechanic, narratively explained as the Apparatus bleed-through affecting even the Telos commanders' control systems.
Halen Fragment (depth 250): Not a full boss fight. Lore framing: "A shard of the uploaded consciousness, adrift in the bleed-through field. It fights without purpose — but it still knows how." Uses Phase 2's Integrated Mind patterns. No dialogue. Silent. Its death animation is different from the campaign version — it doesn't implode cleanly; it scatters, like a shattered mirror.
At depth 100+, with probability:
boltzmann_chance = clamp((depth - 100) / 500, 0.0, 0.25)
A Boltzmann Construct spawns mid-wave alongside normal enemies. It is not a standard enemy — it is a rare anomaly. Stats:
At depth 200+, with probability:
bleed_chance = clamp((depth - 200) / 600, 0.0, 0.35)
A Bleed-Through Event triggers. This is a brief environmental disruption (5–8 seconds) where:
The event ends cleanly. It is a skill check — survive the brief escalation.
Endless mode rewards are calculated per wave cleared, not per stage.
base_credits = 15 + (depth × 0.8)
combo_multiplier = current_score_multiplier // same 1×–10× system as campaign
wave_credits = floor(base_credits × combo_multiplier × 0.80) // 80% of equivalent campaign rate
The 0.80 factor is the endless mode credit discount (see 00_amendments.md §Amendment 6, 09_economy.md). This ensures campaign remains the efficient path for credit grinding.
Pilot XP: 0. Endless mode does not contribute to Pilot Rank. This is a hard design decision — endless is mastery territory, not progression territory.
Ship Mastery XP: 100% rate. Endless is the best place to master a ship because run duration is limited only by skill, not by content volume.
At specific depth milestones, one-time rewards trigger (per endless run):
| Depth | Reward |
|---|---|
| 10 | 200 bonus credits |
| 25 | 1 Blueprint Shard |
| 50 | 500 bonus credits + boss clear bonus |
| 75 | 2 Blueprint Shards |
| 100 | 800 bonus credits + Boltzmann unlocks |
| 125 | 3 Blueprint Shards |
| 150 | 1,200 bonus credits |
| 200 | 2,000 bonus credits + 5 Blueprint Shards + Voren encounter bonus |
| 250 | 3,000 bonus credits + Halen Fragment bonus + 1 Veiled Token |
| 300 | 4,000 bonus credits + 8 Blueprint Shards + 1 Veiled Token |
| 500 | 6,000 bonus credits + 12 Blueprint Shards + 2 Veiled Tokens |
| 1000 | 12,000 bonus credits + 20 Blueprint Shards + 3 Veiled Tokens + cosmetic title |
Cosmetic title at depth 1000: "Beyond Physics" — displayed on the pilot profile and leaderboard entry.
When a Boltzmann Construct is killed, it drops one of:
| Reward | Weight |
|---|---|
| 2 Blueprint Shards | 40% |
| 4 Blueprint Shards | 25% |
| 1 Veiled Token | 20% |
| 500 bonus credits | 10% |
| Exclusive cosmetic shard | 5% |
Exclusive cosmetic shards are only obtainable from Boltzmann Constructs — they unlock cosmetics unavailable in the shop (engine trail colors, hull decal patterns). This makes hunting Boltzmann Constructs a meaningful secondary objective within endless runs.
The depth counter drives not only mechanical difficulty but a narrative escalation. The deeper the player goes, the more the Apparatus's bleed-through has eroded the physics of the engagement zone. This is reflected in:
| Depth range | Visual state |
|---|---|
| 1–50 | Normal deep-space background, stable colors |
| 50–100 | Subtle edge distortion, occasional lensing shimmer |
| 100–200 | Background color temperature shifts warmer, particle density increases |
| 200–300 | Screen edges show physics-tear artifacts (brief pixel displacement) |
| 300–500 | Background geometry becomes partially abstract — star fields warp and stretch |
| 500–1000 | Near-full abstraction. The "background" is now patterns of light and distortion. Enemy sprite edges flicker. |
| 1000+ | The playfield is barely recognizable as space. Light bends in real-time. Enemy colors shift toward Apparatus-gold. The game's UI elements flicker at the edges. |
Design constraint: The HUD (HP bar, heat bar, score, depth counter) remains fully readable at all depths. The visual degradation affects only background and enemy sprites — never the player ship or UI chrome.
| Depth range | Audio state |
|---|---|
| 1–50 | Sector 5 music theme (ambient, cosmological) |
| 50–150 | Music layers drop out one by one |
| 150–300 | Only bass drone and percussion remain |
| 300–500 | Irregular choral fragments (Telos liturgical, distorted) |
| 500–1000 | Near-silence; occasional sub-bass impact sounds |
| 1000+ | Sub-bass only; the sound of gravity |
At specific depths, Sira-Vel and Veth transmit brief pre-recorded fragments. These are not generated — they are authored lines:
| Depth | Speaker | Transmission |
|---|---|---|
| 25 | Sira-Vel | "You're going deeper than our telemetry suggested. Keep moving." |
| 50 | Sira-Vel | "Signal strength degrading. Whatever you're finding out there — come back." |
| 100 | Veth | "The constructs at this depth aren't Telos. They're not anything. Thermal noise given momentum. Fascinating and deeply unsettling." |
| 150 | Sira-Vel | "We're losing your position on our scans. I'm keeping the channel open." |
| 200 | Veth | "Voren's calculations did not include this. I suspect he would have been disturbed. Perhaps that is something." |
| 250 | Sira-Vel | "That was a piece of Halen. A fragment of the uploaded mind, scattered in the bleed-through field. He is already breaking apart. Whatever he was, Commander — it did not survive the machine." |
| 300 | Veth | "I cannot calculate what you are flying through. That is the first time I have said those words." |
| 500 | Sira-Vel | "[long static] ...still here. Keep flying." |
| 1000 | — | No transmission. Silence. Then a single tone — the same tone that played in Stage 30 when Halen died. |
// Core endless loop — called when previous wave is cleared
EndlessWave generate_wave(int depth) {
// 1. Budget
double budget = clamp(20 + depth * 2.5 * (1.0 + depth / 200.0), 20, 2400);
int density_cap = clamp(8 + depth ~/ 8, 8, 65);
int heat_level = clamp(depth ~/ 25, 0, 10);
// 2. Check special events
if (depth % 50 == 0) return generate_boss_event(depth);
if (roll(boltzmann_chance(depth))) inject_boltzmann_construct();
if (roll(bleed_chance(depth))) schedule_bleed_event();
// 3. Enemy pool
List<EnemyType> pool = build_pool(depth, heat_level, recent_history);
// 4. Formation
FormationTemplate template = pick_formation(budget, density_cap, heat_level);
// 5. Fill
List<Enemy> enemies = fill_formation(template, pool, budget);
// 6. Patterns
for (Enemy e in enemies) {
e.pattern = pick_pattern(heat_level, template, e.type);
}
// 7. Hazards
List<Hazard> hazards = inject_hazards(depth, heat_level);
// 8. Milestone reward
if (depth in MILESTONE_DEPTHS) grant_milestone_reward(depth);
return EndlessWave(enemies, template, hazards);
}
double boltzmann_chance(int depth) =>
depth < 100 ? 0.0 : clamp((depth - 100) / 500.0, 0.0, 0.25);
double bleed_chance(int depth) =>
depth < 200 ? 0.0 : clamp((depth - 200) / 600.0, 0.0, 0.35);
Endless mode submits scores to the platform leaderboard (Google Play Games / Apple Game Center) on run end (death or quit).
Score submitted:
score = depth × 1000 + total_credits_earned_this_run
Depth is the primary component (1000 points per depth) and credits secondary. This means reaching depth 100 with modest play (100,000 + credits) ranks above someone who reached depth 99 with exceptional play. Depth first, score second.
Leaderboard types:
Score sanity filter: Scores with depth > (Pilot Rank × 20 + 200) are flagged as implausible and excluded from leaderboard submission locally. This is a heuristic, not anti-cheat — it simply avoids contributing clearly-modified saves to the public leaderboard.
clamp(20 + depth × 2.5 × (1 + depth/200), 20, 2400)clamp(8 + depth/8, 8, 65) — hard max 65 enemies on screenclamp(depth/25, 0, 10) — drives bullet pattern vocabularydepth × 1000 + run_credits, platform-submitted, monthly seasonalDocument version: 1.0 (locked) Part of: Tao Baryon GDD — Tier 3 Meta Systems