169 lines
5.5 KiB
JavaScript
169 lines
5.5 KiB
JavaScript
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;
|
|
});
|
|
});
|
|
});
|
|
|