100 lines
3.5 KiB
TypeScript
100 lines
3.5 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import * as THREE from "three";
|
|
import { createOfficeWalker } from "../interiors/officeWalker.ts";
|
|
import { Plan } from "../interiors/plan.ts";
|
|
import type { Level, Office, Room, Wall } from "../interiors/types.ts";
|
|
|
|
const FLOOR: Room = {
|
|
id: "floor",
|
|
name: "Floor",
|
|
floor: "floor" as never,
|
|
outline: [{ x: 0, z: 0 }, { x: 10, z: 0 }, { x: 10, z: 8 }, { x: 0, z: 8 }],
|
|
};
|
|
|
|
function makePlan(walls: Wall[] = [], elevation = 2.5): Plan {
|
|
const level: Level = {
|
|
id: "ground",
|
|
name: "Ground",
|
|
elevation,
|
|
wallHeight: 3,
|
|
wallThickness: 0.1,
|
|
floorplan: { rooms: [FLOOR], walls },
|
|
};
|
|
const office: Office = { id: "actor-test", name: "Actor Test", levels: [level], viewpoints: [] };
|
|
return new Plan(office, { warn: false });
|
|
}
|
|
|
|
describe("office walker actor adapter", () => {
|
|
it("is inert by default and becomes a floor-aware humanoid when activated", () => {
|
|
const actor = createOfficeWalker(makePlan(), {
|
|
levelId: "ground",
|
|
position: { x: 2, z: 2 },
|
|
speed: 1,
|
|
fixedStep: 0.1,
|
|
});
|
|
assert.equal(actor.state().actor, "humanoid");
|
|
assert.equal(actor.root.position.y, 2.5);
|
|
actor.setAction({ x: 2, z: 0 });
|
|
actor.tick(0.2);
|
|
assert.deepEqual(actor.state().position, { x: 2, z: 2 });
|
|
actor.setActive(true);
|
|
assert.deepEqual(actor.action(), { x: 0, z: 0 }, "activation never replays stale input");
|
|
actor.setAction({ x: 2, z: 0 });
|
|
actor.tick(0.2);
|
|
assert.ok(actor.state().position.x > 2.19);
|
|
assert.equal(actor.view.position.y, 2.5);
|
|
assert.ok(Math.abs(actor.root.rotation.y + Math.PI / 2) < 1e-12);
|
|
actor.dispose();
|
|
});
|
|
|
|
it("builds an anonymous dog and publishes a defensive chase-camera contract", () => {
|
|
const actor = createOfficeWalker(makePlan(), {
|
|
levelId: "ground",
|
|
position: { x: 4, z: 4 },
|
|
actor: { kind: "anonymous-dog", coatColor: 0x222222 },
|
|
active: true,
|
|
});
|
|
assert.equal(actor.root.userData.actorType, "anonymous-dog");
|
|
const pose = actor.followPose();
|
|
assert.ok(pose.position.z > 4, "camera is behind a -Z-facing actor");
|
|
assert.ok(pose.target.z < 4, "camera aims ahead of the actor");
|
|
pose.position.x = 999;
|
|
assert.notEqual(actor.followPose().position.x, 999);
|
|
actor.dispose();
|
|
});
|
|
|
|
it("uses resolved door gaps while retaining wall collision and reset state", () => {
|
|
const plan = makePlan([{
|
|
id: "divider",
|
|
from: { x: 5, z: 0 },
|
|
to: { x: 5, z: 8 },
|
|
openings: [{ kind: "door", start: 3.4, width: 1.2, sill: 0, head: 2.1 }],
|
|
}]);
|
|
const actor = createOfficeWalker(plan, {
|
|
levelId: "ground",
|
|
position: { x: 4, z: 4 },
|
|
speed: 2,
|
|
fixedStep: 0.1,
|
|
active: true,
|
|
});
|
|
actor.setAction({ x: 1, z: 0 });
|
|
for (let index = 0; index < 10; index += 1) actor.tick(0.1);
|
|
assert.ok(actor.state().position.x > 5.5);
|
|
const reset = actor.reset({ levelId: "ground", position: { x: 2, z: 2 } });
|
|
assert.deepEqual(reset.position, { x: 2, z: 2 });
|
|
assert.deepEqual(reset.action, { x: 0, z: 0 });
|
|
assert.equal(reset.distance, 0);
|
|
actor.dispose();
|
|
});
|
|
|
|
it("removes and disposes its actor root idempotently", () => {
|
|
const actor = createOfficeWalker(makePlan(), { levelId: "ground", position: { x: 2, z: 2 } });
|
|
const parent = new THREE.Group();
|
|
parent.add(actor.root);
|
|
actor.dispose();
|
|
actor.dispose();
|
|
assert.equal(actor.root.parent, null);
|
|
});
|
|
});
|