Files
restitution/1
kaykayyali 8fc45968b5 M2.3 + M2.4 + TeamManager integration: projectile sprites, death handling, build menu, production panel, building placer
- ProjectileSprite.js: physics arcade sprite, faction tint, off-screen culling
- CombatSystem: refactored enemy selection to use TeamManager instead of legacy containers
- Death handling: DYING alpha tween (500ms), smoke puff (300ms), unit:killed event, cleanup
- TeamManager: centralized team registry replacing goodGuys/badGuys containers
- HealthBarSystem, ResourceBar, CaptureProgressUI, BuildMenu, BuildingPlacer, BuildingRenderer, ProductionPanel
- Map_Player: wired new subsystems, removed legacy container creation
- Tests: ProjectileSprite (4), DeathHandling (13), CombatSystem updated

47 tests passed at dev time (M2.3), 158/158 at dev time (M2.4)
2026-06-01 05:18:33 +00:00

697 lines
30 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.