Web → Unity Migration Plan¶
Porting the Shadow/Thornwood Raiders web prototype (game.js + config.js, single-file
HTML5-canvas) to a maintainable Unity 6 / URP project. The web build is the design source of
truth; this plan covers every validated mechanic, the target architecture, the toolset, and the
order of work.
0. Status snapshot — 2026-06-29¶
The in-raid core loop is ported and faithful (verified vs game.js): zone/theme/vault map
generation + spawning + portals + loot scatter; creature & rival-raider AI; hazards (fire/web/rocket/
bomb) + lunge; the vault (key/gate/Keykeeper); the keyRacer rival; the stage boss + Descent
Portal; fog/vision/LOS/noise/stealth + backstab silent-kill; loot (chests, ground loot, spill,
magnet, rarity rolls); status effects + channels; a config-tunable floater/banner feedback
system; and the presentation layer — a pooled prefab-view system (ViewPool/—Views—) for
floaters, name/loot labels, health bars and channel halos; a unified text-config (size/colour/bold +
outline/shadow) on a baked Lato SDF font; minimap + fit-all tactical map; and off-screen threat
arrows (Shapes triangles, gated to mobs/rivals actually hunting the player). Config is one
GameConfig SO + per-thing defs (no magic numbers).
Completed since the 2026-06-27 snapshot¶
Minimap + tactical map (fit-all) + zone tint; pooled prefab-view system (floaters/labels/bars/halos,
rent-on-vision); unified text config + Lato SDF; off-screen threat arrows (triangles, target-gated);
salve→usable-potion + "+HP" heal floater + heal bar; 3-way damage-source threading (Health.LastAttacker)
fixing mob aggro; tileset group tints. Then (2026-06-29 session): #1 Gear stats, #2 Meta/save/hub,
4 Biome ladder — see below.¶
DONE (verified vs game.js)¶
- ✅ Gear stats & loadout —
GearStats/GearItem/Loadout/GearTables/Progression: computeStats, itemStats (per-level growth), sets (hunter/stalker/bulwark), perks (backstab/bush/mob-dmg/heal-on-kill), power. Player Power is gear-driven (671→1476 starter);LootRollerrolls gear ongear.rar*. The in-run inventory (Loot Station / field-equip / bag / Safe Pocket / corpse looting) is PARKED — see Remaining. - ✅ Meta / save / hub — JSON
SaveData(stash/loadout/biome ladder/currencies/records),endRunbanking (gear→stash, valuable→gold, auto-equip-empty, run value), and a throwaway hub/loadout (equip/unequip/upgrade/scrap)/result UI (MetaHud) imitating the web lobby/inventory/result. - ✅ Full multi-phase extraction (commits f25f1fb…6344c34) — call→incoming→open→board→close machine, ring-guards, rival multi-portal contention, Shapes escape/descend visuals. PARKED by default.
- ✅ Biome ladder — config-driven
prog.*: per-(biome,floor) map size/zone-specs/vault gate,bDanger lootMulscaling, rarity zone tint (minimap), biome unlock + floor/biome select.MapDefinitionreduced to a per-biome recipe; de-duplicated config↔asset. Endless-ladder extension (2026-07-10, Unity-only — web parked you on the final boss floor):save.biomeis a virtual rung counting past the authoredBiomeSet(ever-increasing analytics numbers); content clamps to the last authored biome while danger/spawn ramps keep climbing (MetaLoopController.AdvanceLadder,Progression.Danger(…, extraRungs),MapPopulator.Populatetaking the virtual index). Authoring a real next biome later picks the same save index up automatically — no migration. Details inPROJECT_CONTEXT.md→ "Unity port — current state".- ✅ In-raid polish (cc026c9…0df86a0) — HP regen, projectile velocity inheritance, backstab crit + strict-arc auto-stab + range-gated 🗡 tell, world-event escalation + Wildmagic storm, enemy power-scaling, ⚡ threat nametags, PvP power-gap. Gear perks now wired (Phase A). Balance values verified web-exact.
REMAINING to migrate¶
- Real HUD / screens (MVC) — the in-raid HUD + hub/loadout/result are throwaway (
TesterHud+MetaHud+PlayerVitalsView). Need real MVC: vitals/ammo/power/stamina HUD, inventory/loadout + (parked) loot-sheet screens, result screen — plus the pause menu (Resume/Retry/Tuning/Lobby), how-to/help screen, and the in-game Developer Tuning panel (none exist in Unity; the Inspector +GameConfigEditorcover authoring). Tactical map remaining: player-centred zoom, on-map noise rings, legend panel. - Map authoring tools —
MapDefinitioncustom Editor Window (params + live preview); theMapLayoutfixed-layout asset + loader (prereq for FTUE). Gen works in code today. - FTUE — the scripted tutorial stages (web 1-7:
TUTORIALtable,buildTutorialMap, per-stage grants), on theMapLayoutloader. The save carries atutorialstub; no curriculum.
PARKED (intentionally off — matches web toggles; code absent in Unity)¶
- In-run inventory (
loot.inRunInventory=false) — Loot Station sheet, field-equip (equipNow), run-bag UI, Safe Pocket, corpse looting (loot.corpseMinItems). Live web = auto-collect → auto-equip (ported). - Shield / cells (
player.shieldEnabled=false) — overshield pool + regen, shield cells (drop/channel/ button).Healthhas no shield pool. - Weak-point armor (
combat.weakpointArmor=false) — directional front/rear armor multipliers. - Valuables (
loot.valuableChance=0) — sellable trinkets (loot is consumables/equipment/blueprints/coins).
DEFERRED nuances — ✅ DONE (commit 3217a56)¶
- ✅
stealth.silenceWindow— a backstab on an UNAWARE victim (alert < 0.5) stays quiet for the config window (was a hardcoded 0.05s; nowEnemyController.MarkSilentKill). - ✅
stealth.bushSightCutoff— the player can't see an IDLE bushed enemy past 70px (EnemyController.Revealed). - ✅
combat.bandHysteresis+combat.stanceCommit— the PLAYER's melee↔ranged stance arbiter is now sticky (enter/exit hysteresis + commit timer, webupdatePlayerAttacks); was a plainmeleeBandsingle threshold.
Camera / screen shake — ✅ DONE (commit 3217a56)¶
The web G.shake system (was entirely missing from CameraRig): RaidState.Shake + AddShake; CameraRig
decays it and offsets the ground plane. Wired at every web trigger — hit/crit/player-hit, chest (+legendary),
explosion, boss/guard death, gate open, portal open (ritual beat), storm. Amounts in a shake config group
(web magic numbers). Verified end-to-end (hit→0.2, decay 1.0→0).
Assets / polish (remaining)¶
TMP emoji font (🗡/🔑/⚡/👑 render as boxes — pending a fallback font asset); real entity sprites
(emoji/placeholder art today); in-world 3D zone floor tint (minimap tint done); chest open-FX polish
(Chest.cs TODO).
Maps/minimap design: docs/superpowers/specs/2026-06-27-minimap-tactical-map-design.md;
pooled views: docs/superpowers/specs/2026-06-29-pooled-prefab-views-design.md.
(The phase plan + feature table below predate this snapshot — treat §0 as the live status.)
1. Principles¶
- Faithful feel, native tech. Don't port the immediate-mode canvas renderer 1:1 — re-implement visuals with Unity-native tech (sprites, chunked meshes, Shapes). Port the math, formulas, and tuning values verbatim so the game feels identical.
- Data-driven. Every magic number lives in a config asset (ScriptableObject), mirroring
config.js/CFG.*. Keep the key names so balancing carries over. - Testable domain. Pure-C# simulation (pathing, detection, noise, combat math, loot rolls) lives in engine-agnostic classes with EditMode unit tests. The web has none; Unity should.
- DI over globals. Replace the web's global
Gstate with an injectedRaidStateand services (Zenject). No singletons-by-convenience. - MVC UI. Game logic never touches UI widgets directly; controllers mediate model ↔ view.
- Mobile-first. Pooling, batching, the world-space fog + Shapes component-mode work already done.
2. Toolset¶
| Concern | Choice | Notes |
|---|---|---|
| DI | Zenject (Extenject) | ProjectContext (config, save, signals) + per-scene SceneContext (raid systems, factories). |
| UI | UI Toolkit + uGUI, mixed + MVC | Use UI Toolkit where it maps cleanly (data/list-heavy panels, the eventual tuning panel — UXML≈HTML, USS≈CSS) and uGUI where it's simpler (world-space bars, quick HUD). Pick per screen; both drive through MVC. |
| Events | Zenject SignalBus | Decouples systems↔UI (HpChanged, NoiseEmitted, PlayerSpotted, LootCollected, RaidEnded, ExtractProgress…). |
| Config | One aggregate GameConfig ScriptableObject |
A single SO holds everything tunable, as [Serializable] nested groups mirroring window.CONFIG (move, combat, stealth, noise, loot, …). Injected whole; systems read config.move.* etc. A runtime tuning panel is deferred (the Inspector covers it for now); when added it just walks this one SO like the web walks CONFIG. |
| Vector FX | Shapes (component mode) | Already integrated — route, click ripple, noise rings, UI rings. (Immediate mode is dead under URP Render Graph here.) |
| Rendering | URP | World-space fog-of-war (done), chunked tile mesh (done), sprite billboards for entities. |
| Input | Unity Input System | Already in use (tap / double-tap; add swipe-dodge). |
| Save | JSON @ persistentDataPath via SaveService |
Replaces localStorage (thornwood_save_v1). |
| Pooling | custom/Zenject MemoryPool |
Projectiles, floaters, effects, noise rings, enemies. |
| Tests | NUnit EditMode | Domain systems validated against values pulled from game.js. |
3. Target architecture¶
Assets/Scripts/
Config/ ScriptableObjects mirroring CFG groups (injected, designer-tunable)
Domain/ pure C#, engine-agnostic, unit-tested
World/ runtime entities + MonoBehaviours (the live raid)
Presentation/ visuals only (read state, never mutate gameplay)
UI/ MVC screens (UXML/USS + View/Controller, observable Models)
Infrastructure/ DI installers, state machine, save, signals, factories, pooling
- Config/ — one aggregate
GameConfigScriptableObject holding every tunable as[Serializable]nested groups that mirrorwindow.CONFIG(move,stamina,combat,stealth,noise,loot,gear,extraction,map,progression,ui, plusenemies/weaponstables). Injected whole; systems readconfig.move.*. Parked toggles live here too (default off).TileSetand the newMapDefinition/MapLayout(see §8) are separate art/level assets, referenced from it or the scene. - Domain/ —
Pathfinder✅,Los✅,DetectionModel(gradual alert),NoiseModel(propagation + recruit selection),CombatResolver(damage, armor/weakpoints, crit/backstab),LootRoller(type odds + rarity ladder),ExtractionStateMachine,WorldEventSchedule,StaminaModel. NoUnityEnginedeps where avoidable → unit-testable. - World/ —
Player,Enemy(Mob + Raider behaviours),Chest,Gate,Portal,Projectile; plus the already-builtMapGenerator✅,FogOfWar/VisionField✅,GazeCone✅,NoisePing✅,PathAgent✅,CameraRig✅. Entities query injected Domain services; theRaidState(entity lists, player ref, run timer, alarm level) replaces globalG. - Presentation/ —
EntityView(billboard sprite + above-head bars),FloaterSystem,EffectSystem(burst/dash/spark/shake),ThreatIndicators,TargetArrow,Minimap,TacticalMap;PathVisualizer✅,ClickRipple✅. Reads state, emits nothing gameplay. - UI/ — per screen
Model(observable) /View(UXML binding) /Controller(subscribes to SignalBus + model, drives view):Hud,Inventory/LootStation,Loadout,Result,Menu,TuningPanel,PauseMenu. - Infrastructure/ —
GameStateMachine(Boot → Menu → Raid → Result),SaveService,ProjectInstaller+RaidInstaller,SignalBussignal definitions,EnemyFactory/ProjectileFactory(Zenject factories),ObjectPool.
Zenject wiring
- ProjectInstaller: bind all config SOs, SaveService, SignalBus + signals, scene loader.
- RaidInstaller (SceneContext): bind RaidState, all Domain systems, factories, presentation
systems; install signals consumed in-raid.
- Replace G.player/enemies/effects/... with RaidState injected into systems and entities.
4. Feature inventory (web → status → target)¶
Status: ✅ done · 🟡 partial · ⬜ todo. Section refs are game.js.
| Web system | Status | Target module |
|---|---|---|
| Pathfinding (A* 8-dir + string pull) | ✅ | Domain/Pathfinder |
| LOS raycast (DDA) | ✅ | Domain/Los |
| Map generation — zone-based (colored zones + gray connective, POIs, doors) | 🟡 basic random | World/MapGenerator + MapDefinition SO authored via custom Editor Window (§8) |
| Fixed pre-made layouts (tutorial stages, verbatim) | ⬜ | MapLayout asset + loader (§8) — prereq for FTUE |
| Tile art (variants, walls, bush cover) | ✅ | World/TileSet |
| Fog of war + player vision cone | ✅ | World/FogOfWar + VisionField |
| Movement: walk / sprint (double-tap) | ✅ | World/PathAgent + ClickToMove |
| Movement: dodge roll (swipe) + i-frames | ✅ | World/PlayerController + StaminaModel |
| Stamina (drain/regen, gating) | ✅ | Domain/StaminaModel |
| Camera: iso follow, dynamic zoom, sprint pull-back, look-ahead | ✅ | World/CameraRig |
| Enemy vision cones (gaze) — AI use | 🟡 viz only | Domain/DetectionModel + World/Enemy (GazeCone ✅ is viz) |
Detection: gradual alert (suspicious→spotted), p.spotted, bush slow-fill |
⬜ | Domain/DetectionModel |
Noise: makeNoise, sonar rings, hearing range, recruit-nearest |
🟡 rings ✅ | Domain/NoiseModel (NoisePing ✅ is viz) |
| Stealth: bush cover mult, backstab crit, blind-spot tell, stealth-chase sprint, silence window | ⬜ | Domain/Stealth + World/Player/Enemy |
| Ranged combat: archetypes (assault/smg/shotgun/sniper), projectiles, reload, auto-fire, pellets/spread | ⬜ | Domain/CombatResolver + World/Projectile + WeaponTable |
| Melee: lunge (windup→dash→recovery), auto-melee range handoff, backstab crit, hitstop | ⬜ | World/Player/Enemy + CombatConfig |
| Armor / weakpoints (front armor, rear flank) | ⏸ parked | deferred — see §7 |
| Bombs / explosions / hazards | ⬜ | World/Projectile + EffectSystem |
| Enemy AI — PvE creatures: smart patrol, pack broadcast, surround, flee, lastSeen search | ⬜ | World/Enemy (Mob) + Domain |
| Enemy AI — rival raiders: loot, fight/fear, self-extract, carry real haul | ⬜ | World/Enemy (Raider) |
| Loot types: gear / blueprint / coin / consumable (valuables parked) | ⬜ | Domain/LootRoller + LootConfig |
| Chests: crack channel, rarity, tables, coin/consumable extras | ⬜ | World/Chest + Domain/LootRoller |
| Inventory triage: Loot Station, field-equip, safe-pocket, dump, bag cap, corpse looting | ⏸ parked | deferred — see §7 (active model = auto-collect + auto-equip) |
| Vault gate: proximity/tap force-open channel, loud open | ⬜ | World/Gate |
| Extraction: multi-phase Arc-style portals, safe vs progression exits, guard | ⬜ | Domain/ExtractionStateMachine + World/Portal |
| World events / escalation (run timer, wildmagic storm) | ⬜ | Domain/WorldEventSchedule |
| Render: iso sprites, above-head bars, threat-edge arrows, target arrow, floaters, effects | 🟡 map only | Presentation/* |
| Route line + reticle, click ripple | ✅ | Presentation/PathVisualizer + ClickRipple |
| Minimap + full tactical map | ⬜ | Presentation/Minimap + TacticalMap |
| HUD / screens (vitals, ammo, result, menu) | ⬜ | UI/* (MVC) |
| Meta/save: gold, blueprints, stash, loadout, upgrades, power, bestHaul | ⬜ | Infrastructure/SaveService + UI/Loadout |
| FTUE: 6 scripted tutorial stages + coach driver + guided lobby spotlight | ⬜ | data-driven StageDefs + TutorialController |
| Dev tuning panel (auto-built from CONFIG, live + persisted) | ⬜ (inspector covers it short-term) | UI/TuningPanel |
| Input: tap / double-tap / swipe | 🟡 tap+double ✅ | World/InputService |
| Frame cap (60) | ✅ | AppBootstrap |
5. Implementation order¶
Phase 0 — Foundation (architecture spine). ✅ Done. Install Zenject; create ProjectInstaller +
RaidInstaller; port config.js groups to ScriptableObjects (verbatim values/names); build
GameStateMachine (Boot→Menu→Raid→Result) + SaveService; introduce RaidState and refactor the
existing World scripts to take injected config instead of loose public fields where it makes
sense. Stand up the EditMode test project. Deliverable: empty raid scene boots through DI with
config-driven systems.
Phase 1 — Vertical slice (a playable raid). ✅ Core done (Raid_Slice scene). Pure-C# Domain
(StaminaModel/DetectionModel/NoiseModel); one Mob that detects → chases → melees; player
auto-fire + projectile + damage/death; one Chest (auto-collect); one extraction Portal → save bank;
death → DOWNED. Verified end-to-end in play. Still owed before Phase 2 polish: dodge-roll + swipe
input, camera zoom/look-ahead, projectile pooling, an entity sprite/EntityView pass (placeholder
primitives for now). Deliverable met: move, fight/sneak past one enemy, loot a chest, extract.
Phase 2 — Stealth core (the pillar). 🟡 Core done. ✅ DetectionModel (gradual alert,
suspicious/spotted, bush slow-fill, committed); enemy vision cones driving AI (correct
visA/2 half-angle, alert>=1 sees 360°); NoiseSystem/NoiseModel (propagation, hearing range,
recruit-nearest, sonar rings); bush cover detect-mult (model) + visual occlusion (map); enemy
search/investigate (POI + give-up); IsInBlindSpot helper; ✅ silent backstab crit (player
auto-backstab from a blind spot, CombatResolver). Owed (Phase 3 melee / Phase 7 UI): 🗡
blind-spot tell + stealth-chase sprint + silence-window mute. Mirrors the web's FTUE order.
Phase 3 — Combat depth. 🟡 In progress: CombatResolver ✅; unified player auto-melee
(frontal + blind-spot silent backstab) ✅; damage→aggro ✅; weapon archetypes + pellets/spread +
stance arbiter + magazine/reload ✅; symmetric stealth-fire (mutual perception) ✅;
heal potion ✅. Todo: melee lunge windup/dash juice + hitstop; bombs/explosions/hazards.
No front-armor/weak-point model (parked, §7).
Phase 4 — Enemy AI breadth. 🟡 In progress: EnemySpawner (Zenject-instantiated from
EnemyDatabase, per-instance cones) ✅; damage→aggro ✅; enemy ranged attacks (mutual-gated,
telegraphed direct fire + standoff) ✅; pack broadcast (a chaser rouses nearby pack-mates) ✅;
smart patrol (calm mobs wander at half pace, web patrolMove) ✅; combat-weave (chasers hold
range + strafe to open the rear, web combatMove) ✅. Todo: surround/flank,
lastSeen-search refinement; rival raiders (loot, fight/fear mobs, self-extract, carry
haul). Patrol zone-leash / portal-guard / loot-cache guarding deferred (need zones/extraction).
Phase 5 — Loot (active model only). Loot types (gear / blueprint / coin / heal potion — no
valuables), LootRoller + chest tables, auto-collect into the uncapped bag, auto-equip into
empty loadout slots on extract, result tally. No in-run Loot Station / field-equip / safe-pocket
/ dump (parked, §7). First MVC screens here are the Result + lobby Loadout (not an in-run
inventory).
Phase 6 — Interactions, extraction & events. Chest-crack + vault-gate channels; multi-phase Arc portals + safe/progression exits + guard; world events / escalation (timer, wildmagic storm).
Phase 7 — UI/HUD polish (MVC). Vitals/ammo/stamina, minimap, full tactical map, threat-edge arrows, target arrow, floaters, effects.
Phase 8 — Meta / progression. Save; gold/blueprints; stash; loadout equip; gear upgrades (spend blueprints); power calc; bestHaul/raidsWon.
Phase 9 — FTUE. Data-driven tutorial stage defs + coach driver + guided lobby spotlight.
Phase 10 — Polish. Runtime tuning panel; audio; juice (shake/hitstop done); settings; build & perf pass (mobile).
6. Risks & guidance¶
- Port formulas, not pixels. Lift detection/noise/combat/loot math directly from the cited
game.jsfunctions into Domain classes; unit-test against hand-checked values. - Kill the global. All shared run state goes in injected
RaidState; entities never reach for a static. - Tuning parity. Each
CFG.*value → a config-SO field with the same name, so the existing balance transfers and a future tuning panel maps 1:1. - Reuse what's done. Fog of war, map mesh, route/ripple/noise visuals, pathing, camera follow, frame cap are already migrated — build on them.
- Don't over-abstract early. DI + layers yes; premature ECS/event-sourcing no. Vertical slice first (Phase 1) to validate the architecture before breadth.
7. Parked features — NOT migrated initially¶
The web keeps several systems behind "parked" master toggles (code present but inert). We do not port these in the first pass — add them later only if the design calls for them. Until then we mirror the web's active fallback behaviour.
| Parked system | Web flag | Active behaviour we DO migrate |
|---|---|---|
| Shield / overshield + shield cells | player.shieldEnabled = false |
No shield bar/cells; the only consumable is the heal potion. |
| In-run inventory: Loot Station, field-equip, bag UI, safe-pocket, dump, bag cap, corpse looting, rummage noise | loot.inRunInventory = false |
Chests/kills auto-collect into an uncapped bag; gear auto-equips into empty loadout slots on extract (autoEquipEmpty). No triage UI. |
| Valuables (sellable 💎 trinkets) | loot.valuableChance = 0 |
Loot rolls are gear / blueprint / coin / consumable only. |
| Front-armor / rear-weak-point | combat.weakpointArmor = false |
Mobs take normal damage from all angles; the FTUE weak-point lesson is skipped. |
Keep the toggles as fields on GameConfig (default off) so re-enabling later is a flip + the
deferred code, exactly like the web. This trims Phase 3 (combat: no armor model) and Phase 5 (loot:
no inventory UI — just auto-collect + auto-equip + result tally).
8. Map authoring — two paths, one loader¶
Map generation gets reworked around data assets, supporting both real raids (procedural) and the FTUE (hand-authored, fixed):
MapDefinition(ScriptableObject) — procedural raids. Holds the parameters: width/height, wall/bush density, loot %, enemy %, available zones/POIs, seed policy. Authored in a custom Editor Window (configure values → live preview of a sample generation → save to the SO). At runtimeMapGeneratorloads aMapDefinitionand generates from it (replacing today's hardcoded generator).MapLayout(ScriptableObject/asset) — fixed pre-made levels (tutorial stages). An explicit, hand-authored layout: the exact tile grid (walls/bush/floor) plus placed content (spawn, chests, scripted enemies, vault/gate, extraction points). Loaded verbatim — never regenerated on stage init, so a tutorial stage is identical every time. An FTUE stage = aMapLayout(static geometry + placements) + a stage script (coach steps, scripted triggers) layered on top.- One loader:
MapGeneratortakes either aMapDefinition(generate) or aMapLayout(load as-is) and produces the sameMapData+ chunk meshes downstream, so fog/pathing/vision are agnostic to which path built the map.
The custom Editor Window (preview + save-to-SO) is its own slice — fits in the Phase 1 "finish map
gen" / Phase 6 area; the MapLayout path is a prerequisite for Phase 9 (FTUE).
9. Tuning & authoring tools (designer-facing)¶
Everything tunable is a designer-editable asset — no code edits to balance the game. The full set of authoring surfaces:
GameConfigSO — global balance. One asset, nested groups mirroringwindow.CONFIG(move,player,weapons,melee,combat,stealth,noise,fog,camera,ui,extraction,map,prog,zones,bot,enemyScale,loot,gear).[Tooltip]s carry the web's per-field blurbs. Edited in the Inspector now; a runtime tuning panel (deferred) later walks this same object like the web's dev panel walksCONFIG.EnemyDefSOs +EnemyDatabase— per-creature tuning. Eachenemies.*entry becomes oneEnemyDefasset (hp/atk/speed/visR/visA/radius/melee/ranged/projectile/flags/art); anEnemyDatabaselists them for spawning. Add a creature = add an asset, no code.- Weapon archetypes —
weapons.*table asWeaponDefSOs (or a sub-table onGameConfig). MapDefinitionSO + custom Editor Window — procedural raid params with live preview → save (see §8).MapLayoutasset +StageDef— fixed FTUE layouts + their coach scripts (see §8). A simple layout inspector/painter can come with the FTUE phase.
Designer workflow goal: tune balance in GameConfig, add/adjust creatures via EnemyDef assets,
author maps via the Map editor window, and lay out tutorials via MapLayout/StageDef — all in the
Editor, all version-controlled assets.
10. Completeness check (audit)¶
Cross-checked against every game.js section and config.js group. Explicitly covered:
map-gen/zones, pathing, LOS, fog, movement+stamina+dodge, camera (zoom/look-ahead), detection,
noise+recruit, stealth/backstab/bush, ranged+melee combat, projectiles/bombs/hazards, PvE + rival
AI, loot/chests (active model), interactions/gate, multi-phase extraction, world events, render
(sprites/floaters/effects/threat arrows/target arrow), minimap + tactical map, HUD/screens, save +
meta/progression + power calc, FTUE stages + lobby spotlight, input (tap/double-tap/swipe), frame
cap. Explicitly deferred (§7): shield/cells, inventory triage, valuables, armor/weak-point.
Explicitly dropped (web-only, not needed): the immediate-mode canvas renderer (replaced by
Unity-native), the in-browser tuning panel (Inspector + later runtime panel), localStorage (→ JSON
save). Dev-only devKit/sandbox and weapons.override map to Inspector/debug toggles, low
priority.
11. Owed within active features (revisit)¶
Things that are implemented but partial — a faithful subset shipped, with the rest deferred (often blocked by a not-yet-ported system). Tracked here so they're not lost; revisit when the blocking piece lands.
- Stealth (Phase 2): stealth-chase sprint (tap an unaware enemy from its blind spot → free
auto-sprint at it until spotted) is now ported (
PlayerController.SetHuntfreeChase +_chaseSprintno-drain in the stamina block + spotted-ends-chase). The silence-window (a backstab on an unaware victim keeps the kill quiet for a beat) is still owed. - Enemy ranged (Phase 4): direct-fire works (mutual-gated, telegraphed, standoff). Deferred:
bomb lob (bombardier,
projSpeed=0) → needs a hazard/AoE system; rocket splash (rocketeer) → needs explosion radius; fire burn (emberball) → needs a burn DoT; spider web (atkWeb→ slow) → needs the web/slow effect. combatMove weave/juke is ported (EnemyController.CombatMove— radial range-hold + perpendicular strafe, per-axis wall collision, A* fallback). Distinct bullet colour is ported (Projectile.Init(tint): player cyan #9fe8ff, mobs toxic-green #a6ff7a, boss/fire orange #ff8a3a — webfireWeapon/fireArcShotcolours). Enemy gunshot noise is ported (EnemyController.FireRanged→NoiseSystem.Emit(gunshot), throttled ~everynoise.gunCd-th shot, webfireProjectilemakeNoise) — recruits nearby mobs via the same Emit→Recruit→Investigate path the player's gunshot uses. - Noise sources (web
makeNoise). Every noise whose feature exists in the slice is now wired: player gunshot + melee clash (frontal only — a backstab is silent), enemy gunshot, death (deathMob"big" loud kill — draws mobs to the hotspot — vs a near-silentbackstab"shot" on a clean stealth kill,EnemyController.OnDied+MarkSilentKill), and hunter footsteps (hunterStep"step",recruit:false— a roused, moving, currently-UNSEEN mob pings through walls so a threat closing behind cover is audible;EnemyController.HuntNoise). The sonar rings render viaNoisePing(lazily spawned byNoiseSystem). Still deferred with their features (no source exists yet): chest-crack/pop, gate force/open, loot rummage, bomb-lob/explosion, extraction/portal calls, and rival-raider chest/vault noises. - Mob tiers (web
MOB_TIER) ✅. A danger TIER (1 harasser · 2 warden · 3 rocketeer/bombardier/sentry · 4 boss) onEnemyDef— a classification, NOT a stat multiplier (the web never scales hp/atk by tier). Drives lost-trail tenacity (tier >=combat.heavyTier=3 → decay ×heavyTenacity0.35, clings longer) — replaces the old hardcoded 1 — plus the lunge gate and (later) loot rarity. - Facing lerp (web
updateMob+moveAlongPathturnRate). Mobs turn at a limited rate instead of snapping, in every mode: (a) a visible chase steers the cone toward the player viaEnemyController.FaceLerp(web turn 6 melee / 3.4 ranged); (b) path movement — patrol, search, A*-fallback — lerps the facing toward the heading atcombat.patrolTurn(webpatrolMovepasses it tomoveAlongPath: "turn the head/cone gradually, not in one frame") viaPathAgent.turnRate, set per-enemy; (c)ScanSpinsweeps INCREMENTALLY from the current facing (no absolute-yaw jump).PathAgentlerps from the LIVEtransform.forward, so handing off between direct-steer combat and path-following never jump-cuts. The cone lags → circling to the unwatched rear works; the shot still aims straight at the target (facing is cosmetic to the aim). - Visual de-flicker (web
_litHold).EnemyController.Shownholds the drawn state forfog.litHold(0.2s) after LOS drops, so an enemy peeking a corner / brushing sight's edge stays solid instead of strobing. Presentation (EntityView,HealthBarView, gaze cone,Perceived/ tap-targeting) readsShown; the raw per-frameRevealedstill gates the mutual fire window + footstep silence. (Thefog.litHoldknob existed but was previously unused.) - Melee: hitstop (the backstab-kill freeze-frame, web
G.hitstop), the render-only attack lean (websetLean), and the windup→dash→recovery lunge (webmeleeSequence:2539+ the chase arbitration:2761) are all ported (RaidController.Hitstop,EntityView.Lean,EnemyController.MeleeSequence/LungerChase/IsLunger). The lunge gates onIsLunger(melee && !ranged && !boss && tier >= melee.lungeMinTier,lungeMinTier=2). The skeleton is promoted to a tier-2 BRUTE (a deliberate slice tweak — webMOB_TIERhad skeleton=1) so it's the slice's one visible lunger; the other melee stay tier-1 walk-in, and the tier-2 warden stays a non-lunger (it's ranged). SetlungeMinTier=1to give every melee the lunge. A lunger's close-combat: IN REACH → quick simple attacks (atk·simpleMult) + weave; MID RANGE (off cd) → the telegraphed lunge (windup tracks → locks a destination just past the target → dash atlungeSpeed, per-axis wall-stop, plants on connect / overshoots on a dodge/whiff to bare its rear → recover); else walk in. Tier-1 melee keep the non-lunger path (Chaseweave + point-blank hit). - Gaze cones / projectiles: destroyed on death, not pooled yet (pooling is Phase 10).
- Potion: auto-used when critical as an interim; needs the HUD potion button + channel.