Zone-Based Map Generation Implementation Plan¶
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Replace the Unity tester build's throwaway random scatter with the web prototype's zone-based map generation — tiered colored zones (rare/epic/legendary) with themed carving, leashed guards, and case chests — driven by designer-authored ScriptableObjects.
Architecture: A two-stage generate → populate pipeline. MapGenerator.Generate(MapDefinition) builds tiles + a MapData.Zones list (pure structure, no entities); MapPopulator.Populate(MapData, MapDefinition) spawns everything from that data. Three SOs (MapDefinition, ZoneTierDef, ThemeDef) hold all tunables. Ported faithfully from game.js:genMap (444–709), ZONE_TIERS (406), ZONE_THEMES (416), stageZoneSpecs/placeCenter.
Tech Stack: Unity 6000.3 / URP, C#, Zenject DI. Game code lives in Assembly-CSharp (no asmdef). Shapes (component mode) for any vector visuals.
Global Constraints¶
- No magic numbers. Every tunable lives on a SO (
MapDefinition/ZoneTierDef/ThemeDef) orGameConfig; reference it at the use site. (Project rule +unity-migration-workflowmemory.) - Faithful to the web. Port values/formulas verbatim from the cited
game.jslines; a final audit task diffs the result. The web is the source of truth — when porting verbatim internals (cover scatter, theme carves, placeCenter constants), copy the numbers fromgame.js, do not invent. - Verification = Unity MCP, not a test framework (this project has no NUnit/EditMode harness). Each task compiles via
mcp__unity-mcp__Unity_RunCommand(AssetDatabase.Refresh()), checksmcp__unity-mcp__Unity_GetConsoleLogs(logTypes "Error") is empty, then runs aRunCommandthat constructs/loads the new code and asserts viaresult.Log(...). - MCP RunCommand gotchas (see
mcp-runcommand-gotchasmemory):CommandScript : IRunCommand; noSystem.Reflection; fully-qualifyUnityEditor.Editor; can't edit scripts during play; a compile error anywhere keeps the last-good assembly loaded (so a new type reads as "namespace does not exist" → checkGetConsoleLogsfor the real error). Don't be in play mode when editing scripts. map.tile(px→world divisor) stays a globalGameConfigconstant; per-map size/density moves ontoMapDefinition.- Commit after each task. End commit messages with the
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>trailer. Branch:migrate-unity. - Namespaces: SOs in
ShadowRaiders.Config; runtime inShadowRaiders.World. - Px→world: world units = px /
GameConfig.map.tile(tile = 40). Web works in px; convert at the boundary.
File Structure¶
| File | Responsibility |
|---|---|
Assets/Scripts/Config/ZoneTierDef.cs (new) |
SO: one tier (rare/epic/legendary) → mob roster, guard count, rarity |
Assets/Scripts/Config/ThemeDef.cs (new) |
SO: one theme → name/emoji + a data-driven carve descriptor (CarveKind + params) |
Assets/Scripts/Config/MapDefinition.cs (new) |
SO: the per-map recipe — size, cover densities, zone list, theme pool, roamer/rival/portal counts, seed |
Assets/Scripts/World/MapGenerator.cs (modify) |
Generate(MapDefinition): tiles + cover + zone placement + theme carve; exposes MapData.Zones |
Assets/Scripts/World/MapPopulator.cs (new) |
Populate stage: zone guards (leashed) + case chests, gray roamers, rivals, portals, player spawn |
Assets/Scripts/World/EnemyController.cs (modify) |
Leash box + Home; patrol-inside / chase-out / return-home |
Assets/Scripts/Tester/RaidBootstrap.cs (modify) |
Slim orchestrator: pick MapDefinition → Generate → Populate |
Assets/Scripts/World/EnemySpawner.cs, RaiderSpawner.cs (delete) |
Absorbed into MapPopulator |
Assets/Scripts/Config/GameConfig.cs (modify) |
Remove fields moved to MapDefinition (size table, cover, roamDiv, botDiv/bots) |
| Authored assets | Assets/Config/Zones/*.asset (3 tiers), Assets/Config/Themes/*.asset (2), Assets/Config/Maps/*.asset (≥2) |
Dependency order: ZoneTierDef → ThemeDef → MapDefinition → MapGenerator → (Theme carve) → Leashing → MapPopulator → RaidBootstrap/cleanup → audit.
Task 1: ZoneTierDef ScriptableObject + tier assets¶
Files:
- Create: Assets/Scripts/Config/ZoneTierDef.cs
- Create (via MCP): Assets/Config/Zones/Zone_rare.asset, Zone_epic.asset, Zone_legendary.asset
Interfaces:
- Produces: ShadowRaiders.Config.ZoneTierDef : ScriptableObject with public fields
LootRarity rarity, string[] mobKeys, Vector2Int guardCount, bool allowMiniBoss.
- [ ] Step 1: Create the SO
using UnityEngine;
using ShadowRaiders.Loot; // LootRarity
namespace ShadowRaiders.Config
{
/// <summary>
/// One colored-zone tier (web ZONE_TIERS[tier], game.js:406). The tier IS an item rarity, so it drives
/// the zone's tint, its case-chest rarity, and its loot. Designer-authored: add a tier = add an asset.
/// </summary>
[CreateAssetMenu(fileName = "Zone_", menuName = "Shadow Raiders/Zone Tier Def")]
public class ZoneTierDef : ScriptableObject
{
[Tooltip("The item rarity this zone yields — also its colour (rare=blue/epic=purple/legendary=gold).")]
public LootRarity rarity = LootRarity.Rare;
[Tooltip("Guard roster (EnemyDef keys) spawned inside the zone, picked at random per guard.")]
public string[] mobKeys;
[Tooltip("Guard count [min,max] BEFORE map-area scaling (web ZONE_TIERS count).")]
public Vector2Int guardCount = new Vector2Int(3, 4);
[Tooltip("May host an 'Elder' mini-boss (a buffed guard) when the map definition requests one.")]
public bool allowMiniBoss = true;
}
}
- [ ] Step 2: Compile + check no errors
Run mcp__unity-mcp__Unity_RunCommand: AssetDatabase.Refresh(); then mcp__unity-mcp__Unity_GetConsoleLogs (logTypes "Error"). Expected: empty.
Note:
LootRaritymust haveCommon/Rare/Epic/Legendary. IfLegendaryis missing, add it toAssets/Scripts/Loot/LootRarity.csin this step and re-compile.
- [ ] Step 3: Author the 3 tier assets via MCP
RunCommand (web ZONE_TIERS, game.js:407-409):
using UnityEngine; using UnityEditor; using ShadowRaiders.Config; using ShadowRaiders.Loot;
internal class CommandScript : IRunCommand {
public void Execute(ExecutionResult r) {
System.IO.Directory.CreateDirectory("Assets/Config/Zones");
void Make(string name, LootRarity rar, string[] mobs, int lo, int hi) {
var z = ScriptableObject.CreateInstance<ZoneTierDef>();
z.rarity = rar; z.mobKeys = mobs; z.guardCount = new Vector2Int(lo, hi); z.allowMiniBoss = true;
AssetDatabase.CreateAsset(z, $"Assets/Config/Zones/Zone_{name}.asset");
}
Make("rare", LootRarity.Rare, new[]{"goblin","hound","spider","emberball"}, 3, 4);
Make("epic", LootRarity.Epic, new[]{"warden","skeleton","emberball","rocketeer"}, 3, 4);
Make("legendary", LootRarity.Legendary, new[]{"rocketeer","bombardier","sentinel","warden"}, 2, 3);
AssetDatabase.SaveAssets();
r.Log("created 3 ZoneTierDef assets");
}
}
- [ ] Step 4: Verify assets load with correct data
RunCommand: load Assets/Config/Zones/Zone_rare.asset as ZoneTierDef, result.Log its rarity, mobKeys.Length, guardCount. Expected: Rare, 4, (3,4).
- [ ] Step 5: Commit
git add unity/raiders/Assets/Scripts/Config/ZoneTierDef.cs unity/raiders/Assets/Config/Zones
git commit -m "Map gen: ZoneTierDef SO + rare/epic/legendary tier assets (web ZONE_TIERS)"
Task 2: ThemeDef ScriptableObject + 2 theme assets¶
Files:
- Create: Assets/Scripts/Config/ThemeDef.cs
- Create (via MCP): Assets/Config/Themes/Theme_graveyard.asset, Theme_swamp.asset
Interfaces:
- Produces: ShadowRaiders.Config.ThemeDef : ScriptableObject with string displayName, string emoji,
CarveKind kind, and carve params int gravestoneCount, float bushChance, int doorCount,
int wallSegments. Public enum ShadowRaiders.Config.CarveKind { Graveyard, Swamp, Church, Ruins, Village }.
- [ ] Step 1: Create the SO + enum
using UnityEngine;
namespace ShadowRaiders.Config
{
/// <summary>How a theme shapes its zone rect (web ZONE_THEMES carve, game.js:416). Data-driven so themes are
/// authorable; MapGenerator runs the matching carve. Graveyard + Swamp ship now; the rest are authored later.</summary>
public enum CarveKind { Graveyard, Swamp, Church, Ruins, Village }
/// <summary>One zone theme — its label + the carve descriptor that gives the zone its signature structure.</summary>
[CreateAssetMenu(fileName = "Theme_", menuName = "Shadow Raiders/Theme Def")]
public class ThemeDef : ScriptableObject
{
public string displayName = "Theme";
[Tooltip("Label glyph shown with the zone name.")] public string emoji = "";
public CarveKind kind = CarveKind.Graveyard;
[Header("Carve params (only the ones the kind uses apply)")]
[Tooltip("Graveyard: gravestone wall dots scattered in the zone (web 18).")] public int gravestoneCount = 18;
[Tooltip("Graveyard/Swamp: chance a floor tile becomes bush.")] [Range(0,1)] public float bushChance = 0.4f;
[Tooltip("Church: breach doors in the wall ring (web 3).")] public int doorCount = 3;
[Tooltip("Ruins/Village: rubble/hut wall segments (web ~10).")] public int wallSegments = 10;
}
}
-
[ ] Step 2: Compile + check no errors (as Task 1 Step 2).
-
[ ] Step 3: Author Graveyard + Swamp assets via MCP
RunCommand (web ZONE_THEMES Graveyard game.js:417, Witch's Swamp game.js:427):
using UnityEngine; using UnityEditor; using ShadowRaiders.Config;
internal class CommandScript : IRunCommand {
public void Execute(ExecutionResult r) {
System.IO.Directory.CreateDirectory("Assets/Config/Themes");
var g = ScriptableObject.CreateInstance<ThemeDef>();
g.displayName="Graveyard"; g.emoji="🪦"; g.kind=CarveKind.Graveyard; g.gravestoneCount=18; g.bushChance=0.4f;
AssetDatabase.CreateAsset(g, "Assets/Config/Themes/Theme_graveyard.asset");
var s = ScriptableObject.CreateInstance<ThemeDef>();
s.displayName="Witch's Swamp"; s.emoji="🌿"; s.kind=CarveKind.Swamp; s.bushChance=0.5f;
AssetDatabase.CreateAsset(s, "Assets/Config/Themes/Theme_swamp.asset");
AssetDatabase.SaveAssets(); r.Log("created 2 ThemeDef assets");
}
}
-
[ ] Step 4: Verify — load both,
result.LogdisplayName+kind. Expected:Graveyard/Graveyard,Witch's Swamp/Swamp. -
[ ] Step 5: Commit
git add unity/raiders/Assets/Scripts/Config/ThemeDef.cs unity/raiders/Assets/Config/Themes
git commit -m "Map gen: ThemeDef SO + CarveKind + Graveyard/Swamp assets (web ZONE_THEMES)"
Task 3: MapDefinition ScriptableObject + map assets¶
Files:
- Create: Assets/Scripts/Config/MapDefinition.cs
- Create (via MCP): Assets/Config/Maps/Map_small.asset, Map_large.asset
Interfaces:
- Produces: ShadowRaiders.Config.MapDefinition : ScriptableObject with fields:
int width, height; float wallClusters, bushLines, bushClusters; ZoneEntry[] zones
(serializable { ZoneTierDef tier; bool miniBoss; }); ThemeDef[] themePool;
string[] roamerKeys; int roamDiv; int rivalDiv, rivalCap; int portalCount; int seed.
- [ ] Step 1: Create the SO
using UnityEngine;
namespace ShadowRaiders.Config
{
/// <summary>The per-map recipe (the "stagedef") — procedural params for one map. Replaces RaidBootstrap's
/// inline values + the old prog.stageSizes table. Designer-authored: add a map = add an asset.</summary>
[CreateAssetMenu(fileName = "Map_", menuName = "Shadow Raiders/Map Definition")]
public class MapDefinition : ScriptableObject
{
[System.Serializable] public class ZoneEntry { public ZoneTierDef tier; public bool miniBoss; }
[Header("Grid")]
public int width = 40;
public int height = 40;
[Header("Connective cover (× map area; web map.cover*)")]
public float wallClusters = 0.0138f;
public float bushLines = 0.0055f;
public float bushClusters = 0.0075f;
[Header("Zones")]
public ZoneEntry[] zones;
[Tooltip("Distinct themes assigned per zone (shuffled). Needs >= zones.Length to avoid repeats.")]
public ThemeDef[] themePool;
[Header("Gray roamers")]
public string[] roamerKeys = { "goblin", "spider" };
[Tooltip("1 gray roamer per N tiles (web prog.roamDiv).")] public int roamDiv = 450;
[Header("Rivals + exits")]
[Tooltip("1 rival per N tiles (web prog.botDiv).")] public int rivalDiv = 1500;
[Tooltip("Rival upper clamp (web map.bots).")] public int rivalCap = 5;
public int portalCount = 2;
[Tooltip("-1 = fresh random seed per run.")] public int seed = -1;
}
}
-
[ ] Step 2: Compile + check no errors.
-
[ ] Step 3: Author Map_small + Map_large via MCP
RunCommand: load the Zone/Theme assets, build a small map (36×36, 1 rare zone, mini-boss) and a large (70×70, rare+epic+epic). Example for small:
var def = ScriptableObject.CreateInstance<MapDefinition>();
def.width=36; def.height=36; def.wallClusters=0.0138f; def.bushLines=0.0055f; def.bushClusters=0.0075f;
var rare = AssetDatabase.LoadAssetAtPath<ZoneTierDef>("Assets/Config/Zones/Zone_rare.asset");
def.zones = new[]{ new MapDefinition.ZoneEntry{ tier=rare, miniBoss=true } };
def.themePool = new[]{ AssetDatabase.LoadAssetAtPath<ThemeDef>("Assets/Config/Themes/Theme_graveyard.asset"),
AssetDatabase.LoadAssetAtPath<ThemeDef>("Assets/Config/Themes/Theme_swamp.asset") };
def.roamerKeys = new[]{"goblin","spider"}; def.roamDiv=450; def.rivalDiv=1500; def.rivalCap=5; def.portalCount=2; def.seed=-1;
AssetDatabase.CreateAsset(def, "Assets/Config/Maps/Map_small.asset");
-
[ ] Step 4: Verify — load
Map_small,result.Logwidth,zones.Length,zones[0].tier.rarity,themePool.Length. Expected:36,1,Rare,2. -
[ ] Step 5: Commit
git add unity/raiders/Assets/Scripts/Config/MapDefinition.cs unity/raiders/Assets/Config/Maps
git commit -m "Map gen: MapDefinition SO + small/large map assets (per-map recipe)"
Task 4: MapData.Zones + MapGenerator.Generate(MapDefinition)¶
Files:
- Modify: Assets/Scripts/World/MapGenerator.cs
Interfaces:
- Produces: MapData.Zones (public IReadOnlyList<Zone>); MapGenerator.Generate(MapDefinition def).
public class Zone { public int Tx0,Ty0,Tx1,Ty1; public Vector3 CenterWorld; public ZoneTierDef Tier; public ThemeDef Theme; public bool MiniBoss; }.
Consumes: MapDefinition (Task 3).
- [ ] Step 1: Add the
Zonetype +Zoneslist toMapData
In MapGenerator.cs, add to MapData a readonly List<Zone> ZonesMutable exposed as public IReadOnlyList<Zone> Zones. Define Zone (fields above) in the ShadowRaiders.World namespace.
- [ ] Step 2: Add
Generate(MapDefinition)overload
Add a public void Generate(MapDefinition def) that sets mapW=def.width; mapH=def.height; wallClusterDensity=def.wallClusters; bushChance=def.bushLines+def.bushClusters; then runs BuildGrid() (existing cover scatter, faithful to game.js:494-496 — keep current behavior) and the zone-placement below, then BuildChunks. Keep the existing param-less Generate() for back-compat or route it through a default.
Zone placement (port game.js:507-551, no entities yet):
// max-spread placeCenter (web game.js:507): over 240 candidates pick the one whose nearest placed
// center is farthest. Footprint scales with map, capped at the classic 13x11 (web hw/hh clamps).
// For each MapDefinition.ZoneEntry: PlaceCenter -> ClearArea -> assign a distinct theme -> Carve (Task 5
// stubs to ClearArea for now) -> re-open a 3x3 core pocket -> record a Zone.
PlaceCenter(int hw,int hh, List<Vector2> placed), ClearArea, WallRing mirroring game.js:468-479, 507-515. Theme assignment: shuffle def.themePool, index per zone (web themePool/themeIdx).
-
[ ] Step 3: Compile + check no errors.
-
[ ] Step 4: Verify zone placement via MCP
RunCommand: find the scene MapGenerator (or new GameObject), set tileSet, call Generate(Map_large). result.Log Map.Zones.Count (expect 3), each zone's box (assert Tx0>0 && Tx1<width-1 in bounds), and min pairwise center distance (assert > a few tiles → max-spread working). Assert each zone's Tier/Theme non-null.
- [ ] Step 5: Commit
git add unity/raiders/Assets/Scripts/World/MapGenerator.cs
git commit -m "Map gen: MapGenerator.Generate(MapDefinition) — tiles + max-spread zone placement (web genMap)"
Task 5: Theme carving (Graveyard + Swamp)¶
Files:
- Modify: Assets/Scripts/World/MapGenerator.cs
Interfaces:
- Consumes: Zone.Theme (ThemeDef), the tile set helpers (Set/Get/InBounds).
- Produces: void Carve(Zone z, ThemeDef theme) called during zone placement (Task 4 Step 2 hook).
- [ ] Step 1: Implement
Carvefor Graveyard + Swamp
Port game.js:417-419 (Graveyard) + 427-429 (Swamp):
// Graveyard (web 417): scatter `gravestoneCount` wall dots (40% get a 2nd wall below), then ~14 bush dots.
// Swamp (web 427): fill the zone rect with bush at `bushChance`, then re-clear a 5x5 core.
// Church/Ruins/Village: switch-default no-op for now (assets authored in the fast-follow).
System.Random seeded from the map seed for determinism. Call Carve after ClearArea, before the core-pocket re-open.
-
[ ] Step 2: Compile + check no errors.
-
[ ] Step 3: Verify carve output via MCP
RunCommand: Generate a map whose single zone uses Graveyard; count WALL tiles inside the zone box (assert > 0, roughly gravestoneCount-ish). Then a Swamp zone; count BUSH tiles inside (assert > 0) and assert the 5×5 core is FLOOR.
- [ ] Step 4: Commit
git add unity/raiders/Assets/Scripts/World/MapGenerator.cs
git commit -m "Map gen: Graveyard + Swamp theme carves (web ZONE_THEMES)"
Task 6: Leashing on EnemyController¶
Files:
- Modify: Assets/Scripts/World/EnemyController.cs
Interfaces:
- Produces: public void SetLeash(Rect worldBox, Vector3 home) (null/empty box = free roam);
patrol clamps wander targets to the box; lost-trail returns to home/inside the box.
- Consumes: nothing new (uses existing patrol/wander + _agent).
- [ ] Step 1: Add leash state + setter
Add Rect _leash; bool _hasLeash; Vector3 _home; and public void SetLeash(Rect box, Vector3 home){ _leash=box; _home=home; _hasLeash = box.width>0; }. World-space Rect on XZ (x→x, y→z).
- [ ] Step 2: Clamp patrol wander + return-home
In the patrol/wander pick (where it chooses a wander destination) clamp the target inside _leash when _hasLeash (web updateMob leash). In the lost-trail/give-up branch (after a search times out), if _hasLeash and the agent is outside the box with no target, set destination toward _home (web homeX/homeY). Chasing is unchanged (may leave the box).
-
[ ] Step 3: Compile + check no errors.
-
[ ] Step 4: Verify in play
RunCommand (enter play): spawn one enemy, SetLeash(new Rect(...), home) a small box away from the player, wait (next command) and assert its position stays within the box (+ a small leashPad) over time. (Use the spawn-safe trick — keep the player from dying isn't needed; just read the leashed mob's position across two commands.)
- [ ] Step 5: Commit
git add unity/raiders/Assets/Scripts/World/EnemyController.cs
git commit -m "Map gen: mob leashing (patrol-inside / chase-out / return-home, web en.leash)"
Task 7: MapPopulator — spawn everything from map data¶
Files:
- Create: Assets/Scripts/World/MapPopulator.cs
Interfaces:
- Produces: MapPopulator.Populate(MapData map, MapDefinition def, Transform player). Spawns zone guards
(leashed) + case chests (rarity = zone tier), gray roamers (loose leash), rivals, portals, player spawn.
- Consumes: MapData.Zones (Task 4), EnemyController.SetLeash (Task 6), the Enemy/Raider/Chest/Portal
prefabs + EnemyDatabase (Zenject), GameConfig (tile, spawnSafe, leashPad, grayLeash).
- [ ] Step 1: Create
MapPopulator
A Zenject-injected MonoBehaviour (or plain class taking DiContainer). Port the placement from game.js:530-544 (zone guards + case chest), 535-536 (rare cluster optional), gray roamers (game.js roamer loop), plus the current RaiderSpawner rival math and RaidBootstrap portal/player placement. For each zone: instantiate guardCount area-scaled guards (web areaScale game.js:540) inside the footprint via _container.InstantiatePrefab (mirror current EnemySpawner), assign def + gaze, and SetLeash(zoneBoxWorld + leashPad, zoneCenter). Place a Chest prefab at the zone center with rarity = zone.Tier.rarity. Gray roamers: area/roamDiv mobs from def.roamerKeys, each SetLeash a grayLeash-sized box around its spawn. Rivals + portals + player spawn: lift from the current RaiderSpawner/RaidBootstrap (spawn-safe).
-
[ ] Step 2: Compile + check no errors.
-
[ ] Step 3: Verify counts + leashes via MCP (play)
RunCommand (play): after Populate, result.Log total Chest count == zones with a case (1 per zone), each chest's rarity matches its zone tier; total enemies ≈ Σ guard counts + roamers; sample a zone guard and assert its leash keeps it near its zone (read position across two commands). Assert portals == def.portalCount, player placed on floor.
- [ ] Step 4: Commit
git add unity/raiders/Assets/Scripts/World/MapPopulator.cs
git commit -m "Map gen: MapPopulator — leashed zone guards + case chests, roamers, rivals, portals (web genMap populate)"
Task 8: Wire RaidBootstrap, remove old spawners, migrate config¶
Files:
- Modify: Assets/Scripts/Tester/RaidBootstrap.cs
- Delete: Assets/Scripts/World/EnemySpawner.cs, Assets/Scripts/World/RaiderSpawner.cs (+ .meta)
- Modify: Assets/Scripts/Config/GameConfig.cs (remove moved fields)
- Modify (via MCP scene edit): Assets/Scenes/Raid_Slice.unity
Interfaces:
- Consumes: MapGenerator.Generate(MapDefinition), MapPopulator.Populate, the stage→MapDefinition map list.
- [ ] Step 1: Slim
RaidBootstrap
Replace its inline-param generation with: a serialized MapDefinition[] stageMaps; pick stageMaps[clamp(TesterRunState.Stage, 0, len-1)]; map.Generate(mapDef); populator.Populate(map.Map, mapDef, player). Drop the player/chest/portal/spawner wiring that MapPopulator now owns. Keep the round-timer-death + restart flow (unchanged, lives in RaidController/TesterHud).
- [ ] Step 2: Remove the old spawners + migrate
GameConfig
Delete EnemySpawner.cs/RaiderSpawner.cs (+ metas). In GameConfig.cs remove the now-per-map fields: prog.stageSizesB1/B2/B3, map.coverWallClusters/coverBushLines/coverBushClusters, map.roamDiv, prog.botDiv, map.bots. Keep map.tile/roundTime/spawnSafe, prog.leashPad/grayLeash/zoneSeparation, zones.fillAlpha, and the biome-ladder fields. Update GameConfigEditor section arrays if any removed field was referenced.
-
[ ] Step 3: Compile + check no errors (watch for references to the deleted fields/spawners — fix each).
-
[ ] Step 4: Scene edit via MCP
RunCommand (edit mode, not play): on Raid_Slice, remove the EnemySpawner/RaiderSpawner GameObjects; add a MapPopulator (and assign prefabs/refs); assign RaidBootstrap.stageMaps = [Map_small, Map_large]; set MapGenerator.generateOnStart=false; EditorSceneManager.MarkSceneDirty + SaveScene. (Mirror the wiring approach from the original tester-build scene setup.)
- [ ] Step 5: Verify a full raid in play
RunCommand (enter play): assert map.Map.Zones.Count matches the stage's def, chests/enemies/rivals/portals spawned, player on floor spawn-safe, no console errors. Visually confirm (Camera capture or scene-view) zones render tinted/themed.
- [ ] Step 6: Commit
git add -A unity/raiders/Assets
git commit -m "Map gen: wire RaidBootstrap to MapDefinition pipeline; remove tester spawners; migrate config to MapDefinition"
Task 9: Faithfulness audit vs genMap + fixes¶
Files:
- Modify: whichever files have deltas (likely MapGenerator.cs, MapPopulator.cs).
- [ ] Step 1: Diff against the web (migration workflow step 3). Compare, line-by-line, citing
game.js: PlaceCenter(240 candidates, max-min) + footprint clamps (hw=clamp(W*0.10,4,6),hh=clamp(H*0.085,3,5)) — game.js:507-522.- Cover scatter densities + shapes — game.js:494-496.
areaScaleguard scalingclamp(((hw*2+1)*(hh*2+1))/(13*11),0.4,1)— game.js:540.ZONE_TIERSrosters/counts; tier→rarity→tint — game.js:406-413.- Graveyard/Swamp carves (counts/chances) — game.js:417-419, 427-429.
- Leashing patrol-inside/chase-out/return-home — game.js
updateMobleash branch + 528-530. -
Gray-roamer loose leash (
prog.grayLeash) + case-chest tier. -
[ ] Step 2: Fix any deltas inline. For deltas blocked on deferred systems (progression danger scaling, the boss/vault), note them in the spec's "deferred" list rather than fixing.
-
[ ] Step 3: Verify — re-run the Task 8 play check; confirm a couple of regenerations produce web-plausible layouts (zone count/spread/theme structure). Optional: a Camera/scene-view capture for eyeballing.
-
[ ] Step 4: Commit
git add -A unity/raiders/Assets
git commit -m "Map gen: faithfulness audit vs web genMap — fix placement/scaling/carve deltas"
Self-Review¶
Spec coverage: ZoneTierDef (T1) · ThemeDef + 2 carves (T2,T5) · MapDefinition + config migration (T3,T8) · generate→populate option A (T4,T7) · max-spread placement + footprint scaling (T4) · leashing (T6) · case chests + tier rarity + guards + roamers + rivals + portals + player (T7) · spawner removal (T8) · faithfulness audit (T9) · verification via MCP throughout. Deferred items (vault, biome ladder, 3 themes, MapLayout) are explicitly out of scope in the spec and not tasked. ✓
Placeholder scan: Carve/placement internals cite exact game.js lines to port verbatim (the web is the canonical source — transcribing it fully into the plan risks divergence); SO schemas + interfaces + verification commands are concrete. No TBD/TODO. ✓
Type consistency: ZoneTierDef.rarity/mobKeys/guardCount/allowMiniBoss, ThemeDef.kind/CarveKind, MapDefinition.ZoneEntry{tier,miniBoss}, MapData.Zones/Zone, MapGenerator.Generate(MapDefinition)/Carve, EnemyController.SetLeash(Rect,Vector3), MapPopulator.Populate(MapData,MapDefinition,Transform) — used consistently across tasks. ✓