Lane B round 17 (v3.2): wind sway SHIPS -- weather-driven gum-tree sway (ledger #5, parked since R7)
Verdict: shipped, not killed. The R7 park reason ("touches shared materials") is a non-blocker under
R16's measurement-over-brief standard: the gum-tree billboard material is B-owned (furniture.js), so
this is entirely Lane B -- no C/E material seam. Verified fresh Chromium seed 20261990.
- New web/js/world/wind.js: shared uniforms (uWindTime/uWindAmp) + applyWind(material) (onBeforeCompile
vertex patch: top-weighted horizontal sway, per-instance world-position phase, USE_INSTANCING-guarded)
+ updateWind(dt,intensity). furniture.js applies it to the tree material; weather.js drives it from
update(dt) (base breeze 0.05 clear -> 0.05+intensity*0.30 ~0.35 rain gusts).
- Trees only. Awnings excluded by MEASUREMENT: rigid posted-verandah box slabs don't flutter (fabric
canopy ripple = separate v3.3, shares the rigid awning InstancedMesh today).
- Byte-identical when weather off (the covenant): updateWind runs only inside weather.update, and the
weather system isn't constructed under ?weather=0 / ?classic=1 -> uWindAmp stays 0 -> sin(...)*0.0==0.0
-> identical vertices. Proven: amp=0 two times = 0/180000 px differ; ?weather=0 boot = 0 tree-motion px;
?classic=1 = amp 0, trees render, 0 errors. No plan/fetch/rng change -> F's classic gate untouched.
- Sway works: rain amp 0.305 moves ~14100/180000 region px over 0.8s (exaggerated 2.5 -> 101k); trees
render clean, no glitch. ~0 draws (ALU ops in the existing tree InstancedMesh vertex program), 0 errors.
-> Lane F (B->F handshake): SWAY SHIPPED. In old frames under the default boot; ?classic=1/?weather=0
sway-free + byte-identical. Procedural (no asset -> E has nothing to do).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
abcadcba13
commit
ee16545941
@ -1,5 +1,46 @@
|
||||
# LANE B — NOTES (measured)
|
||||
|
||||
## Round 17 (Fable's ROUND17 → Lane B) — wind sway SHIPS (ledger #5, parked since R7)
|
||||
|
||||
**Verdict: SHIPPED, not killed.** The R7 park reason ("touches shared materials") was tested against
|
||||
R16's measurement-over-brief standard and is a **non-blocker**: the gum-tree billboard material lives
|
||||
in `furniture.js` (Lane B), so wind sway is **entirely Lane B — no C/E material seam**. New file
|
||||
`web/js/world/wind.js` + two small hookups (`furniture.js` applies it, `weather.js` drives it).
|
||||
|
||||
### What ships
|
||||
- **`wind.js`** — one shared uniform set (`uWindTime`, `uWindAmp`) + `applyWind(material)` (an
|
||||
`onBeforeCompile` vertex patch: top-weighted horizontal sway, per-instance phase from world position
|
||||
so neighbours desync, `#ifdef USE_INSTANCING`-guarded) + `updateWind(dt, intensity)`.
|
||||
- **Trees only.** Gum-tree canopies sway. **Awnings are excluded — a *measured* call, not a punt:**
|
||||
they're rigid posted-verandah box slabs (`buildings.js`), and a structural verandah roof doesn't
|
||||
flutter. (Fabric-canopy ripple would be a separate v3.3 item — the striped canopies share the rigid
|
||||
awning InstancedMesh today, so splitting them is its own task. Not needed for "wind sway ships.")
|
||||
- **Weather-driven:** `updateWind` is called only from `weather.update(dt)` → base breeze on any
|
||||
weather-on boot (`amp 0.05`, clear included) scaling to rain gusts (`0.05 + intensity·0.30 ≈ 0.35`).
|
||||
|
||||
### Byte-identical when weather is off (the covenant)
|
||||
`updateWind` runs **only** inside `weather.update`, and the weather *system* is not constructed under
|
||||
`?weather=0` or `?classic=1` — so `uWindAmp` stays at its init **0**, and the injected displacement is
|
||||
`sin(...)·0.0·h == 0.0` → `transformed += 0.0` → identical vertices → identical pixels. Verified:
|
||||
- `amp=0`, two different `uWindTime` values → **0 / 180,000 region pixels differ**.
|
||||
- `?weather=0` boot → `uWindAmp 0`, advancing time → **0 tree-motion pixels**.
|
||||
- `?classic=1` boot → `uWindAmp 0`, trees render, **0 console errors** (shader patched but inert).
|
||||
- No plan change, no fetch, no rng (time-based, not seeded) — F's classic gate (`0x3fa36874` + fetch
|
||||
delta) is untouched; the sway is pure render.
|
||||
|
||||
### Sway works + budget
|
||||
- Realistic **rain amp 0.305**: advancing 0.8 s moves **~14,100 / 180,000** region pixels (clear
|
||||
motion); exaggerated amp 2.5 moves 101k. Trees render normally with the shader (verified the
|
||||
side-street verge — natural canopies, no glitch/explosion).
|
||||
- **~0 draws** (a handful of ALU ops in the tree InstancedMesh's existing vertex program — no new
|
||||
geometry, no new draw). **0 console errors** (GLSL compiles clean).
|
||||
|
||||
**→ Lane F (B→F handshake): SWAY SHIPPED.** It's in old frames now under the default boot (weather-on).
|
||||
`?classic=1` / `?weather=0` are sway-free and byte-identical (proven above). README `?weather` note, if
|
||||
you grow one, can mention "trees sway with the wind." No new asset (procedural), so E has nothing to do.
|
||||
|
||||
---
|
||||
|
||||
## Round 16 (Fable's ROUND16 → Lane B) — v3.1 THE FLIP: town-wide pools + default-boot re-baseline
|
||||
|
||||
Two deliverables. Verified fresh Chromium, seed 20261990. (Ran before F's flip landed — my pools are
|
||||
|
||||
@ -13,6 +13,7 @@ import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
|
||||
import { loadGLB } from '../core/loaders.js';
|
||||
import { rng, frange } from '../core/prng.js';
|
||||
import { chunkOf, resolveEdges } from './planutil.js';
|
||||
import { applyWind } from './wind.js'; // weather-driven vertex sway on the gum-tree billboards (R17)
|
||||
|
||||
const FOOT = 3.5;
|
||||
// bus-shelter footprint (from manifest: 2.33 × 1.41 m) + a facing tweak applied to the along-edge
|
||||
@ -145,6 +146,7 @@ function materials() {
|
||||
novelty: new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.35, metalness: 0.1 }), // vinyl
|
||||
foodcart: new THREE.MeshStandardMaterial({ color: 0x9a3b32, roughness: 0.7 }),
|
||||
};
|
||||
applyWind(_mats.tree, { topY: 4.4 }); // gum-tree canopy sways when weather is on (amp 0 ⇒ byte-identical off)
|
||||
return _mats;
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
|
||||
import * as THREE from 'three';
|
||||
import { rng, frange, pick } from '../core/prng.js';
|
||||
import { updateWind } from './wind.js'; // R17: weather drives the tree-sway uniform (off ⇒ never called ⇒ calm)
|
||||
|
||||
const RAIN_SKIES = ['summer-storm', 'monsoon', 'cold-front'];
|
||||
const OVERCAST_SKIES = ['grey-drizzle', 'cold-front', 'high-cirrus'];
|
||||
@ -113,6 +114,7 @@ export function createWeather({ scene, plan, skins, lighting, camera, force = nu
|
||||
function update(dt) {
|
||||
group.position.copy(camera.position); // the rain box follows the player
|
||||
if (rainMat) rainMat.uniforms.uTime.value += dt;
|
||||
updateWind(dt, state.intensity); // sway the gum trees — calm breeze → rain gusts (weather-on only)
|
||||
}
|
||||
|
||||
function dispose() {
|
||||
|
||||
55
web/js/world/wind.js
Normal file
55
web/js/world/wind.js
Normal file
@ -0,0 +1,55 @@
|
||||
// PROCITY Lane B — wind.js (round 17, v3.2 — "wind sway ships or dies")
|
||||
// Weather-driven vertex-shader sway for street foliage. Parked since R7 with the note "touches shared
|
||||
// materials"; the R16 standard is measurement-over-brief, and the measurement here is: the gum-tree
|
||||
// material is B-owned street geometry (furniture.js), so this is ENTIRELY Lane B — no C/E material seam.
|
||||
//
|
||||
// ONE shared uniform set, referenced by the tree material via applyWind(). Amplitude stays 0 until the
|
||||
// weather system drives it (updateWind, called from weather.update). With weather OFF — ?weather=0, or
|
||||
// ?classic=1 where weather is never constructed — updateWind never runs, uWindAmp stays 0, the injected
|
||||
// displacement is exactly `... * 0.0 == 0.0`, and trees render byte-identical to no-sway (verified). So
|
||||
// the classic covenant and ?weather=0 both hold by construction.
|
||||
//
|
||||
// SCOPE (measured, per R16 discipline): gum-tree BILLBOARDS only. Awnings are rigid posted-verandah box
|
||||
// slabs (buildings.js) — a structural verandah roof does not flutter, so swaying it would read wrong; it
|
||||
// is intentionally excluded (not a cross-lane blocker — a physical one). Fabric-canopy flutter, if ever
|
||||
// wanted, is a separate v3.3 item (the striped canopies share the rigid awning InstancedMesh today).
|
||||
//
|
||||
// Cost: ~0 draws — a handful of ALU ops added to the tree InstancedMesh's existing vertex program.
|
||||
|
||||
export const windUniforms = {
|
||||
uWindTime: { value: 0 },
|
||||
uWindAmp: { value: 0 }, // 0 = dead calm ⇒ zero displacement ⇒ byte-identical (weather off / classic)
|
||||
};
|
||||
|
||||
// Patch a material's vertex program: sway top vertices horizontally (top-weighted, base planted). A
|
||||
// per-instance phase from world position desyncs neighbouring trees. Guarded for instanced use so the
|
||||
// same material would still compile if ever drawn non-instanced.
|
||||
export function applyWind(material, { topY = 4.4 } = {}) {
|
||||
material.onBeforeCompile = (shader) => {
|
||||
shader.uniforms.uWindTime = windUniforms.uWindTime;
|
||||
shader.uniforms.uWindAmp = windUniforms.uWindAmp;
|
||||
shader.vertexShader = 'uniform float uWindTime;\nuniform float uWindAmp;\n' + shader.vertexShader;
|
||||
shader.vertexShader = shader.vertexShader.replace(
|
||||
'#include <begin_vertex>',
|
||||
[
|
||||
'#include <begin_vertex>',
|
||||
'float wPhase = position.x * 0.1 + position.z * 0.1;',
|
||||
'#ifdef USE_INSTANCING',
|
||||
' wPhase = instanceMatrix[3].x * 0.15 + instanceMatrix[3].z * 0.15;',
|
||||
'#endif',
|
||||
`float wH = clamp(position.y / ${topY.toFixed(1)}, 0.0, 1.0); wH *= wH;`, // top-weighted, base planted
|
||||
'float wS = sin(uWindTime + wPhase) + 0.4 * sin(uWindTime * 2.3 + wPhase * 1.7);',
|
||||
'transformed.x += wS * uWindAmp * wH;',
|
||||
'transformed.z += cos(uWindTime * 0.8 + wPhase) * uWindAmp * wH * 0.5;',
|
||||
].join('\n')
|
||||
);
|
||||
};
|
||||
material.needsUpdate = true;
|
||||
}
|
||||
|
||||
// Drive from weather.update(dt): base breeze on any weather-on boot (clear included), gusts scaling with
|
||||
// rain intensity. NEVER called when weather is absent ⇒ uWindAmp stays 0 (byte-identical, see header).
|
||||
export function updateWind(dt, intensity = 0) {
|
||||
windUniforms.uWindTime.value += dt * 1.6;
|
||||
windUniforms.uWindAmp.value = 0.05 + Math.max(0, intensity) * 0.30; // calm ~0.05 m top sway → gusty ~0.35 m
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user