Skip to content

Pivot parity audit — web (truth) vs Unity port

STATUS 2026-08-10: Wave 1 landed in d24dbbc (+ playtest feedback: defmob HP bars/melee, building bars+pips, timber currency UI, dash-proof blockers, DropIconSet.timber). Wave 2 landed in 72cad4e (all listed items; G50 verified a false positive — GearValue already matches web; G22 skipped, tap input parked; G49 folded into the existing Result body strings). Wave 3 landed (presentation/harness + the D17-D19 popup/contour bugs). Still open after wave 3: D46 (balance preview tooling), S15/S17 (rival telegraph, echo badges — rivals parked / needs echo-view text), S22+A41+A13+A39 (tutorial-pipeline patterns), A4 (tap fall-through — tap parked), G39 (fog-gated murk — needs fog-layer work), G42/G43 (map corruption bands — needs a MapView band element), G51–G54 (smoke harness needs a headless populate path), D34-partial (spikes are web-tinted cubes, not iso triangles — art pass), plus the parked-dependent items (shield cells, safe pocket, rival neutrality, stationary sentinels A11-A14).

2026-08-10. Post-implementation audit of everything landed on bza-unity-pivot-parity (P1–P10, 68042ce..abd506a) against web game.js/config.js at ebd6c39. Five independent audit passes; ~200 findings, only differences listed — verified-matching behaviour was checked and is recorded at the end of each section where it closes a question.

ID prefixes: D defense · S stealth/targeting/ring · N nest/tells · A AI/combat/input · G generator/loot/corruption/run-flow. Tags: BUG (gameplay wrong), BEHAVIOR (sim diverges from web), VISUAL / MISSING (presentation or feature gap), LATENT (wrong but dormant under current config).


0. Root causes of the reported defects

Report Root cause Finding
"defense mobs not destroying buildings, walking to exit portal" DefenseMob.Update goal ladder is player → unit → entry → PadWorld. Web's is player → unit → nearest live building at any range → player fallback. The building branch was never ported; meleeTarget stays null on the pad path so mobs idle on the pad. D1
"archer towers aren't shooting" Tower attack is instant Health.TakeDamage with no projectile. Web spawns a real travelling projectile (spd 640, life 0.9, #ffd27a, 16px muzzle offset) that can miss. Nothing visible ⇒ reads as "not shooting". D2
"engage ring should be dashed, show dashed line to target, hide when unused" Ring is a solid always-on LineRenderer. Web: dashed [7,9], hidden unless a valid visible target is within engageRange×1.6, hidden entirely mid-fight (peace-only), plus a separate dashed→solid gold aim line to the target which Unity lacks entirely. S1–S11
"nest spawn reticles should look like web" Unity tell is a small violet pulsing quad. Web: 3-layer red/orange reticle, radius 44px = 1.10u, inner disc grows with the countdown (T-driven, not pulsing), vision-gated. N1–N4

1. DEFENSE (holdout, waves, buildings, spikes)

Bugs

  • D1 [BUG] Defense mobs never target buildings. Web defPickTarget (game.js:6803-6813): player <130px → unit (d−r <70px) → nearest live building at ANY range (box-clamp for gates, d − r others, Base included) → player fallback. Unity DefenseMob.cs:58-74: player → unit → entry → PadWorld; no building branch, no DefenseController.NearestBuilding, meleeTarget null on pad path. Fix: add NearestBuilding(pos, out aim) (gate = box-clamp vs DoorTiles span), insert between unit and fallback; fallback = player, never the pad.
  • D2 [BUG] Towers deal instant invisible damage. Web game.js:6897-6911 spawns a real projectile (origin +16px along aim, spd defense.tower.projSpd 640, dmg defStat, faction player, #ffd27a, r4, life 0.9, can miss); fires regardless of fightOn. Unity DefenseController.cs:592-609 calls TakeDamage directly. Fix: spawn shared Projectile prefab (Faction.Player, 640/U, life 0.9, gold tint).
  • D3 [BUG] T-doorway spawn box inset on the wrong edge. Web trims the apron half nearest the chamber (sy1 -= TILE); after the z-flip that's z0 += ts, but DefenseController.cs:375-377 does z1 -= ts — trims the OUTER half, biasing T spawns onto the gate line. L/R correct.
  • D8 [BUG] "Past the doorway" is a 24px proximity latch to EntryWorld, not web's room test (defInRoom = strictly inside chamberBox, game.js:6957). A mob deflected around the entry point by a gate/crowd never clears its goal and grinds on the doorway forever. Fix: _pastEntry |= Chamber.WorldBox.Contains(me.xz).
  • D9 [BUG] Mobs not frozen during the SPIKES sweep. Web skips the whole per-mob block when phase === "spikes" (game.js:6950); Unity mobs keep walking and keep killing the player during the ~1.4s sweep. Fix: early-out in DefenseMob.Update on DefPhase.Spikes.
  • D17 [BUG] Gates practically unselectable in the build popup. Web defNearZone measures a gate by box-clamp to its 6-tile span (buildable anywhere along the run), others by d − r (r 40/20/26), accept <46px. Unity uses point distance to slot centre minus flat 1f/0.5f — a gate needs standing within 66px of the centre of a 240px wall that is itself solid. Fix: store gw/gh, box-clamp; real per-type radii.
  • D18 [BUG] Destroyed buildings show no restore contour. Web draws contours for unbuilt and dead slots with ⟳ cost; Unity gates on s.Lvl == 0 && !s.Dead and destroys s.Viz — dead building = empty floor, no affordance.
  • D19 [BUG] Contours never torn down. UpdateContours only runs from UpdatePopup (Idle phase); once the pad arms, the quads persist through the whole holdout. Fix: call unconditionally from Update().
  • D25 [BUG] Player-eject from sealed doorways is a slow per-frame drift toward the pad with no wall test (can shove through side rock) and no path cancel (PathAgent walks back in → jitter loop). Web defResolvePlayer (game.js:6599-6617) snaps to the best of 4 edge candidates (min d(cand,pad) + d(cand,player)*0.25) and clears the path. Fix: port the edge snap + PathAgent.Stop().
  • D30 [BUG, latent] Hand-clear calls OpenPortal which only unseals; it works today because incoming/window = -1AlwaysOpen, but any positive extraction.incoming re-arms a second arrival wait, and Portal.TickIdle refuses while PlayerHunting (force-true while DefMobs exist + fightLinger). Fix: force Portal.Phase = Open directly.

Behavior

  • D4 Threat-cost trash reference is iteration-order-dependent (trashRef = last Trash entry; web hardcodes swarm). Deterministic pick needed.
  • D5 el unclamped in the scheduler (duration − timer can exceed duration on the last frame; web clamps).
  • D7 Defense mobs spawn with 0 initial melee cd; web seeds rand(0,0.5) to avoid synchronized alpha strikes.
  • D10 Gate chew fires off a forward point-probe only when an axis is blocked; web resolves the body circle vs overlapping WALL/FENCE tiles (defResolveTiles) in both walk branches — diagonal sliders never chew in Unity.
  • D11 Melee reach hardcodes 13px target radius; web uses the actual target's r (wrong for Base r40/barracks r26 once D1 lands).
  • D12 No steering wobble (sin(m.wob)*0.12, wob += dt*2) — web packs weave, Unity mobs stack in a line.
  • D13 engageUnit ignores unit radius (~11px tighter than web).
  • D21 Pad activation combat-locks the player: Unity stamps PlayerChannelFrame (suppresses fire/melee/reload) during the 2.5s arm; web sets summoningZone only — you can shoot while it fills. Fix: display-only flag.
  • D24 SPIKES button never hides on death (web re-evaluates p.alive every frame).
  • D26 Blocker test is a point test; web tests x ± p.r — player sinks half a body into sealed doorways.
  • D27 Entry-only exemption is per-rect-set (CurrentlyIn any of L/R/T), web is per-rect. Harmless today.
  • D31 Board radius mismatch on the defense pad: activation 60px, Portal.radius 1.3u = 52px (web 60 for both).
  • D32 Spike sweep-end hardcodes descended:true and session++ unconditionally; web routes through endRun with G._defWon && !G.sandbox guard and extractKind.
  • D35 UpdateChamberFog writes PlayerSightRadius/VisionField.sightRadius every frame even at fogK 0 (clobbers any future sight modifier) + per-frame FindFirstObjectByType until resolved. Web keeps widening as a pure read (fogRadiusNow() at consumers).
  • D36 Defense targeting bypasses the grade ladder and switchMargin lock — SelectFireTarget returns nearest defmob immediately (flip-flops between equidistant mobs; a rival attacking you can't win the target; at peace between packets the bow drops because the fall-through peace gate doesn't know defFight). Web folds defmobs in as grade-1 candidates. (= S32.)
  • D37 Passive arena ring only shows while mobs are alive; web shows it from phase incoming|finale onward. (= S8.)
  • D6 Spawn-credit drain equivalent-but-not-by-construction (window-close flush is inside the gate; web drains outside). Note only.

Visual / missing

  • D14 [MISSING] No hit/death feedback on defmobs: web hurt-flash 0.18s, -N floater #ffe9a8, colored death burst, G.kills++ only when source is the player (Unity credits kills only on the spike path).
  • D15 [VISUAL] Infantry muster on the wrong side of the barracks (sign not z-flipped: web +y toward entrance ⇒ Unity −z, code uses +0.75z).
  • D16 [MISSING] Infantry have no HP bar / hurt flash / spawn+death bursts (web #b58cff bursts, white hit sparks, 20×3 bar).
  • D20 [MISSING] Build popup has no stat chips / desc / next-level preview / letter badge. Web shows level N/max · HP, flavour desc, and per-type chips at lvl+1 (Base→unlock ladder; Tower→Dmg/Rate/Range/HP; Gate→HP/Armor; Barracks→Spawn/Cap/UnitHP/UnitDmg), rebuilt on signature change.
  • D22 [MISSING] No pulse on build/upgrade/damage/unlock (web pops the building 1.1×, and a Base upgrade pulses every newly-unlocked slot).
  • D23 [MISSING] No build/restore/destroy burst FX (web colored bursts; red #ff6b6b on destruction).
  • D28 [MISSING] Gate slots don't store gw/gh (needed by D1/D17 fixes, contour rects, plank orientation).
  • D29 [MISSING] Holdout is silent: no assignPortalGuards on call/open, no extractCall noise, no portalIncoming pulse + green burst cadence.
  • D33 [VISUAL] Spike grid alignment: web half-step offset + exclusive end, spark #8b95a8 per spike pop; Unity flush-aligned inclusive, no spark. (Spacing/pad/delay/kill-radius/envelope all match.)
  • D34 [VISUAL] Spikes are white cubes; web draws shadow + dark iso triangle #2c3444 + highlight sliver, height 42×envelope.
  • D38 [MISSING] Off-screen red edge-darts for live defmobs (web up to 40, #ff6b6b, α0.55, clamped to screen edges) — "a doorway you can't see never quietly chews your fort".
  • D39 [MISSING] The pulsing red dashed seal line across the S door during incoming/finale/spikes. The invisible blocker has zero visual feedback.
  • D40 [MISSING] Gate planks don't degrade with HP (web: 3 posts/tile, standing = max(1, round(3*(0.35+0.65*hp)))).
  • D41 [MISSING] Single gate HP sliver on the designated middle tile (_hpTile).
  • D42 [MISSING] Building letters (B/AT/G/IB), level pips, appear-when-bitten HP bar.
  • D43 [MISSING] Animated dashed contours with letter + cost label (gold affordable / orange not, restoring, near-slot highlight).
  • D44 [MISSING] Defmob token art: web per-kind palette (swarm #f2a3a3, grunt #e0685f, runner #e8b04a, brute #a3455f), eyes, hurt flash, HP bar; names Gnawer/Marauder/Skitterer/Crusher. Unity: untextured capsules, different palette, names Swarmling/Grunt/Runner/Brute.
  • D45 [MISSING] Melee/chew spark FX (#ff8a6b mob melee, #c9a37a gate chew, white infantry hits).
  • D46 [MISSING] defBalanceRows headless scheduler preview (tuning surface; needs an ISpawnSink seam in EmitGroup).

Verified at parity: threat/counts/midShare math, shape sampling mean, group size, sides rotation, apron insets, stream weights, all four defmob stat tables, gate tile geometry/spans after z-flip, restore economics, save-write discipline, CanBuild rule, Base-death ladder collapse, KeyE shortcut, uSpd/uSeek/uCd, infantry gate-passing.


2. STEALTH / TARGETING / ENGAGE RING

Engage ring

  • S1 [VISUAL] Ring solid; web dashed [7,9], lw 1.5 → 2.5 at full draw (game.js:8800).
  • S2 [BUG] Ring unconditionally on; web hides unless a valid target (alive, visible, not woken nest, not unmarked-neutral rival) is within engageRange×1.6 (=256px) (game.js:8774-8790).
  • S3 [BUG] Peace-only rule missing: while hunted web draws NO ring (except a sleeping nest inside R×1.6); Unity shows a red ring mid-fight.
  • S4 [BUG] Precedence inverted: web tests aiming before war (nest zone-war while drawing = gold, not red); Unity checks PlayerAtWar first.
  • S5 [BUG] Gold math wrong: web = fixed 255,214,100 with alpha 0.2+0.5·aimK; Unity uses that ramp as a Color.Lerp t from blue (20% gold at 0, never pure).
  • S6 [VISUAL] Color triples off: war rgba(255,96,72,.30), peace rgba(158,232,255,.28) — Unity's are more saturated/opaque.
  • S7 [BUG] Woken-nest rule inverted: web skips the nest as a ring candidate (ring survives on other targets); Unity blanks the whole ring when FireTarget is a woken nest (flickers as arrows retarget — also N26).
  • S8 [BEHAVIOR] Defense arena ring should show from incoming|finale phase, not only DefMobs.Count > 0. (= D37.)
  • S9 [MISSING] No ui.engageRing kill-switch (web gates the whole draw).
  • S10 [BUG] Aiming state doesn't require a live _aimTarget and starts one frame late (keys off AimDrawFraction > 0).

Aim telegraph line — the single largest presentation gap

  • S11 [MISSING] The player's aim line does not exist: web game.js:8803-8813 — player→target, gold rgba(255,214,100, .25+.6·aimK), lw 2→3, round caps, dashed [4,6] while aimK<1, snapping SOLID at full draw; only while aiming (peace); movement wipes _plantT ⇒ line vanishes ("move to cancel").
  • S12 [MISSING] _aimTarget is private — expose public ICombatant AimTarget.
  • S13 [MISSING] Camera lean into the draw: web leads toward the mark at leadFight*(0.4+0.6·aimK) when not fighting.

Enemy telegraphs & badges

  • S14 [BUG] Ranged charge line never snaps solid at chargeLockLead — the hard dodge cue is gone. Web: locked ⇒ rgba(255,60,35,.92) lw4 solid; tracking ⇒ dashed [4,5] lw2 α.2+.55k. Unity DrawCharge is always dashed; only DrawLunge implements the snap. Fix: ChargeLocked accessor + branch like DrawLunge. (= A22.)
  • S15 [MISSING] Rival "drawing a bead" telegraph (thin dashed [3,6] line 200px along facing).
  • S16 [MISSING] No ❗ / ? / ☠ detection glyphs anywhere (web: ❗ red at alert≥1, ? amber under binary detection, ☠ for calm boss). Alert is conveyed only by cone tint/echo color/arrows. Fix: badge slot on CharacterLabel fed from det state.
  • S17 [MISSING] Same badges on echo-sensed blips.
  • S18 [MISSING] Alerted gaze-cone edge stroke (rgba(255,80,60,.45) lw1.5 outline when aware).
  • S19 [VISUAL] Cone tiers wrong: web has TWO states (aware #ff2e2e @0.26, else #ffb45a @0.09); Unity has three, Suspect gets orange not red, all 2–4× more opaque.
  • S20 [VISUAL] Unity cones have a radial alpha falloff; web is deliberately flat.
  • S21 [BEHAVIOR] hideLockedCones adds _targetIsPlayer — a mob that switched target keeps a cone web would hide.
  • S22 [MISSING] Tutorial scanning-sentry cone telegraph (tutScan bright outline + sweep-edge dot) — pairs with A41.

Backstab tell

  • S23 [VISUAL] 🗡 pulsing emoji on the player's side of the enemy replaced with a static gold ground ring.
  • S24 [BUG] Only ONE tell shown (nearest-wins); web draws it on every qualifying enemy.
  • S25 [BUG] Tell gated on !HasEyesOnPlayer — that's backstabReady's rule, not backstabTell's. With flankCrit on, web's tell survives as a "you're in the crit arc" indicator even when seen. Fix: only skip when !flankCrit && e.HuntsPlayer.
  • S26 [BUG] Tell radius includes meleeReach (+25px); web is p.r + en.r + backstabTellRange only.
  • S27 [BUG] No wind-up/lunge/recover exclusion — 🗡 lights on a committed lunger.

Stance arbiter / grade ladder

  • S28 [BUG] AutoMelee is a second, independent attack system. Web has ONE arbiter: one nearestEngaged() target, meleeStance picks melee OR ranged; only the blind-spot backstab is stance-free. Unity's AutoMelee scans Hostiles and strikes any seen foe regardless of grade/lock/stance; during stanceCommit the player melees AND shoots. Fix: route normal swings through the arbiter's target.
  • S29 [BUG] AutoMelee has no visibility/LOS gate (distance only) — can punch through thin walls/unlit foes.
  • S30 [BUG] Lock stickiness re-adopts _engLock when the fresh scan yields nothing; web drops the lock and stops firing.
  • S31 [BUG] Lock re-grading skips LOS (raycast only in the candidate loop) — a lock that broke LOS is retained via S30.
  • S32 [BUG] Defense branch short-circuits instead of merging as grade-1 candidates. (= D36.)
  • S33 [BEHAVIOR] Weapon-reach candidate cap (projSpd*life) not in web — a grade-1 attacker is valid at ANY range (drives facing/camera too).
  • S34 [MISSING] Rival Tier-0 neutrality absent from the whole grammar (grading, auto-stab, tell, ring near-scan, vision circle). No neutral/_marked concept; every rival is always hostile. Design decision needed: rivals are disabled anyway (botsFromBiome 99) — defer or port with the raider re-enable.
  • S35 [BUG] PlayerStabReady (melee-outranks-looting) laxer than web backstabReady: missing wind-up exclusion + isVisibleToPlayer — chest cracking cancelled by a mid-lunge or unseen enemy web would ignore.

Misc presentation

  • S36 [MISSING] 🌿 concealed glyph (alpha drop exists, glyph doesn't).
  • S37 [MISSING] Bouncing gold target caret above the hunt/lock target (both stances).

3. NESTS & SPAWN TELLS

Tell visual (web spec: game.js:8333-8350, radius 44px = 1.10u, k = t/dur)

Layers: (1) constant base disc rgba(255,60,50,.10+.28k); (2) growing inner disc r·k rgba(255,120,80,.22+.34k) — the fill IS the countdown; (3) constant rim stroke rgba(255,90,70,.5+.45k) lw 2+2k. Monotonic, no pulse. - N1 [VISUAL] Unity is a Time.time ping-pong pulse (phase-shared across all tells), not T-driven growth; store Dur and render 3 layers. - N2 [VISUAL] Violet → must be the red/orange spawn-danger palette above (violet is the nest-event color, not the birth zone). - N3 [VISUAL] ~2.4× too small (0.45u vs 1.10u radius). - N4 [VISUAL] Not fog/vision-gated; web if (!inVision(s.x,s.y)) continue — leaks nest activity through walls. Gate at the tell's position. - N5 [MISSING] No hatch VFX (web violet burst #b06aff at birth). - N6 [QUALITY] Tell viz allocates GO+Quad+Material per birth and Destroys at hatch — pool via Views.Rent + MaterialPropertyBlock. - N7 [MISSING] No hatch-time open-floor re-test: web relocates via nearestOpenSpot (ring search r≤4) if the tile became solid; Unity spawns blind at tell.Pos. - N8 [VISUAL, minor] Unity kills orphaned tells on nest death immediately (web lets them lapse) — arguably better, keep deliberate. - N9 [BEHAVIOR] Brood leash not "hard" — no post-move fence clamp (web leashHard clamps position when state ≠ chase). (Superset: A36.) - N10 [BEHAVIOR, trivial] Mob scale snapshot at hatch vs web's at tell creation.

Wake / rally

  • N11 [BUG] Nest doesn't wake from sourceless damage: OnDamaged returns on LastAttacker == null before the IsNest branch — corruption/DoT hits never wake it. Web sets _woken = true unconditionally, then gates only the rally on source. Fix: hoist the wake above the null guard.
  • N12 [BUG] Rally doesn't commit the brood to the attacker: web sets alert=1, lastSeen, state=chase, _searchT=0, target=source per rallied mob; Unity's Rouse only force-chases a point — they walk there instead of hunting you.
  • N13 [BUG] Already-alerted brood skipped by the rally (Rouse early-returns at alert>0.5; web's nest branch has no guard — mid-search brood snap back to the attacker).
  • N14 [BUG, latent] BornOf rally wrongly gated behind PackId != null (web's _nestOf clause is independent). Populator always sets pid today.
  • N15 [VISUAL] Wake beat wrong: web "The X stirs!" #b06aff size 14 + ripple2 double ring 0.5s; Unity uses the gatekeeper style (11px #9FB2FF), no ripple. Fix: add a nestEvent TextStyle.

Payout / death

  • N17 [BEHAVIOR] Web's G.blueprints += randi(1,3) is DIRECT currency; Unity spills 1–3 extra Common blueprint items — bigger shower, wrong route. (Guaranteed gear + 5–8 valuables/bp rolls match.)
  • N18 [VISUAL] "NEST DESTROYED!" should be violet size 18 at body origin (Unity: gatekeeper style 11 at +2.0).
  • N19 [VISUAL, minor] Death burst should use the theme mob's color (knock-on of N23).

Presentation / HUD

  • N21 [BUG/VISUAL] Sleeping nest shows its HP bar — web hides it until woken (the bar appearing IS the wake tell; before that the nest reads as scenery).
  • N22 [VISUAL] Nest HP bar not boss-width (web 50×5 vs 34×4 for boss || nestDef).
  • N23 [VISUAL] Nest body/label not tinted to theme color (web n.color = MOB_DEF[theme].color).
  • N24 [VISUAL] Name derived from the key, not the theme's display-name first word ("Emberball Nest" vs web "Emberling Nest"). (= G12.)
  • N25 [VISUAL] Woken-but-unseen nest echo blip turns red ("has spotted you" — a lie; nests have no senses). Web blips are always calm blue: pass alert 0.
  • N27 [MISSING] No nest POI/map label (web ◈ Nest in-world at <540px + tactical map). (⊂ G15.)
  • N28 [BEHAVIOR, minor] Revealed drops the + en.r slack (nest pops ~0.45u later than web).

Map population

  • N29 [MISSING] No 3×3 bush clearing around nests (web game.js:392-402 stamps BUSH→FLOOR; Unity can seat a nest inside concealment, blocking clean shots at the win condition). Re-run BuildBushClusters after clearing.

Verified at parity: corner-seat scoring, brood gbox, mirrored pickup + lootSafeR, liveCap counting, first-interval halving, birth sampler, warZone LOS, all IsNest combat exemptions, death-noise suppression, NoDrop brood.


4. ENEMY AI / COMBAT / INPUT

Action input / dash

  • A1 [MISSING] Dash button wind-up pulse (web: off-cooldown AND any enemy charging/winding within 380px ⇒ pulse; the dodge-lesson window).
  • A2 [BEHAVIOR] Dash fires on release (Button.onClick), web on press (pointerdown) — implement IPointerDownHandler.
  • A3 [BEHAVIOR] Stick magnitude rescaled past the deadzone; web is unrescaled min(1, r/R) (jumps to ~0.15 at the edge — the shipped feel).
  • A4 [MISSING] Tap fall-through on release (<12px, <260ms ⇒ map tap / minimap open / sheet close). Parked with tap-to-move but the classification hook is absent entirely.
  • A5 [BEHAVIOR] stickRadius 160 ref-px vs web 56 CSS-px — close relative to reference widths but never verified; confirm against ui reference resolution.
  • A6 [BEHAVIOR] Dash collision radius hardcoded 0.32u (=12.8px) vs web p.r*0.8 = 10.4px; also in PathAgent.DirectSteer. Derive from config.
  • A7 [MISSING] Dash VFX: launch burst + dashEnd settle puff.
  • A8 [BEHAVIOR, deliberate] Unity sub-steps the roll and breaks when wedged (web burns dodgeT unstepped) — keep, it's better; recorded as a deliberate divergence.
  • A9 [BEHAVIOR] Dash calls _agent.Stop(); web keeps the path. Moot while tap-to-move is parked.
  • A10 [BEHAVIOR] PLANT timer: Unity also zeroes on IsMoving || _dodgeT > 0; web only on steer input — a dash should NOT break the plant.

Stationary sentinels

  • A11 [BUG] No stationary/turret branch: web early-outs for en.stationary (never moves, tracks while charging, fires only at alert≥1); Unity routes them through Chase/CombatMove/Search — a sentry would walk. Currently unreachable (see A14) but must land before sentinels are enabled.
  • A12 [MISSING] Idle gaze wander (rand-gaze creep) — Unity uses the generic sinusoidal ScanSpin.
  • A13 [BEHAVIOR] Suspect-state tracking (turn toward half-noticed target at 3 rad/s below alert 1) — dormant under aggroOnly, live in the tutorial pipeline.
  • A14 [CONTENT] sentinel (Stone Sentry, tier 3, legendary pool, mobIntro 8) exists as [UNUSED] Enemy_sentinel.asset with rankAndFile: 0 — never rolled. Flip only after A11.

Vision / melee / defs

  • A16 [BEHAVIOR] Vision "stir" boost doesn't reach combat: _visRMul applies to detection only; Chase recomputes visR unboosted (web mutates en.visR itself, growing the fire gate + standoff).
  • A17 [BUG] Rocketeer + Bombardier have melee: 1 in their assets; web authors both melee: false. Set 0.
  • A18 [BEHAVIOR] Per-def meleeCd overrides (brute 1.5 / grunt 1.0 / runner 0.7 / swarm 0.8) diverge from web's flat mobMeleeCd 0.7 for normal mobs — the field was added for defense-mob parity but the values now differ in the raid context too. Decide source of truth; zeroing the overrides restores web parity (defense stats read their own table).
  • A19 [BEHAVIOR] Melee swat range: web en.r + tgt.r + 8 (~r+21px, target-aware); Unity def.radius + meleeReach(25) + 0.2u (~r+33px, no target radius) — also shifts the CombatMove hold band ~12px out.
  • A20 [BEHAVIOR] Lunger eligibility: web = pure-melee non-boss tier ≥ lungeMinTier 2 (⇒ NO current mob lunges); Unity = per-def flag, Enemy_skeleton has it ⇒ skeletons lunge only in Unity. Add the tier gate or clear the flag.
  • A21 [BEHAVIOR] Lunger in-reach simple attack omits applyWeb + slash effect.

Charge / fire details

  • A22 [BUG] = S14 (charge line lock snap missing).
  • A23 [BEHAVIOR] Anti-standoff push-in: web passes pushIn = ranged && !canFire so a blocked shooter closes to flush you out; Unity passes ranged || lobs always and holds standoff doing nothing. Fix: pass !seen.
  • A24 [BEHAVIOR] Fire gate: web (mutualSightFire off) = pure distance ≤ fog.radius + r; Unity uses Revealed (adds LOS + bush cutoff, per-frame, no litHold) — stricter and flickerier.
  • A25 [BEHAVIOR] Muzzle origin at body centre; web offsets (r+4) along the aim for player AND mobs.
  • A26 [BEHAVIOR] Mob projectile lifetime computed (max(1.2, visR*1.2/speed)); web uses flat default 1.6.
  • A27 [BEHAVIOR] weapons.inheritVel asset = 0.33, web = 1.0 — sync the asset.
  • A28 [BEHAVIOR] Player gunshot-noise throttle is a 0.35s timer; web counts shots (ping every gunCd 2 shots) for every shooter.
  • A29 [BEHAVIOR, deliberate?] Predictive aim lead (aimLead 0.35, two-pass intercept) is Unity-only; web aims at the live position. Flag for a decision — it changes player hit rates vs web tuning.

CombatMove / pathing

  • A31 [BEHAVIOR] Wedged A* fallback repaths every frame; web throttles at 0.35s. Add a cooldown.

Patrol / drift / guards

  • A32 [BUG] Chest drift gated on _hasLeash — an unleashed roamer never drifts (web rolls 35% for every non-bot mob, leash box only constrains if present).
  • A33 [BEHAVIOR] No world-bounds clamp on the wander goal.
  • A34 [BEHAVIOR] Return-home bypasses the guaranteed dwell (web substitutes home only inside the leg-pick).
  • A35 [BEHAVIOR] Guard assignment missing web's tutorial early-return and !e.siege exclusion.

Hard leash

  • A36 [MISSING] No hard-leash post-move clamp anywhere: web clamps leashHard mobs (garrisons, room residents, nest brood) into the box after AI movement (state ≠ chase, only when within ±1 tile of the box so returners walk through the mouth). Unity's _leash never constrains investigate/search movement. Port the clamp + a leashHard flag from the populator. (⊃ N9.)

Search / investigate

  • A37 [BEHAVIOR] Search scan: web spins one direction at 0.9 rad/s (per-mob sign); Unity sweeps sinusoidally.
  • A38 [BEHAVIOR] Investigate: web arrival 44px + 9s hard timeout; Unity 24px, no cap, stall-detector instead. Align radius, add the cap.
  • A39 [BEHAVIOR] Suspect creep (stop at visR·0.45, turn 4 rad/s, never fire) unported — dormant under aggroOnly, live in tutorial.
  • A40 [BEHAVIOR] POI stamped from a live seen target — combined with A38 a mob decaying out of Chase walks to a stale point.
  • A41 [BEHAVIOR] tutScan sweeping sentry pattern unported (pairs with S22).

Minor

  • A42 [BEHAVIOR] Footstep-hunt velocity: web smoothed EMA, Unity raw per-frame delta — spikier at the 20px/s threshold.
  • A43 [BEHAVIOR] Eyes-memory ticks before Seen/Lost instead of after — one-frame skew.

Verified at parity: speed derivation, all weave/turn/charge numerics, chargeEntryArc, dwell, guard-ring geometry, lunge sequence, bodyBlocked rule, sealed-vault blocking, hitstop, binary detection.


5. GENERATOR / LOOT / CORRUPTION / RUN FLOW

Generator

  • G1 [BUG] Grove bush density hardcoded 0.03 in StructureForrooms.bushDensity tuning silently ignored for groves.
  • G2 [BEHAVIOR] Room centre wrong for EVEN widths: web x0 + ((x1-x0)>>1) (+0.5); Unity x0 + (W>>1) — half/one-tile shift for w∈{8,10,12} on cache chest, camp anchors, nest mirror pivot, protected pillar. (Height/z derivation is correct.)
  • G3 [BEHAVIOR, accept?] Pillar/outcrop "don't bury a mob" rejection dropped (structures run pre-populate). Web's check only ever saw earlier rooms' mobs — likely accept.
  • G4 [BEHAVIOR] MapCap hardcoded 120×120; read from config.

Populator

  • G5 [BUG] Trash pool one tier too narrow: web heavyTier−1 = tier ≤2; Unity AvailableKeys(step, 1) — plain rooms measurably softer.
  • G6 [BEHAVIOR] Plain-room guard loop breaks on first placement failure (web: no break — a failed spot loses one guard, not the garrison).
  • G7 [BEHAVIOR] Nest brood loop breaks on first failure — continue instead.
  • G8 [BUG] Guards/brood/timber can land on BUSH tiles (web requires strict FLOOR; RoomFloor tests !IsWall) — mobs spawn inside concealment, piles hide in bushes.
  • G9 [BEHAVIOR] spawnSafe gets an extra W*0.3 cap not in web (~35% smaller quiet bubble on narrow floors; timber gap shrinks with it).
  • G10 [LATENT] px→world conversions omit *ts in portalClear/spawnSafe (harmless at tileSize 1; same class in G34).
  • G11 [BEHAVIOR, keep] Second cache chest is clamped InRoomFloor (web is raw and can sit in a wall) — Unity is better; record as deliberate.
  • G12 [VISUAL] Nest display name from key not display-name first word. (= N24.)
  • G13 [VISUAL] Nest theme tint unapplied. (= N23.)
  • G14 [BEHAVIOR] Nest theme fallback order differs when the themed set is empty (web random roster pick; Unity prefers goblin).
  • G15 [MISSING] No POI list at all — web pushes Nest/Cache/Extraction-Chamber POIs drawn on minimap + tactical map. Add MapData.Pois + emit.
  • G16 [BEHAVIOR] Crackable loot pre-rolled at gen (web stores a closure evaluated on open). Accept or wrap in a Func.

Timber budget

  • G17 [BEHAVIOR] Pile placement gets ~12× attempts (RoomFloor's 12 tries inside the 300 guard) — web's remainder-at-spawn rail almost never fires in Unity, changing floor distribution. Sample once per iteration.
  • G20 [MISSING] Timber HUD chip — RaidState.Timber is never surfaced in any UI. Web shows #timberChip from the first plank.

Crack channel / interactions

  • G21 [BEHAVIOR] Crack reach 56px vs web 43 (p.r + c.r + 14).
  • G22 [MISSING] Explicit-tap override (a tapped container beats fight/stab suppression) — no tap path exists.
  • G23 [BEHAVIOR] Fresh-channel pick is first-Update-wins, web is nearest-eligible — matters in camps with adjacent crackables.
  • G24 [BEHAVIOR] "Empty" nudge requires stationary (web doesn't) and ignores fight/stab state.
  • G25 [VISUAL] Crack-tick spark #ffd964 missing.
  • G26 [VISUAL] Chest-open rarity burst missing (TODO in code).
  • G27 [BUG] Gate doesn't defer to an in-progress chest crack — both channels fill simultaneously and stomp PlayerChannelProgress the same frame. Web: chest channel ⇒ gate decays.
  • G28 [BEHAVIOR] Gate ignores stabReady suppression.

Loot pipeline

  • G29 [BUG] Collected shield CELLS never granted — CellsThisRaid++ only, no AddCell() on the player (contrast salves). The item is a no-op in-raid.
  • G30 [BEHAVIOR, deliberate] Magnet arming: settle grace alone arms under a stationary player (web never self-collects). Documented intentional — keep or align.
  • G31 [BEHAVIOR] Pre-spawned floor loot carries a magnet grace web doesn't have.
  • G32 [VISUAL] Floaters: timber +N 🪵 #d8a35e (Unity "+N timber" gold); web also floats +name at the item's position; ClearOfWalls nudge is a Unity-only fix (keep).

Corruption

  • G33 [BUG] First damage bite a full tick late (counts up from 0; web counts down from 0 ⇒ immediate bite on entry and on re-entry). Seed _tickT = tick on entry.
  • G34 [LATENT] warnDist/killMobs grace omit tileSize conversion.
  • G35 [BEHAVIOR] Death cause lost (TakeDamage(dmg, null)) — web's card reads "the Corruption".
  • G36 [VISUAL] Murk color brighter/pinker than web rgba(96,44,150,.34).
  • G37 [VISUAL] 46px rising glow gradient off the front missing.
  • G38 [VISUAL] Front line should be an animated sine (sin(x*0.045+t*2.2)*5, rgba(196,124,255,.85) lw3); Unity is a static quad.
  • G39 [VISUAL] Murk not fog-gated — renders over unexplored area (web draws under the fog).
  • G40 [MISSING] In-wave pulsing screen wash.
  • G41 [MISSING] Purple particles on the player inside + burst per swallowed creature.
  • G42/G43 [MISSING] Minimap + tactical-map corruption band/front line (already in known-debt).
  • G44 [BEHAVIOR] Spawn conditions: web gates on corruption.enabled && sys.timer !== false and nulls it in the defense sandbox; Unity spawns whenever a chamber exists — no suppression hooks.

Run flow / banking

  • G45 [MISSING] The Safe Pocket does not exist — web death banks safeItems() (top-safeSlots() non-stash bag items) + manual safe; Unity's death branch banks nothing. The single biggest endRun divergence: Unity death forfeits strictly more than web. (Timber/gold/bp forfeits verified correct.)
  • G46 [MISSING] Run-loadout persistence on win (found equipped pieces get origin:"stash" and write into save.loadout; death deliberately doesn't write).
  • G47 [BEHAVIOR] raidsWon skips FTUE extracts (web counts them).
  • G48 [BEHAVIOR] Re-clearing a finished biome yanks the player to the next biome's floor 1 (web moves the cursor only if the next biome was locked; else stays with "replay any floor to grind"). Guard on !wasUnlocked.
  • G49 [BEHAVIOR] progNote result-card strings absent (floor/biome unlocked, loot banked, progress safe).
  • G50 [BEHAVIOR] autoEquipEmpty ranks by level-scaled synthetic stats valued at level 1 — different ordering for levelled pieces than web's lootValue.

Smoke harness

  • G51 [MISSING] Populator never exercised (no chests/timber/nests/guards observed) — every §B/§C risk untested.
  • G52 [MISSING] Timber-budget invariant (== timberBudget per floor) unasserted — the load-bearing contract of the build economy.
  • G53 [MISSING] Chest-in-wall check covers only room centres/nest seats, not actual chest positions.
  • G54 [MISSING] Distribution stats (sideSteps, crackable counts, aspect histograms).
  • G55 [BEHAVIOR] Unseeded, unlogged sweep — failures unreplayable. Seed per floor from a printed base seed.

6. Suggested fix order

Wave 1 — the reported defects + hard gameplay bugs 1. D1 building targeting (+ D28 gw/gh, D11 target radius) · D2 tower projectiles · D3 T-spawn inset · D8 room-test latch · D9 spikes freeze · D10 gate chew 2. S1–S11 engage ring rework + aim telegraph line (one view rewrite) · S14/A22 charge lock snap 3. N1–N4 tell reticle (+ N5 hatch burst, N7 open-floor re-test) 4. N11–N14 nest wake/rally · G33 corruption first bite · G29 cells no-op · G27 gate-vs-chest channel · G5/G6/G8 populator (trash tier, garrison break, bush tiles) · A17 rocketeer/bombardier melee 5. S28–S32 stance arbiter collapse + defense-target merge (D36) · G45 Safe Pocket

Wave 2 — behavior parity (D-behavior block, A16/A19/A23–A28, A32/A36/A38, G2/G9/G17/G21–G24, G44/G47–G50, N17, D21, D30–D32)

Wave 3 — presentation + harness (D14–D16, D20, D22–D24, D33–D34, D38–D45, S15–S27, S36–S37, N15/N18/N21–N25/N27/N29, G12–G15, G20, G25–G26, G32, G36–G43, G51–G55, A1–A7)

Deliberate divergences to keep (record, don't fix): A8 dash sub-stepping, G11 clamped second cache chest, G32c ClearOfWalls nudge, N8 orphan-tell cleanup, G30 magnet settle-arm (user call). Decisions needed: A18 per-def meleeCd in raids, A29 predictive aim lead, S34 rival neutrality (parked with rivals), G16 pre-rolled crackables.