aether-shards/test/systems/MovementSystem.test.js

170 lines
5.5 KiB
JavaScript
Raw Normal View History

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;
});
});
});