The office gets a room you cannot see all of from a doorway
Lumbridge HQ was 34 x 18 m, 612 m², one storey, fifteen rooms. It is now
48 x 18 m over two storeys, twenty-six rooms and a hundred and
twenty-six seats — and the fourteen metres that were added are one room
rather than a second row of cellular offices, because area is not the
thing the building was short of.
**The commons** is the east wing: one volume, open all the way to the
roof at 7.0 m, with the block's old east curtain wall now looking into it
instead of onto a street. Two archways through that glass, four trees,
one refectory table on the axis of the corridor, and nothing along the
glazing.
**Level 2** sits at 4.2 m — floor to floor, not floor to ceiling, which
is the number people get wrong. A studio with nineteen metres of
unbroken north-lit floor, a library, two meeting rooms, a project room,
and a gallery that reaches 4.2 m out over the void so the commons can be
looked down into rather than merely walked through.
`WIDTH` became `BLOCK_E`: every room, wall and seat authored before the
wing existed is measured against the old east line, and folding the two
into one number would have stretched fifteen rooms sideways.
Three things worth knowing:
- Every existing seat id is unchanged. The README says to keep them
stable for the same reason street numbers survive repainting, and a
`Presence` binds to one.
- The balustrade is a wall and not a prop. `Plan` derives the walk
collider from the wall list, so a rail authored as furniture is a
balcony you can walk off, and the drop is a storey onto terrazzo.
- The wing's walls carry an explicit `height`, because "double height"
is a property of the walls and `ceiling: null` alone would leave a
lid you cannot see but the light rig can.
`src/test/office.test.ts` is new and is the README's own checklist, which
nothing had ever run: problems empty, seat ids unique building-wide, the
balustrade impassable from all three open edges, and a 0.1 m flood fill
proving every room on both levels is reachable on foot from the way in.
That last one caught a door authored 200 mm past the end of its wall —
silently dropped, room sealed, and invisible from every camera angle.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
/**
|
||||
* 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 4.2 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";
|
||||
|
||||
const plan = new Plan(LUMBRIDGE_HQ, { warn: false });
|
||||
|
||||
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})`),
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
it("has both storeys, at floor-to-floor and not floor-to-ceiling", () => {
|
||||
assert.deepEqual(
|
||||
plan.levels.map((l) => [l.id, l.floorY]),
|
||||
[
|
||||
["level-1", 0],
|
||||
["level-2", 4.2],
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
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, 8);
|
||||
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));
|
||||
for (const id of ["eng-01", "eng-12", "studio-01", "studio-12", "studio-b-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.2, z: 4.9 },
|
||||
"level-2": { x: 2.4, z: 4.0 },
|
||||
};
|
||||
|
||||
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 gallery balustrade", () => {
|
||||
const OUTSIDE = 0.6;
|
||||
|
||||
it("stops a walker at all three open edges", () => {
|
||||
const edges: [string, { x: number; z: number }, { x: number; z: number }][] = [
|
||||
// North edge, at z = 2.0: standing on the gallery, stepping north.
|
||||
["north", { x: 36.0, z: 2.6 }, { x: 36.0, z: 2.0 - OUTSIDE }],
|
||||
// East edge, at x = 38.2: stepping out over the commons.
|
||||
["east", { x: 37.6, z: 9.0 }, { x: 38.2 + OUTSIDE, z: 9.0 }],
|
||||
// South edge, at z = 16.0.
|
||||
["south", { x: 36.0, z: 15.4 }, { x: 36.0, z: 16.0 + OUTSIDE }],
|
||||
];
|
||||
for (const [name, from, to] of edges) {
|
||||
assert.ok(
|
||||
plan.blocked("level-2", from, to, 0.3),
|
||||
`the ${name} edge of the gallery lets a walker off a 4.2 m drop`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 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("does not seal the gallery off from the studio", () => {
|
||||
assert.ok(!plan.blocked("level-2", { x: 33.0, z: 7.4 }, { x: 35.0, z: 7.4 }, 0.3));
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 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 commons is open to both storeys", () => {
|
||||
it("has no ceiling", () => {
|
||||
const level1 = plan.levels.find((l) => l.id === "level-1");
|
||||
const commons = level1?.rooms.find((r) => r.id === "commons");
|
||||
assert.ok(commons, "the commons is missing");
|
||||
assert.equal(commons.ceiling, null);
|
||||
});
|
||||
|
||||
it("is enclosed to two storeys rather than one", () => {
|
||||
const level1 = plan.levels.find((l) => l.id === "level-1");
|
||||
assert.ok(level1);
|
||||
// 7.0 m: one storey of 4.2 plus one room of 2.8. Asserted against the walls
|
||||
// as resolved, so a level default leaking through would be caught.
|
||||
//
|
||||
// `solid` only. The splitting pass emits a run per piece, so a wall with a
|
||||
// door in it also yields a `lintel` over the opening — 4.6 m of wall above a
|
||||
// 2.4 m head, which is correct and is not the wall's height.
|
||||
const wingWalls = level1.runs.filter(
|
||||
(run) => run.wallId.startsWith("wing-") && run.role === "solid",
|
||||
);
|
||||
assert.ok(wingWalls.length > 0, "the wing has no walls");
|
||||
for (const run of wingWalls) {
|
||||
assert.equal(run.top - run.bottom, 7, `${run.wallId} is ${run.top - run.bottom} m tall`);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user