b25f217e3e5ee26ec0526a81cb769173d7cd8630
12 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b25f217e3e |
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> |
||
|
|
f81d5218d4 |
feat: SFO, LAX, both bridges, a road that reads as a road, and aeroplanes that move
**The aeroplanes were stuck because the wire could not describe motion.** `WireAircraft` carried position, altitude and heading and nothing else, so the client could only interpolate between the last two observations: every aircraft replayed a segment it had already flown, arrived at the newest known point, and sat still until the next poll landed five to fifteen seconds later. The feed had the missing numbers the whole time and the server threw them away. Sampled live from `api.adsb.lol/v2/point` while writing this — `gs` ground speed, `track`, `baro_rate`, plus `r` registration and `t` type designator. They are on the wire now in SI, aircraft dead-reckon along their own track and correct toward the truth when a fix lands, and the click card an anonymous visitor gets says "B739 · N68834". That last part is the enrichment FR24 was wanted for, obtained from an ODbL feed we may actually republish. **SFO and LAX exist.** A new `engine/airports.ts` composes an airport from runways, taxiways, aprons and terminal masses, with markings drawn on a canvas rather than modelled; the pattern of the runways is what the eye recognises from altitude, long before any building does. SFO is the two crossing pairs on the bay fill; LAX is the four parallels either side of the terminal horseshoe, plus the Southland fields under the traffic that actually flies there. **The Golden Gate and the Bay Bridge are those bridges.** One kit in `engine/bridges.ts`, because a suspension bridge is a repeated tower, a catenary main cable, a series of hangers and a deck — so both are configurations rather than two private implementations. The Bay Bridge carries the real 2013 topology: two suspension towers west of Yerba Buena, one east, then the piered causeway. The freeway stopped being a wireframe overlay and became a road, with shoulders, a median, and lane markings as texture. **And the board got faster while all of that landed.** California went from 728,744 triangles and 562 draw calls to 391,169 and 371 — headroom from 2.8% to 47.8%. The Bay Area board is 506,550 triangles lighter than before this work. Two things paid for it: - `transmission: 0.08` on the aircraft cockpit glass. three.js runs a full transmission backdrop pass whenever any rendered material has transmission above zero, re-drawing the entire opaque scene into a second target every frame — so the city was rendering terrain, every block and every freeway piece TWICE. Measured by patching only that number in a copy of the built bundle: 703,267 tris / 562 draws with it, 398,608 / 371 without. The material was already `transparent: true, opacity: 0.86`, so it was buying nothing. - Flatness-adaptive terrain LOD, which collapses runs of lattice cells wherever the height and colour agree with the quad replacing them. The coastline is provably untouched — a patch collapses only when every point is on land and agrees about `park` — and a test asserts the drawn footprint matches the cell-by-cell area to 1e-6. `createTerrain` got *faster*: the vertices it stops emitting cost more than the flatness scan costs to run. **The budget now watches the boards this was built on.** There was no `bay-area` or `socal` cell — so SFO, LAX and both bridges all landed in frames nothing measured, which is how a cap you do not have looks from the inside. Both are in the matrix now with caps set from measurement, and the rationale lives in the harness because JSON cannot hold a comment. Two known defects ship with this, both recorded in TODO.md rather than hidden: - `bay-area.desktop` drops about one frame in twenty (p50 16.7, p95 33.3). It is desktop-only and not fill rate — mobile runs the same 2.26 M triangles at a comparable pixel count and holds 16.7 flat — which points at the 2048 shadow map desktop uses against handheld's 1024. Measured at the commit before this work with the same harness: identical p95 33.3. Pre-existing, and invisible until the cell existed. - The aeroplane glyph is still about 1.5x the Golden Gate's main span at chapter zoom, down from 2.5x. `GLYPH_MAX_SCALE` is 52 because the raw scale at the far end of the California orbit is 51.0 at a 60-degree field of view, and 26 — tried first — put the glyph at 0.0123 of the frame against the 0.012 where the wings stop resolving. The real fix is to clamp against the camera's focus distance rather than the aircraft's, which is a signature change. Tests 1020 -> 1137. Typecheck, build, eight budget cells, no-binaries, provenance, zero-config boot, dependency licences, arena source hashes and the UI smoke across two viewports and two access tiers all pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8fb85cd2e5 |
feat: give the boards a horizon, a sea that reflects, and a state worth flying over
The wide shot, which is what an anonymous visitor actually lands on. **The sea was `MeshLambertMaterial`** — a material with no specular term at all, by construction — on a board where water is half the frame. It is now a low-roughness dielectric that reads `scene.environment`, with a runtime-generated tiling swell normal map sampled twice per fragment at two scales and two headings, so the sun breaks into a moving glitter path instead of a mirror point. An `onBeforeCompile` patch takes the body colour toward the deep value looking straight down and leaves it to the reflection at grazing, and walks roughness up past 1.6 board spans so the far water cannot shimmer. The swell spectrum is 1/k^2 and not 1/k because the first attempt was photographed: at 1/k every component carries the same slope, the shortest wave wins, and the sea renders as hard diagonal corduroy. A test holds it now. **The board no longer ends in a diamond.** The sea plane went from 1.8 board spans to 18, past the fog's far plane from anywhere the orbit reaches, and the sky is a world-space dome rather than a screen-space gradient. That gradient was wrong in a way dusk made obvious: the sunset band was painted along the *bottom* of the picture, under the board, while the true horizon at the top of frame stayed zenith blue. `daylight.ts` pinning the horizon stop to the fog colour to hide the seam was a symptom of it. **Terrain casts shadows.** Left off before because double-sided terrain against a ~16 m-per-texel shadow map gives acne; `shadowSide = BackSide` is the cure, shot at four sun elevations down to +0.0 degrees to confirm no stippling. The caster is a stride-2 decimation appended to the same index buffer and swapped in by `onBeforeShadow`/`onAfterShadow` via `drawRange`: no extra draw call, a quarter of the depth cost, and indistinguishable from the full-resolution caster in a side-by-side crop. Stride 1 was measured at +65,566 triangles and would have missed the budget by ~47,000, so it was not shipped. **California reads as California.** It was a beige kite: the eastern edge one ruled line for five degrees of latitude, the south closing in a diagonal V, the whole south-east a featureless tan wedge. Now the coast runs to the Mexican border with San Diego on it, the eastern edge follows the Colorado and the Nevada diagonal, and the south-east is the Basin and Range — forty parallel desert ridges throwing shadows east, Death Valley as a white pan between the Panamints and the Black Mountains, the Salton Sea the one cool value for two hundred kilometres. The opening pose is retuned to the bigger board; the old 452/392 stand-off left a slab of empty ocean where the state should be. **The aircraft were six pixels.** Measured, by enlarging a screenshot 200% to find one at all — indistinguishable from a dead pixel, on a board whose entire claim is that the sky is live. They are airliners now, with planform and trail, and clicking one raises its card for a signed-out visitor. **The Model X is off the wall.** It stood at floor level outside a studio 188 m up a Transbay tower, reading as a car balanced on a parapet. The apron is now chosen from `site.elevation`, which the pack already carries — not from an office id, which is the bug class this repo already hit once when a door marker gated on `id === "sf"` and would have pinned the Los Angeles building to San Francisco. Also fixed, and nearly shipped: sea z-fighting dithered every flat piece of ground on the Bay Area and SoCal boards. And one test asserted an exact source line for the water material, so the better multi-line implementation failed it — it now asserts the property (dielectric, metalness 0, low roughness) rather than the author's first guess at formatting. Tests 964 -> 1015. California desktop 562/650 draw calls and 728,744/750,000 triangles — 2.8% of triangle headroom left, which is the number the next person should check first. No budget was raised. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
db074e9cf7 |
feat: tone-mapped render rig, studio devices, LA fidelity pass, UI overhaul
The build the studios needed, across eight workstreams and one strict file partition. **The render rig was the quality ceiling.** The renderer ran three's NoToneMapping default while atmosphere drove the sun to 2.35 and assets set emissives to 3.2, so every value above 1.0 hard-clipped to flat white — which is why walls blew out and every fitting looked like a white rectangle. ACES filmic tone mapping and an explicit output colour space land in `stage.ts`, and the atmosphere intensity table and palette headroom are re-tuned against the new curve rather than left tuned for the clipping we removed. `engine/environmentRig.ts` builds a PMREM environment at runtime, procedurally, so nothing binary is committed. There was no environment map anywhere before, so every `metalness > 0` role had nothing to reflect and rendered dull grey — a defect the code already documented against itself in `office/optimus.ts`, where a whole material role was abandoned over it, and worked around in `modelX.ts` with a fake emissive that this change deletes. Atmosphere remains the sole light owner; the rig derives from the `LightingState` it already produced. **Studio hardware exists.** There was no device concept anywhere in the product: no type, no route, no state. `devices/types.ts` fixes a declaration/state/ capability/command contract that a smart light, a thermostat, a door sensor and a charger all fit without a schema change, and both studios now carry a desk mic and a computer speaker with deterministic simulated behaviour behind an adapter seam a real API can occupy later. Reads are the demo and are open; commands are a signed-in action and are kept off the read body entirely, because a shared cache replaying a GET that turned a microphone on is exactly what the fail-closed cache default exists to prevent. **The ADS-B licence hole is closed.** `TERA_ADSB_ENDPOINT` accepted any URL, the response was served publicly cacheable, and the attribution hardcoded adsb.lol regardless of where the endpoint pointed — one env var away from republishing non-redistributable data under an open-terms credit. The host is now allowlisted, the credit is derived from the host actually configured, public cacheability is conditional on redistributability, and a refused endpoint demotes to simulated flights and says so in `degraded[]`. The gate is on the source, not the feature: live aircraft and their detail cards stay open to anonymous visitors. **The LA studio was never the smaller pack** — 16 rooms and 248 props against SF's 4 and 28. Its deficit was fidelity per square metre: 98 of those props were ceiling troffers, it bound no props to seats, placed none of the habitat kit, and 12 of its 16 rooms had no viewpoint. Density comes from new asset kinds rather than more instances, because `furnish.ts` draws once per kind and folds colour into the batch key, so repeat instances add nothing the eye can read. **The interface stops being forty imperative mutations.** Every visibility decision moves into a pure, tested `ui/chromeState.ts` and one applier, so the chrome has coverage for the first time. Deleted: ~100 lines of CSS and two bindings targeting elements that no longer exist, and a `body:has()` rule that shifted the desktop layout by 160px for touch controls hidden there. Fixed: the office picker tabs that drew their label and their badge on top of each other. Added: a first-run flow, because the product is two verbs and neither was ever stated on screen. Mobile is designed on its own terms instead of being the desktop with things hidden — the plan view comes back, and the keyboard-only shortcuts button is replaced by touch controls. `arena/studioOps.ts` frames the whole thing as the multi-variable environment it is, wrapping the same simulators the renderer drives rather than a headless copy. Also removed `input/vehicle.ts`, which nothing but its own test imported. Tests 385 -> 961, all passing. Typecheck, build, performance budgets across six matrix cells, no-binaries, provenance, dependency licences, zero-config boot and arena source hashes all green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2d87d9f354 |
The city points at its own buildings, and the sky stops depending on an API
**Clouds were invisible to everyone who had not wired up NWS.** The layer
took `currentWeather()?.cloudCover ?? 0`, and `currentWeather()` is null on
any deployment without a weather source — which is the default, and the
exact configuration this repo is held to: a stranger clones it, runs one
command, and gets a city with no account and no key. Their sky was
permanently, silently empty. `atmosphere.ts` already models a sky when
nobody has observed one; it now models cover too, an observed reading
still wins outright, and the clouds are there on a bare clone.
**Both offices are pins on the city, and clicking one walks you in.** Each
pack has carried a real `site` since the sun needed one, and that
coordinate was known to the lighting and to nothing else — a visitor
looking at the board had no way to tell that two of those buildings are
ones they can go inside. The coordinates move to a tiny eagerly-imported
`offices/sites.ts` that the packs import *from*, because a pack is a 25 kB
lazy chunk and the board wants its pins long before anybody opens a door.
A test asserts the pack and the table hold the **same object**, not merely
equal values: a drifted coordinate would put the marker on one building
and the sun on another and both would look entirely plausible.
**Aircraft bank into their turns.** The roll channel existed and was never
written, so every turn was flat. Bank comes from the coordinated-turn
relation against the measured turn rate, damped by a first-order lag so it
settles rather than oscillates, and clamped at 30° like a real limiter.
Six regression tests, because roll is the one channel that feeds itself —
position and heading are recomputed from the last two observations and
wash out a bad value, while a NaN in the roll would persist for the life
of the track.
That fed straight into a real defect: `AdsbFlights` substituted
`heading: 0` for records with no `track` field, which is harmless for a
symmetrical dart and is a **sustained full-scale artefact** once aircraft
bank — a target whose real heading is 200° reported as 0° reads as a 160°
turn and pins the roll at its limiter for as long as it is in the feed.
Those records are dropped now. An aeroplane the feed will not give a
heading for is one this layer cannot draw honestly.
**The office empties out overnight.** A full complement of seated people
at one in the morning, under house lights that came on because the sun is
down, was the least believable thing left in the room once the clock
became real. A live roster always wins — an API that says the building is
empty is telling the truth about the building.
**Robots go somewhere.** They pick real addresses — a seat, a room — and
turn to face the seat when they arrive, rather than stopping at a random
angle. Godmode gets an office section: house lights forced on or off or
following the sun, robots and ceilings toggled, with a readout.
**The bundle is split.** Entry chunk 758 kB to 208 kB, with three.js and
satellite.js in a vendor chunk that survives an app deploy instead of
being re-downloaded on every one. Rollup's 500 kB warning still fires and
should — it now points at three.js, where it is true, instead of at our
code, where it was pointing at three.js all along.
Reviewers caught two false geography claims in the new prose ("both
shipped buildings stand in San Francisco" — one is across the estuary at
Alameda Point) and several miscounted figures. Fixed. In a codebase where
the comments are the design record, those are defects.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
51979feea0 |
Shadows land on the building, and the sky layers stop repeating themselves
**Shadows were the right size and pointed at nothing.** Last round fixed the missing `updateProjectionMatrix()`, so the frustum finally became the size every caller asks for — but nothing aimed it, and `sun.target` sits at the world origin. A pack's origin is the **north-west corner of its slab**, so for lumbridge-hq the box was off-centre by half the building: 14.4 m of a 48 m plate, about a third of the floor, fell outside the frustum and neither cast nor received. Invisible while three's broken ±5 default made shadows useless everywhere; obvious the moment they started working. `SceneKitOptions` takes a `shadowTarget` now, both callers pass one, and the light's target is added to the scene — which is the part that actually matters, because `LightShadow.updateMatrices` reads `target.matrixWorld` and an unparented `Object3D` is never reached by the traversal that updates it. The sun is also placed relative to the target rather than the origin, so light-to-target is exactly `sunDistance` for every direction, which is the invariant each caller's `shadowNear`/`shadowFar` were chosen against. **`flights.ts` could not be tested, and that is why it was untested.** It used a TypeScript parameter property — the one piece of TS syntax that *emits code* rather than annotating a type — so Node's type stripping refused the whole module. The bundler never cared, so nobody found out until the first `node --test` file tried to import it. The module carrying the worst bug this project has shipped was, by construction, the one module that could not have a test. It has eleven now, including one that fails if the live-aircraft repeat-skip is removed. **Robots are on the plan panel** — a turned marker with a bow for heading, in the one hue left that is neither the people-blue nor the camera-amber. Review findings cleared across the four new sky/robot modules: a real 24 mm void at the ankle and an 8 mm hole through each forearm, a per-frame allocation in the robot heading picker, a per-frame sort in the starlink ranking, `uTime` growing unbounded until the cloud breath quantises, and `DAY_REFERENCE`'s derivation which did not reproduce. `createStarlinkMeshLayer` now takes a **board** radius — the same unit its sibling takes — instead of a dome radius with nothing in the types to tell them apart. That is the exact confusion that has already caused one real bug here. `DOME_RADIUS_FACTOR` has one owner and is imported, not copied: the points and the meshes must be on the same dome or a satellite that grows geometry also jumps. Several comments were wrong rather than merely stale — a fabricated claim about `Object3D.clone`, a fabricated attribution to `Plan`, an inverted `DoubleSide` argument, a triangle ledger citing a function that no longer exists, and a defensive-call rationale that contradicted the paragraph above it. In a codebase where the comments are the design record, those are defects. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
af0d4a7d57 |
The office keeps its lights on, and something walks around under them
**Lights.** A sited office follows the real sun, and the real sun spends
half its time below the horizon — which was producing a technically
correct and completely useless picture: an unlit floor plate at midnight
in a building whose whole premise is that you can see who is at which
desk. `luminaires.ts` brings the diffusers up as the sun goes down and
reports one scalar for how much interior light there is; `withHouseLights`
adds it to the rig. CONTRACT §4's rule that a fitting emits no light is
kept in full — nothing here is a light source, and the rig still has one
owner.
**And they notice you.** A fitting within four metres of somebody walking
underneath brightens and fades back as they leave, which is what an
occupancy-sensed floor actually does at night. They are one `InstancedMesh`
sharing one material, so `emissiveIntensity` cannot vary between them —
`instanceColor` can, but three multiplies it into the diffuse term only, so
six lines of `onBeforeCompile` carry it into the emissive term as well. The
alternative was one mesh per fitting: forty draw calls of ceiling in a
building that spends about twenty on everything.
**Optimus.** A posable Gen-3 humanoid — eleven articulating joints, pale
shells over a dark frame, a black visor — with a walk cycle driven by
*distance travelled* rather than wall-clock, so the feet do not slide when
a robot slows down. Two per floor, derived from the pack's levels, so the
two-storey tower gets four and the hangar gets two without either pack
knowing robots exist. They wander between reachable points using
`Plan.blocked` — the collider the wall split already produces — and they
are deliberately **not** gated on `depth`: the build-time-exclusion rule is
about occupancy, and a robot is nobody.
**Starlinks stop being pixels.** The sixty-four nearest the centre of view
grow real geometry — a flat bus with ONE large solar array, which is the
actual signature and the thing everybody draws symmetrically and wrong —
fading in so there is no pop where a point becomes a mesh. Two draw calls.
The sun for their attitude comes from `solar.ts` and not from the rig,
because `atmosphere.ts` floors the light direction to keep the shadow
camera usable, and a sun ten degrees *down* is exactly the dusk geometry
that makes a pass visible.
**Aircraft** are airliners now — swept wings, nacelles, a fin — instead of
an arrowhead, still one shared geometry facing +Z as `flights.ts` requires.
**Clouds** drift over the board, driven by observed cover, lit by the rig
rather than by themselves.
Four modules were built by subagents and reviewed by another; every one
came back `needs-work` and the reviews were right. Fixed before wiring:
- The walk cycle's arms were a quarter cycle out of step with its legs —
the legs are cosine-shaped and the arms were on `sin`, so at the
instant the left leg reached full forward the left shoulder was at dead
neutral. Uncanny, and hard to name until it is pointed at.
- Every Optimus shell used a `roundedBox` radius of 0.12–0.22, which that
primitive turns into a near-circular cross-section — the figure was
built out of lozenges, not panels. The rest of the library uses
0.02–0.09.
- The cloud material was `transparent` + `DoubleSide` without
`forceSinglePass`, so three rendered it twice per frame *and* bumped
`material.version` on each pass — rebuilding the program cache key
forever, on the one layer that is fill-rate bound.
- `starlinkMesh.dispose()` freed the geometries but not the
`InstancedMesh`es, orphaning their instance buffers on every city
switch.
- The airliner's tailplane roots sat outside the tail cone and hung in
free air over most of their chord.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
06455f7424 |
The office learns where it stands, and the sky stops being a backdrop
**Aircraft actually move now, and the reason they did not is the headline.**
`HttpFlights` holds a frozen snapshot between network refreshes and is
polled at 1 Hz, so a live feed handed the layer the same position five to
fifteen times and then jumped. `span` therefore measured the poll interval
rather than the gap between the two positions that differ, the teleport
test saw an airliner covering 36 units in a "second" against a ceiling of
8, and **every live track's history was wiped on every refresh** — so no
aircraft on the deployed site could ever grow a trail, however long
TRAIL_POINTS was set. Skipping the repeat fixes the motion and the trail
at once. Trails then go to 72 points / 240 s, which is about seventy
seconds of flying.
Three more defects in the same file, found while looking: trail
truncation dropped the segments nearest the aircraft (leaving a streak
with no aeroplane attached), MAX_TRACKS was declared and never enforced,
and one missing target deleted its whole trail. The buffer now uploads
only what it wrote, rather than 46 MB/s of untouched array.
**You can get above the constellation.** Dome to 1.05 board *radii* and
the orbit to 2.0 spans. Radii, not spans: scene space is centred on the
city and the Bay Area board runs forty kilometres down the peninsula, so
the furthest corner is 0.94 spans out where the half-diagonal is 0.65 —
sized off the half-diagonal the dome sat inside its own city. The far
plane goes to 4 spans to stop clipping the sky from off-centre chapters,
and `PointsMaterial` defaults `fog: true`, which was quietly dimming the
whole constellation with the city's haze.
**An office can say where it stands.** `Office.site` — lat, lng, height
above the ground outside, and the compass bearing the pack's −Z points
along — and with one it gets the same sun the city does, a sky, and a
horizon at `-elevation`. CONTRACT §4 reserved this as "a later
refinement"; it is taken up rather than overturned, and `daylight.ts`
computes no light of its own. It does the two things a room needs that a
map does not: turn the sun into the building's frame, and move the fog
outdoors before it greys out the far wall.
Two buildings now, and they are deliberately unalike: Lumbridge HQ 188 m
up a Transbay tower facing 205°, and **Frontier Valley**, a startup in a
hangar at Alameda Point — one room, 54 x 30 m, nine metres to the
trusses, four metres above reclaimed ground.
Floor-to-floor in the reference pack is now 16.8 m: the interstitial is
ten times a real one, so the space between the slabs is somewhere things
can hang. It is frankly not architecture, `PLENUM` is the one number to
change, and the file says so.
Also fixed, all found by review rather than by looking at the screen:
- `sun.shadow.camera.updateProjectionMatrix()` was never called, so
three's default ±5 unit box has been in force this whole time and
every `shadowExtent` this repo passes — including the city's ±752 —
has been silently ignored.
- A missing aircraft was kept alive by the new grace period and *drawn*,
so it froze in mid-air at full opacity for 32 s.
- Frontier Valley's mezzanine was a `Room`, which carries no height: its
slab lay on the concrete, its chairs floated 4.4 m over it, and its
balustrade fenced off a patch of ground floor. It is a `Level`.
- Overlapping floor slabs z-fought. The format permits overlap and
resolves later-first, so `shell.ts` now lifts a slab a hair per
earlier slab it overlaps — and by nothing at all in a pack, like the
reference office, whose rooms only ever abut.
- `switchOffice` bypassed the `entering` guard (leaking a whole scene
per double-click) and tore down the old room before knowing the new
one would load, with no way back.
Known and not fixed: raising MAX_SPAN to 30 s doubles the worst-case
re-base snap when a feed's gap shortens. It is bounded, pre-existing in
kind, and the fix wants carrying the live head into the next leg.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
a229fb2721 |
The sky gets the things above the aeroplanes
Satellites, end to end: CelesTrak element sets behind the same TTL cache
the weather and the flights use, served as TLEs rather than as positions,
and propagated in the browser with SGP4.
Sending elements is the same trick `flights/plan.ts` plays and it has a
better excuse here — a TLE *is* the closed form, valid for days either
side of its epoch, so one cacheable fetch every six hours replaces a poll
and every viewer agrees about where everything is.
Two things are worth knowing about the shape of it:
- There is no region parameter. An aeroplane at 10,000 m is local and
a satellite at 550 km is above the horizon for a circle two thousand
kilometres across, so one catalogue serves both boards and the client
decides what is above its own horizon. Only the observer is per-city,
which is why `main.ts` shares the elements and rebuilds the catalogue.
- The layer draws on a dome, because it cannot draw anywhere else.
`world.metres(550_000)` is 21,000 scene units against a far plane at
3,000. Azimuth and elevation are real; the radius carries nothing.
Off by default: a clone that started pulling CelesTrak on `npm run dev`
would have volunteered somebody else's bandwidth for its onboarding.
Godmode gets the two dials that point at the sky rather than at the
light — fabricated traffic, which composes with a live ADS-B feed instead
of replacing it, and a switch for the satellite layer with a count beside
it. Both are god-only lies about the inputs, in the manner of the weather
override.
`satellite.js` is the second runtime dependency this package has taken.
Its entry point star-exports an Emscripten build that cannot be shaken
out, so `noWasmPropagator` in the Vite config cuts it: 308 kB of WASM
loader for a bulk propagator nothing calls, against 26 kB for the SGP4
that does the work.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
e41c90fe8d |
Real weather, real aircraft, a heightfield off the main thread, and instruments
Three things that were built and never connected, connected.
**The weather was already there.** `observe()` has always taken a
`WeatherObservation` and `main.ts` has always passed null, so the cloud,
precipitation, visibility and marine-layer paths in atmosphere.ts had never run
outside a test. The server already shipped NWS, met.no and Open-Meteo, all
configured off. What was actually missing was that a single TERA_ORIGIN_LAT/LNG
served one metro and lied to the other — so weather and traffic are per-region
now, derived from the city's own bounds, and the Bay Area gets its fog while
Long Beach gets its own sky. The route takes ?city= or a validated ?lat=&lng=
and refuses to become an open geocoding proxy for the planet.
**The heightfield moved to a Worker.** 2.3 s of blocked main thread at boot, and
another ~950 ms of point-in-polygon on top of it: the park mask is filled in the
worker now, and block placement samples four corners and only runs the exact
test on a cell that straddles an edge — 8 buildings differ out of 185,036.
createScene is async and takes a Stage as a consequence, and there is a
main-thread fallback because "clone it and it works" has no exception clause.
**Spaces is a chunk you fetch when you reach for the door**, not one everybody
downloads. Same for the godmode tools. The entry chunk is 722 kB rather than
772; three.js is most of what is left and splitting it is a different job.
**Godmode is an instrument panel now** rather than one slider: the date and the
season, not just the hour, so the Meeus moon and the sun's seasonal arc become
visible instead of merely correct; a weather override that says on screen when
it is lying; a frame-time and draw-call readout; and a pose editor that emits a
paste-ready Chapter block, which is the thing that makes adding New York cheap.
Two blockers the review caught:
- Every city switch leaked 8 GPU textures — one of them a 2048x2048 shadow map
— and ~10.5 shader programs, and deleteTexture had never been called once in
the app's lifetime. The renderer was being built per scene; it belongs to the
canvas, for the life of the page.
- An upstream fetch that threw rather than returning null skipped the cache
stamp, so the TTL — the only rate limit on outbound calls — collapsed to one
upstream request per inbound request, and the caller got a 500.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
44c5a79424 |
SoCal, the whole bay, a moon, and gates that actually run
Six agents in parallel, and the two city packs independently reported the same blocker: `focusRegions` and `coarseFactor` existed on the `City` type and nothing implemented them. Uniform lattices would have been 2.9M points for Southern California and 3.7M for the expanded bay. Both packs were unloadable as written. `buildAxis` is the answer, and it is honest about its limits: refinement is per axis, not per rectangle, so a focus region sharpens its whole row *and* its whole column. Two regions at opposite corners refine nearly everything between them. Measured, not guessed — the bay went 0.53M points with one region and 1.64M with three, for detail nobody is looking at from a board this wide. One region each, coarse factor ten, and the builds land at 3.8 s and 2.3 s. Then three things that were only ever right because San Francisco was the only city. `maxDistance: 340` and a 170-unit shadow box were constants tuned for a 230-unit board; the bay is 1003 units across and the camera physically could not retreat far enough to frame it. Fog distances were scene units pinned to the same assumption. And `minVisibilityM` defaulted to 4.5 km of honest weather, which over ninety-four kilometres of bay correctly hides three quarters of it — the night view was a black rectangle for a completely reasonable reason. All three now derive from the board. The moon is a real ephemeris and its light is a deliberate lie: 1.15, against a physical ratio of one to four hundred thousand. What is being reproduced is what a moonlit night looks like on a screen in a lit room. The CI gate caught itself, which is the part worth keeping. Port 8431 was already held by a server from an earlier session, so the boot check polled a healthy stranger while the process it started died on EADDRINUSE. It now refuses to run rather than pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
67f8df8d53 |
Lumbridge Simulate Engine — the city, and the licence it can actually ship under
LSE is the third of the three, beside lumbridge-compute and lumbridge-bench: a 3D engine for walkable places. This first commit is the outside of the world — San Francisco — plus the seams the inside will attach to. The engine renders a City and a list of Markers and knows nothing else. It does not know markers are usually companies and it will never learn that "rejected" is red; that mapping lives in an adapter. Which is what lets one renderer serve a private map, a public one, and a self-hoster with no Lumbridge account, none of them a fork of the others. Three things were designed around the licence rather than discovered after it, because each one is a promise Apache 2.0 makes that is easy to break by accident. No trademarks in the repo — logos are fetched at runtime, and public/logos/ is gitignored. No OpenStreetMap-derived coordinates, which is why every coastline in cities/sf.ts was traced by hand: Nominatim output is ODbL, share-alike, and would attach to the whole pack. And no FlightRadar24 client — their terms forbid scraping and redistribution, so flights are an interface with a simulator and open community ADS-B behind it. The privacy constraint and the licence constraint turned out to want the same thing. Geocoded company positions and pipeline status both stay behind Workie's API; the open repo holds the city and the renderer. The tempting shortcut — commit an sf-companies.json — breaks both at once. Ported out of Workie, where a 3D city engine had no business living. Workie's /live is deleted rather than deprecated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |