feat: real fire on the boards, the LA office as a twin, and a night sky worth reading
The world stops being a simulation of California and starts being California. **THE PROMOTION GATE WAS THE FIRST COMMIT, BEFORE ANY ORANGE PIXEL EXISTED.** On today's live store the SoCal board contains 22 incidents. Every one has NULL acreage and fifteen are nameless LA County dispatch numbers. Drawn naively that is 22 orange marks over Los Angeles on a day nothing is burning — in a frame that contains no other warm colour, so one glyph would be the most salient object on the board and twenty-two would spend its credibility permanently. `acres >= 10 AND contained < 80 AND type != 'RX' AND last_seen = max(last_seen)` returns 0 on SoCal, exactly 5 on California, 0 on the Bay — same body, same day, three correct answers. The empty board is a deliverable, not a fallback: it says "No active fire on this board — CAL FIRE and WFIGS, just now", states that 21 records were gated and why, lists the largest fires burning OUTSIDE the frame with distances, and counts the hot pixels it is deliberately not drawing. **The privacy leak is structurally impossible rather than carefully avoided.** cloud-1 serves a projection; the four home-relative columns never leave that box. `observations.threat` was the one that nearly got through — it is `(16/distance)^2 x log10(acres) x momentum x containment x wind-alignment`, so with acreage and containment public it inverts to a distance circle around a house and three fires give an intersection. A grep of the built bundle for distance_km, bearing_deg, threat, 7762 and the street name returns nothing. **Deliberately not used, and both would have produced a confident wrong answer:** the store's `air` table retains only the last parameter of each poll, so all 78 rows read "Good" while the live feed reports ozone 101 "Unhealthy for Sensitive Groups" — haze driven off it would clear the sky during a smoke event. And `weather` is written only inside the NWS alerts loop, so a quiet day stores no wind at all. Tera's own per-region NWS wind is already correct and already what the clouds drift on. Satellite detections are drawn as evidence and never as incidents. The permanent industrial heat source 4.7 km from the owner's house is flagged persistent and dropped, asserted by a test that first proves it is present in the fixture. MODIS integer confidence and VIIRS string confidence are branched on `sat`. **The LA office is a twin.** Its entire authored second storey — Model Loft, Model Bay, The Materials Room, 430 lines nobody had ever stood in — is reachable on foot: a walker crosses level-1 to level-2 in 73 fixed steps, floorY 0 to 5, verified against the real pack rather than a synthetic plan. Its two studio devices read real hardware through a field-allowlisted bridge: mute, volume and reachability only. Never level, because there is no passive level upstream and obtaining one would record a room with people in it. Never dB, because upstream is gainPct across four different native scales. The bridge refuses all writes. Fixed at its root: an anonymous visitor was getting permanently at-rest instruments backing off against a 401. The tier moves into `createDeviceSource`, so anon gets the living simulator three file headers already promised. **Item 8 is closed, not fixed, and the correction is the point.** The Bay Area "stutter" was GPU power management — the card sat at 500 MHz of 2725 through every run that reproduced it, 4096/2048/1024/256 shadow maps all render in 1.21-1.31 ms, and two consecutive runs over a byte-identical dist gave 33.4 then 16.7. The allowance is removed and the cell is back to 16.7. Geometry is the gate; frame time is advisory. Item 7 was re-scoped after measuring: 1,069,006 of the Bay Area's 2,265,056 triangles were the second submission of the same buildings into the shadow pass. Mobile now has its own triangle caps and bay-area mobile draws 1,266,096. Also: bridges and the freeway corridor light up at night as emission, not lights — 1,614 deck lamps and 18 tower heads on the Bay in two draw calls. The single change that made US-101 legible was moving its edge lines from the lit material to the unlit one: retroreflective paint, the argument the SFO night frame already makes. California went 21,991 lamps to 4,051, clustered at the 17 town districts, because a rural interurban corridor genuinely is unlit. Tests 1137 -> 1340, server 280. All ten budget cells pass on first attempt with no cap raised. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+151
-2
@@ -29,13 +29,34 @@
|
||||
* Orbit mode hides them wholesale (`shell.ceilings.visible = false`) and that is
|
||||
* the entire mechanism. No CSG, no clipping planes, no per-camera cutaway: a
|
||||
* dollhouse is a room with its lid off, and a lid is a thing you can take off.
|
||||
*
|
||||
* ### Stairs are built from the record a walker climbs
|
||||
*
|
||||
* `ASSET_RESEARCH.md` listed a stair as the catalogue's one missing piece, and
|
||||
* `mateo-court` shipped without one: a `STEEL` floor finish the shape of the
|
||||
* flight, no treads, with the comment "the kit has no stair asset". The reason
|
||||
* it belongs *here* rather than in `src/assets/office/` is the interesting part.
|
||||
* A prop is placed by a coordinate somebody typed; a flight of stairs has to
|
||||
* agree with the two footprints, the two floor heights and the dog-leg the walk
|
||||
* controller actually traverses, and every one of those is already in the
|
||||
* resolved `Transition`. Building the treads from that record rather than beside
|
||||
* it makes the failure `src/offices/README.md` used to warn about —
|
||||
* a staircase nobody can climb — structurally inexpressible: the drawn flight
|
||||
* and the walked one are the same list of points.
|
||||
*/
|
||||
|
||||
import * as THREE from "three";
|
||||
import type { MaterialRegistry, SurfaceRole } from "../assets/materials.ts";
|
||||
import { MeshBin, parts as sharedParts, type PartBin } from "../assets/parts.ts";
|
||||
import { TEXTURE_TILE_METRES } from "../assets/textures.ts";
|
||||
import type { LevelPlan, Plan, ResolvedOpening, ResolvedRoom, WallRun } from "./plan.ts";
|
||||
import type {
|
||||
LevelPlan,
|
||||
Plan,
|
||||
ResolvedOpening,
|
||||
ResolvedRoom,
|
||||
ResolvedTransition,
|
||||
WallRun,
|
||||
} from "./plan.ts";
|
||||
import type { Outline, Point2 } from "./types.ts";
|
||||
|
||||
/** Jamb and head width on an opening's lining, in metres. */
|
||||
@@ -45,6 +66,27 @@ const FRAME_PROUD = 0.008;
|
||||
/** Depth of a window's sill board past the wall face, per side. */
|
||||
const SILL_PROUD = 0.03;
|
||||
|
||||
/**
|
||||
* The riser height a flight is divided into, in metres.
|
||||
*
|
||||
* 0.178 m is the middle of a commercial stair and is what makes the step count
|
||||
* come out right without a pack ever stating one: `mateo-court`'s 2.5 m flight
|
||||
* lands on fourteen risers, which is the number its own comment already claimed.
|
||||
* The flight is divided into a whole number of equal risers, never into
|
||||
* 0.178 m ones with a short step at the top — an uneven riser is the single most
|
||||
* reliable way to make a staircase read as wrong, and it is also how people fall
|
||||
* down real ones.
|
||||
*/
|
||||
const TARGET_RISER_M = 0.178;
|
||||
|
||||
/** Bounds on the division, so an absurd `elevation` cannot emit ten thousand boxes. */
|
||||
const MIN_FLIGHT_STEPS = 2;
|
||||
const MAX_FLIGHT_STEPS = 40;
|
||||
|
||||
/** Tread slab and riser board thickness, in metres. */
|
||||
const TREAD_THICKNESS = 0.055;
|
||||
const RISER_THICKNESS = 0.03;
|
||||
|
||||
export interface ShellOptions {
|
||||
materials: MaterialRegistry;
|
||||
/** Defaults to the shared bin, which is what everything else uses. */
|
||||
@@ -81,6 +123,15 @@ export interface Shell {
|
||||
floors: THREE.Group;
|
||||
/** Hide this to get the dollhouse. */
|
||||
ceilings: THREE.Group;
|
||||
/**
|
||||
* Treads, risers and half landings, one merged mesh per surface.
|
||||
*
|
||||
* Separate from `floors` because a stair is not a slab and is not hidden with
|
||||
* a lid, and separate from `walls` because it must never be ghosted: fading
|
||||
* out the way upstairs when the camera happens to be behind it is worse than
|
||||
* seeing through it.
|
||||
*/
|
||||
stairs: THREE.Group;
|
||||
/** Frames and glazing. Separate because glass must not cast a shadow. */
|
||||
openings: THREE.Group;
|
||||
/** Every wall mesh, each carrying a `WallInfo` on `userData.wall`. */
|
||||
@@ -137,7 +188,9 @@ export function createShell(plan: Plan, options: ShellOptions): Shell {
|
||||
ceilings.name = "ceilings";
|
||||
const openings = new THREE.Group();
|
||||
openings.name = "openings";
|
||||
group.add(walls, floors, ceilings, openings);
|
||||
const stairs = new THREE.Group();
|
||||
stairs.name = "stairs";
|
||||
group.add(walls, floors, ceilings, openings, stairs);
|
||||
|
||||
const wallMeshes: THREE.Mesh[] = [];
|
||||
// Every geometry this file makes is a merge or a triangulation it owns
|
||||
@@ -169,6 +222,15 @@ export function createShell(plan: Plan, options: ShellOptions): Shell {
|
||||
}
|
||||
}
|
||||
|
||||
// A transition is built with its lower storey, so a shell restricted to one
|
||||
// level draws the flight rising out of it rather than nothing at all.
|
||||
const built = new Set(levels.map((level) => level.id));
|
||||
for (const transition of plan.transitions) {
|
||||
if (transition.kind !== "stair") continue;
|
||||
if (!built.has(transition.lower.levelId)) continue;
|
||||
buildStair(transition);
|
||||
}
|
||||
|
||||
if (drawOpenings) {
|
||||
for (const mesh of frameBin.build("openings").children) openings.add(mesh);
|
||||
// Glass casts no shadow and receives none. A shadow-casting pane makes a
|
||||
@@ -187,6 +249,92 @@ export function createShell(plan: Plan, options: ShellOptions): Shell {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One flight — or one dog-leg — as treads, risers and half landings.
|
||||
*
|
||||
* The path is the resolved transition's own: the same points the crossing in
|
||||
* `officeWalker` interpolates over, in office-world metres with both floor
|
||||
* heights already in them. A leg that climbs is a flight; a leg that does not
|
||||
* is a landing, and gets one slab.
|
||||
*
|
||||
* The **top tread of every flight is not drawn**, and that is the one detail
|
||||
* worth knowing. A flight always arrives at something that already has a
|
||||
* surface — a half landing, or the floor of the storey above — and drawing a
|
||||
* tread there puts two coplanar slabs at the same height, which is a z-fight
|
||||
* and which storey wins is the GPU's business. So a flight of fourteen risers
|
||||
* draws fourteen riser boards and thirteen treads, and the fourteenth surface
|
||||
* is the thing it lands on. That is also what a real stair is.
|
||||
*/
|
||||
function buildStair(transition: ResolvedTransition): void {
|
||||
const material = materials.forSurface(transition.surface, "plaster");
|
||||
const bin = new MeshBin();
|
||||
let drew = false;
|
||||
|
||||
for (let index = 1; index < transition.path.length; index += 1) {
|
||||
const from = transition.path[index - 1]!;
|
||||
const to = transition.path[index]!;
|
||||
const dx = to.x - from.x;
|
||||
const dz = to.z - from.z;
|
||||
const run = Math.hypot(dx, dz);
|
||||
if (run < 1e-4) continue;
|
||||
const ux = dx / run;
|
||||
const uz = dz / run;
|
||||
// The same convention `splitWall` uses: a part's local +X at yaw φ points
|
||||
// along (cos φ, −sin φ).
|
||||
const yaw = Math.atan2(-uz, ux) + 0;
|
||||
const rise = to.y - from.y;
|
||||
|
||||
if (rise <= 1e-4) {
|
||||
// A half landing. One slab, the width of the flight, spanning the leg.
|
||||
bin.box(material, {
|
||||
x: from.x + ux * (run / 2),
|
||||
y: from.y - TREAD_THICKNESS,
|
||||
z: from.z + uz * (run / 2),
|
||||
yaw,
|
||||
size: [run + transition.width, TREAD_THICKNESS, transition.width],
|
||||
});
|
||||
drew = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const steps = Math.min(
|
||||
MAX_FLIGHT_STEPS,
|
||||
Math.max(MIN_FLIGHT_STEPS, Math.round(rise / TARGET_RISER_M)),
|
||||
);
|
||||
const riser = rise / steps;
|
||||
const going = run / steps;
|
||||
for (let step = 0; step < steps; step += 1) {
|
||||
const foot = step * going;
|
||||
// The vertical face, at the leading edge of the step it climbs to.
|
||||
bin.box(material, {
|
||||
x: from.x + ux * foot,
|
||||
y: from.y + riser * step,
|
||||
z: from.z + uz * foot,
|
||||
yaw,
|
||||
size: [RISER_THICKNESS, riser, transition.width],
|
||||
});
|
||||
// The tread. The last one is the landing above, which already exists.
|
||||
if (step === steps - 1) continue;
|
||||
bin.box(material, {
|
||||
x: from.x + ux * (foot + going / 2),
|
||||
y: from.y + riser * (step + 1) - TREAD_THICKNESS,
|
||||
z: from.z + uz * (foot + going / 2),
|
||||
yaw,
|
||||
size: [going, TREAD_THICKNESS, transition.width],
|
||||
});
|
||||
}
|
||||
drew = true;
|
||||
}
|
||||
|
||||
if (!drew) return;
|
||||
for (const child of bin.build(`stair:${transition.id}`).children) {
|
||||
const mesh = child as THREE.Mesh;
|
||||
mesh.userData.transitionId = transition.id;
|
||||
owned.push(mesh.geometry);
|
||||
stairs.add(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
function buildWall(
|
||||
levelId: string,
|
||||
wallId: string,
|
||||
@@ -366,6 +514,7 @@ export function createShell(plan: Plan, options: ShellOptions): Shell {
|
||||
walls,
|
||||
floors,
|
||||
ceilings,
|
||||
stairs,
|
||||
openings,
|
||||
wallMeshes,
|
||||
setGhosted(mesh, ghosted) {
|
||||
|
||||
Reference in New Issue
Block a user