630 lines
24 KiB
TypeScript
630 lines
24 KiB
TypeScript
/**
|
|
* The reference pack, against the checklist its own README ends with.
|
|
*
|
|
* `src/offices/README.md` closes with six things to check before calling a pack
|
|
* done, and until this file existed the pack those instructions are written
|
|
* about had none of them checked. That was survivable while it was one storey
|
|
* and fifteen rooms authored in one sitting. It stopped being survivable when a
|
|
* second level and a wing arrived, because the failure mode of a floorplan is
|
|
* not a crash — `Plan` never throws — it is a room nobody can get into, which
|
|
* renders perfectly and looks completely fine from every camera angle.
|
|
*
|
|
* Two of the checks here are worth their weight on their own:
|
|
*
|
|
* - **Reachability.** Item 2 on the list, and the one that cannot be eyeballed:
|
|
* a door authored 200 mm past the end of its wall is silently dropped, the
|
|
* room behind it is sealed, and the only evidence is a line in
|
|
* `plan.problems` that nothing reads. That exact bug happened once while the
|
|
* wing was being written.
|
|
* - **The balustrade.** The gallery is 16.8 m above terrazzo. `Plan` derives
|
|
* the walk collider from the wall list, so the difference between a
|
|
* balustrade and a decorative rail is whether it is in that list — and both
|
|
* look identical in a screenshot.
|
|
*/
|
|
|
|
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import { Plan } from "../interiors/plan.ts";
|
|
import LUMBRIDGE_HQ from "../offices/lumbridge-hq.ts";
|
|
import FRONTIER_VALLEY from "../offices/frontier-valley.ts";
|
|
import MATEO_COURT from "../offices/mateo-court.ts";
|
|
import { OFFICE_SITES } from "../offices/sites.ts";
|
|
|
|
const plan = new Plan(LUMBRIDGE_HQ, { warn: false });
|
|
|
|
/**
|
|
* Which rooms on a level can be walked to from a point, at 0.1 m.
|
|
*
|
|
* Shared by all three packs, because it was written three times otherwise and
|
|
* the third copy is where a subtle divergence lives. The grid is finer than it
|
|
* looks like it needs to be and that is not optional: `blocked` inflates by the
|
|
* walker's radius, so threading a 0.9 m door with a 0.3 m radius leaves 0.3 m of
|
|
* clear width, and a 0.25 m grid can straddle that and report a sealed room that
|
|
* is fine. Erring the other way is not possible — a coarser grid only ever
|
|
* reaches fewer rooms, so a pass is always a real pass.
|
|
*/
|
|
function reachableRoomsOf(
|
|
plan: Plan,
|
|
levelId: string,
|
|
entry: { x: number; z: number },
|
|
): Set<string> {
|
|
const STEP = 0.1;
|
|
const RADIUS = 0.3;
|
|
const b = plan.bounds;
|
|
const cols = Math.ceil(b.width / STEP);
|
|
const rows = Math.ceil(b.depth / STEP);
|
|
const at = (c: number, r: number) => ({ x: b.minX + c * STEP, z: b.minZ + r * STEP });
|
|
|
|
const room: (string | null)[] = new Array(cols * rows).fill(null);
|
|
for (let r = 0; r < rows; r += 1) {
|
|
for (let c = 0; c < cols; c += 1) {
|
|
room[r * cols + c] = plan.roomAt(levelId, at(c, r))?.id ?? null;
|
|
}
|
|
}
|
|
|
|
const sc = Math.round((entry.x - b.minX) / STEP);
|
|
const sr = Math.round((entry.z - b.minZ) / STEP);
|
|
assert.ok(room[sr * cols + sc], `${levelId}'s entry point is not inside a room`);
|
|
|
|
const seen = new Uint8Array(cols * rows);
|
|
const queue: [number, number][] = [[sc, sr]];
|
|
seen[sr * cols + sc] = 1;
|
|
while (queue.length > 0) {
|
|
const [c, r] = queue.pop() as [number, number];
|
|
for (const [dc, dr] of [
|
|
[1, 0],
|
|
[-1, 0],
|
|
[0, 1],
|
|
[0, -1],
|
|
]) {
|
|
const nc = c + (dc as number);
|
|
const nr = r + (dr as number);
|
|
if (nc < 0 || nr < 0 || nc >= cols || nr >= rows) continue;
|
|
const i = nr * cols + nc;
|
|
if (seen[i] === 1 || room[i] === null) continue;
|
|
if (plan.blocked(levelId, at(c, r), at(nc, nr), RADIUS)) continue;
|
|
seen[i] = 1;
|
|
queue.push([nc, nr]);
|
|
}
|
|
}
|
|
|
|
const reached = new Set<string>();
|
|
for (let i = 0; i < seen.length; i += 1) {
|
|
const id = room[i];
|
|
if (seen[i] === 1 && id != null) reached.add(id);
|
|
}
|
|
return reached;
|
|
}
|
|
|
|
|
|
|
|
describe("the reference pack resolves cleanly", () => {
|
|
/** Checklist item 1, and the one everything else here depends on. */
|
|
it("reports no problems at all", () => {
|
|
assert.deepEqual(
|
|
plan.problems.map((p) => `${p.where}: ${p.message} (${p.action})`),
|
|
[],
|
|
);
|
|
});
|
|
|
|
/**
|
|
* The relationship, not the number.
|
|
*
|
|
* Floor-to-floor is deliberately exaggerated in this pack — see `PLENUM` in
|
|
* `lumbridge-hq.ts` — and an assertion on the literal would have to be edited
|
|
* every time somebody dials it, which makes it a change-detector rather than a
|
|
* test. What must stay true is that level 2 sits a clear interstitial *above*
|
|
* level 1's ceiling, and never at or below it: `elevation` is floor-to-floor,
|
|
* and setting it to the ceiling height is the classic way to bury one storey's
|
|
* slab inside the one below.
|
|
*/
|
|
it("is an honest twelve-by-nine metre single-level studio", () => {
|
|
assert.deepEqual(plan.levels.map((level) => [level.id, level.floorY]), [["level-1", 0]]);
|
|
assert.deepEqual(LUMBRIDGE_HQ.levels[0]?.floorplan.rooms[3]?.outline, [
|
|
{ x: 0.2, z: 3.15 },
|
|
{ x: 0.2, z: 8.8 },
|
|
{ x: 11.8, z: 8.8 },
|
|
{ x: 11.8, z: 3.15 },
|
|
]);
|
|
});
|
|
|
|
it("keeps every viewpoint on a level that exists", () => {
|
|
// `Plan` drops a viewpoint whose level does not resolve, so this is really
|
|
// an assertion that none were dropped — which the count makes visible in a
|
|
// way `problems` being empty already implies but does not state.
|
|
assert.equal(plan.viewpoints.length, 5);
|
|
const levels = new Set(plan.levels.map((l) => l.id));
|
|
for (const v of plan.viewpoints) assert.ok(levels.has(v.levelId), `${v.id} is on nothing`);
|
|
});
|
|
});
|
|
|
|
describe("seat ids", () => {
|
|
const seats = plan.allSeats();
|
|
|
|
/**
|
|
* The one property the README asks for in capitals, because a `Presence` binds
|
|
* to a seat id and ids are unique **per kind and building-wide**, not per
|
|
* level. Two storeys is exactly when somebody reuses a prefix.
|
|
*/
|
|
it("are unique across the whole building, not merely per storey", () => {
|
|
const ids = seats.map((s) => s.id);
|
|
assert.equal(new Set(ids).size, ids.length);
|
|
});
|
|
|
|
/**
|
|
* Not a count for its own sake. `DeskBank` expansion is contractual — stations
|
|
* numbered from 1 along each row and then down the rows, zero-padded to two —
|
|
* and a bank that silently expanded to the wrong size would change every id
|
|
* after it.
|
|
*/
|
|
it("expand the desk banks to the documented ids", () => {
|
|
const ids = new Set(seats.map((s) => s.id));
|
|
assert.equal(seats.length, 5);
|
|
for (const id of ["sf-agent-01", "sf-agent-02", "sf-island-01", "sf-island-02", "sf-lounge-01"]) {
|
|
assert.ok(ids.has(id), `${id} is missing`);
|
|
}
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Checklist item 2: every room you can walk into can actually be walked into.
|
|
*
|
|
* A flood fill rather than a spot check, because a spot check finds the door you
|
|
* remembered to look at. The grid is 0.1 m, which is finer than it looks like it
|
|
* needs to be and is not optional: `blocked` inflates by the walker's radius, so
|
|
* threading a 0.9 m door with a 0.3 m radius leaves 0.3 m of clear width, and a
|
|
* 0.25 m grid can straddle that and report a sealed room that is fine. Getting
|
|
* that wrong the other way — a coarse grid that "passes" — is not possible here,
|
|
* since a coarser grid only ever reaches fewer rooms.
|
|
*/
|
|
describe("every room is reachable on foot", () => {
|
|
const STEP = 0.1;
|
|
const RADIUS = 0.3;
|
|
|
|
/** Where somebody arrives: the entrance downstairs, the landing upstairs. */
|
|
const ENTRY: Record<string, { x: number; z: number }> = {
|
|
"level-1": { x: 1.02, z: 7.68 },
|
|
};
|
|
|
|
function reachableRooms(levelId: string): Set<string> {
|
|
const b = plan.bounds;
|
|
const cols = Math.ceil(b.width / STEP);
|
|
const rows = Math.ceil(b.depth / STEP);
|
|
const at = (c: number, r: number) => ({ x: b.minX + c * STEP, z: b.minZ + r * STEP });
|
|
|
|
const room: (string | null)[] = new Array(cols * rows).fill(null);
|
|
for (let r = 0; r < rows; r += 1) {
|
|
for (let c = 0; c < cols; c += 1) {
|
|
room[r * cols + c] = plan.roomAt(levelId, at(c, r))?.id ?? null;
|
|
}
|
|
}
|
|
|
|
const entry = ENTRY[levelId];
|
|
assert.ok(entry, `no entry point declared for ${levelId}`);
|
|
const sc = Math.round((entry.x - b.minX) / STEP);
|
|
const sr = Math.round((entry.z - b.minZ) / STEP);
|
|
assert.ok(room[sr * cols + sc], `${levelId}'s entry point is not inside a room`);
|
|
|
|
const seen = new Uint8Array(cols * rows);
|
|
const queue: [number, number][] = [[sc, sr]];
|
|
seen[sr * cols + sc] = 1;
|
|
while (queue.length > 0) {
|
|
const [c, r] = queue.pop() as [number, number];
|
|
for (const [dc, dr] of [
|
|
[1, 0],
|
|
[-1, 0],
|
|
[0, 1],
|
|
[0, -1],
|
|
]) {
|
|
const nc = c + (dc as number);
|
|
const nr = r + (dr as number);
|
|
if (nc < 0 || nr < 0 || nc >= cols || nr >= rows) continue;
|
|
const i = nr * cols + nc;
|
|
if (seen[i] === 1 || room[i] === null) continue;
|
|
if (plan.blocked(levelId, at(c, r), at(nc, nr), RADIUS)) continue;
|
|
seen[i] = 1;
|
|
queue.push([nc, nr]);
|
|
}
|
|
}
|
|
|
|
const reached = new Set<string>();
|
|
for (let i = 0; i < seen.length; i += 1) {
|
|
// `!= null` rather than `!== null`: indexing a `(string | null)[]` under
|
|
// `noUncheckedIndexedAccess` widens to include `undefined` too.
|
|
const id = room[i];
|
|
if (seen[i] === 1 && id != null) reached.add(id);
|
|
}
|
|
return reached;
|
|
}
|
|
|
|
for (const level of plan.levels) {
|
|
it(`${level.id}: from the way in, without passing through a wall`, () => {
|
|
const reached = reachableRooms(level.id);
|
|
const sealed = level.rooms.map((r) => r.id).filter((id) => !reached.has(id));
|
|
assert.deepEqual(sealed, [], `sealed rooms on ${level.id}`);
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* The gallery is a balcony over a double-height room, and the drop is a storey.
|
|
*
|
|
* `Plan.blocked` is the collider a walk controller is told to use, so this is
|
|
* the only place the difference between a balustrade and a handrail-shaped prop
|
|
* is observable. A prop would leave the edge open and nothing would look wrong.
|
|
*/
|
|
describe("the studio arrival", () => {
|
|
it("starts inside the live/work room with collision-clear movement", () => {
|
|
const arrival = LUMBRIDGE_HQ.viewpoints[0]?.focus.at;
|
|
assert.ok(arrival);
|
|
assert.equal(plan.roomAt("level-1", arrival)?.id, "live-work");
|
|
assert.equal(plan.blocked("level-1", arrival, { x: arrival.x + 0.6, z: arrival.z }, 0.3), false);
|
|
});
|
|
|
|
/**
|
|
* And the way *on* is still open, which is the other half of the same claim: a
|
|
* balcony you cannot fall off and also cannot reach is a balcony nobody has
|
|
* noticed is broken.
|
|
*/
|
|
it("keeps all five authored views meaningful and distinct", () => {
|
|
assert.deepEqual(
|
|
LUMBRIDGE_HQ.viewpoints.map((view) => view.id),
|
|
["arrival", "studio", "agent-bench", "demo-lounge", "kitchen-home"],
|
|
);
|
|
});
|
|
});
|
|
|
|
/**
|
|
* The commons is the reason the wing exists, and "double height" is a property
|
|
* of the walls rather than of the room. A `Room` carries `ceiling: null` and a
|
|
* `Wall` carries a height; get the second wrong and the void has a lid at 2.8 m
|
|
* that you cannot see but the light rig can.
|
|
*/
|
|
describe("the studio programme", () => {
|
|
it("names every promised live/work zone without pretending it is a tower floor", () => {
|
|
const level1 = plan.levels.find((l) => l.id === "level-1");
|
|
assert.deepEqual(
|
|
level1?.rooms.map((room) => room.name),
|
|
["Kitchen", "Sleeping Alcove", "Bath / Storage", "Live / Work Studio"],
|
|
);
|
|
});
|
|
|
|
it("uses the complete original habitat kit", () => {
|
|
const kinds = new Set((LUMBRIDGE_HQ.levels[0]?.floorplan.props ?? []).map((prop) => prop.kind));
|
|
for (const id of [
|
|
"tera:bed.platform",
|
|
"tera:sofa.modular",
|
|
"tera:kitchen.run",
|
|
"tera:kitchen.island",
|
|
"tera:storage.wardrobe",
|
|
"tera:seat.stool",
|
|
"tera:light.floor",
|
|
]) assert.ok(kinds.has(id), `${id} is not placed in SF HQ`);
|
|
});
|
|
});
|
|
|
|
/**
|
|
* The second pack, held to the same bar as the first.
|
|
*
|
|
* A shipped pack that nothing asserts on is a pack that quietly stops resolving
|
|
* the first time somebody edits a shared constant. It gets the two checks that
|
|
* catch that — clean resolution and a walkable building — plus the one thing
|
|
* that is specific to it: it is a *hangar*, and the point of shipping it is that
|
|
* the format describes one room fifty-four metres across as readily as it
|
|
* describes a corridor with fifteen doors off it.
|
|
*/
|
|
describe("the Frontier Valley pack", () => {
|
|
const fv = new Plan(FRONTIER_VALLEY, { warn: false });
|
|
|
|
it("resolves with no problems", () => {
|
|
assert.deepEqual(
|
|
fv.problems.map((p) => `${p.where}: ${p.message} (${p.action})`),
|
|
[],
|
|
);
|
|
});
|
|
|
|
/**
|
|
* The deck is a level, and that is the fix for a real defect rather than a
|
|
* modelling preference: authored as a `Room` it had no floor height, so its
|
|
* slab lay on the concrete while its furniture floated 4.4 m over it.
|
|
*/
|
|
it("puts the mezzanine on its own level, at deck height", () => {
|
|
assert.deepEqual(
|
|
fv.levels.map((l) => [l.id, l.floorY]),
|
|
[
|
|
["level-1", 0],
|
|
["level-mezz", 4.4],
|
|
],
|
|
);
|
|
});
|
|
|
|
it("has unique seat ids, which must not collide with the other pack's", () => {
|
|
const mine = fv.allSeats().map((s) => s.id);
|
|
assert.equal(new Set(mine).size, mine.length);
|
|
// Not a hard requirement of the format — ids are unique per *building* — but
|
|
// a deployment serving both from one presence API would find out the hard
|
|
// way, and the two packs cost nothing by staying disjoint.
|
|
const theirs = new Set(plan.allSeats().map((s) => s.id));
|
|
const shared = mine.filter((id) => theirs.has(id));
|
|
assert.deepEqual(shared, [], "seat ids shared between the two shipped packs");
|
|
});
|
|
|
|
it("can be walked from the personnel door to every room", () => {
|
|
const STEP = 0.1;
|
|
const RADIUS = 0.3;
|
|
const b = fv.bounds;
|
|
const cols = Math.ceil(b.width / STEP);
|
|
const rows = Math.ceil(b.depth / STEP);
|
|
const at = (c: number, r: number) => ({ x: b.minX + c * STEP, z: b.minZ + r * STEP });
|
|
|
|
const room: (string | null)[] = new Array(cols * rows).fill(null);
|
|
for (let r = 0; r < rows; r += 1) {
|
|
for (let c = 0; c < cols; c += 1) {
|
|
room[r * cols + c] = fv.roomAt("level-1", at(c, r))?.id ?? null;
|
|
}
|
|
}
|
|
|
|
// Just inside the west gable's personnel door.
|
|
const sc = Math.round((1.0 - b.minX) / STEP);
|
|
const sr = Math.round((14.4 - b.minZ) / STEP);
|
|
assert.ok(room[sr * cols + sc], "the way in is not inside a room");
|
|
|
|
const seen = new Uint8Array(cols * rows);
|
|
const queue: [number, number][] = [[sc, sr]];
|
|
seen[sr * cols + sc] = 1;
|
|
while (queue.length > 0) {
|
|
const [c, r] = queue.pop() as [number, number];
|
|
for (const [dc, dr] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
|
const nc = c + (dc as number);
|
|
const nr = r + (dr as number);
|
|
if (nc < 0 || nr < 0 || nc >= cols || nr >= rows) continue;
|
|
const i = nr * cols + nc;
|
|
if (seen[i] === 1 || room[i] === null) continue;
|
|
if (fv.blocked("level-1", at(c, r), at(nc, nr), RADIUS)) continue;
|
|
seen[i] = 1;
|
|
queue.push([nc, nr]);
|
|
}
|
|
}
|
|
|
|
const reached = new Set<string>();
|
|
for (let i = 0; i < seen.length; i += 1) {
|
|
const id = room[i];
|
|
if (seen[i] === 1 && id != null) reached.add(id);
|
|
}
|
|
// Level 1 only. The mezzanine is a level of its own now and is reached by a
|
|
// stair this pack does not model, so a flood fill across the slab cannot and
|
|
// should not walk onto it.
|
|
const sealed = fv.levels[0]!.rooms.map((r) => r.id).filter((id) => !reached.has(id));
|
|
assert.deepEqual(sealed, [], "sealed rooms in the hangar");
|
|
});
|
|
|
|
it("guards the mezzanine edge, which is a 4.4 m drop onto concrete", () => {
|
|
// On the deck's own level — the balustrade belongs to it, not to the slab
|
|
// below, which is the whole point of the deck being a level.
|
|
assert.ok(fv.blocked("level-mezz", { x: 39.0, z: 25.0 }, { x: 37.0, z: 25.0 }, 0.3));
|
|
assert.ok(fv.blocked("level-mezz", { x: 45.0, z: 21.0 }, { x: 45.0, z: 19.0 }, 0.3));
|
|
});
|
|
|
|
/**
|
|
* And the deck is genuinely up there. `Plan` resolves a seat's `y` from its
|
|
* level's floor, so this is the assertion that would have caught the original
|
|
* defect: authored as a ground-floor room, every one of these was at y = 0
|
|
* with a chair drawn 4.4 m above it.
|
|
*/
|
|
it("stands its occupants on the deck rather than on the concrete", () => {
|
|
for (const id of ["mezz-01", "mezz-02", "mezz-03"]) {
|
|
assert.equal(fv.seat(id)?.y, 4.4, `${id} is not on the deck`);
|
|
}
|
|
});
|
|
});
|
|
|
|
/**
|
|
* All shipped packs declare where they stand, and the three are deliberately
|
|
* nothing alike — which is the entire argument for the field existing.
|
|
*/
|
|
describe("the sites", () => {
|
|
const packs = [LUMBRIDGE_HQ, FRONTIER_VALLEY, MATEO_COURT];
|
|
|
|
it("are all declared", () => {
|
|
for (const pack of packs) assert.ok(pack.site, `${pack.name} has no site`);
|
|
});
|
|
|
|
it("put one high in the air and one on the ground", () => {
|
|
assert.ok(
|
|
(LUMBRIDGE_HQ.site?.elevation ?? 0) > 100,
|
|
"the tower office should be a long way up",
|
|
);
|
|
assert.ok(
|
|
(FRONTIER_VALLEY.site?.elevation ?? 999) < 20,
|
|
"the hangar should be near the ground",
|
|
);
|
|
});
|
|
|
|
it("carry headings inside the compass", () => {
|
|
for (const pack of packs) {
|
|
const h = pack.site?.heading ?? 0;
|
|
assert.ok(h >= 0 && h < 360, `${pack.id} has a heading of ${h}`);
|
|
}
|
|
});
|
|
|
|
it("puts the Bay Area offices on the board the city view draws", () => {
|
|
// Not a format requirement — an office may stand anywhere — but these two
|
|
// are meant to be places in *this* product's San Francisco, and a
|
|
// coordinate typo that put one in Nevada would otherwise render fine.
|
|
for (const pack of [LUMBRIDGE_HQ, FRONTIER_VALLEY]) {
|
|
const site = pack.site;
|
|
assert.ok(site);
|
|
assert.ok(site.lat > 37.2 && site.lat < 38.2, `${pack.id} latitude ${site.lat}`);
|
|
assert.ok(site.lng > -122.8 && site.lng < -121.8, `${pack.id} longitude ${site.lng}`);
|
|
}
|
|
});
|
|
|
|
it("gives every map destination a finite, aligned exterior glyph", () => {
|
|
for (const pack of packs) {
|
|
const site = pack.site;
|
|
assert.ok(site, `${pack.id} has no site`);
|
|
const exterior = site.exterior;
|
|
assert.ok(exterior, `${pack.id} has no exterior glyph`);
|
|
assert.equal(exterior.kind, "building");
|
|
assert.equal(exterior.heading, site.heading, `${pack.id} exterior faces away from its plan`);
|
|
for (const [field, value] of Object.entries({
|
|
width: exterior.width,
|
|
depth: exterior.depth,
|
|
height: exterior.height,
|
|
storeys: exterior.storeys,
|
|
})) {
|
|
assert.ok(Number.isFinite(value) && value > 0, `${pack.id} has invalid ${field}: ${value}`);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
/**
|
|
* `offices/sites.ts` and the packs must not drift.
|
|
*
|
|
* The city draws its doors from `OFFICE_SITES` because a pack is a lazy chunk
|
|
* and the board wants the pins before anybody opens one. That means two places
|
|
* name the same building, and nothing in the type system ties them together —
|
|
* a coordinate edited in the pack and not in the table would put the marker on
|
|
* one building and the sun on another, and both would look entirely plausible.
|
|
*/
|
|
describe("the office site table", () => {
|
|
it("lists exactly the packs this build ships", () => {
|
|
assert.deepEqual(
|
|
OFFICE_SITES.map((e) => e.id).sort(),
|
|
[FRONTIER_VALLEY.id, LUMBRIDGE_HQ.id, MATEO_COURT.id].sort(),
|
|
);
|
|
});
|
|
|
|
it("hands each pack the very same site object it publishes", () => {
|
|
for (const pack of [LUMBRIDGE_HQ, FRONTIER_VALLEY, MATEO_COURT]) {
|
|
const entry = OFFICE_SITES.find((e) => e.id === pack.id);
|
|
assert.ok(entry, `${pack.id} is missing from OFFICE_SITES`);
|
|
// Identity, not equality: the packs import from the table, so anything
|
|
// less than the same reference means somebody has restated a coordinate.
|
|
assert.equal(entry.site, pack.site, `${pack.id} has a site of its own`);
|
|
}
|
|
});
|
|
|
|
it("publishes truthful environment names and runtime status", () => {
|
|
assert.deepEqual(
|
|
OFFICE_SITES.map(({ id, name, status }) => ({ id, name, status })),
|
|
[
|
|
{ id: "lumbridge-hq", name: "SF HQ · Studio", status: "active" },
|
|
{ id: "frontier-valley", name: "Frontier Valley", status: "building" },
|
|
{ id: "mateo-court", name: "LA HQ · Office", status: "active" },
|
|
],
|
|
);
|
|
});
|
|
});
|
|
|
|
/**
|
|
* The third pack, and the first on the other board.
|
|
*
|
|
* A courtyard block: a ring of rooms round an open-air yard with **no corridor
|
|
* anywhere** — every door opens onto the yard, and the yard does the job a
|
|
* corridor does in the other two. That makes it the first pack whose
|
|
* circulation is outdoors, and the first whose `ceiling: null` means there is
|
|
* genuinely no ceiling rather than "take the lid off so the shot can see in".
|
|
*/
|
|
describe("the Mateo Court pack", () => {
|
|
const mc = new Plan(MATEO_COURT, { warn: false });
|
|
|
|
it("resolves with no problems", () => {
|
|
assert.deepEqual(
|
|
mc.problems.map((p) => `${p.where}: ${p.message} (${p.action})`),
|
|
[],
|
|
);
|
|
});
|
|
|
|
it("stacks its upper floor floor-to-floor", () => {
|
|
assert.deepEqual(
|
|
mc.levels.map((l) => [l.id, l.floorY]),
|
|
[
|
|
["level-1", 0],
|
|
["level-2", 5],
|
|
],
|
|
);
|
|
});
|
|
|
|
it("keeps its seat ids disjoint from both other packs", () => {
|
|
const mine = mc.allSeats().map((s) => s.id);
|
|
assert.equal(new Set(mine).size, mine.length);
|
|
const others = new Set([
|
|
...plan.allSeats().map((s) => s.id),
|
|
...new Plan(FRONTIER_VALLEY, { warn: false }).allSeats().map((s) => s.id),
|
|
]);
|
|
assert.deepEqual(mine.filter((id) => others.has(id)), []);
|
|
});
|
|
|
|
it("caps the authored office at twenty-four useful seat addresses", () => {
|
|
const ids = mc.allSeats().map((seat) => seat.id);
|
|
assert.equal(ids.length, 24);
|
|
for (const id of ["works-a-01", "works-a-12", "loft-a-01", "loft-a-02", "review-04", "loggia-01"]) {
|
|
assert.ok(ids.includes(id), `${id} is missing`);
|
|
}
|
|
assert.equal(ids.some((id) => id.startsWith("works-b-")), false);
|
|
});
|
|
|
|
/**
|
|
* **The check that matters most in this pack.**
|
|
*
|
|
* Every door through the 0.25 m courtyard skin is 1.2 m rather than the 0.9 m
|
|
* the rest of the library uses, and that is not a style choice: `blocked`
|
|
* inflates by the walker's radius *and* by the wall's thickness, so a 0.9 m
|
|
* leaf in a wall this thick leaves too little clear width and seals the room
|
|
* behind it — while rendering perfectly. Two rooms were sealed exactly that
|
|
* way on the pack's first pass. A well-meaning edit back to 0.9 for
|
|
* consistency would do it again, and this is the only thing that would say so.
|
|
*/
|
|
for (const [levelId, entry] of [
|
|
["level-1", { x: 18.1, z: 1.0 }],
|
|
["level-2", { x: 8.4, z: 10.4 }],
|
|
] as const) {
|
|
it(`${levelId}: every room reachable from the way in`, () => {
|
|
const reached = reachableRoomsOf(mc, levelId, entry);
|
|
const sealed = mc.levels
|
|
.find((l) => l.id === levelId)!
|
|
.rooms.map((r) => r.id)
|
|
.filter((id) => !reached.has(id));
|
|
assert.deepEqual(sealed, [], `sealed rooms on ${levelId}`);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* The loggia rail, and the gap in it.
|
|
*
|
|
* Both halves matter and only together: a rail that also sealed the stair head
|
|
* would pass an assertion that only checked you cannot fall off.
|
|
*/
|
|
it("guards the loggia edge without sealing the way up", () => {
|
|
assert.ok(mc.blocked("level-2", { x: 8.8, z: 9.0 }, { x: 10.6, z: 9.0 }, 0.3));
|
|
assert.ok(mc.blocked("level-2", { x: 8.8, z: 14.0 }, { x: 10.6, z: 14.0 }, 0.3));
|
|
assert.ok(mc.blocked("level-2", { x: 18.0, z: 7.6 }, { x: 18.0, z: 9.4 }, 0.3));
|
|
assert.ok(
|
|
!mc.blocked("level-2", { x: 8.8, z: 10.4 }, { x: 10.4, z: 10.4 }, 0.3),
|
|
"the stair head is walled off",
|
|
);
|
|
});
|
|
|
|
it("stands its upper-floor occupants on the deck", () => {
|
|
for (const id of ["loggia-01", "loft-a-01"]) {
|
|
assert.equal(mc.seat(id)?.y, 5, `${id} is not on the deck`);
|
|
}
|
|
});
|
|
|
|
it("leaves the yard open to the sky", () => {
|
|
const court = mc.levels[0]?.rooms.find((r) => r.id === "court");
|
|
assert.ok(court, "no courtyard");
|
|
assert.equal(court.ceiling, null);
|
|
});
|
|
|
|
/** The point of the pack: the Southland board finally has a door. */
|
|
it("stands on the Southland board", () => {
|
|
const site = MATEO_COURT.site;
|
|
assert.ok(site);
|
|
assert.ok(site.lat > 33.28 && site.lat < 34.36, `latitude ${site.lat}`);
|
|
assert.ok(site.lng > -118.88 && site.lng < -117.22, `longitude ${site.lng}`);
|
|
});
|
|
});
|