master
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
389245fd60 |
docs: two stale references in the ground material — the fade's name, and why two materials may share one cache key
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
76ca903cd2 |
fix: the ground's detail was switched off everywhere, and the map was a grid of stars
Three defects in the round before this one, all of them found by rendering the board and looking at it, none of them by a test that existed. **1. The fade was wrong by a factor of nine, so the map did nothing.** It copied `SEA_CALM_NEAR`'s form: `length( vViewPosition )` against a window converted from real metres by `metresPerUnit`. That conversion assumes the camera's distance is a plan measurement. On this board it is not — `verticalExaggeration` is 15, so a camera lifted to 0.6 of its stand-off sits `sqrt(1 + (0.6 x 15)²) = 9.06` stand-offs away in scene units — so a window written as "25 km to 70 km" closed at about 2.8 km of stand-off and the detail was off everywhere a viewer can actually go. Proved rather than guessed: a fivefold amplitude produced a byte-identical PNG of the Central Valley. It now measures the quantity instead of inferring it. `fwidth` of the detail UV is exactly how much of one repeat a pixel covers, so its reciprocal is the repeat's width in pixels — independent of board scale, exaggeration, field of view, viewport and device pixel ratio, all five of which the old form was guessing at. Off under 2 px, full over 8: the same thresholds the stand-off study arrived at, without any of its assumptions. The geometric mean of the two axes floored at an eighth of the larger, because that is what `anisotropy = 8` resolves. **2. The repeat went 220 m -> 440 m, and the recipe with it.** With the map finally visible, the flat valley came out as a regular grid of four-pointed stars, one per repeat, marching to the horizon. The cause is `tileableNoise` at a `scale` of 2: below about 4, the blend's own wrapped cross-fade is the largest feature in the tile. The layers now run 8 / 22 / 60 on a 128 lattice at 512², so a repeat holds eight or so features instead of one and nothing in it is identifiable. Mean slope 9.9°, peak 39.4°. **3. The shore plate needed the map, not just the material.** A textured terrain against a smooth plate makes the grid's stair-stepped rim legible — which is the one thing `createShorePlates` exists to hide — and at San Francisco it came back as a zigzag. Both now come out of one `groundMaterial`, which is two material objects (the plate is a flat colour, the terrain a vertex ramp, and three compiles those to different programs regardless) and the same one material per mesh as before. The bytes of the detail map are built once and shared; the `DataTexture`s are not, because `scene.ts` disposes a board by traversing it. Zero new draw calls, unchanged: 437 at the whole-board pose, 348 at 7.7 km over SF, 389 at 7.7 km over LA. Triangles unchanged to within the drift of the moving vessels and aircraft. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0d7eb28bb5 |
feat: the land is a surface, not paint — standard material and a 220 m detail relief
The ground was `MeshLambertMaterial` with a vertex colour and no maps of any
kind, next to a sea that has been a `MeshStandardMaterial` with a swell map on
it since `createWater` was written. Two things came out of that gap.
The louder one is invisible in the code and obvious in a picture: three forwards
`scene.environment` only to `isMeshStandardMaterial`, so the whole PMREM sky
`environmentRig.ts` builds — sun lobe, `SKY_RADIANCE_SHARE`, the ground bounce —
was reaching the water, the ports and the vessels and not one photon of it was
reaching the state. The land has been paying the counterweight for it the whole
time: the day stops gave up about a tenth of `hemiIntensity` and a quarter of
`ambientIntensity` to make room for an environment term the ground could not
receive.
The quieter one is the facets. `lodPatches` collapses flat ground into patches
up to eight 300 m cells across, so at a close pose the state is kilometre-wide
triangles with one normal each, and nothing on them.
So: one `MeshStandardMaterial` at roughness 0.92, metalness 0, carrying a tiling
detail normal map built from `textures.ts`' own `tileableNoise` / `sampleField` /
`normalMapData` at a **220 m** repeat rather than the office's 2 m — which is
sub-pixel at every stand-off this board can reach, orbit floor included. 220 m is
legible from about 45 km down and mip-flat above about 110 km, and it is not
300 m because a tile the size of a terrain cell paints the same pattern on every
cell and lands its repeat on the facet edges it exists to hide.
Zero new draw calls, and that was the constraint: same 18 objects, same one
shared material, same one attribute triple, same one program switch.
`cost-at.mjs` reads 355,763 / 437 draws at the whole-board pose, unchanged.
Three shader edits, none of which the material has a dial for:
1. The detail UV is `position.xz`, in the vertex shader. A real `uv` attribute
would be 369 KB against the ground's current 1.66 MB and would have to be
added as one object shared by all 18 geometries or the arrangement quietly
duplicates it seventeen times. The plan projection is also the natural
parameterisation of a heightfield.
2. The tangent-space slope is scaled by the cosine of the *drawn* slope.
`verticalExaggeration` is 15 here, so a real 10° hillside draws at 69° and a
real 30° Sierra face at 83°, and a plan-projected tile on a face at θ is
stretched 1/cos θ along the fall line — 2.8x and 8.7x. Scaling by cos θ
restores the same bumps instead of a smear of vertical stripes, for a dot
product against world up rather than the three fetches triplanar would cost.
3. It fades out between 25 km and 70 km of view distance, as the sea's does at
`SEA_CALM_NEAR`. That matters more here than for the water: the board's own
budget pose is 400 km out, where a 220 m tile is 0.6 device pixels.
The shore plate goes standard with it, mapless. It is the one place on the board
where two materials meet edge to edge in the same colour, and a Lambert plate
against a standard ground would have put a step of about a fifth of the
hemisphere's diffuse contribution around every coastline in the state.
A handheld gets the material and the environment and not the map — "off" is *no*
map, the way `MaterialQuality`'s `low` rung is, because the cost is one fetch and
`getTangentFrame`'s three derivative pairs per fragment and a smaller map costs
exactly the same fetch.
`tileableNoise`, `sampleField` and `normalMapData` are exported from
`textures.ts` rather than reimplemented, and `normalMapData` takes the tile size
it converts against instead of reading the office's constant. Same reason that
file takes `fbm` from `engine/world.ts`: a second noise is a second thing to keep
tiling, and a second Sobel is a second place to get the flipY reasoning wrong.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|