# Progression Two independent weapon progressions run at the same time, each fed by its own resource and its own fixed Pay Zone. They are not related and must not be merged into one counter. ## Bat level — `BatLevelC` * Currency: wood, dropped by crates (`ResourceC`, spawned by `BreakablePropC`) and delivered into the bat's own Pay Zone. * Displayed by `WeaponHudC` (the bottom-left octagon). * Added to the bat's damage per crate hit (`PlayerC.updateCombat` -> `BreakablePropC.hit`), on top of the debug panel's own base `hit_damage`. * Visual payoff: that Pay Zone's reward pedestal shows a floating bat (`PlayerC.createBatDisplay`). ## Pistol level — `PistolLevelC` * Currency: green zombie-part chunks, dropped by dying zombies (`ZombiePartC`, spawned by `ZombieC`) and delivered into the pistol's own Pay Zone. * Displayed by `PistolHudC`, stacked directly below `WeaponHudC`'s octagon. * Added to the pistol's base damage per shot (see `PlayerC.fireAt`). * Visual payoff: `WeaponAuraC`, an additive `Sprite` glow on the drawn pistol that grows with the level and flattens out at its own `MAX_EFFECT_LEVEL`. A `Sprite` specifically because `Character_Pistol` is a `SkinnedMesh` (a same-geometry overlay would need re-binding to the skeleton) and because a light would do nothing against unlit materials. * That Pay Zone's reward pedestal shows a floating pistol (`PlayerC.createPistolDisplay`). Both `createBatDisplay`/`createPistolDisplay` share one private helper in `PlayerC` (`createWeaponDisplay`) that rebuilds a static, unskinned copy of whichever prop's geometry — never a `.clone()` of the live mesh, since `Character_Pistol` is skinned and would otherwise stay bound to the character's own skeleton and follow the player's hand around the map. ## Pay Zones Both Pay Zones are `PayZoneC` instances, constructed in `TestSceneC` with the resource/level pair they each belong to (`getCollectedCount`, `createDepositVisual`, `onFilled`, `buildRewardIcon`). Filling one raises only its own level, then it shrinks away, pauses, and grows back **in the same spot** — there is no relocation and no randomness. * The map ships one interactive-zone marker (`UI_Interactive_Zone_02`). It is scaled down by `PAY_ZONE_SCALE` and used as-is for the pistol zone; the bat zone is a plain clone of it, offset alongside by its own width plus `PAY_ZONE_GAP` (see `TestSceneC.createPayZoneNodes`). * `PayZoneC.deposited` is a **lifetime** total per instance and must never be reset — each HUD's displayed balance is `collected - deposited`, so zeroing it would make the counter jump. Per-round progress is `deposited - zoneBaseline`. * The marker nodes are nested in the map's own hierarchy, so the bat zone's clone is positioned via `parent.worldToLocal()` rather than a world delta written straight onto `position` — same reasoning as the crate respawn path below. * Each zone's centre also carries a reward preview: a static copy of the matching weapon (see above) plus a projected DOM `+1 LVL` label. ## Crate respawn Destroyed crates come back after `RESPAWN_DELAY_MS` (30 s) at their original spot — without it a long run strips the map of wood and the bat progression dies with it. The same collider **nodes** are reused across the whole break/respawn cycle; only the `PhysicsBody` is rebuilt. `BreakablePropC.init` therefore takes both an `onDestroyed` and an `onRespawned` callback, and `PlayerC.removeObstacle` / `PlayerC.addObstacle` are mirrors of each other. `respawn()` calls `root.updateWorldMatrix(true, true)` after restoring the resting transform. `Box3.setFromObject` only refreshes the node it is handed, using its parent's *existing* `matrixWorld` — without the explicit update, the obstacle box would be sampled at the sunk, toppled transform the crate was left in when it broke.