M4: Magic World
Philosophy: The magical geological layer. Ley lines are geological features, not game mechanics. Mana is terrain output, like sunlight or rainfall. Corruption is a physical force, like salinity. Magic is another natural system layered on top of the physical world (M1), biological world (M2), and dynamic events (M3).
When this milestone is complete: generate a map with M1 climate + M2 ecology + M3 events, simulate ley line formation from geological anchors (volcanoes, mountain peaks, natural wonders), observe mana density gradients, watch corruption spread along Death-aligned ley segments, and see magical biomes form in high-mana zones. Magical events (enchanted groves, chaos rifts, arcane cataclysms) fire from M3's event dispatch. All deterministic, all observable in the guide visualizer before any gameplay exists.
No game rules yet. No spells, no Archons, no players casting magic. Just the world's magical geology.
Prerequisite: M3 must be fully complete and verified. This milestone builds on M1 terrain/climate, M2 ecology, and M3 event dispatch.
Reference: @magic-civilization.messy/ FEATURE_GAP sections 3.1, 3.2, 3.3. Also .messy/engine/src/modules/climate/anchor_decay.gd and .messy/engine/src/modules/ley/.
Dependency Order
M3-complete -> terrain-mana-fields -> ley-anchors -> voronoi-ley-generator
|
mana-density-field
|
school-alignment <- anchor-school
|
magical-affinity <- terrain-mana-color-pie
|
corruption-mechanics <- death-alignment
|
anchor-decay <- M3-geological-events
|
magical-events <- M3-event-dispatch
|
magical-ecology (enchanted biomes)
|
transpiler-update -> guide-ley-visualizer -> proof
Tasks
Block 0: Terrain Data Extension
| # |
Task |
Agent |
Blockers |
Spec |
| 0.1 |
Extend terrain/land.json with magical fields on all terrain types: mana_major (school, 0-2/turn), mana_minor (school, 0-1/turn), ley_anchor_strength (0-5), ley_anchor_school (school or null), infiltration_magic (ley energy bleed into soil, 0-1), terrain_power (passive gameplay effect id or null), culture_yield (0-3) |
game-data |
M1:0.1 |
FEATURE_GAP 3.1, 3.2 |
| 0.2 |
Extend data/climate_spec.json with magical sections: ley_anchors (anchor types, strength multipliers, school affinities), mana_decay_rates (per school: Nature 0.3x, Life 0.5x, Aether 1.0x, Chaos 2.0x, Death 0x permanent), corruption_spread_rate, corruption_ley_multiplier, mana_density_thresholds (nexus/line/adjacent/fringe/dead), anchor_decay_rates (per event type) |
game-data |
M0:0.4 |
FEATURE_GAP 3.3 |
| 0.3 |
Author terrain/magical.json -- magical terrain types: enchanted_forest (Nature mana_major, +1 culture, rare), corrupted_land (Death mana_major, corruption spread), mana_nexus_terrain (special wonder, mana density 1.0), bermuda_anomaly (wonder, Aether-aligned, navigation disruption), ley_nexus (wonder, requires 3+ converging ley lines) |
game-data |
0.1 |
FEATURE_GAP 3.3 |
| 0.4 |
Author data/ley_line_params.json -- Voronoi power diagram parameters: anchor weight formula, edge detection threshold, nexus detection (3+ converging edges), school mixing rules (contested segment determination), density field falloff curve |
game-data |
0.2 |
FEATURE_GAP 3.3 |
Block 1: Terrain Mana Color Pie
| # |
Task |
Agent |
Blockers |
Spec |
| 1.1 |
Implement mana yield calculation in tile.gd -- get_mana_yield() -> Dictionary[String, float]. Returns per-school mana output based on terrain's mana_major/mana_minor fields and ley_density. Example: grassland yields {Life: 1.0}, volcano yields {Chaos: 1.0} |
game-systems |
0.1, M1:1.2 |
FEATURE_GAP 3.2 |
| 1.2 |
Terrain magical affinity in tile.gd -- get_spell_modifier(school: String) -> float. Matched school: 0.5x decay / +25% spell power. Opposed school (Life<->Death, Chaos<->Nature, Aether neutral): 2.0x decay / -25%. Neutral: 1.0x |
game-systems |
0.1, 1.1 |
FEATURE_GAP 3.2 |
| 1.3 |
Terrain powers -- passive per-terrain effects encoded as data: grassland_heals_life_units, mountains_boost_chaos_damage, swamp_regens_undead, tundra_weakens_life. Store as terrain_power id in terrain.json, define effect params in terrain_powers.json |
game-data |
0.1 |
FEATURE_GAP 3.2 |
Block 2: Ley Anchor Identification
| # |
Task |
Agent |
Blockers |
Spec |
| 2.1 |
Implement anchor extraction in engine/src/modules/ley/ley_line_generator.gd -- scan GameMap for ley anchor tiles. Anchor types: natural_wonder (str 5), volcano (3), mountain_peak (2), wellspring (3), enchanted_forest (1), standing_stone (2). School from tile's ley_anchor_school |
game-algorithms |
0.4, M1:2.1 |
FEATURE_GAP 3.3 |
| 2.2 |
Anchor school determination -- if ley_anchor_school is null, determine from surrounding biome: hot -> Chaos, wet -> Nature, cold -> Aether, etc. Nearest-biome majority vote within radius 3 |
game-algorithms |
2.1, M1:2.2 |
-- |
Block 3: Ley Line Generation (Voronoi)
| # |
Task |
Agent |
Blockers |
Spec |
| 3.1 |
Implement weighted Voronoi power diagram in ley_line_generator.gd -- given anchor set, compute Voronoi edges (ley segments) and vertices (nexus points). Weighted by strength |
game-algorithms |
2.1, 0.4 |
FEATURE_GAP 3.3 |
| 3.2 |
Ley segment school alignment -- each edge derives school from bordering anchors. Same school: that school. Mixed: contested. Store: {from, to, school, contested} |
game-algorithms |
3.1, 2.2 |
FEATURE_GAP 3.3 |
| 3.3 |
Nexus detection -- Voronoi vertices with 3+ converging edges. Higher mana density (1.0x vs 0.7x on edge). Store nexus positions in GameState |
game-algorithms |
3.1 |
FEATURE_GAP 3.3 |
| 3.4 |
Ley segment -> hex tile mapping -- determine which hex edges each segment crosses, which tiles are adjacent. Basis for tile.ley_density |
game-algorithms |
3.1, M1:1.1 |
-- |
Block 4: Mana Density Field
| # |
Task |
Agent |
Blockers |
Spec |
| 4.1 |
Compute tile.ley_density [0,1] -- nexus=1.0, on-edge=0.7, adjacent=0.4, 2-tiles=0.2, farther=0.05. Dead zone (<0.1) = mana 0.1x |
game-algorithms |
3.4 |
FEATURE_GAP 3.3 |
| 4.2 |
Compute tile.ley_school -- dominant school of nearest ley segment, or none in dead zone |
game-algorithms |
3.2, 4.1 |
-- |
| 4.3 |
Mana density -> mana yield multiplier -- nexus 1.5x, on-line 1.0x, adjacent 0.75x, fringe 0.4x, dead 0.1x. Wire into tile.get_mana_yield() |
game-systems |
4.1, 1.1 |
FEATURE_GAP 3.3 |
| 4.4 |
Wire ley recompute into climate orchestrator -- climate.gd process_turn() recomputes mana density field when ley network is dirty (anchor changes) |
game-algorithms |
4.1, M1:3.1 |
FEATURE_GAP 3.3 |
Block 5: Corruption Mechanics
| # |
Task |
Agent |
Blockers |
Spec |
| 5.1 |
Implement corruption spreading -- engine/src/modules/climate/climate_corruption.gd (split from .messy/ climate.gd step 11). Per turn, Death-aligned ley tiles spread corruption. Rate: base * ley_density * corruption_ley_multiplier (2x on ley, 3x Death-aligned). Non-ley spread at base rate only |
game-algorithms |
4.2, M1:1.2 |
FEATURE_GAP 3.3 |
| 5.2 |
Corruption persistence -- decay 0.01/turn on non-Death tiles, never on Death-aligned. High corruption (>0.7) overrides biome -> corrupted_land |
game-algorithms |
5.1 |
FEATURE_GAP 3.3 |
| 5.3 |
Corruption terrain effects -- corrupted_land: Death mana_major, negative food/production (M2 ecology breaks down: vegetation_density -> 0), ley_anchor_school forced to Death |
game-systems |
5.2, 0.3 |
-- |
| 5.4 |
Wire corruption into climate orchestrator -- step 11, after terrain evolution (M3) and before ley network recompute |
game-algorithms |
5.1, M3:6.2 |
-- |
Block 6: Anchor Decay System
| # |
Task |
Agent |
Blockers |
Spec |
| 6.1 |
Port + implement engine/src/modules/climate/anchor_decay.gd -- when M3 geological events create/destroy anchors (volcanic eruption -> new Chaos anchor, earthquake -> destroy mountain anchor, corruption -> convert to Death), apply gradual decay of old ley field and growth of new. Rate = school_mult * terrain_affinity |
game-algorithms |
4.4, 5.1 |
.messy/engine/src/modules/climate/anchor_decay.gd |
| 6.2 |
Ley network dirty flag -- when anchor set changes (from events or decay), mark dirty. Recompute on next process_turn() |
game-algorithms |
6.1, 3.1 |
-- |
| 6.3 |
Standing Stone natural placement -- spawn on high-ley_anchor_strength terrain during map generation. Adds strength-2 anchor. Include in anchor scan |
game-data, game-algorithms |
2.1 |
FEATURE_GAP 3.3 |
Block 7: Magical Events
| # |
Task |
Agent |
Blockers |
Spec |
| 7.1 |
Implement events_magical.gd -- new file added to M3's event dispatch. Categories: T1 enchanted grove (positive, Nature anchor), T2 chaos rift (Chaos surge, terrain damage), T3 50/50 wellspring/death_rift, T4 spirit nexus (Aether surge, all schools), T5 arcane cataclysm (massive ley disruption, anchor destruction/creation) |
game-algorithms |
M3:1.1, 4.2 |
.messy/engine/src/modules/climate/ecological_events.gd (magical category) |
| 7.2 |
Wire magical events into M3 dispatch -- update ecological_events.gd dispatch to route magical category to events_magical.gd. Set magical.json frequency to non-zero |
game-algorithms |
7.1, M3:0.2 |
-- |
| 7.3 |
Magical event -> anchor effects -- enchanted grove creates Nature anchor, chaos rift creates Chaos anchor, death_rift creates Death anchor. Triggers ley network recompute via 6.2 |
game-algorithms |
7.1, 6.2 |
-- |
Block 8: Magical Ecology
| # |
Task |
Agent |
Blockers |
Spec |
| 8.1 |
Enchanted forest formation -- tiles with ley_school=Nature AND ley_density>0.4 AND biome=forest have per-turn conversion probability to enchanted_forest. Enchanted forests become Nature anchors (str 1) -- positive feedback. Rate from climate_spec.json |
game-algorithms |
4.2, M2:2.1 |
FEATURE_GAP 3.2 |
| 8.2 |
Magical biome transitions -- high Aether over ocean -> moisture increase; high Chaos over mountains -> volcanic event probability increase; high Life over grassland -> M2 vegetation density bonus. World-physics effects, not game buffs |
game-algorithms |
4.2, M2:2.1 |
-- |
| 8.3 |
Wellspring formation -- river source tiles in high-Life/Nature ley density zones -> probability of becoming wellspring (str 3 anchor, +1 Life/Nature mana) |
game-algorithms |
4.2, M1:2.6 |
-- |
| 8.4 |
Mana terrain culture yield -- enchanted_forest +1 culture/turn, sacred_grove +2, mana_nexus +1 per school. Store in tile.culture_yield. Consumed by city economy in future milestone |
game-systems |
8.1, 0.1 |
FEATURE_GAP 3.2 |
Block 9: Transpiler Update
| # |
Task |
Agent |
Blockers |
Spec |
| 9.1 |
Add ley line modules + anchor_decay + corruption + magical events to transpiler. Regenerate TypeScript |
guide-web |
6.1, 5.1, 7.1 |
-- |
| 9.2 |
Update packages/engine-ts/src/index.ts -- re-export new ley/corruption/magic functions |
guide-web |
9.1 |
-- |
Block 10: Guide Ley Visualizer
| # |
Task |
Agent |
Blockers |
Spec |
| 10.1 |
Ley line overlay -- Voronoi edges as colored lines. Life=white, Death=dark purple, Chaos=red, Nature=green, Aether=blue, Contested=grey. Nexus = starburst. Anchors = school icon |
guide-web |
9.2 |
FEATURE_GAP 3.3 |
| 10.2 |
Mana density heatmap -- [0,1] gradient (black=dead, bright school color=high). Toggle per-school / combined |
guide-web |
9.2, 10.1 |
-- |
| 10.3 |
Corruption heatmap -- [0,1] purple gradient. Toggle Death-aligned ley segments only |
guide-web |
9.2 |
-- |
| 10.4 |
Ley network animation -- step through turns, watch segments form/dissolve as anchors change. Visual decay on anchor destruction |
guide-web |
10.1, 9.2 |
-- |
| 10.5 |
Mana yield panel -- total mana per school per turn. Updates with simulation |
guide-web |
10.2 |
-- |
| 10.6 |
Magical biome overlay -- enchanted_forest, corrupted_land, ley_nexus shown separately from base biome |
guide-web |
10.1 |
-- |
Block 11: Golden Test Vectors (Magic Layer)
| # |
Task |
Agent |
Blockers |
Spec |
| 11.1 |
Extend GDScript golden vectors -- add ley_density, ley_school, corruption. Seed 42, 20x20, 20 turns. Verify ley network deterministic |
game-algorithms |
6.2, 5.2 |
-- |
| 11.2 |
Extend TypeScript golden vectors -- match GDScript within tolerance |
guide-web |
11.1, 9.2 |
-- |
Block 12: Proof
| # |
Task |
Agent |
Blockers |
Spec |
| 12.1 |
Guide proof -- seed 42, 30 turns. Screenshot: ley overlay (school colors), mana density, enchanted forest formation, corruption spread from Death segment |
guide-web |
10.1-10.6, 11.2 |
CLAUDE.md Phase Gate Protocol |
| 12.2 |
Godot proof scene -- engine/scenes/tests/ley_proof.tscn. 20x20, 30 turns, ley + corruption lenses. Auto-screenshot + SCP to plum |
godot-renderer |
4.1, 5.2, 6.2 |
CLAUDE.md Phase Gate Protocol |
Acceptance Criteria
This milestone is DONE when ALL of the following are true:
- Ley lines form as Voronoi edges between geological anchors, school-colored
- Mana density field populated per-tile -- nexus = 1.0, dead zones = 0.05
- Corruption spreads along Death-aligned ley segments, persists, overrides biome
- Enchanted forests form naturally in high-Nature ley zones (positive feedback loop)
- Anchor decay responds to M3 geological events (eruption -> new Chaos anchor)
- Magical events fire from M3 dispatch (groves, rifts, cataclysms)
- Magical biome transitions work (Aether -> calm seas, Chaos -> volcanic activity)
- Wellsprings form at river sources in high-Life/Nature zones
- GDScript and TypeScript produce identical ley + corruption + magic output on seed
42
- Guide visualizer shows all ley lenses (overlay, density, corruption, magical biomes)
- Proof screenshots reviewed and approved
Balance Notes (tune after implementation)
- Corruption spread rate: 0.02/turn on Death-ley, 0.005 off-ley. Target: ~20% map corrupted after 100 turns without Life counterbalance
- Enchanted forest probability: 0.3% per turn per eligible tile. Target: 5-10 clusters by turn 50
- Ley anchor density: 8-15 anchors on 40x24 map. Too few = dead zones. Too many = uniform coverage
- Mana density falloff: (0.7/0.4/0.2/0.05) may need tuning. Guide visualizer is the primary balance tool