2025-12-28 00:54:03 +00:00
|
|
|
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
|
2025-12-31 21:52:59 +00:00
|
|
|
const mockGameStateManager = createMockGameStateManagerForDeployment();
|
|
|
|
|
if (!mockGameStateManager.rosterManager) {
|
|
|
|
|
mockGameStateManager.rosterManager = { roster: [] };
|
|
|
|
|
}
|
|
|
|
|
gameLoop.gameStateManager = mockGameStateManager;
|
2025-12-28 00:54:03 +00:00
|
|
|
|
2026-01-02 00:08:54 +00:00
|
|
|
// Mock MissionManager with no enemy_spawns (will use default)
|
|
|
|
|
const mockMissionManager = createMockMissionManager([]);
|
|
|
|
|
mockMissionManager.setUnitManager = sinon.stub();
|
|
|
|
|
mockMissionManager.setTurnSystem = sinon.stub();
|
|
|
|
|
mockMissionManager.setupActiveMission = sinon.stub();
|
|
|
|
|
gameLoop.missionManager = mockMissionManager;
|
|
|
|
|
|
2025-12-28 00:54:03 +00:00
|
|
|
// 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
|
2026-01-02 00:08:54 +00:00
|
|
|
await gameLoop.finalizeDeployment();
|
2025-12-28 00:54:03 +00:00
|
|
|
|
|
|
|
|
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
|
2025-12-31 21:52:59 +00:00
|
|
|
const mockGameStateManager = createMockGameStateManagerForDeployment();
|
|
|
|
|
if (!mockGameStateManager.rosterManager) {
|
|
|
|
|
mockGameStateManager.rosterManager = { roster: [] };
|
|
|
|
|
}
|
|
|
|
|
gameLoop.gameStateManager = mockGameStateManager;
|
2025-12-28 00:54:03 +00:00
|
|
|
|
2025-12-31 21:52:59 +00:00
|
|
|
// Mock MissionManager with enemy_spawns and required methods
|
2025-12-28 00:54:03 +00:00
|
|
|
// Use ENEMY_DEFAULT which exists in the test environment
|
2025-12-31 21:52:59 +00:00
|
|
|
const mockMissionManager = createMockMissionManager([
|
2025-12-28 00:54:03 +00:00
|
|
|
{ enemy_def_id: "ENEMY_DEFAULT", count: 2 },
|
|
|
|
|
]);
|
2025-12-31 21:52:59 +00:00
|
|
|
mockMissionManager.setUnitManager = sinon.stub();
|
|
|
|
|
mockMissionManager.setTurnSystem = sinon.stub();
|
|
|
|
|
mockMissionManager.setupActiveMission = sinon.stub();
|
|
|
|
|
gameLoop.missionManager = mockMissionManager;
|
2025-12-28 00:54:03 +00:00
|
|
|
|
|
|
|
|
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
|
2026-01-02 00:08:54 +00:00
|
|
|
await gameLoop.finalizeDeployment();
|
2025-12-28 00:54:03 +00:00
|
|
|
|
|
|
|
|
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
|
2025-12-31 21:52:59 +00:00
|
|
|
const mockGameStateManager = createMockGameStateManagerForDeployment();
|
|
|
|
|
if (!mockGameStateManager.rosterManager) {
|
|
|
|
|
mockGameStateManager.rosterManager = { roster: [] };
|
|
|
|
|
}
|
|
|
|
|
gameLoop.gameStateManager = mockGameStateManager;
|
|
|
|
|
|
|
|
|
|
// Mock MissionManager with no enemy_spawns and required methods
|
|
|
|
|
const mockMissionManager = createMockMissionManager([]);
|
|
|
|
|
mockMissionManager.setUnitManager = sinon.stub();
|
|
|
|
|
mockMissionManager.setTurnSystem = sinon.stub();
|
|
|
|
|
mockMissionManager.setupActiveMission = sinon.stub();
|
|
|
|
|
gameLoop.missionManager = mockMissionManager;
|
2025-12-28 00:54:03 +00:00
|
|
|
|
|
|
|
|
await gameLoop.startLevel(runData, { startAnimation: false });
|
|
|
|
|
|
|
|
|
|
// Finalize deployment should fall back to default behavior
|
|
|
|
|
const consoleWarnSpy = sinon.spy(console, "warn");
|
2026-01-02 00:08:54 +00:00
|
|
|
await gameLoop.finalizeDeployment();
|
2025-12-28 00:54:03 +00:00
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
});
|
2025-12-31 21:52:59 +00:00
|
|
|
|
|
|
|
|
it("CoA 7: deployUnit should restore Explorer progression from roster", async () => {
|
|
|
|
|
const runData = createRunData({
|
|
|
|
|
squad: [{ id: "u1", classId: "CLASS_VANGUARD", name: "Test Explorer" }],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Mock gameStateManager with roster containing progression data
|
|
|
|
|
const mockGameStateManager = createMockGameStateManagerForDeployment();
|
|
|
|
|
// Ensure rosterManager exists
|
|
|
|
|
if (!mockGameStateManager.rosterManager) {
|
|
|
|
|
mockGameStateManager.rosterManager = { roster: [] };
|
|
|
|
|
}
|
|
|
|
|
mockGameStateManager.rosterManager.roster = [
|
|
|
|
|
{
|
|
|
|
|
id: "u1",
|
|
|
|
|
classId: "CLASS_VANGUARD",
|
|
|
|
|
name: "Test Explorer",
|
|
|
|
|
status: "READY",
|
|
|
|
|
activeClassId: "CLASS_VANGUARD",
|
|
|
|
|
classMastery: {
|
|
|
|
|
CLASS_VANGUARD: {
|
|
|
|
|
level: 5,
|
|
|
|
|
xp: 250,
|
|
|
|
|
skillPoints: 3,
|
|
|
|
|
unlockedNodes: ["ROOT", "NODE_1"],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
gameLoop.gameStateManager = mockGameStateManager;
|
|
|
|
|
|
|
|
|
|
await gameLoop.startLevel(runData, { startAnimation: false });
|
|
|
|
|
|
|
|
|
|
const unitDef = runData.squad[0];
|
|
|
|
|
const validTile = gameLoop.playerSpawnZone[0];
|
|
|
|
|
const unit = gameLoop.deployUnit(unitDef, validTile);
|
|
|
|
|
|
|
|
|
|
expect(unit).to.exist;
|
|
|
|
|
expect(unit.type).to.equal("EXPLORER");
|
|
|
|
|
expect(unit.rosterId).to.equal("u1");
|
|
|
|
|
|
|
|
|
|
// Verify progression was restored
|
|
|
|
|
expect(unit.classMastery).to.exist;
|
|
|
|
|
expect(unit.classMastery.CLASS_VANGUARD).to.exist;
|
|
|
|
|
expect(unit.classMastery.CLASS_VANGUARD.level).to.equal(5);
|
|
|
|
|
expect(unit.classMastery.CLASS_VANGUARD.xp).to.equal(250);
|
|
|
|
|
expect(unit.classMastery.CLASS_VANGUARD.skillPoints).to.equal(3);
|
|
|
|
|
expect(unit.classMastery.CLASS_VANGUARD.unlockedNodes).to.include("ROOT");
|
|
|
|
|
expect(unit.classMastery.CLASS_VANGUARD.unlockedNodes).to.include("NODE_1");
|
|
|
|
|
expect(unit.activeClassId).to.equal("CLASS_VANGUARD");
|
|
|
|
|
|
|
|
|
|
// Verify stats were recalculated based on level
|
|
|
|
|
// Level 5 means 4 level-ups, so health should be higher than base
|
|
|
|
|
expect(unit.baseStats.health).to.be.greaterThan(100); // Base is 100
|
|
|
|
|
});
|
2026-01-02 00:08:54 +00:00
|
|
|
|
|
|
|
|
it("CoA 8: finalizeDeployment should spawn mission objects with placement_strategy", async () => {
|
|
|
|
|
const runData = createRunData({
|
|
|
|
|
squad: [{ id: "u1", classId: "CLASS_VANGUARD" }],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Mock gameStateManager for deployment phase
|
|
|
|
|
const mockGameStateManager = createMockGameStateManagerForDeployment();
|
|
|
|
|
if (!mockGameStateManager.rosterManager) {
|
|
|
|
|
mockGameStateManager.rosterManager = { roster: [] };
|
|
|
|
|
}
|
|
|
|
|
gameLoop.gameStateManager = mockGameStateManager;
|
|
|
|
|
|
|
|
|
|
// Mock MissionManager with mission_objects
|
|
|
|
|
const mockMissionManager = createMockMissionManager([]);
|
|
|
|
|
mockMissionManager.getActiveMission = sinon.stub().resolves({
|
|
|
|
|
enemy_spawns: [],
|
|
|
|
|
mission_objects: [
|
|
|
|
|
{
|
|
|
|
|
object_id: "OBJ_SIGNAL_RELAY",
|
|
|
|
|
placement_strategy: "center_of_enemy_room",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
mockMissionManager.setUnitManager = sinon.stub();
|
|
|
|
|
mockMissionManager.setTurnSystem = sinon.stub();
|
|
|
|
|
mockMissionManager.setupActiveMission = sinon.stub();
|
|
|
|
|
gameLoop.missionManager = mockMissionManager;
|
|
|
|
|
|
|
|
|
|
await gameLoop.startLevel(runData, { startAnimation: false });
|
|
|
|
|
|
|
|
|
|
// Finalize deployment should spawn mission objects
|
|
|
|
|
await gameLoop.finalizeDeployment();
|
|
|
|
|
|
|
|
|
|
// Verify mission object was spawned
|
|
|
|
|
expect(gameLoop.missionObjects.has("OBJ_SIGNAL_RELAY")).to.be.true;
|
|
|
|
|
const objPos = gameLoop.missionObjects.get("OBJ_SIGNAL_RELAY");
|
|
|
|
|
expect(objPos).to.exist;
|
|
|
|
|
expect(objPos).to.have.property("x");
|
|
|
|
|
expect(objPos).to.have.property("y");
|
|
|
|
|
expect(objPos).to.have.property("z");
|
|
|
|
|
|
|
|
|
|
// Verify visual mesh was created
|
|
|
|
|
const mesh = gameLoop.missionObjectMeshes.get("OBJ_SIGNAL_RELAY");
|
|
|
|
|
expect(mesh).to.exist;
|
|
|
|
|
expect(mesh.position.x).to.equal(objPos.x);
|
|
|
|
|
expect(mesh.position.z).to.equal(objPos.z);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("CoA 9: finalizeDeployment should spawn mission objects with explicit position", async () => {
|
|
|
|
|
const runData = createRunData({
|
|
|
|
|
squad: [{ id: "u1", classId: "CLASS_VANGUARD" }],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Mock gameStateManager for deployment phase
|
|
|
|
|
const mockGameStateManager = createMockGameStateManagerForDeployment();
|
|
|
|
|
if (!mockGameStateManager.rosterManager) {
|
|
|
|
|
mockGameStateManager.rosterManager = { roster: [] };
|
|
|
|
|
}
|
|
|
|
|
gameLoop.gameStateManager = mockGameStateManager;
|
|
|
|
|
|
|
|
|
|
await gameLoop.startLevel(runData, { startAnimation: false });
|
|
|
|
|
|
|
|
|
|
// Find a valid walkable position
|
|
|
|
|
const validTile = gameLoop.enemySpawnZone[0];
|
|
|
|
|
const walkableY = gameLoop.movementSystem.findWalkableY(
|
|
|
|
|
validTile.x,
|
|
|
|
|
validTile.z,
|
|
|
|
|
validTile.y
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Mock MissionManager with mission_objects using explicit position
|
|
|
|
|
const mockMissionManager = createMockMissionManager([]);
|
|
|
|
|
mockMissionManager.getActiveMission = sinon.stub().resolves({
|
|
|
|
|
enemy_spawns: [],
|
|
|
|
|
mission_objects: [
|
|
|
|
|
{
|
|
|
|
|
object_id: "OBJ_DATA_TERMINAL",
|
|
|
|
|
position: { x: validTile.x, y: walkableY, z: validTile.z },
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
mockMissionManager.setUnitManager = sinon.stub();
|
|
|
|
|
mockMissionManager.setTurnSystem = sinon.stub();
|
|
|
|
|
mockMissionManager.setupActiveMission = sinon.stub();
|
|
|
|
|
gameLoop.missionManager = mockMissionManager;
|
|
|
|
|
|
|
|
|
|
// Finalize deployment should spawn mission objects
|
|
|
|
|
await gameLoop.finalizeDeployment();
|
|
|
|
|
|
|
|
|
|
// Verify mission object was spawned at the specified position
|
|
|
|
|
expect(gameLoop.missionObjects.has("OBJ_DATA_TERMINAL")).to.be.true;
|
|
|
|
|
const objPos = gameLoop.missionObjects.get("OBJ_DATA_TERMINAL");
|
|
|
|
|
expect(objPos.x).to.equal(validTile.x);
|
|
|
|
|
expect(objPos.z).to.equal(validTile.z);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("CoA 10: checkMissionObjectInteraction should dispatch INTERACT event when unit moves to object", async () => {
|
|
|
|
|
const runData = createRunData({
|
|
|
|
|
squad: [{ id: "u1", classId: "CLASS_VANGUARD" }],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Mock gameStateManager for deployment phase
|
|
|
|
|
const mockGameStateManager = createMockGameStateManagerForDeployment();
|
|
|
|
|
if (!mockGameStateManager.rosterManager) {
|
|
|
|
|
mockGameStateManager.rosterManager = { roster: [] };
|
|
|
|
|
}
|
|
|
|
|
gameLoop.gameStateManager = mockGameStateManager;
|
|
|
|
|
|
|
|
|
|
await gameLoop.startLevel(runData, { startAnimation: false });
|
|
|
|
|
|
|
|
|
|
// Use a player spawn zone position so we can deploy a unit there
|
|
|
|
|
const validTile = gameLoop.playerSpawnZone[0];
|
|
|
|
|
const walkableY = gameLoop.movementSystem.findWalkableY(
|
|
|
|
|
validTile.x,
|
|
|
|
|
validTile.z,
|
|
|
|
|
validTile.y
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Manually add a mission object for testing at the same position
|
|
|
|
|
const objPos = { x: validTile.x, y: walkableY, z: validTile.z };
|
|
|
|
|
gameLoop.missionObjects.set("OBJ_TEST_RELAY", objPos);
|
|
|
|
|
gameLoop.createMissionObjectMesh("OBJ_TEST_RELAY", objPos);
|
|
|
|
|
|
|
|
|
|
// Create a unit at the object position (in player spawn zone, so deployUnit will work)
|
|
|
|
|
const unitDef = runData.squad[0];
|
|
|
|
|
const unit = gameLoop.deployUnit(unitDef, validTile);
|
|
|
|
|
expect(unit).to.exist; // Ensure unit was deployed
|
|
|
|
|
|
|
|
|
|
// Mock MissionManager to spy on onGameEvent
|
|
|
|
|
const mockMissionManager = createMockMissionManager([]);
|
|
|
|
|
const interactSpy = sinon.spy();
|
|
|
|
|
mockMissionManager.onGameEvent = interactSpy;
|
|
|
|
|
gameLoop.missionManager = mockMissionManager;
|
|
|
|
|
|
|
|
|
|
// Check interaction (simulating movement to object position)
|
|
|
|
|
gameLoop.checkMissionObjectInteraction(unit);
|
|
|
|
|
|
|
|
|
|
// Verify INTERACT event was dispatched
|
|
|
|
|
expect(interactSpy.calledOnce).to.be.true;
|
|
|
|
|
expect(interactSpy.firstCall.args[0]).to.equal("INTERACT");
|
|
|
|
|
expect(interactSpy.firstCall.args[1].objectId).to.equal("OBJ_TEST_RELAY");
|
|
|
|
|
expect(interactSpy.firstCall.args[1].unitId).to.equal(unit.id);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("CoA 11: findObjectPlacement should find valid positions for different strategies", async () => {
|
|
|
|
|
const runData = createRunData({
|
|
|
|
|
squad: [{ id: "u1", classId: "CLASS_VANGUARD" }],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Mock gameStateManager for deployment phase
|
|
|
|
|
const mockGameStateManager = createMockGameStateManagerForDeployment();
|
|
|
|
|
if (!mockGameStateManager.rosterManager) {
|
|
|
|
|
mockGameStateManager.rosterManager = { roster: [] };
|
|
|
|
|
}
|
|
|
|
|
gameLoop.gameStateManager = mockGameStateManager;
|
|
|
|
|
|
|
|
|
|
await gameLoop.startLevel(runData, { startAnimation: false });
|
|
|
|
|
|
|
|
|
|
// Test center_of_enemy_room strategy
|
|
|
|
|
const enemyPos = gameLoop.findObjectPlacement("center_of_enemy_room");
|
|
|
|
|
expect(enemyPos).to.exist;
|
|
|
|
|
expect(enemyPos).to.have.property("x");
|
|
|
|
|
expect(enemyPos).to.have.property("y");
|
|
|
|
|
expect(enemyPos).to.have.property("z");
|
|
|
|
|
|
|
|
|
|
// Test center_of_player_room strategy
|
|
|
|
|
const playerPos = gameLoop.findObjectPlacement("center_of_player_room");
|
|
|
|
|
expect(playerPos).to.exist;
|
|
|
|
|
expect(playerPos).to.have.property("x");
|
|
|
|
|
expect(playerPos).to.have.property("y");
|
|
|
|
|
expect(playerPos).to.have.property("z");
|
|
|
|
|
|
|
|
|
|
// Test middle_room strategy
|
|
|
|
|
const middlePos = gameLoop.findObjectPlacement("middle_room");
|
|
|
|
|
expect(middlePos).to.exist;
|
|
|
|
|
expect(middlePos).to.have.property("x");
|
|
|
|
|
expect(middlePos).to.have.property("y");
|
|
|
|
|
expect(middlePos).to.have.property("z");
|
|
|
|
|
|
|
|
|
|
// Test random_walkable strategy
|
|
|
|
|
const randomPos = gameLoop.findObjectPlacement("random_walkable");
|
|
|
|
|
expect(randomPos).to.exist;
|
|
|
|
|
expect(randomPos).to.have.property("x");
|
|
|
|
|
expect(randomPos).to.have.property("y");
|
|
|
|
|
expect(randomPos).to.have.property("z");
|
|
|
|
|
|
|
|
|
|
// Test invalid strategy (should return null or fallback)
|
|
|
|
|
const invalidPos = gameLoop.findObjectPlacement("invalid_strategy");
|
|
|
|
|
// Should either return null or fallback to random_walkable
|
|
|
|
|
if (invalidPos) {
|
|
|
|
|
expect(invalidPos).to.have.property("x");
|
|
|
|
|
expect(invalidPos).to.have.property("y");
|
|
|
|
|
expect(invalidPos).to.have.property("z");
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-12-28 00:54:03 +00:00
|
|
|
});
|