import { expect } from "@esm-bundle/chai"; import sinon from "sinon"; import { GameLoop } from "../../../src/core/GameLoop.js"; import { createGameLoopSetup, cleanupGameLoop, createRunData, createMockGameStateManagerForDeployment, createMockMissionManager, } from "./helpers.js"; describe("Core: GameLoop - Deployment", function () { this.timeout(30000); let gameLoop; let container; beforeEach(() => { const setup = createGameLoopSetup(); gameLoop = setup.gameLoop; container = setup.container; gameLoop.init(container); }); afterEach(() => { cleanupGameLoop(gameLoop, container); }); it("CoA 3: Deployment Phase should separate zones and allow manual placement", async () => { const runData = createRunData({ squad: [{ id: "u1", classId: "CLASS_VANGUARD" }], }); // Mock gameStateManager for deployment phase gameLoop.gameStateManager = createMockGameStateManagerForDeployment(); // startLevel should now prepare the map but NOT spawn units immediately await gameLoop.startLevel(runData, { startAnimation: false }); // 1. Verify Spawn Zones Generated // The generator/loop should identify valid tiles for player start and enemy start expect(gameLoop.playerSpawnZone).to.be.an("array").that.is.not.empty; expect(gameLoop.enemySpawnZone).to.be.an("array").that.is.not.empty; // 2. Verify Zone Separation // Create copies to ensure we don't test against mutated arrays later const pZone = [...gameLoop.playerSpawnZone]; const eZone = [...gameLoop.enemySpawnZone]; const overlap = pZone.some((pTile) => eZone.some((eTile) => eTile.x === pTile.x && eTile.z === pTile.z) ); expect(overlap).to.be.false; // 3. Test Manual Deployment (User Selection) const unitDef = runData.squad[0]; const validTile = pZone[0]; // Pick first valid tile from player zone // Expect a method to manually place a unit from the roster onto a specific tile const unit = gameLoop.deployUnit(unitDef, validTile); expect(unit).to.exist; expect(unit.position.x).to.equal(validTile.x); expect(unit.position.z).to.equal(validTile.z); // Verify visual mesh created const mesh = gameLoop.unitMeshes.get(unit.id); expect(mesh).to.exist; expect(mesh.position.x).to.equal(validTile.x); // 4. Test Enemy Spawning (Finalize Deployment) // This triggers the actual start of combat/AI gameLoop.finalizeDeployment(); const enemies = gameLoop.unitManager.getUnitsByTeam("ENEMY"); expect(enemies.length).to.be.greaterThan(0); // Verify enemies are in their zone // Note: finalizeDeployment removes used spots from gameLoop.enemySpawnZone, // so we check against our copy `eZone`. const enemyPos = enemies[0].position; const isInZone = eZone.some( (t) => t.x === enemyPos.x && t.z === enemyPos.z ); expect( isInZone, `Enemy spawned at ${enemyPos.x},${enemyPos.z} which is not in enemy zone` ).to.be.true; }); it("CoA 5: finalizeDeployment should spawn enemies from mission enemy_spawns", async () => { const runData = createRunData({ squad: [{ id: "u1", classId: "CLASS_VANGUARD" }], }); // Mock gameStateManager for deployment phase gameLoop.gameStateManager = createMockGameStateManagerForDeployment(); // Mock MissionManager with enemy_spawns // Use ENEMY_DEFAULT which exists in the test environment gameLoop.missionManager = createMockMissionManager([ { enemy_def_id: "ENEMY_DEFAULT", count: 2 }, ]); await gameLoop.startLevel(runData, { startAnimation: false }); // Copy enemy spawn zone before finalizeDeployment modifies it const eZone = [...gameLoop.enemySpawnZone]; // Finalize deployment should spawn enemies from mission definition gameLoop.finalizeDeployment(); const enemies = gameLoop.unitManager.getUnitsByTeam("ENEMY"); // Should have spawned 2 enemies (or as many as possible given spawn zone size) expect(enemies.length).to.be.greaterThan(0); expect(enemies.length).to.be.at.most(2); // Verify enemies are in their zone enemies.forEach((enemy) => { const enemyPos = enemy.position; const isInZone = eZone.some( (t) => t.x === enemyPos.x && t.z === enemyPos.z ); expect( isInZone, `Enemy spawned at ${enemyPos.x},${enemyPos.z} which is not in enemy zone` ).to.be.true; }); }); it("CoA 6: finalizeDeployment should fall back to default if no enemy_spawns", async () => { const runData = createRunData({ squad: [{ id: "u1", classId: "CLASS_VANGUARD" }], }); // Mock gameStateManager for deployment phase gameLoop.gameStateManager = createMockGameStateManagerForDeployment(); // Mock MissionManager with no enemy_spawns gameLoop.missionManager = createMockMissionManager([]); await gameLoop.startLevel(runData, { startAnimation: false }); // Finalize deployment should fall back to default behavior const consoleWarnSpy = sinon.spy(console, "warn"); gameLoop.finalizeDeployment(); // Should have warned about missing enemy_spawns expect(consoleWarnSpy.calledWith(sinon.match(/No enemy_spawns defined/))).to .be.true; const enemies = gameLoop.unitManager.getUnitsByTeam("ENEMY"); // Should still spawn at least one enemy (default behavior) expect(enemies.length).to.be.greaterThan(0); consoleWarnSpy.restore(); }); });