FAIL tests/unit/ProjectileSprite.test.js
  ProjectileSprite
    ✕ sets velocity toward target angle (16 ms)
    ✕ destroys itself when off-screen (1 ms)
    ✕ tints blue for player faction and red for enemy faction (2 ms)
    ✕ applies damage and destroys on hit (1 ms)

  ● ProjectileSprite › sets velocity toward target angle

    TypeError: this.body.setVelocity is not a function

      22 |     // Velocity from angle (set directly — velocityFromAngle in constructor
      23 |     // fires before body is fully initialized, leaving velocity at 0,0)
    > 24 |     this.body.setVelocity(
         |               ^
      25 |       Math.cos(angle) * speed,
      26 |       Math.sin(angle) * speed
      27 |     );

      at new setVelocity (src/systems/ProjectileSprite.js:24:15)
      at Object.<anonymous> (tests/unit/ProjectileSprite.test.js:78:15)

  ● ProjectileSprite › destroys itself when off-screen

    TypeError: this.body.setVelocity is not a function

      22 |     // Velocity from angle (set directly — velocityFromAngle in constructor
      23 |     // fires before body is fully initialized, leaving velocity at 0,0)
    > 24 |     this.body.setVelocity(
         |               ^
      25 |       Math.cos(angle) * speed,
      26 |       Math.sin(angle) * speed
      27 |     );

      at new setVelocity (src/systems/ProjectileSprite.js:24:15)
      at Object.<anonymous> (tests/unit/ProjectileSprite.test.js:90:15)

  ● ProjectileSprite › tints blue for player faction and red for enemy faction

    TypeError: this.body.setVelocity is not a function

      22 |     // Velocity from angle (set directly — velocityFromAngle in constructor
      23 |     // fires before body is fully initialized, leaving velocity at 0,0)
    > 24 |     this.body.setVelocity(
         |               ^
      25 |       Math.cos(angle) * speed,
      26 |       Math.sin(angle) * speed
      27 |     );

      at new setVelocity (src/systems/ProjectileSprite.js:24:15)
      at Object.<anonymous> (tests/unit/ProjectileSprite.test.js:98:21)

  ● ProjectileSprite › applies damage and destroys on hit

    TypeError: this.body.setVelocity is not a function

      22 |     // Velocity from angle (set directly — velocityFromAngle in constructor
      23 |     // fires before body is fully initialized, leaving velocity at 0,0)
    > 24 |     this.body.setVelocity(
         |               ^
      25 |       Math.cos(angle) * speed,
      26 |       Math.sin(angle) * speed
      27 |     );

      at new setVelocity (src/systems/ProjectileSprite.js:24:15)
      at Object.<anonymous> (tests/unit/ProjectileSprite.test.js:116:15)

PASS tests/unit/EconomySystem.test.js
  EconomySystem
    initPlayer
      ✓ registers a player with default resources (8 ms)
      ✓ registers a player with custom starting resources (3 ms)
      ✓ emits economy:updated on registration (3 ms)
      ✓ partial defaults fill missing keys (1 ms)
    getResources
      ✓ returns undefined for unregistered player (2 ms)
      ✓ returns a snapshot of the resource object (2 ms)
    canAfford
      ✓ returns true when player has enough resources (1 ms)
      ✓ returns false when player lacks fuel (1 ms)
      ✓ returns false when player lacks ammo (7 ms)
      ✓ returns false for unknown player (2 ms)
      ✓ null/undefined cost keys are treated as free (1 ms)
      ✓ emits economy:purchaseFailed on failure (5 ms)
    deduct
      ✓ deducts fuel and ammo correctly (22 ms)
      ✓ does not change resources on insufficient funds (2 ms)
      ✓ emits economy:updated on success (1 ms)
      ✓ partial deduct works (only fuel) (1 ms)
    addIncome
      ✓ adds income to existing player (5 ms)
      ✓ auto-initialises player if not yet registered (13 ms)
      ✓ emits economy:incomeReceived and economy:updated (2 ms)
      ✓ skips null fields gracefully (3 ms)
    update
      ✓ does not throw when called (2 ms)
      ✓ _lastTick advances after enough time (1 ms)
      ✓ _lastTick does not advance within same tick interval (1 ms)
    destroy
      ✓ clears all players and event listeners (3 ms)

PASS tests/CombatSystem.test.js
  CombatSystem
    acquireTarget
      ✓ should return null when no enemies in range (9 ms)
      ✓ should return closest enemy when multiple in range (3 ms)
      ✓ should filter out dead enemies (3 ms)
    canHit
      ✓ should return false for friendly fire (2 ms)
      ✓ should return false for dead target (5 ms)
      ✓ should return false when out of range (5 ms)
      ✓ should return true when all conditions met (1 ms)
    applyDamage
      ✓ should apply damage with armor reduction (9 ms)
      ✓ should apply minimum 1 damage (2 ms)
      ✓ should apply critical hit multiplier (1 ms)
    fireProjectile
      ✓ creates a ProjectileSprite and adds it to the group (6 ms)
      ✓ tints enemy projectiles red (2 ms)
      ✓ emits combat:projectileHit on _onHit (3 ms)

PASS tests/unit/BuildingRenderer.test.js
  BuildingRenderer
    ✓ constructor creates buildings container with correct depth (8 ms)
    ✓ render() creates rectangle with Barracks color #4a90d9 (3 ms)
    ✓ render() creates rectangle with VehicleDepot color #8b4513 (1 ms)
    ✓ render() creates rectangle with Logistics color #d4a017 (4 ms)
    ✓ render() creates rectangle with AmmoFactory color #d94a4a (2 ms)
    ✓ render() creates rectangle with CommandCenter color #ffd700 (6 ms)
    ✓ render() calls orchestrator.registerBuilding() (6 ms)
    ✓ render() wires pointerdown for selection (2 ms)
    ✓ update() sets CONSTRUCTING alpha to 0.4 (2 ms)
    ✓ update() sets ACTIVE alpha to 1.0 (2 ms)
    ✓ update() pulses PRODUCING alpha between 0.7 and 1.0 (6 ms)
    ✓ select() shows selection highlight around building (3 ms)
    ✓ deselect() hides selection highlight (27 ms)
    ✓ destroyBuilding() unregisters from orchestrator and destroys graphics (1 ms)
    ✓ destroy() cleans up all buildings and container (4 ms)

PASS tests/unit/DeathHandling.test.js
  Death Handling
    DYING state — opacity tween
      ✓ infantry DYING onEnter adds opacity tween via scene.tweens.add (15 ms)
      ✓ infantry DYING onEnter sets dead flag and stops movement (4 ms)
      ✓ tank DYING onEnter adds opacity tween with same parameters (7 ms)
    Corpse / smoke-puff effect
      ✓ infantry death spawns a Graphics smoke puff at unit position (6 ms)
      ✓ tank death spawns a Graphics smoke puff at tank position (6 ms)
      ✓ smoke puff tweens scale and alpha over 300ms (2 ms)
      ✓ smoke puff is destroyed after its tween completes (3 ms)
    Kill event
      ✓ infantry DYING emits unit:killed after 500ms (4 ms)
      ✓ tank DYING emits unit:killed after 500ms (8 ms)
      ✓ unit:killed payload includes entity reference (18 ms)
    Cleanup
      ✓ DYING onEnter removes unit from its parent container (3 ms)
      ✓ unit is destroyed after kill event (via tween onComplete) (5 ms)
      ✓ scene shutdown destroys all tracked effects (2 ms)

PASS tests/unit/CombatSystem.test.js
  CombatSystem
    constructor
      ✓ initializes projectiles group and damage modifiers (9 ms)
      ✓ teamManager is stored (1 ms)
    acquireTarget
      ✓ returns null when enemy container has no units (3 ms)
      ✓ returns null when all enemies are dead (2 ms)
      ✓ uses per-entity weaponRange from components.combat (2 ms)
      ✓ falls back to components.combat.range when weaponRange absent (3 ms)
      ✓ finds closest enemy within range (2 ms)
      ✓ filters by fov cone (1 ms)
      ✓ prioritizes weakest when specified (6 ms)
      ✓ returns null for null enemy container (2 ms)
    canHit
      ✓ returns false for null entities (2 ms)
      ✓ returns false for friendly fire (same container) (3 ms)
      ✓ returns false for dead target (2 ms)
      ✓ returns false when target is out of range (1 ms)
    applyDamage
      ✓ deals damage reducing health (2 ms)
      ✓ returns 0 for dead entity (1 ms)
      ✓ armor reduces damage taken (1 ms)
      ✓ deals at least 1 damage (1 ms)
      ✓ calls handleDeath when health drops to 0 (1 ms)
      ✓ emits combat:unitDamaged on scene (1 ms)
    fireProjectile
      ✓ returns null for invalid entities (1 ms)
      ✓ creates a ProjectileSprite and stashes data (5 ms)
      ✓ sets projectile data (damage, damageType, attacker, target) (5 ms)
    update
      ✓ handles empty projectile group (1 ms)
      ✓ destroys expired projectiles (8 ms)
      ✓ destroys inactive projectiles (11 ms)
      ✓ auto-engage finds target and fires projectile (2 ms)
      ✓ auto-engage no-op when container is empty
      ✓ auto-engage respects per-entity range config (1 ms)
    hasLineOfSight
      ✓ returns true when no rockLayer (3 ms)
      ✓ returns true when worldToTileXY returns null (1 ms)

PASS tests/VictoryScene.test.js
  VictoryScene
    create
      ✓ should create dark overlay background (7 ms)
      ✓ should show VICTORY when local player is winner (3 ms)
      ✓ should show DEFEAT when local player is not winner (4 ms)
      ✓ should display elapsed time formatted as mm:ss (2 ms)
      ✓ should display unit kill count (2 ms)
      ✓ should display buildings built count (5 ms)
      ✓ should display CP captured count (16 ms)
      ✓ should create a Play Again button (2 ms)
    interaction
      ✓ should launch Server_Connector scene on play again click (3 ms)

/root/restitution/src/systems/PathfindingSystem.js:199
      const sx = clamp(Math.round(startTileX), this.grid[0].length - 1);
                                                            ^

[TypeError: Cannot read properties of undefined (reading 'length')]

Node.js v22.22.3
PASS tests/WinCondition.test.js
  WinCondition
    initialization
      ✓ should set threshold to 100 by default (3 ms)
      ✓ should accept custom threshold (1 ms)
      ✓ should track game start time (1 ms)
      ✓ should register combat:unitDamaged listener on economy events (1 ms)
    victory detection
      ✓ should emit game:victory when a player reaches threshold (1 ms)
      ✓ should emit victory only once per game (1 ms)
      ✓ should not emit victory when no player has reached threshold (1 ms)
      ✓ should detect victory for the correct player when multiple exist (1 ms)
    stats tracking
      ✓ should increment unitsKilled on combat:unitDamaged when target dies (1 ms)
      ✓ should not increment unitsKilled when target does not die (2 ms)
      ✓ should increment buildingsBuilt on building:spawned (1 ms)
      ✓ should increment cpCaptured on economy:incomeReceived with capturePoints (4 ms)
    elapsed time
      ✓ should calculate elapsed time from game start to victory (4 ms)
    cleanup
      ✓ should remove listeners on destroy (1 ms)

PASS tests/unit/EntityStateMachine.test.js
  EntityStateMachine
    constructor
      ✓ stores entity and machine config (2 ms)
      ✓ default initial state is IDLING
    send
      ✓ sends event to service when available (1 ms)
      ✓ sends event with context (1 ms)
      ✓ does not throw when service is null (1 ms)
      ✓ does not throw when service has no send method (1 ms)
    getState
      ✓ returns service state value when available (4 ms)
      ✓ falls back to _currentState when service is null (1 ms)
      ✓ falls back to _currentState when service.state is null (4 ms)
    state transitions
      ✓ starts in IDLING (1 ms)
      ✓ can simulate state changes via send (1 ms)
    tick
      ✓ does not throw when called
      ✓ can be called multiple times without side effects (1 ms)
      ✓ auto-engage sends ATTACK when combat finds a target (1 ms)
      ✓ auto-engage no-op when no target found (1 ms)
      ✓ auto-engage no-op when state is not IDLING (2 ms)
    destroy
      ✓ stops service if it has a stop method (1 ms)
      ✓ handles service without stop method gracefully (1 ms)
      ✓ handles null service gracefully
    edge cases
      ✓ handles empty machineConfig (4 ms)
      ✓ handles rapid send calls (1 ms)

PASS tests/Unit.test.js
  Unit
    Component Access
      ✓ should have health component (18 ms)
      ✓ should have owner component (2 ms)
      ✓ should have combat component (6 ms)
      ✓ should update component with setComponent (5 ms)
    Damage System
      ✓ should apply damage with armor reduction (2 ms)
      ✓ should apply minimum 1 damage (2 ms)
      ✓ should emit unit:damaged event (2 ms)
      ✓ should mark unit as dead when health reaches 0 (1 ms)
      ✓ should not damage if already dead (5 ms)
    Heal System
      ✓ should heal unit (2 ms)
      ✓ should not exceed max HP (5 ms)
    Combat
      ✓ should return true when target in range (2 ms)
      ✓ should return false when target out of range (12 ms)
      ✓ should return false when target is dead (1 ms)
      ✓ should attack target when in range (11 ms)
      ✓ should not attack when target out of range (5 ms)
      ✓ should respect fire rate (2 ms)
    Selection
      ✓ should select unit (1 ms)
      ✓ should unselect unit (9 ms)
      ✓ should tint based on team (1 ms)
    Movement
      ✓ should move to tile (1 ms)
      ✓ should set target tile data (1 ms)
      ✓ should orient to target (5 ms)
    State Machine
      ✓ should initialize state machine (1 ms)
      ✓ should tick state machine in preUpdate (1 ms)
    Death
      ✓ should trigger death when health reaches 0 (3 ms)
      ✓ should cleanup on destroy (1 ms)

PASS tests/unit/BuildMenu.test.js
  BuildMenu
    ✓ constructor creates a container fixed to camera (4 ms)
    ✓ container has 4 building buttons (2 ms)
    ✓ each button has a text label (3 ms)
    ✓ each button shows its resource cost (2 ms)
    ✓ clicking a button calls onSelect with building type (2 ms)
    ✓ updateAffordability disables buttons player cannot afford (3 ms)
    ✓ updateAffordability enables affordable buttons (2 ms)
    ✓ destroy cleans up container and buttons (2 ms)

PASS tests/Map_Player.test.js
  Map_Player — F-key spawn
    ✓ F-key spawns unit in scene (17 ms)
    ✓ spawned unit is selectable (5 ms)
  Interface — init(false)
    ✓ init(false) does NOT wire pointer DOWN/MOVE/UP or create pathfinder (2 ms)

PASS tests/unit/ControlPointManager.test.js
  ControlPointManager
    constructor
      ✓ creates 4 control points (4 ms)
      ✓ accepts optional teamManager parameter (2 ms)
      ✓ falls back to scene.teamManager when no teamManager passed (2 ms)
      ✓ CPs are at clearing centers converted to world coords (2 ms)
      ✓ tileToWorldXY is called for each CP (2 ms)
      ✓ each CP has type=controlPoint, captureTime=60000, radius=5 tiles (2 ms)
    update
      ✓ ticks every CP (3 ms)
    CP income
      ✓ each CP is wired with the economy system on construction (2 ms)
      ✓ does NOT call addIncome when state is not CAPTURED (2 ms)
      ✓ does NOT call addIncome when owner is null (1 ms)
    destroy
      ✓ destroys all CPs and clears the array (5 ms)

PASS test/systems/TeamManager.test.js
  TeamManager
    ✓ createTeam returns Team with id/color/name, duplicate returns same object (2 ms)
    ✓ getTeam returns undefined for unknown teamId (1 ms)
    ✓ setPlayerTeam maps playerId to teamId (1 ms)
    ✓ getTeamPlayers returns set of players in team (1 ms)
    ✓ addUnit sets unit data, adds to Team.units, appears in getTeamUnits (5 ms)
    ✓ removeUnit removes from one team, re-add to another works (1 ms)
    ✓ getUnitTeam returns null for unregistered unit (15 ms)
    ✓ getAllUnits returns flat array of all units (4 ms)
    ✓ getAllUnitsGrouped returns Map keyed by teamId (2 ms)
    ✓ addBuilding/removeBuilding/getBuildingTeam follow same pattern (1 ms)
    ✓ isEnemy/isSameTeam: same team=false, different=true, null=not enemy (2 ms)
    ✓ getEnemyUnits returns all units NOT in given team (1 ms)

PASS tests/unit/ProductionPanel.test.js
  ProductionPanel
    ✓ constructor creates container fixed to camera at bottom-right (4 ms)
    ✓ constructor starts hidden (2 ms)
    ✓ show() renders building name and state (5 ms)
    ✓ show() renders Add Unit buttons for production building (19 ms)
    ✓ show() with COMMAND_CENTER has no unit buttons (1 ms)
    ✓ clicking Add Unit deducts cost and adds to queue (6 ms)
    ✓ clicking Add Unit when unaffordable does not deduct or queue (4 ms)
    ✓ Add Unit disabled when queue is full (5 ms)
    ✓ hide() hides container and clears selection (6 ms)
    ✓ destroy() cleans up container and buttons (2 ms)
    ✓ update() sets progress bar width based on production time (2 ms)
    ✓ show() with new building clears previous unit buttons (5 ms)
    ✓ scene pointerdown outside panel hides it (7 ms)

PASS tests/unit/BuildingPlacer.test.js
  BuildingPlacer
    ✓ constructor creates a hidden ghost sprite (5 ms)
    ✓ constructor wires pointermove and pointerdown (3 ms)
    ✓ startPlacement shows ghost and remembers building type (4 ms)
    ✓ updateGhost snaps ghost to tile grid (5 ms)
    ✓ isValidPlacement returns true for open ground (1 ms)
    ✓ isValidPlacement returns false for collision tiles (rock) (8 ms)
    ✓ isValidPlacement returns false for water tiles (1 ms)
    ✓ isValidPlacement returns false when overlapping existing buildings (1 ms)
    ✓ ghost tint is green when placement is valid (7 ms)
    ✓ ghost tint is red when placement is invalid (1 ms)
    ✓ tryPlace deducts resources via economy (1 ms)
    ✓ tryPlace calls orchestrator.registerBuilding (1 ms)
    ✓ tryPlace emits building:placed event (1 ms)
    ✓ tryPlace hides ghost after placement (1 ms)
    ✓ tryPlace returns false when player cannot afford (1 ms)
    ✓ cancel hides ghost and resets state (1 ms)
    ✓ destroy removes ghost and unregisters listeners (2 ms)

PASS test/scenes/Map_Player.test.js
  Map_Player — TeamManager rewiring
    ✓ does NOT create goodGuys / badGuys Phaser containers (16 ms)
    ✓ creates TeamManager with three teams for FFA (9 ms)
    ✓ UnitFactory receives teamManager as second arg (8 ms)
    ✓ spawn calls use teamId strings (team-A, team-B) (7 ms)
    ✓ 3-team spawn: each team gets different color (10 ms)
    ✓ ProductionPanel onProductionComplete passes teamId to UnitFactory (6 ms)
    ✓ CombatSystem and ControlPointManager receive teamManager (7 ms)
    ✓ combat resolves correctly across all 3 teams (18 ms)
    ✓ control point captures correctly with 3 teams (8 ms)
    ✓ UI shows correct team colors for all 3 teams (5 ms)

PASS tests/unit/CaptureProgressUI.test.js
  CaptureProgressUI
    ✓ update lazily draws a bar for a new CP (3 ms)
    ✓ bar fill reflects capture progress at 50% (1 ms)
    ✓ bar fill width scales with progress fraction (1 ms)
    ✓ getColor returns grey for NEUTRAL (8 ms)
    ✓ getColor returns yellow for CONTESTED (1 ms)
    ✓ getColor returns green when captured by player (4 ms)
    ✓ getColor returns red when captured by enemy (1 ms)
    ✓ destroy(cp) removes bar from Map and destroys graphics (10 ms)
    ✓ shutdown destroys all bars and clears Map (2 ms)

PASS test/systems/ControlPointStateMachine.test.js
  ControlPointStateMachine (multi-team)
    ✓ registerUnitContainers is NOT a method on the instance (2 ms)
    ✓ getUnitsInRadius counts units per teamId via TeamManager (2 ms)
    ✓ NEUTRAL → CONTESTED when units from multiple teams present (2 ms)
    ✓ CONTESTED → CAPTURED when one team reaches majority (progress hits 100) (2 ms)
    ✓ owner stored as teamId string after capture (2 ms)
    ✓ constructor accepts teamManager via config (1 ms)
    ✓ falls back to scene.teamManager if no teamManager in config (3 ms)

PASS test/systems/CombatSystem.test.js
  CombatSystem (multi-team)
    ✓ constructor accepts TeamManager, not containers (3 ms)
    ✓ registerUnitContainers removed from public API (6 ms)
    ✓ _processCombatGroup iterates all team groups (4 ms)
    ✓ _checkOverlap checks all teams (2 ms)
    ✓ acquireTarget returns enemy unit from any team (2 ms)
    ✓ friendly fire prevented by team check in canHit (1 ms)
    ✓ projectile from team-A hits team-B and team-C units (2 ms)
    ✓ projectile from team-A does NOT hit team-A units (1 ms)

PASS test/entities/Unit.test.js
  Unit (team-aware)
    ✓ getEnemyContainer removed — no longer exists on prototype (3 ms)
    ✓ getFriendlyContainer removed — no longer exists on prototype (1 ms)
    ✓ select() uses teamId for tint color via TeamManager (2 ms)
    ✓ isEnemyOf delegates to TeamManager (1 ms)
    ✓ Unit without teamId has null team data (1 ms)

PASS tests/EconomySystem.test.js
  EconomySystem
    initPlayer
      ✓ should initialize player with default resources (1 ms)
      ✓ should initialize player with custom resources (1 ms)
    canAfford
      ✓ should return true when player has enough resources (1 ms)
      ✓ should return false when player lacks fuel
      ✓ should return false when player lacks ammo (1 ms)
      ✓ should return false for non-existent player
    deduct
      ✓ should deduct resources and return true (1 ms)
      ✓ should not deduct and return false when insufficient resources (1 ms)
      ✓ should emit economy:purchaseFailed on insufficient resources (1 ms)
    addIncome
      ✓ should add income to player resources (1 ms)
      ✓ should auto-initialize player if not exists (1 ms)
      ✓ should emit economy:incomeReceived and economy:updated (1 ms)
    update
      ✓ should track elapsed time for income tick guard (1 ms)
      ✓ should not fire tick before 1000ms

PASS tests/unit/HealthBarSystem.test.js
  HealthBarSystem
    ✓ drawBar creates a bar whose width matches unit displayWidth (2 ms)
    ✓ getColor returns green at 100%, yellow at 50%, red below 25% (1 ms)
    ✓ bar is hidden at full HP and shown when damaged (4 ms)
    ✓ destroy(unit) destroys the health bar graphics (1 ms)
    ✓ flash sets tint color briefly (1 ms)

PASS tests/unit/ResourceBar.test.js
  ResourceBar
    ✓ constructor creates a Phaser Text HUD element (4 ms)
    ✓ HUD text is fixed to camera (scrollFactor 0) (8 ms)
    ✓ HUD text is at top-left by default (1 ms)
    ✓ updateFromResources shows fuel / ammo / CP (2 ms)
    ✓ format includes emoji/color icons (12 ms)
    ✓ setEconomySystem wires economy:updated listener (3 ms)
    ✓ economy:updated auto-updates the bar (1 ms)
    ✓ ignores economy:updated for other players (1 ms)
    ✓ bar text shows default starting resources on creation (5 ms)
    ✓ destroy removes the Phaser Text object (1 ms)

PASS tests/unit/BuildingIncome.test.js
  BuildingStateMachine income tick
    ✓ tick returns null when no income configured (1 ms)
    ✓ tick returns income when ACTIVE and first tick (1 ms)
    ✓ tick returns null when CONSTRUCTING even with income config (1 ms)
    ✓ income is rate-limited to once per 1000ms (15 ms)
    ✓ startActive flag sets state to ACTIVE immediately (1 ms)
    ✓ playerId is stored and accessible (4 ms)
    ✓ income with both fuel and ammo (1 ms)
  SystemOrchestrator building income wiring
    ✓ simulated update loop adds income for ACTIVE buildings only (1 ms)
    ✓ simulated update loop skips CONSTRUCTING and no-income buildings (1 ms)

PASS test/systems/UnitFactory.test.js
  UnitFactory (with TeamManager)
    ✓ constructor stores scene and teamManager (2 ms)
    ✓ spawnInfantry adds unit to correct team via TeamManager (1 ms)
    ✓ spawnTank adds unit to correct team via TeamManager
    ✓ no references to scene.goodGuys or scene.badGuys remain
    ✓ skin selection: team index 0 -> Ukrainian infantry (1 ms)
    ✓ skin selection: team index 0 -> Ukrainian tank
    ✓ skin selection: team index 1 -> Russian infantry (1 ms)
    ✓ skin selection: team index 1 -> Russian tank (1 ms)
    ✓ skin selection: team index 2+ -> Russian fallback infantry
    ✓ skin selection: team index 2+ -> Russian fallback tank (6 ms)

PASS test/systems/ControlPointManager.test.js
  ControlPointManager
    ✓ constructor accepts teamManager parameter (6 ms)
    ✓ update delegates tick to each CP using teamManager for unit counts (2 ms)
    ✓ each CP has a reference to teamManager on construction (2 ms)
    ✓ falls back to scene.teamManager when no teamManager passed (1 ms)

PASS test/entities/components/OwnerComponent.test.js
  OwnerComponent (team-aware)
    ✓ teamId replaces good/enemy string (1 ms)
    ✓ isEnemy delegates to TeamManager (1 ms)
    ✓ isSameTeam delegates to TeamManager

FAIL tests/App.test.js
  ● Test suite failed to run

    Cannot find module 'bufferutil' from 'node_modules/colyseus.js/dist/colyseus.js'

    Require stack:
      node_modules/colyseus.js/dist/colyseus.js
      src/systems/ColyseusClient.js
      src/components/app.jsx
      tests/App.test.js

    > 1 | import { Client } from "colyseus.js";
        | ^
      2 |
      3 | class ColyseusClient {
      4 |   constructor() {

      at Resolver._throwModNotFoundError (node_modules/jest-resolve/build/index.js:895:11)
      at node_modules/colyseus.js/dist/colyseus.js:3:242
      at Object.<anonymous> (node_modules/colyseus.js/dist/colyseus.js:6:3)
      at Object.require (src/systems/ColyseusClient.js:1:1)
      at Object.require (src/components/app.jsx:5:1)
      at Object.require (tests/App.test.js:38:1)

PASS tests/LobbyScreen.test.js
  LobbyScreen
    ✓ renders Create Game and Join Game buttons on initial load (38 ms)
    ✓ calls createGame and shows CircularProgress when Create is clicked (45 ms)
    ✓ displays the invite code and player count after creation (27 ms)
    ✓ shows Join mode with TextField when Join Game is clicked (26 ms)
    ✓ disables Join button until exactly 4 characters are entered (20 ms)
    ✓ shows Alert error when joinGame rejects (38 ms)
    ✓ calls onGameStart when 2+ players connect after creating (11 ms)

/root/restitution/src/systems/PathfindingSystem.js:199
      const sx = clamp(Math.round(startTileX), this.grid[0].length - 1);
                                                            ^

[TypeError: Cannot read properties of undefined (reading 'length')]

Node.js v22.22.3
/root/restitution/src/systems/PathfindingSystem.js:199
      const sx = clamp(Math.round(startTileX), this.grid[0].length - 1);
                                                            ^

[TypeError: Cannot read properties of undefined (reading 'length')]

Node.js v22.22.3
/root/restitution/src/systems/PathfindingSystem.js:199
      const sx = clamp(Math.round(startTileX), this.grid[0].length - 1);
                                                            ^

[TypeError: Cannot read properties of undefined (reading 'length')]

Node.js v22.22.3
FAIL tests/unit/PathfindingSystem.test.js
  ● Test suite failed to run

    Jest worker encountered 4 child process exceptions, exceeding retry limit

      at ChildProcessWorker.initialize (node_modules/jest-runner/node_modules/jest-worker/build/index.js:801:21)

Summary of all failing tests
FAIL tests/unit/ProjectileSprite.test.js
  ● ProjectileSprite › sets velocity toward target angle

    TypeError: this.body.setVelocity is not a function

      22 |     // Velocity from angle (set directly — velocityFromAngle in constructor
      23 |     // fires before body is fully initialized, leaving velocity at 0,0)
    > 24 |     this.body.setVelocity(
         |               ^
      25 |       Math.cos(angle) * speed,
      26 |       Math.sin(angle) * speed
      27 |     );

      at new setVelocity (src/systems/ProjectileSprite.js:24:15)
      at Object.<anonymous> (tests/unit/ProjectileSprite.test.js:78:15)

  ● ProjectileSprite › destroys itself when off-screen

    TypeError: this.body.setVelocity is not a function

      22 |     // Velocity from angle (set directly — velocityFromAngle in constructor
      23 |     // fires before body is fully initialized, leaving velocity at 0,0)
    > 24 |     this.body.setVelocity(
         |               ^
      25 |       Math.cos(angle) * speed,
      26 |       Math.sin(angle) * speed
      27 |     );

      at new setVelocity (src/systems/ProjectileSprite.js:24:15)
      at Object.<anonymous> (tests/unit/ProjectileSprite.test.js:90:15)

  ● ProjectileSprite › tints blue for player faction and red for enemy faction

    TypeError: this.body.setVelocity is not a function

      22 |     // Velocity from angle (set directly — velocityFromAngle in constructor
      23 |     // fires before body is fully initialized, leaving velocity at 0,0)
    > 24 |     this.body.setVelocity(
         |               ^
      25 |       Math.cos(angle) * speed,
      26 |       Math.sin(angle) * speed
      27 |     );

      at new setVelocity (src/systems/ProjectileSprite.js:24:15)
      at Object.<anonymous> (tests/unit/ProjectileSprite.test.js:98:21)

  ● ProjectileSprite › applies damage and destroys on hit

    TypeError: this.body.setVelocity is not a function

      22 |     // Velocity from angle (set directly — velocityFromAngle in constructor
      23 |     // fires before body is fully initialized, leaving velocity at 0,0)
    > 24 |     this.body.setVelocity(
         |               ^
      25 |       Math.cos(angle) * speed,
      26 |       Math.sin(angle) * speed
      27 |     );

      at new setVelocity (src/systems/ProjectileSprite.js:24:15)
      at Object.<anonymous> (tests/unit/ProjectileSprite.test.js:116:15)

FAIL tests/App.test.js
  ● Test suite failed to run

    Cannot find module 'bufferutil' from 'node_modules/colyseus.js/dist/colyseus.js'

    Require stack:
      node_modules/colyseus.js/dist/colyseus.js
      src/systems/ColyseusClient.js
      src/components/app.jsx
      tests/App.test.js

    > 1 | import { Client } from "colyseus.js";
        | ^
      2 |
      3 | class ColyseusClient {
      4 |   constructor() {

      at Resolver._throwModNotFoundError (node_modules/jest-resolve/build/index.js:895:11)
      at node_modules/colyseus.js/dist/colyseus.js:3:242
      at Object.<anonymous> (node_modules/colyseus.js/dist/colyseus.js:6:3)
      at Object.require (src/systems/ColyseusClient.js:1:1)
      at Object.require (src/components/app.jsx:5:1)
      at Object.require (tests/App.test.js:38:1)

FAIL tests/unit/PathfindingSystem.test.js
  ● Test suite failed to run

    Jest worker encountered 4 child process exceptions, exceeding retry limit

      at ChildProcessWorker.initialize (node_modules/jest-runner/node_modules/jest-worker/build/index.js:801:21)


Test Suites: 3 failed, 28 passed, 31 total
Tests:       4 failed, 332 passed, 336 total
Snapshots:   0 total
Time:        4.307 s
Ran all test suites.
