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:
+143
-10
@@ -373,6 +373,50 @@ export const DOME_RADIUS_FACTOR = 1.05;
|
||||
*/
|
||||
const DOT_PIXELS = 3.5;
|
||||
|
||||
/**
|
||||
* The range, in kilometres, at which an object is drawn at `DOT_PIXELS`, and how
|
||||
* hard apparent size follows range.
|
||||
*
|
||||
* Every dot used to be the same object: the same size, the same square silhouette,
|
||||
* the same colour per constellation, with alpha the only thing that varied. A few
|
||||
* hundred pixel-identical squares on a sphere sample the pixel grid as a regular
|
||||
* lattice, which is the moire this exists to break. The two cues that turn a
|
||||
* lattice into a population are **size** and **something else in the sky to read
|
||||
* it against**; the moon is the second, in `scenekit.ts`.
|
||||
*
|
||||
* The size cue has to come from something true or it is decoration, and there is
|
||||
* exactly one such number already in a `SatelliteFix`: the slant range. It is
|
||||
* genuinely what sets how bright a naked-eye pass looks, and on this catalogue it
|
||||
* spans a factor of ninety — a station at 400 km against a navigation bird at
|
||||
* 36,000. Rendered as an inverse square that would be a factor of 8,000 in
|
||||
* brightness and most of the sky would vanish, so the exponent is 0.35: a gentle
|
||||
* curve that puts a low pass at four and a half pixels, a Starlink overhead at
|
||||
* three and a half, one near the horizon at about three, and a distant navigation
|
||||
* satellite on the floor. Enough spread that no two neighbouring dots are the
|
||||
* same, nowhere near enough for a Starlink train to smear.
|
||||
*
|
||||
* 800 km is the reference because it is a Starlink a little off zenith, which is
|
||||
* the object `DOT_PIXELS` was chosen against in the first place.
|
||||
*/
|
||||
const RANGE_REFERENCE_KM = 800;
|
||||
const RANGE_EXPONENT = 0.35;
|
||||
|
||||
/** Floor and ceiling on the drawn size, in pixels. Below 2 a dot is noise; above 6 it is a planet. */
|
||||
const MIN_DOT_PIXELS = 2.1;
|
||||
const MAX_DOT_PIXELS = 6;
|
||||
|
||||
/**
|
||||
* How much of the drawn size an eclipsed object loses.
|
||||
*
|
||||
* A point source at the threshold of vision blooms: a bright one occupies more of
|
||||
* the retina — and more of a sensor — than a faint one at the same true angular
|
||||
* size, which is why stars on a photograph have magnitudes you can read off their
|
||||
* diameters. So a fully lit satellite is drawn at its full size and one in the
|
||||
* earth's shadow shrinks toward this, which is the same fact `SHADOW_ALPHA`
|
||||
* already states about its brightness and reinforces rather than repeats.
|
||||
*/
|
||||
const SHADOW_SIZE = 0.72;
|
||||
|
||||
/** Ceiling on dots, so the buffers are allocated once and never grow. */
|
||||
const MAX_DOTS = 4096;
|
||||
|
||||
@@ -416,6 +460,26 @@ const HORIZON_FADE_DEG = 8;
|
||||
*/
|
||||
const SHADOW_ALPHA = 0.16;
|
||||
|
||||
/**
|
||||
* How large one object is drawn, in pixels.
|
||||
*
|
||||
* Exported and pure because it is the whole of the "satellites are objects rather
|
||||
* than a lattice" claim, and a claim like that is worth a test rather than a
|
||||
* screenshot: a regression here is a sky that quietly goes back to being graph
|
||||
* paper, which nobody notices until somebody photographs it at dusk.
|
||||
*
|
||||
* Both inputs are already in a `SatelliteFix` and neither is invented. See
|
||||
* `RANGE_REFERENCE_KM` for why the range curve is so gentle and `SHADOW_SIZE`
|
||||
* for why an eclipsed object also shrinks.
|
||||
*/
|
||||
export function dotPixels(fix: Pick<SatelliteFix, "rangeKm" | "shadow">): number {
|
||||
const range = Math.max(1, fix.rangeKm);
|
||||
const lit = 1 - Math.min(1, Math.max(0, fix.shadow));
|
||||
const scaled = DOT_PIXELS * Math.pow(RANGE_REFERENCE_KM / range, RANGE_EXPONENT);
|
||||
const bloomed = scaled * (SHADOW_SIZE + (1 - SHADOW_SIZE) * lit);
|
||||
return Math.min(MAX_DOT_PIXELS, Math.max(MIN_DOT_PIXELS, bloomed));
|
||||
}
|
||||
|
||||
export function createSatelliteLayer(boardRadius: number): SatelliteLayer {
|
||||
const group = new THREE.Group();
|
||||
group.name = "satellites";
|
||||
@@ -424,21 +488,84 @@ export function createSatelliteLayer(boardRadius: number): SatelliteLayer {
|
||||
|
||||
const positions = new Float32Array(MAX_DOTS * 3);
|
||||
const colors = new Float32Array(MAX_DOTS * 4);
|
||||
const sizes = new Float32Array(MAX_DOTS);
|
||||
// Held as locals rather than looked up through `geo.attributes` on every
|
||||
// update: the lookup is a string index into a dictionary typed as possibly
|
||||
// holding nothing, and the alternative to keeping the references is a
|
||||
// non-null assertion on the hot path twice a frame.
|
||||
const positionAttr = new THREE.BufferAttribute(positions, 3);
|
||||
const colorAttr = new THREE.BufferAttribute(colors, 4);
|
||||
const sizeAttr = new THREE.BufferAttribute(sizes, 1);
|
||||
const geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute("position", positionAttr);
|
||||
geo.setAttribute("color", colorAttr);
|
||||
geo.setAttribute("aSize", sizeAttr);
|
||||
geo.setDrawRange(0, 0);
|
||||
|
||||
const material = new THREE.PointsMaterial({
|
||||
size: DOT_PIXELS,
|
||||
sizeAttenuation: false,
|
||||
vertexColors: true,
|
||||
/**
|
||||
* A hand-written points material, and the two reasons `PointsMaterial` could
|
||||
* not stay.
|
||||
*
|
||||
* **Per-object size.** `PointsMaterial.size` is a uniform; there is no
|
||||
* per-vertex size in it at all, and size is the cue that turns this lattice
|
||||
* into a population. That alone forces a shader.
|
||||
*
|
||||
* **The silhouette.** An untextured point is a hard square — `gl_PointCoord`
|
||||
* covers a square and nothing rounds it — so every object in the sky was a
|
||||
* three-and-a-half-pixel axis-aligned box. Photographed at dusk with the camera
|
||||
* tilted to the horizon, a few hundred of those read as graph paper. The round
|
||||
* falloff below is computed analytically rather than sampled from a sprite,
|
||||
* which is both cheaper and sharper at three pixels: a 64-texel sprite at this
|
||||
* size is several mip levels down and comes back as a soft grey blur.
|
||||
*
|
||||
* Still one draw call, still one `Points`, still `MAX_DOTS` vertices. Nothing
|
||||
* about the cost of this layer changed.
|
||||
*/
|
||||
const material = new THREE.ShaderMaterial({
|
||||
uniforms: {
|
||||
/**
|
||||
* three multiplies `PointsMaterial.size` by the renderer's pixel ratio
|
||||
* before uploading it, and `gl_PointSize` is in physical pixels — so a
|
||||
* hand-written points shader that skips this draws dots at a third of the
|
||||
* size on a 3x phone. Written from `onBeforeRender`, which is the only
|
||||
* place this layer can see a renderer.
|
||||
*/
|
||||
uPixelRatio: { value: 1 },
|
||||
},
|
||||
vertexShader: `
|
||||
attribute vec4 color;
|
||||
attribute float aSize;
|
||||
varying vec4 vColor;
|
||||
void main() {
|
||||
vColor = color;
|
||||
// No size attenuation, deliberately: see DOT_PIXELS. Everything on this dome
|
||||
// is the same distance away and stands for something 550 km up, so an object
|
||||
// does not get bigger because the map was zoomed in.
|
||||
gl_PointSize = aSize * uPixelRatio;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
varying vec4 vColor;
|
||||
void main() {
|
||||
// gl_PointCoord runs 0..1 across a square; this is the distance from its
|
||||
// centre in units of the half-width, so 1.0 is the inscribed circle.
|
||||
vec2 offset = gl_PointCoord - vec2( 0.5 );
|
||||
float r = length( offset ) * 2.0;
|
||||
/*
|
||||
* A bright core inside a soft halo, which is what a point source does to any
|
||||
* optic including an eye. One smoothstep would give a flat disc with a
|
||||
* feathered edge and would read as a bubble; the product of the two puts most
|
||||
* of the energy in the middle pixel and lets the rest fall away, so a dot
|
||||
* still looks like a dot at two pixels and like a small star at six.
|
||||
*/
|
||||
float core = 1.0 - smoothstep( 0.0, 0.55, r );
|
||||
float halo = 1.0 - smoothstep( 0.35, 1.0, r );
|
||||
float alpha = vColor.a * ( 0.65 * core + 0.35 * halo * halo );
|
||||
if ( alpha <= 0.0 ) discard;
|
||||
gl_FragColor = vec4( vColor.rgb, alpha );
|
||||
}
|
||||
`,
|
||||
transparent: true,
|
||||
// Dots are drawn over the sky and over each other; letting them write depth
|
||||
// makes whichever drew first punch a hole in the ones behind, which on a
|
||||
@@ -451,18 +578,22 @@ export function createSatelliteLayer(boardRadius: number): SatelliteLayer {
|
||||
/**
|
||||
* Satellites are not in the weather.
|
||||
*
|
||||
* `PointsMaterial` defaults `fog: true`, and the city runs a linear fog
|
||||
* whose far plane is 2.8 board spans — so every dot was being mixed toward
|
||||
* the fog colour by distance, and the constellation dimmed as the camera
|
||||
* pulled back, exactly when more of it came into view. Haze is a property of
|
||||
* the twelve kilometres of air a city sits in; an object 550 km up is on the
|
||||
* far side of all of it.
|
||||
* A material that opts into fog gets mixed toward the fog colour by
|
||||
* distance, and the city runs a linear fog whose far plane is 2.8 board
|
||||
* spans — so the constellation dimmed as the camera pulled back, exactly
|
||||
* when more of it came into view. Haze is a property of the twelve
|
||||
* kilometres of air a city sits in; an object 550 km up is on the far side of
|
||||
* all of it. A `ShaderMaterial` has no fog unless its shader asks, so this is
|
||||
* now true by construction rather than by a flag.
|
||||
*/
|
||||
fog: false,
|
||||
});
|
||||
|
||||
const points = new THREE.Points(geo, material);
|
||||
points.name = "satellite-dots";
|
||||
points.onBeforeRender = (renderer) => {
|
||||
material.uniforms.uPixelRatio!.value = renderer.getPixelRatio();
|
||||
};
|
||||
// The buffer is rewritten in scene space every update, so its bounding sphere
|
||||
// is permanently stale and culling on it would cull the whole sky.
|
||||
points.frustumCulled = false;
|
||||
@@ -533,6 +664,7 @@ export function createSatelliteLayer(boardRadius: number): SatelliteLayer {
|
||||
colors[n * 4 + 1] = scratch.g;
|
||||
colors[n * 4 + 2] = scratch.b;
|
||||
colors[n * 4 + 3] = horizon * (SHADOW_ALPHA + (1 - SHADOW_ALPHA) * lit) * skyDarkness;
|
||||
sizes[n] = dotPixels(fix);
|
||||
n += 1;
|
||||
}
|
||||
|
||||
@@ -540,6 +672,7 @@ export function createSatelliteLayer(boardRadius: number): SatelliteLayer {
|
||||
geo.setDrawRange(0, n);
|
||||
positionAttr.needsUpdate = true;
|
||||
colorAttr.needsUpdate = true;
|
||||
sizeAttr.needsUpdate = true;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user