import { expect } from "@esm-bundle/chai"; import { MovementSystem } from "../../src/systems/MovementSystem.js"; import { VoxelGrid } from "../../src/grid/VoxelGrid.js"; import { UnitManager } from "../../src/managers/UnitManager.js"; describe("Systems: MovementSystem", function () { let movementSystem; let grid; let unitManager; let mockRegistry; beforeEach(() => { // Create mock registry mockRegistry = new Map(); mockRegistry.set("CLASS_VANGUARD", { id: "CLASS_VANGUARD", name: "Vanguard", base_stats: { health: 100, attack: 10, defense: 5, speed: 10, movement: 4, }, }); unitManager = new UnitManager(mockRegistry); grid = new VoxelGrid(20, 20, 20); // Create a simple walkable floor at y=1 for (let x = 0; x < 20; x++) { for (let z = 0; z < 20; z++) { // Floor at y=0 grid.setCell(x, 0, z, 1); // Air at y=1 (walkable) grid.setCell(x, 1, z, 0); // Air at y=2 (headroom) grid.setCell(x, 2, z, 0); } } movementSystem = new MovementSystem(grid, unitManager); }); describe("CoA 1: Validation", () => { it("should fail if tile is blocked/occupied", () => { const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER"); unit.position = { x: 5, y: 1, z: 5 }; unit.currentAP = 10; grid.placeUnit(unit, unit.position); // Try to move to an occupied tile const occupiedPos = { x: 6, y: 1, z: 5 }; const otherUnit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER"); grid.placeUnit(otherUnit, occupiedPos); const result = movementSystem.validateMove(unit, occupiedPos); expect(result.valid).to.be.false; }); it("should fail if no path exists (out of range)", () => { const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER"); unit.position = { x: 5, y: 1, z: 5 }; unit.currentAP = 10; unit.baseStats.movement = 2; // Limited movement grid.placeUnit(unit, unit.position); // Try to move too far const farPos = { x: 10, y: 1, z: 10 }; const result = movementSystem.validateMove(unit, farPos); expect(result.valid).to.be.false; }); it("should fail if unit has insufficient AP", () => { const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER"); unit.position = { x: 5, y: 1, z: 5 }; unit.currentAP = 1; // Not enough AP grid.placeUnit(unit, unit.position); // Try to move 2 tiles away (costs 2 AP) const targetPos = { x: 7, y: 1, z: 5 }; const result = movementSystem.validateMove(unit, targetPos); expect(result.valid).to.be.false; expect(result.cost).to.equal(2); }); it("should succeed if all conditions are met", () => { const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER"); unit.position = { x: 5, y: 1, z: 5 }; unit.currentAP = 10; grid.placeUnit(unit, unit.position); const targetPos = { x: 6, y: 1, z: 5 }; const result = movementSystem.validateMove(unit, targetPos); expect(result.valid).to.be.true; expect(result.cost).to.equal(1); expect(result.path.length).to.be.greaterThan(0); }); }); describe("CoA 2: Execution", () => { it("should update unit position in grid", async () => { const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER"); unit.position = { x: 5, y: 1, z: 5 }; unit.currentAP = 10; grid.placeUnit(unit, unit.position); const targetPos = { x: 6, y: 1, z: 5 }; const success = await movementSystem.executeMove(unit, targetPos); expect(success).to.be.true; expect(unit.position.x).to.equal(6); expect(unit.position.z).to.equal(5); }); it("should update grid occupancy map", async () => { const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER"); unit.position = { x: 5, y: 1, z: 5 }; unit.currentAP = 10; grid.placeUnit(unit, unit.position); const targetPos = { x: 6, y: 1, z: 5 }; // Old position should be occupied expect(grid.isOccupied({ x: 5, y: 1, z: 5 })).to.be.true; await movementSystem.executeMove(unit, targetPos); // New position should be occupied expect(grid.isOccupied({ x: 6, y: 1, z: 5 })).to.be.true; // Old position should be free expect(grid.isOccupied({ x: 5, y: 1, z: 5 })).to.be.false; }); it("should deduct correct AP cost", async () => { const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER"); unit.position = { x: 5, y: 1, z: 5 }; unit.currentAP = 10; grid.placeUnit(unit, unit.position); const targetPos = { x: 7, y: 1, z: 5 }; // 2 tiles away const initialAP = unit.currentAP; await movementSystem.executeMove(unit, targetPos); // Should have deducted 2 AP (Manhattan distance) expect(unit.currentAP).to.equal(initialAP - 2); }); }); describe("getReachableTiles", () => { it("should return all reachable positions within movement range", () => { const unit = unitManager.createUnit("CLASS_VANGUARD", "PLAYER"); unit.position = { x: 5, y: 1, z: 5 }; unit.baseStats.movement = 2; grid.placeUnit(unit, unit.position); const reachable = movementSystem.getReachableTiles(unit, 2); // Should include starting position and nearby tiles expect(reachable.length).to.be.greaterThan(0); expect(reachable.some((pos) => pos.x === 5 && pos.z === 5)).to.be.true; }); }); });