PASS tests/PathfindingService.test.ts
  PathfindingService
    constructor
      ✓ creates an EasyStar instance with correct config (11 ms)
    setGrid
      ✓ sets the grid and acceptable tiles on EasyStar (2 ms)
    setWalkable
      ✓ marks a tile as blocked (1) and updates EasyStar grid (1 ms)
      ✓ marks a tile as walkable (0) (1 ms)
      ✓ no-ops on out-of-bounds coordinates (1 ms)
      ✓ no-ops when value unchanged
    findPath
      ✓ resolves with path when EasyStar finds one (2 ms)
      ✓ resolves with null when no path exists (2 ms)
    isValidMove
      ✓ returns true when a path exists between adjacent tiles (2 ms)
      ✓ returns false when no path exists (1 ms)
      ✓ returns false when to tile is blocked (1 ms)
    straight-line path
      ✓ returns direct horizontal path on clear grid (1 ms)

PASS tests/roomLogic.test.ts
  roomLogic
    nextTeam
      ✓ should return ukraine for empty player list (9 ms)
      ✓ should balance: 1 ukraine → next is russia (2 ms)
      ✓ should balance: 1 ukraine + 1 russia → next is ukraine (1 ms)
      ✓ should maintain balance with 2 ukraine + 1 russia → next is russia (1 ms)
      ✓ should maintain balance with 2 each → next is ukraine (1 ms)
    canJoin
      ✓ should allow join when below max (1 ms)
      ✓ should reject when at max (1 ms)
      ✓ should reject when above max (1 ms)
    createPlayer
      ✓ should create a player with correct defaults (1 ms)
      ✓ should create a russia player (1 ms)
    disconnectPlayer
      ✓ should set connected to false and ready to false (1 ms)

PASS tests/index.test.ts
  server
    ✓ placeholder — integration test passed via curl (7 ms)

PASS tests/inputHandler.test.ts
  handleInput - spawnUnit
    ✓ delegates to UnitManager.spawnUnit and returns the unit (2 ms)
    ✓ uses infantry if unitType is infantry (1 ms)
  handleInput - moveUnit
    ✓ delegates to UnitManager.moveUnit
  handleInput - attackUnit
    ✓ delegates to UnitManager.attackUnit (1 ms)
  handleInput - damageUnit
    ✓ delegates to UnitManager.damageUnit
  handleInput - removeDeadUnits
    ✓ delegates to UnitManager.removeDeadUnits (1 ms)
  handleInput - applyEvent
    ✓ applies a UnitEvent to a unit (1 ms)
  handleInput - unknown type
    ✓ returns null for unrecognized message type
  handleInput - getUnitsInRange
    ✓ returns units in range for the given team
  handleInput - full client flow
    ✓ spawn → move → damage → cleanup (1 ms)

PASS tests/EconomyService.test.ts
  EconomyService
    ✓ should initialize a player with default values (0 resources, 0 incomeRate, lastTick=0) (7 ms)
    ✓ should return 0 for an unknown player
    ✓ should return current resources for a known player (1 ms)
    ✓ should add income when 1000ms has elapsed since lastTick (1 ms)
    ✓ should NOT add income before 1000ms has elapsed
    ✓ should accumulate income for multiple elapsed intervals (1 ms)
    ✓ should add nothing when incomeRate is 0
    ✓ should only count whole intervals (1500ms → 1 tick, not 1.5) (1 ms)
    ✓ should track lastTick per player independently (1 ms)
    ✓ should return true when player has sufficient resources (1 ms)
    ✓ should return false when player has insufficient resources
    ✓ should return true when cost equals resources exactly (1 ms)
    ✓ should return false for an unknown player
    ✓ should reduce resources and return true on success
    ✓ should return false and leave resources unchanged when insufficient (1 ms)
    ✓ should return false for an unknown player without mutating state
    ✓ should add income to an existing player
    ✓ should auto-initialize a player when addIncome is called before initPlayer (1 ms)
    ✓ should default to adding 0 when no amount is provided
    ✓ should set the income rate for a known player
    ✓ should auto-initialize a player when setIncomeRate is called before initPlayer (1 ms)
    ✓ should keep multiple players' economies independent
    ✓ should allow initializing a player multiple times (reset) (1 ms)

PASS tests/unit-states.test.ts
  UnitState enum
    ✓ has 5 states: IDLING, MOVING, ATTACKING, DYING, DESTROYED (7 ms)
  UnitEvent enum
    ✓ has 7 events (1 ms)
  STATE_TRANSITIONS
    ✓ IDLING transitions: MOVE → MOVING, ATTACK → ATTACKING, DIE → DYING (1 ms)
    ✓ MOVING transitions: ARRIVED → IDLING, ENEMY_SPOTTED → ATTACKING, DIE → DYING (1 ms)
    ✓ ATTACKING transitions: TARGET_LOST → IDLING, OUT_OF_RANGE → MOVING, DIE → DYING (1 ms)
    ✓ DYING has no explicit transitions (empty object) (1 ms)
    ✓ DESTROYED has no transitions (terminal)
    ✓ has entries for all 5 states
    ✓ all transition targets are valid UnitState values (2 ms)
  isValidTransition
    ✓ returns true for valid transition: IDLING + MOVE → MOVING
    ✓ returns true for valid transition: ATTACKING + TARGET_LOST → IDLING (1 ms)
    ✓ returns false for invalid transition: IDLING + ARRIVED → whatever
    ✓ returns false for wrong target: IDLING + MOVE → ATTACKING
    ✓ returns false from DESTROYED (terminal)
    ✓ returns false for unknown state (safety)
  validEventsFor
    ✓ returns [MOVE, ATTACK, DIE] for IDLING (1 ms)
    ✓ returns [] for DESTROYED
    ✓ returns [] for unknown state
  nextState
    ✓ returns MOVING for IDLING + MOVE (1 ms)
    ✓ returns DYING for ATTACKING + DIE
    ✓ returns null for invalid transition
    ✓ returns null for unknown state (1 ms)
    ✓ returns null when event not valid for state (2 ms)

PASS tests/UnitManager.test.ts
  UnitManager - spawnUnit
    ✓ creates a unit with id, ownerId, type, team, position (7 ms)
    ✓ new unit starts IDLING with full health
    ✓ different types get the right max health (tank=150, infantry=100)
    ✓ assigns unique IDs to each unit
    ✓ stores units internally (getUnit retrieves by id) (1 ms)
    ✓ getUnit returns undefined for unknown id
  UnitManager - moveUnit
    ✓ sets path and transitions to MOVING (1 ms)
    ✓ does nothing for non-existent unit id
    ✓ does nothing for dead unit
  UnitManager - attackUnit
    ✓ sets targetId and transitions to ATTACKING
    ✓ does nothing for non-existent attacker (2 ms)
    ✓ does nothing for dead attacker (1 ms)
  UnitManager - damageUnit
    ✓ reduces health.current by the damage amount (1 ms)
    ✓ transitions to DYING when health reaches 0
    ✓ clamps health.current to 0 on overkill (1 ms)
    ✓ does nothing for non-existent unit
    ✓ does nothing for already dead unit
  UnitManager - removeDeadUnits
    ✓ returns IDs of units in DYING state (2 ms)
    ✓ removes dead units from internal storage
    ✓ returns empty array when no units are dead (1 ms)
    ✓ returns multiple IDs when multiple units are dead (1 ms)
  UnitManager - getUnitsInRange
    ✓ returns units within the given range (1 ms)
    ✓ filters by team
    ✓ returns empty array when no units in range (1 ms)
    ✓ does not return dead units
    ✓ excludes units from the querying team
  UnitManager - full lifecycle
    ✓ spawn → move → attack → damage → destroy cycle (1 ms)
  UnitManager - applyEvent
    ✓ applies a valid state transition event (1 ms)
    ✓ ignores invalid transition (does not change state)
    ✓ returns null for non-existent unit

PASS tests/GameState.test.ts
  GameState schema
    ✓ should initialize with an empty players array (7 ms)
    ✓ should allow adding a Player with id, team, and ready (2 ms)
    ✓ should track connected status and role (1 ms)
    ✓ should support multiple players with different teams (1 ms)

PASS tests/generateCode.test.ts
  generateCode
    ✓ should return a string of the requested length (6 ms)
    ✓ should return a string for length 6 (1 ms)
    ✓ should only contain uppercase alphanumeric characters (11 ms)
    ✓ should not contain ambiguous characters (0, O, 1, I, L) (25 ms)
    ✓ should generate different codes on successive calls (1 ms)

PASS tests/building-types.test.ts
  BUILDING_TYPES
    ✓ has 5 building types (3 ms)
    ✓ COMMAND_CENTER: no build cost, no productions, no income, health 1000 (2 ms)
    ✓ BARRACKS: cost 50 ammo, build time 10s, produces infantry, health 400, maxQueue 5 (1 ms)
    ✓ VEHICLE_DEPOT: cost 100 fuel, build time 20s, produces tank, health 600, maxQueue 3 (1 ms)
    ✓ LOGISTICS: cost 75 fuel, income +5 fuel/tick, health 350 (1 ms)
    ✓ AMMO_FACTORY: cost 75 ammo, income +5 ammo/tick, health 350
  getBuildingType
    ✓ returns the building config for a valid id (1 ms)
    ✓ returns undefined for an unknown id
  getAllBuildingTypes
    ✓ returns the full BUILDING_TYPES map

PASS tests/systems/CombatResolver.test.ts
  CombatResolver.findTarget
    ✓ returns null for empty target list (3 ms)
    ✓ returns the only target when one is in range (1 ms)
    ✓ returns null when the only target is out of range
    ✓ picks the closest target when multiple are in range
    ✓ skips dead targets
    ✓ returns null when all targets are dead (1 ms)
    ✓ picks weakest (lowest HP) when priority is 'weakest'
    ✓ picks strongest (highest HP) when priority is 'strongest'
    ✓ defaults to closest when priority is omitted
    ✓ filters by line of sight — target behind wall is skipped
    ✓ selects target with clear line of sight when grid is provided
  CombatResolver.hasLineOfSight
    ✓ returns true for adjacent tiles on empty grid
    ✓ returns true for diagonal on empty grid
    ✓ returns false when a wall tile is on the horizontal path (1 ms)
    ✓ returns false when a wall tile is on the vertical path
    ✓ returns false when a wall tile is on the diagonal path
    ✓ does not block on the starting tile
    ✓ returns true when start and target are the same tile
    ✓ returns true for empty grid
    ✓ returns true for long clear horizontal line
    ✓ clamps out-of-bounds coordinates to grid edges
  CombatResolver.calculateDamage
    ✓ applies base damage with no armor and no crit (1 ms)
    ✓ reduces damage by armor
    ✓ applies armor piercing — reduces effective armor
    ✓ always deals at least 1 damage when damage > 0
    ✓ returns 0 damage when base damage is 0 (no min-1 for zero)
    ✓ crits multiply damage (1 ms)
    ✓ crit + armor piercing work together
    ✓ uses default damage modifiers when damageType is unknown
    ✓ includes damage type in result
  CombatResolver.applyDamage
    ✓ subtracts damage from health
    ✓ marks unit as dead when health reaches 0
    ✓ marks unit as dead when health goes below 0
    ✓ does not modify already-dead units
    ✓ returns a new object (does not mutate original)
    ✓ preserves other fields on the unit (1 ms)
  CombatResolver damage modifiers
    ✓ rifle modifier: AP 0.1, critChance 0.05, critMultiplier 1.5
    ✓ cannon modifier: AP 0.5, critChance 0.10, critMultiplier 2.0
    ✓ default modifier for unknown damage types
  CombatResolver distance
    ✓ calculates euclidean distance between two points
    ✓ returns 0 for same point

PASS tests/GameRoom.test.ts
  GameRoom wiring
    ✓ should delegate onJoin to roomLogic helpers (3 ms)
    ✓ should throw on exceeding maxClients (4 ms)
    ✓ should delegate onLeave to disconnectPlayer helper (1 ms)

A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks. Active timers can also cause this, ensure that .unref() was called on them.
Test Suites: 12 passed, 12 total
Tests:       172 passed, 172 total
Snapshots:   0 total
Time:        5.596 s, estimated 6 s
Ran all test suites.
