A garden: the flowers piece grows a field, in one wind on one ground - #44
Open
aj-dev-smith wants to merge 83 commits into
Open
A garden: the flowers piece grows a field, in one wind on one ground#44aj-dev-smith wants to merge 83 commits into
aj-dev-smith wants to merge 83 commits into
Conversation
tools/flowers_shot.mjs — screenshots flowers.html (flower_shot.mjs targets canalisation.html). Waits on window.__fl, drains ff via the HUD (&shotstep= for a step target), settles until the CAMERA is stable rather than for a fixed time — the fixed wait shot the columbine from inside its own corolla — prints stream counts off __fl.B (summed over __fl.garden when that lands), and decodes its own PNG: the documented swiftshader failure is a black frame WITH a full triangle count, so counts alone prove nothing. &speed=0 is the reproducible-still recipe. tools/flowers_perf.mjs — the garden_hitch lesson: a harness that waits cannot see a freeze. In-page rAF gap sampler over ~30s of live growth (ff dropped deliberately); prints median/p95/p99/worst and fps beside the HUD's own. PRINTS, DOES NOT JUDGE — there is no budget yet; this is the baseline. test/flowers_capture.mjs — headless capture cost, no browser: parity's loader, a grown specimen, median of 20 flDrawSpecimen calls, stream float counts. Asserts only structure (the parity organ-range check). n>1 probes for flGardenPlan and prints 'garden: not built yet' today. Baseline (this machine, ANGLE-on-Metal): default page 30s live growth — gap median 8.5ms p95 25.1 p99 33.4 worst 58.3, 66.2 fps measured / 42 HUD. Headless capture: Ember 21 @1400 9.76ms; Parasol 7 @2600 3.32ms. Co-Authored-By: Claude Fable 5 <[email protected]>
flowers.html had no ground geometry — plants stood on an implied floor at
y=0 against the void. flowers/25_ground.js adds one: a 140-unit disc at
y=-0.01 with FL_TRI_FS's shading subsetted (hemisphere ambient, key
diffuse at 0.9, the same flFog at 0.80 through the scene's own fog
uniform objects, alpha carrying linear depth so the lens racks over it).
Environment, the same allowed category as the wind — nothing about the
plant is drawn.
Colour is palette mixes only: soil0 = mix(bgBot, stem0, 0.16), soil1 =
mix(ambBot, stem0, 0.40) under a two-octave hash value mottle, a 0.6x
bgGlow pool under the specimen, and a radial melt to mix(bgBot, bgTop,
0.65) so the rim never draws a line. Three of those numbers were
corrected by looking: a darker melt target cut a visible trough between
fogged soil and sky, and one step brighter soil doubled a Parasol
down-shot's field value against the old void.
Two traps hit and written down in the file:
- flowers/*.js sorts 25_ before 30_scene, so a top-level ${FL_FOG}
template parses clean and dies in the TDZ at load; the shader strings
are built at construction time instead.
- The framing laws predate the ground and walked the eye to y=-16 on a
tall specimen — under the new floor that is a screen-filling ceiling
of soil. The boot now holds the eye just above the plane (40_boot.js),
which also stops the orbit phasing through it.
node flowers/build.js clean, parity 20/20 before and after. Nothing
under src/ touched.
Co-Authored-By: Claude Fable 5 <[email protected]>
…_garden.js) flApplyForm is 40_boot.js's inline homeotic block extracted verbatim; a reference harness sliced the same lines out of git HEAD, wrapped them, and held both paths to identity -- form name, S.sp, plant.sp, both palettes, and every captured stream hashed byte for byte at step 1400, across all four forms and the URL knobs (81/81). parity 20/20. uploadMany concatenates N FlowerBuffers; upload(B) delegates to it, so the solo upload is unchanged. Co-Authored-By: Claude Fable 5 <[email protected]>
…g with sqrt(N) flowers/35_garden.js only. FL_GARDEN_SPACING 2.5 -> 12, and the 2.5 was set by eye against a single corolla. Measured headlessly (8 flowering species x 4 forms, 3000 steps, horizontal reach off the DRAWN streams): max reach min 3.37 / med 9.49 / max 22.13, body reach r90 min 1.22 / med 5.76 / max 16.77. 12 is full clearance of two median bodies and a stated 63% of full clearance of two median arms -- full arm clearance (19.0 median, 44.3 worst) is too sparse to read as a field. Stated fraction, not a derived one. Field radius was a FIXED 7 for any N; now 0.83*s*sqrt(N), from RSA disc capacity, so areal density is constant. ?radius= still overrides. Placement is dart throwing over the disc, uniform in area, accepting the first candidate clearing the spacing, with the BEST failed try as the fallback rather than the LAST. Over seeds 1..64 and N in 2..12 the achieved minimum gap is >= 12.0 in every case, 0/64 exhaustions. Stagger 2400 -> 1200 with a first cohort at step 0 (max(2,min(4,ceil(N/3)))): garden=7 had 2 of 7 germinated at step 1100; it now has 7 of 7 at 1600. Species deck now deals without replacement ACROSS the reshuffle boundary too (n>8 could put two of a kind side by side). Seeds were already injective; the comment now says why. parity 20/20, solo page unchanged.
…r the garden PROFILE FIRST (test/flowers_capture.mjs, rewritten as the profiler). A garden of 7 at seed 21, world step 3000, grown WITH the germination stagger so it is the page's field and not a different one — and it reproduces the browser float for float, which is how the double count in tools/flowers_shot.mjs was found (it added the hero twice: 998 organs for 771, 10.34M tri floats for 8.64M). Where the 115ms goes, per organ kind: petal 45.9 sepal 28.2 leaf 25.3 stamen 13.1 rest 4.4 70% of it is FLORAL organs, and 58% of every float emitted is the petal stream. That is 20_draw.js handing every floral organ detL = 1.0 — the microscope, permanently on — so a petal is built at the leaf's own lattice (~5,600 verts a petal) whether it fills the frame or covers 16 pixels. 1. NEVER FINER THAN THE RASTER (flowers/28_lod.js). bladeMesh is the piece's only LOD with no distance term (CLAUDE.md). It gets one: cap mu,mv at one quad per pixel, px/unit = pxHeight / (2 d tan(fov/2)), measured to the specimen's near face. A cap, never a raise; floor stays 4x2; ceiling stays the leaf's lattice. No taste constant and NO HERO EXEMPTION — the subject is large on screen by construction, so at the close-up's own framing the cap lands above the lattice and does nothing. ?lod=0 is the pre-LOD page. Field capture 115.0 -> 67.7ms headless, 100.2 -> 77.7ms in the browser. 2. PAY IN BATCHES, NOT IN SLICES (40_boot). The pool was spent breadth-first — one step to every specimen — so every specimen stepped, and a specimen that stepped is recaptured: the frame paid the whole field's rebuild to advance garden time one step. Depth-first spends the same budget on fewer plants, at the same average rate each. How few is Nyquist, not taste: FL_RECAP_HZ = 1.78 * 4 (four samples per period of the wind's fastest gust, 15_petal.js's ripple guard applied to time), so m = nAct * HZ / fps. The hero is exempt and always paid first. ?batch=0 is the pre-batching pool. Live, grown field (flowers_shot, ff=3000 then speed=1, real browser on Metal): before 120.9ms capture (7/7) 10 fps after 38.0ms capture (3/7) 21 fps (and 5 more petals — it grew further) LOOKED AT: shots/g7_ff3000_lodoff.png vs _lodon.png, identical seed and step. Nothing is deleted — seven plants, every flower, the same picture. parity 20/20, build clean.
…a maximum it could not miss
A synthesised close-up eye lands INSIDE the specimen's bounding sphere, so the
near-face distance clamps at 0.1 and the corolla check passes at 100% — the
unreachable-maximum trap test/venation.mjs records, arriving in a harness
written twenty minutes earlier. Replaced with a sweep over near-face distance
(2 / 4.51 / 9 / 20 / 45 / 90 units), gated on the row a real browser actually
measured at ?focus=flower:
d px/unit tri kept petal kept vein kept
2 1036 93.6% 99.0% 100.0%
4.51 459 81.2% 96.6% 100.0%
20 104 48.8% 92.4% 100.0%
90 23 22.0% 27.8% 100.0%
Two gates: the corolla keeps >= 95% of the petal stream at the close-up (0.966
measured, floor written first), and the cap touches NO vein, exactly — this law
is about surfaces and must not reach the channel the engine is visible through.
And it corrects 28_lod.js's own comment, which claimed the close-up was
untouched. It is not: -3.4% petal, -18% tri, and NONE of it is leaves. It is
stamens and carpels, because detL = 1.0 is applied by organ IDENTITY, so a
0.1-unit filament is built at the same 75x75 lattice as a petal filling the
frame. The solo page is no longer byte-identical and the difference is
sub-pixel by construction.
Two defects, both found by looking at a garden of seven (seed 21, step 3000) and both in the atmosphere rather than the plants. 1. THE HORIZON WAS A HARD STRAIGHT LINE. The disc melted over its own RADIUS toward one sampled void colour, but the rim was never what a viewer saw: a fully fogged ground is uFog and the sky above it is the bg gradient near uBot, and on the shipped palettes those differ 3-5x in luminance (Sun Coral: fog L 0.041 against bgBot L 0.005). The seam is wherever the plane runs out of screen, which from an eye near ground level is a straight line across the middle of the frame. The ground now melts into THE ACTUAL SKY IN THAT DIRECTION: FL_SKY is the background's own gradient extracted as a function (FL_BG_FS now calls it too, so there is one sky, not two that resemble each other), sampled at the fragment's screen position through a clip-space varying — perspective correct, no resolution uniform. Where the melt completes the ground IS the void, bit for bit, so the join cannot be seen. Alpha melts to the void's 3.0 with it so the defocus finds no edge either. What drives the melt is a ground-hugging haze: the closed-form optical depth of an exponential-height layer along the eye ray, which is uniform when the eye is IN the layer and thins as 1/yEye above it. That is the whole trick — a Lambertian plane under a directional light has ZERO shading variation, so no distance term alone can fade the near ground an eye-level camera is mostly looking at. FL_HAZE = [G 2.0, H 3.0, P 1.5, N 5.0], set by looking at two shots that pull opposite ways (a garden from y=0.6 needs the near ground to go; a solo flower close-up needs a floor under its subject). G alone trades one against the other — 1.5 hides the horizon and empties the close-up, 0.6 the reverse — and P (the curve's near half) and N (ground nearer than 5 units is floor, not air) are what let both be right. Same category as the wind's uRef. ?haze=G,H,P,N sweeps. 2. THE WHOLE FIELD WORE THE HERO'S PALETTE. flFieldPal blends the seven atmosphere colours across the species actually present. The obvious mean is measurably mud — over the eight flowering species the mean fog's saturation is 0.295 against a member mean of 0.607, bgGlow 0.337 against 0.770 — because averaging in RGB cancels hue against hue. So the blend keeps the weighted mean's luminance and hue direction and rescales its chroma back to the members' own mean chroma, clamped so no channel goes negative. The hero LEADS at 0.35, and that number came from a measurement, not taste: a deck of eight dealt seven ways is nearly the whole deck, so at a pure field mean every garden gets the SAME sky — mean pairwise fog-hue angle over 24 base seeds falls to 20.8 degrees, against 85.6 today. 0.35 keeps 64.4 degrees of field-to-field variety while still moving the air off one species (fog saturation 0.714 hero -> 0.574 field, luminance preserved). ?sky= overrides; A/B shots at 1 / 0.35 / 0 are on the branch. The solo page keeps its own palette BY CONSTRUCTION (no plan, or a plan of one, returns the palette object unchanged) and gains the melt, which it wanted too: its close-up had the same visible horizon and now has a floor that fades. parity 20/20, build clean. Co-Authored-By: Claude Opus 5 <[email protected]>
Comment only, plus the rebuilt artifact. Swept the reach measurement over the five seeds a garden actually deals (8 species x 4 forms x 5 seeds, 160 cells): maxR p10 3.6 / med 9.7 / p75 14.4 / p90 23.2 / max 70.9, r90 p10 2.2 / med 5.0 / p75 7.5 / p90 12.1 / max 20.4. The one-seed table got the middle right (9.49 vs 9.7) and the tail wrong. Two findings recorded in the file: - reach is a statement about a SEED as much as a species. Ember Creeper columbine is maxR 7.4 at seed 21 and 65.9 at seed 31697. - reach is not bounded in TIME for that species. Sampled every 200 steps at seed 31697: 11.9 (step 1000) -> 28.8 -> 44.4 -> 70.9 (step 3200), still climbing linearly. A creeper whose axes never arrest. No fixed spacing can keep that off a neighbour and none should be expected to. FL_GARDEN_SPACING stays 12 -- full clearance of two median bodies, 62% of two median arms, and the fraction is now stated against the wider sample. parity 20/20, smoke 73/73.
scratch/g2_placement/ — new paths only, nothing shared touched. Kept
because the whole argument for FL_GARDEN_SPACING is a measurement, and a
measurement you cannot re-take is just a number somebody chose.
loader.mjs the flowers bundle in plain Node (copied from
test/flowers_capture.mjs, plus 35_garden.js so
flApplyForm and flGardenPlan are reachable)
reach.mjs what one specimen occupies: maxR / r90 / r50 off the
DRAWN streams, per species x form
seedsweep.mjs the same over the five seeds a garden deals — the run
that found the one-seed table's tail was wrong
arc.mjs reach over a whole life, every 200 steps — the run that
found Ember Creeper never arrests
plan.mjs statistics of flGardenPlan itself over seeds 1..64:
achieved min gap, depth spread, dart exhaustions,
adjacent species duplicates, seed collisions
field.mjs the plan GROWN on the boot loop's clock: pairwise
overlap at maturity, and what a viewer sees at
step 600 / 1200 / 2400 / 3600
seedsweep.out the 160-cell table quoted in 35_garden.js
…t exist flowers/README.md: the profile table, both laws stated, the before/after matrix at 5/7/10, and the tail the batching costs (isolated with ?batch=0: the LOD improves every column, the batching trades p99 for median). And the negative result, measured rather than assumed: THERE IS NO FROZEN TISSUE TO CACHE. An Ember Creeper at step 3000 with zero live axes changes 56.4% of its triangle floats on the next step, deltas to 0.41 world units — and still 56.4% with the wind at uRef 0. The material shape of a mature lamina is frozen and already baked; where it IS moves every step forever, because the axes are cantilevers and the petioles hang off them. So the answer to 'recapture less' is temporal, not memoised.
…mens rebuilt A harness reading fps can see that the field got faster and not why. stepN and capN are the pool's own two numbers, and the HUD already prints capN as (k/N).
…ADME p99 83.4 -> 108.4ms against median 50.0 -> 34.9. It trades the tail for the median and the next person to write a hitch gate for this page should read that in the code that does it.
…tor.js) The shipped framer frames ONE subject; pointed at a garden it framed the bound of everything at once — measured on ?garden=7&seed=21&ff=3000: radius 36.1, camera 84.2 units out, six plants in a column, most of the frame empty. 45_director.js is a shot list instead: wide field / dolly across it / close-up on the best flower ANYWHERE in the field / low against the sky, cycling with eased 5 s transitions and holds of 20/24/16/12 s. Framing distances are derived from the frame equation (2 d tan(fov/2)) against the field's own median plant height, not from the bound. ?shot=wide|dolly|close|low pins one, for stills. Co-Authored-By: Claude Opus 5 <[email protected]>
…laim instead 'the corolla keeps >= 95% of the petal stream' passed at 1600 steps (96.6%) and FAILED at this harness's own default of 1400 (89.0%) — a younger flower's organs are smaller and a cap in pixels bites a small organ first. A percentage of a stream was never the claim 28_lod.js makes. The gate is now the claim, and it is stage-independent: the LARGEST petal in the close-up is drawn float for float as it was (110400 == 110400 at both stages). If the biggest thing the shot is pointed at loses one vertex, 'no hero exemption' is false.
Sampling the director's own state at 4 Hz over a full 92 s cycle: shot lengths land exactly on the table (dolly 29.0 s, close 21.0, low 16.9, wide 25.1 = hold + 5 s transition each, so nothing ever cuts mid-move), but entering the CLOSE-UP peaked at 59-62 units/s against 19 for every other transition. The close-up is the shipped framer and the shipped framer is three exponential lerps, which is an ease-OUT — and an ease-out starts at its fastest. Holding the camera back toward the pose it cut from, by the same smoothstep the other shots use, takes the peak to 22.7 u/s: 3.9 m/s to 1.4 m/s in WORLD.unitM, a whip to a walk. Also measured: a drag freezes the shot clock for 25 s (verified: el held at 23.8 through the whole hold, then restarted at 0 with a full eased re-entry) and hands autoRotate back to the viewer while it does. Co-Authored-By: Claude Opus 5 <[email protected]>
Also drops three fields the director never read. Co-Authored-By: Claude Opus 5 <[email protected]>
The sight-line clearance is specimen 0's alone (heroCull), and in a stand this dense that clearance is the difference between a photographed corolla and a wall of leaf: 33.9 ms with the flower visible on the hero, 131 ms of the subject's own blades across the lens for ?focus=flower two plants over. The constant is labelled for deletion the day the cull follows the subject. Co-Authored-By: Claude Opus 5 <[email protected]>
# Conflicts: # flowers.html # flowers/35_garden.js
# Conflicts: # flowers.html # flowers/40_boot.js
flowers/README.md gets a GARDEN section in that file's voice — the placement law and why the spacing is a stated 62% of measured arm clearance, the germination cohort and stagger, the four-shot director, the ground and the field palette, the harness. Every number verified against the code or re-taken here before it was written down. The three headlines go where this project keeps numbers: - TUNING: the flower field's knob table, which two are Nyquist and not dials, the 160-cell reach sweep behind FL_GARDEN_SPACING, and FL_SKY_LEAD's measurement (mean pairwise fog-hue angle 84/64/27 deg at lead 1.0/0.35/0.0, reproduced over 40 fields of seven). - JOURNAL 2026-08-12: capture cost was FLORAL and not leaf (the microscope pinned on by organ identity); reach is a statement about a SEED and is not bounded in time, so no spacing should try to contain it; a plain palette mean is mud (fog saturation 0.261 against a member mean of 0.598). - PITFALLS: the node_modules symlink incident, and the fact that a field profile is a profile of that field's germination schedule. Two corrections, both from re-taking the measurement rather than copying it: the per-kind capture table in 28_lod.js and the README's cost section were measured under the older stagger (2400, uniform startAts), and on the shipped plan the petal stream is 46.7% of floats and not 58%, with sepals overtaking petals. The 70%-floral headline survives at 74%. Noted where both are quoted, not silently overwritten. CLAUDE.md had no entry for flowers.html at all; it has one now, with the load-bearing warnings (the LOD cap is Nyquist, the spacing is a stated fraction, the hero lead is measured, garden time slows with N) and the honest headline: nobody has watched this at framerate in a real browser. ROADMAP 0f ranks the debt, watching it first, and books the solo page inheriting the ground as a taste call rather than a bug. Docs only — no .js touched, flowers.html rebuilt to a byte-identical file. Gates: node flowers/build.js OK, parity 20/20, smoke 73/73. Co-Authored-By: Claude Opus 5 <[email protected]>
18_pollen.js walked the hero's axes only, so in a garden of seven six
flowering plants shed nothing. It walks the boot's `specs` list now — one
population, because a grain is not part of a plant: once shed its only
inputs are the wind field and gravity, both scene-wide, and the cap and the
upload are scene-wide resources too. The alternative (one FlPollen per
specimen) is argued down in the file.
Three things came with it:
- TWO CLOCKS, and they are not the same in a garden. A specimen sheds on
the plant time IT was paid this frame (`spec.stepped`), because
dehiscence is a plant process and the step pool is spent depth-first;
every grain drifts on the WORLD clock, because the air is one field over
the clearing and windAt's `t` is a wall clock. The shipped call passed
the hero's age and got the right number by accident — the hero is exempt
from the pool and paid first out of it, so its age IS the world step.
- A MOTE'S COLOUR IS THE KEY LIGHT'S, and in a field the key light is the
FIELD's (flFieldPal, 25_ground.js), not the hero's. Unchanged on a solo
page, where flFieldPal returns the hero's palette itself.
- test/flowers_pollen.mjs, a headless census: anthers, grains, where they
are, where they die, and what a population step costs. It asserts only
that the census balances (emit - died - alive == 0), because a swap-with-
last removal loop is exactly the kind that drops one silently.
MEASURED, garden=7 seed=21, step 3000 (before = HEAD's file, run through
the same harness with `polfile`):
before after
shedding plants 1 of 7 6 of 7
anthers drawn on 16 (hero) 59 (field)
grains alive 348 326
...within 12u of a 67 194
plant (r_mean 44.3) (r_mean 21.3)
population step 0.031 ms 0.051 ms
THE CAP HAS NEVER BOUND AND NEITHER HAS `life`: 0 refusals at 59 anthers,
and 0% of grains die of old age. The population is ADVECTION-limited, not
cap-limited — a grain drifts 0.32 world units per plant-time unit in the
shipped 2.5 m/s air, so it crosses the whole clearing in 0.65 s. Two
numbers that look like they control the population and neither does.
wide was not wide: it distanced off the median FLOWER height (5.9-13.8 on garden=7 seed=21) while the plants standing there are 4.4-49.2 tall, so it framed a thicket with five specimens cropped top and bottom. That pose is kept whole under the name 'bank'; 'wide' is now solved from the field's own plant bounds. - flDirField grows Rout (85th pct of dist-from-centre + that specimen's own canopy reach) and hHi (85th pct of plant height). p85 not max: one Ember Creeper sprawls 46.7 units and takes the outer radius 30.6 -> 62.4. - flDirPoseEstab solves the standoff from the frame equation off the LIVE camera's fov and aspect: width (2*Rout across fW of frame width) and height (hHi across fH of frame height), whichever binds. It swaps inside the shipped range -- garden=3 height-bound at 58.0 vs 33.6, garden=12 width-bound at 117.1 vs 76.4, garden=7 height-bound at 81.8 vs 67.1. - Composition is two more fractions with closed forms: feet at bF up the frame fixes the target, horizon at hz fixes the eye height. - Five shots, holds re-balanced to 96 s (was 92 s for four). wide takes a 7.5 s transition of its own: every cut flips the heading, so standing at 81.8 where bank stands at 47.9 would have peaked at ~39 u/s against the other four's 26. Co-Authored-By: Claude Opus 5 <[email protected]>
A grain is integrated forward-Euler through a 7-octave gust ladder, so the step has to resolve the fastest thing in that ladder. Derived from the shipped field rather than chosen: the shortest mode is 8 world units long and a grain travels uMeanMag + sigmaW per plant-time unit, so space wants 6.2 pt units and the fastest mode's period wants 6.3 — the same four-samples margin 15_petal.js's ripple guard and 35_garden.js's recapture rate already keep. That is exactly the real-time page's per-frame cap, so nSub is 1 in real time and this touches ?ff= only: at its 40-step chunk a grain jumped 12.8 units per call, further than the shortest gust is long. ⚠ BE HONEST ABOUT WHAT IT BUYS, WHICH IS NOT MUCH. Measured, garden=7 seed 21 at step 3000, chunk 40 (the ff path) against chunk 6 (the watched page): 302 -> 276 grains alive, 168 -> 140 of them near a plant, against the watched 326/194. The ff'd population was already within ~15% of the watched one; what the substep fixes is the trajectory each grain takes through the gusts, not the census. A first version of this note claimed a 15 -> 38 fix and that number came from a run with a bug in it (rEdge measured from the origin with the rim unset) — the corrected measurement is above. Also ?pol=rate,beyond,size, so the three legibility numbers are a shootable before/after from one build (?lod=0's argument), and the drawn size is now per-population rather than read off the table at emit.
The comment repeated the retracted 15 -> 38 claim; it says trajectory, not census, and quotes 302 -> 276 against a watched 326.
Over a full director cycle at ?garden=7 the field holds 330-390 grains and the number drawn larger than 2 px is 0 (wide) / 5 (dolly) / 7 (close) / 23 (low), never above 4 px — the camera is never within 10 units of a grain, because the shots stand outside the crowd and a grain only lives within a plant's room of a plant. The mechanism is right and it barely reaches the screen; the lever is the drawn size as a constant ANGULAR size (the vein width floor's argument applied to a mote), ?pol= shows it at 3.2x, and it is not shipped because that is a look decision and looks are decided by watching.
rEdge started at where the outermost plant STANDS, and 35_garden.js already measured why that is not the same thing: median reach 9.7 units, p90 23.2, and one Ember Creeper columbine at 70.9 — a creeper whose axes never arrest. On a solo page the rim is 0, so any anther more than one plant's room from the origin would have shed grains that died on the step they were born. It grows monotonically with the furthest emission instead. Solo Ember seed 21: 38 -> 52 grains alive, the 38 near a plant unchanged.
…at it Three rungs of ?estab= shot at garden=7 and looked at: fH 0.62 (d 103.0) is the tree_shot lesson, a small clump in a dark frame; fH 0.78 (d 81.8) is full but the two nearest spires graze the top edge; fH 0.72 (d 88.7) ships. Verified at all three garden sizes -- the solver reproduces the derivation exactly: 62.74 against 62.8 predicted at garden=3 (height-bound), 88.7 at 7 (height-bound), 117.12 against 117.1 at 12 (width-bound). THE HORIZON: tools/flowers_horizon.mjs run at this pose's own eye and target at 8.0 / 18.4 / 34.0 units of eye height. NO hard horizon at any of them -- 25_ground.js's melt holds at 88 units, which is what that tool exists to catch. There is also no ground: the floor is inferred entirely from where the stems stop. ?haze=2,3,2.5,5 (raising the near shaping P, the one knob built to keep floor near the camera without bringing a horizon back) was shot against the default and is indistinguishable. The limiter is VALUE, not optical depth -- soil albedo 0.03-0.10 by decision lands within a hair of the void. Nothing in the ground was changed. Also: a shot with no subject falls back to 'bank', not 'wide'. A field with nothing flowering is a field of seedlings, where wide is width-bound at maximum standoff, and three of five shots would be an empty clearing. Co-Authored-By: Claude Opus 5 <[email protected]>
The one-frame black square was pre-existing, seed-dependent and only ever seen under ANGLE-on-Metal, and ~128 px is a plausible TBDR tile — so the standing hypothesis was a driver dropping a tile. It is not. It is ours, and each of these exists because the previous one could not answer the next question. flowers_black.mjs proved the hole is in the drawing buffer, but its collapse test is not specific: at seed 1337 it flags 232 tiles in 2,613 frames, in runs of six consecutive frames falling 60 -> 25, which is a camera cut. flowers_hole asks for the artefact's own signature instead — near black, still camera, one frame, recovers — and reduces every post target on the same frame. 8 confirmed holes in 4,026 frames, with the DEFOCUS target collapsing alongside 7 of 8 and carrying non-finite texels on all 8, while the scene target's luminance at that tile does not move at all. "The scene is clean" was a claim it was not entitled to make — it samples 64 texels per 69x65 tile, and one bad texel in ninety is exactly what it would miss. flowers_nan has exact coverage and classifies rather than counting, with no isnan() (GLSL ES 1.0 has none): NaN fails every comparison, +-Inf fails exactly one. 69 frames of 4,921 carry a NaN in rtScene's COLOUR, one 8x8 block at a time, never Inf, never in alpha, and never one in the defocus target without one in the scene. HIDE= bisects it to the tri stream: 0 in 2,155 frames with triMesh hidden, unchanged with petMesh hidden. flowers_vn names the term by rewriting FL_TRI_FS at runtime. Its first design aliased — markers are read per 8x8 block and a degenerate sliver puts several causes in one block — so it keeps the shipped discard and intercepts only what that discard cannot see. 83 markers, ALL of them |vN| == 0, and zero unexplained NaN left over. flowers_zeron settles it in Node with no GPU at all: 15,560 of 915,792 captured vertices carry a normal of exactly zero, matching the no-area triangles 10_capture.js drops species for species, and the population is a GAP — a normal is 0, or it is 1 to within 1e-7, with nothing in between. Co-Authored-By: Claude Fable 5 <[email protected]>
2.30% of the tri stream's vertex normals are exactly zero — 14,485 of 629,364 at seed 1337, 15,560 of 915,792 counting petals — and the comment above FL_GUARD opened by asserting the opposite. They come off the same collapsed blade base that produces the no-area triangles 10_capture.js drops; the counts match species for species. A triangle carrying one of them still has area, so it is drawn, and a fragment that lands on that vertex gets |vN| = 0, divides by it in normalize(), and writes a NaN into the HDR scene target. From there the post chain does the rest. The bloom's three separable blurs spread it over 3.2308 x (1 + 2.6 + 4.2) = 25.2 half-res texels each way, and a separable blur's support is a RECTANGLE, so one texel becomes an axis-aligned square about 101 px across. The comp adds it, aces() and pow() both pass NaN, and the frame gets a screen-axis-aligned hole. Measured on the frames the probe caught: 112 x 112 px in six of eight. It was read off the screen as "~128 px, suspiciously like a GPU tile" — it is the post chain's own footprint, and the absent bloom comb is not evidence against that path either, because a comb is what a finite impulse leaves at sparse taps and a NaN has no magnitude. The fix is the same guard written as the negation of the valid band. It closes the zero end with an exact comparison rather than an epsilon — the measured population is a gap, so 0 is a bit pattern and not a small number — and it is NaN-safe, which the `> 1.01` form was not: every comparison against a NaN is false, so the guard phase 1 added could never fire on the fragments that produced one. For any finite |vN| > 0 the two forms are the same test. Measured at seed 1337 (garden=7, ff=2200, ANGLE-on-Metal, 1100x780): non-finite texels in rtScene 69 frames / 4,921 -> 0 / 4,040 confirmed one-frame holes 8 frames / 4,026 -> 0 / 4,027 near-black candidates 8 -> 0 and flowers_guard.mjs runs both guards against the SAME frame, because a before/after screenshot cannot answer this — the director drifts perpetually and the air never stops, so two sessions of the unchanged build differ by a mean 9.5/255 with 43% of pixels past 8. Over 3,921 frames: 0 blocks where both guards are finite and disagree, against a control floor of 0, and 38 blocks where the old one was non-finite and the new one is not. Seeds 21 and 4242 give 0 non-finite under BOTH guards, which is why they never showed the square. flowers/build.js exits 0, parity.test.mjs stays 24/24. Co-Authored-By: Claude Fable 5 <[email protected]>
…d it loses tools/flowers_motes.mjs reports 0 of 320 grains in frame at `close`, and the diagnosis is neither the grain size that tool was built to measure nor the framing. On ?garden=7&seed=21 the subject is specimen 2, and the field's plumes belong to the other six: 320 grains at mean heights 12.7 to 21.5, mean ndc.y 3.3 to 4.9 — four frame-heights above the picture. A close-up cannot show motes that are not there. flDirPlume is the term that would fix it: how many live grains are inside the candidate corolla's own airspace, asked of the POPULATION rather than of the plant. Re-deriving which organs are dehiscing from whorl/dev/sen would be a second copy of FlPollen._sources's gates that drifts from it, and it answers a worse question — what a close-up needs is not that this flower has anthers but that there is pollen in the air where the camera is about to point. 0.8 to 1.4, the same 1.75x spread as `rim`. AND ON THIS SEED IT LOSES, WHICH IS THE HONEST HEADLINE. Subject scores, with the base term re-implemented in tools/flowers_frame.mjs so the multipliers can be read against it: [2] ax 0 33 petals r 1.81 base 13.68 plume 0.80 product 9.67 [5] ax 0 5 petals r 1.74 base 2.14 plume 1.40 product 2.43 [4] ax 0 7 petals r 2.47 base 2.28 plume 1.40 product 1.74 Nothing defensible closes a factor of six and nothing should: a 33-petal corolla really is the best flower in that field. What it cannot do is shed, and the reason is structural rather than accidental — Sulphur Rosette has `whorlBands: false`, so every one of its 33 floral organs carries the `petal` flag and FlPollen._sources, which skips petals, finds no anther on it at all. Measured across the stand: 33/33 organs petal there, against 23 stamens of 76 on the Abyssal Frond. The showiest corolla and the shedding corollas are different flowers for a reason. The framing half of the suggestion was measured and rejected on the measurement, not on taste: at seed 31 the subject's OWN plume sits at mean height 26.8 against a corolla at 17.2 — nine units above a frame about five units tall — so no headroom this shot could give would reach it. A grain drifts ~0.32 units per unit of plant time and lives a long time; by the time there are grains, they are gone. `low` is where motes read (5 of them over 2 px against `close`'s 7 and `wide`'s 0), and `low` is now a better shot than it was. inFrame at `close` is still 0 of 320. Reported, not softened. flowers/build.js exits 0, parity 24/24. Co-Authored-By: Claude Fable 5 <[email protected]>
It still described an open mystery and a GPU tile. The hole is ours and it is fixed, but two of the sentences that shaped the search were wrong and are worth leaving visible rather than deleting: 128 px was an eyeball (the square is 112 x 112, which is the post chain's own impulse support), and its collapse test counts camera cuts, not events. What it was built to settle — both reads of the canvas agree, so the hole is in the drawing buffer — still stands. Co-Authored-By: Claude Fable 5 <[email protected]>
Five poses stand outside the stand and look in, and this file says twice why —
an eye anywhere inside the ring is inside a canopy, measured, twice, both times
a wall of blurred leaf. But "you cannot stand anywhere inside" and "there is
nowhere inside to stand" are different claims and only the first was ever
measured. A stand of seven planted on a jittered ring has LANES in it.
THE WIDEST LANE IS THE LARGEST GAP IN A PROJECTION. Pick a direction of travel,
project every drawn point onto the perpendicular axis, sort: two consecutive
values with nothing between them are a band of the plan no plant lies in, clear
by construction rather than clear where somebody checked. Sweep the direction at
3 degrees, the same sweep the establishing heading uses. Two constraints, both
stated: the gap's centre must be in the middle half of the field (otherwise the
widest lane skirts the OUTSIDE, which is not a traverse), and only points within
the travelled window count.
That window is the whole reason it works. Asked of the whole 67-unit field the
widest clear lane at ?garden=7&seed=21 is 2.01 units of half-width — a crack.
The camera covers 23 of those 67 units, and matter beyond the travelled segment
is in the PICTURE, not on the lens: 5.14 units of half-width over the window,
with the nearest drawn point coming to 4.29 as it passes.
TWO THINGS THE FIRST BUILD GOT WRONG, both caught by measuring:
- A STATION IS A STEM, NOT A BLADE. The lane cleared every axis point by 6.13
units and the frame was a wall of leaf, because a blade on this catalogue is
10 to 22 units long against a station spacing of about one. `F.drawn` is the
stations plus where every organ reaches (its frame's own axis, at half and
full length), kept separate from `F.plan` because where the plants STAND and
what is in the way of a LENS are different questions.
- A LANE IS THE EMPTIEST LINE THROUGH THE STAND, so aiming along it aims at
the one direction with nothing in it. On the film the last third of the
traverse was a crowded left half and a bare right one. The aim leans onto
the mean lateral position of everything drawn ahead within the lead — the
establishing frame's slide onto the occupied sector, made forwards and
continuously — clamped to a third of the frame's half width.
THE SPEED IS THE FILM'S OWN DRIFT TIMES THREE, and nothing else:
v = FL_GLIDE_K * FL_DRIFT * (frame width at the aim distance) = 0.73 u/s here,
4.6 cm/s in WORLD.unitM. tools/flowers_clip.mjs over 285 s of film measures the
glide's steady rate at 2.131 %frame/s against FL_DRIFT's 0.75 — 2.84x, and the
six shots now read 0.605 / 0.781 / 0.965 / 1.137 / 2.131 / 3.748. It is the
second-fastest shot, under the dolly.
THE RACK NEEDED NOTHING NEW. 40_boot sets uFocus to the camera-to-target
distance and the target is a fixed lead down the lane, so the plane of focus
rides ahead and every flower resolves as it reaches it and softens as it passes.
Verified rather than assumed: the sight-line cull cannot be revealed by this
shot, because captureDirty only lets camera motion dirty specimen 0 and only
when `framedAx >= 0 || heroHadCull`, both of which are false outside the
close-up. Cost is real and reported: capMs 83.1 against the dolly's 69.3 and the
wide shot's 34.5, and it is 28_lod.js doing its job — a near camera earns a
finer blade mesh.
AND A TELEPORT, FOUND TWICE IN THE SAME PLACE. `mid` is the midpoint of a
support interval, so one new hull vertex — an arm that has just grown past the
old extreme — steps it by tens of units, and 40_boot's 12%-a-frame lerp turns a
30-unit step into 3.6 units in one frame. Seven samples of a 280 s film over 50
u/s, all inside `wide`, peaking at 239 against FL_DIR_VPEAK's 26. Damped at
0.01 a frame. Then the re-decision rule below it put the spikes straight back
(196 u/s, in the middle of a HOLD) and now fires only when no shot clock is
running, which is the pinned-capture case and nothing else.
Wide shot, drawn stations across the frame, before this branch -> now:
garden=7 seed=21 1.173 -> 1.526 garden=7 seed=31 1.281 -> 1.519
garden=9 seed=101 1.690 -> 1.739 garden=3 seed=7 1.204 -> 1.191
flowers/build.js exits 0, parity 24/24.
Co-Authored-By: Claude Fable 5 <[email protected]>
…nds its
shot where the plants are
Three follow-ons from watching 290 s of film, all of them the same lesson: a
held frame must not be a function of what the plants did while it was held.
THE HEADING WAS THE TELEPORT, and it took three tries to convict because two
other suspects produce the same signature. `F.pa` is the direction of the field's
plan DIAMETER, and a diameter is not a continuous function of a growing stand:
one arm reaching past the old extreme swaps which pair of points is furthest
apart and swings the answer tens of degrees between two 500 ms measurements. At
the establishing frame's 90-unit standoff, 30 degrees is 47 units of eye, and
40_boot's 12%-a-frame lerp makes that 5.6 units in a frame — 336 u/s against
FL_DIR_VPEAK's 26. tools/flowers_clip.mjs, worst sample over 290 s:
239.5 -> 196.0 -> 168.1 -> 37.7 u/s
(lateral shift damped, then the end re-decision gated, then this)
and samples over 50 u/s went 7 -> 7 -> 6 -> none. The `wide` shot's own mean
speed fell 15.0 -> 8.2 u/s with nothing else changed, which is the same fix
reading as a calmer shot. A shot's heading is now decided when the shot begins,
like its end, its subject, its gap and its lane — except where no clock is
running, which is the ?shot= capture case and is the reason the pinned still had
been framing a fast-forwarded stand of seedlings.
A TRANSITION IS AN ARC. The low shot's POSE was solved out of the canopy and it
still entered through leaf, because a pose is where a move ENDS and the straight
line to it goes wherever it goes — and with the heading flipping end for end at
every cut, a great many of those lines run across the middle of the stand at
flower height. flDirBlendEye interpolates the eye in the field's own polar
coordinates (radius, azimuth the short way round, height), so the camera swings
around the stand rather than through it. Two poses on one bearing still give a
straight radial move, which is what the crane and the bank approach already were.
The target still lerps straight: an aim has nothing to collide with.
THE TRAVERSE SPENDS ITS SHOT ON THE MASS. Centred on the field, its last third
had passed everything flanking the lane and was aimed down the empty end of it —
a measured frame with two soft leaves in one corner and nothing else. The
flanking mass has a centroid along the lane, and the window now runs from 1.5 H
before it to 0.5 H after, so the camera spends the shot approaching and passes
through near the end. The last frame of the traverse is a corridor between two
plants instead of an empty one.
Wide shot, drawn stations across the frame, unchanged by all three: 1.526 at
garden=7 seed=21, and the pinned still is reproducible run to run now, which it
demonstrably was not.
tools/README.md gains flowers_frame.mjs, including the two things about it that
would otherwise be relearned: its dead margin is a quantile because a threshold
returns zero on these palettes, and it keeps a second copy of flowerScore that
will drift.
flowers/build.js exits 0, parity 24/24.
Co-Authored-By: Claude Fable 5 <[email protected]>
… is read Pinning a shot stops the clock, so the glide's u saturates and the camera parks at the end of its own travel — which, since the window is placed on the mass, is inside it. The still is a wall of soft blade; in motion the same blade is a wipe. And a pinned CLIP cannot show this shot at all: 45 s of ?shot=glide travels 1.1 units and scores 94% stationary, because it had arrived before the recording started. Both measured, both written where somebody would reach for the tool. Co-Authored-By: Claude Fable 5 <[email protected]>
flowers/README.md is the argument for this piece, so a stale sentence in it is worse than a stale number in a table: three of them were describing code that no longer ships. The sixth shot (`glide`) is in the table with the reason a pinned traverse is not a useful still; the establishing heading is the field's plan DIAMETER rather than the covariance of its origins, with the 0.584/0.777 fill that decided it and the four seeds' NDC spans; the low shot solves its standoff and its pitch out of the skyline; and the five measures the director now keeps apart (`F.plan`, `F.drawn`, `F.tops`, `F.hull`, `F.discs`) are written down with what each one is the wrong shape for. The pollen paragraph said the drawn size "is not taken here" and it has been: every grain sat at the rasteriser's 1 px clamp with zero variance, which is a thing no screenshot reports. The ground gains the airlight pool and the flat pool that was built, measured and rejected for lighting an overhead magenta. And the second artefact is no longer an open mystery with a GPU tile in it: it was normalize(0) on the same collapsed blade base, the 112 px is the post chain's own impulse support, and the guard that could not see it could not see it because every comparison against a NaN is false. The shipped page's null geometry is now booked as a source fix rather than left as a footnote. Co-Authored-By: Claude Fable 5 <[email protected]>
…ecided Round 4 added a camera language, a floor and a visible plume, and every number in them was either derived from something already shipped or set by looking. TUNING is where that distinction is kept, so it now carries all of them with the label attached: FL_DIR_VPEAK derived (it reproduces the two hand-set transition times it replaced, 5.02 against 5.0 and 7.85 against 7.5), FL_DRIFT and FL_LOW_SKY and minAng by eye with the ladders they were taken off, FL_GLIDE_K stated as a ceiling, and the pool's length scale not a constant here at all but the clearing's own radius. Two of them carry a warning rather than a range. The drift's first write-up quoted a "0.00" baseline that nobody measured — the instrument was written after the baseline run — and the re-run corrects it and shows the close-up was never the offender. And the establishing heading is a diameter, which is not continuous in a growing stand: 336 u/s of eye against a 26 u/s peak, so every per-shot decision is taken on the cut and held. Co-Authored-By: Claude Fable 5 <[email protected]>
…n the fix A person watching said "glitchy flashes", and ten probes later the flash and the black square found while gating it are the same collapsed blade base reaching the screen two ways: a triangle with no barycentric denominator handing a fragment finite-over-nothing varyings, and a vertex normal of exactly zero making normalize() write a NaN. Both are magnified by the same bloom chain, whose rectangular impulse support is where the 112 px came from -- the "~128 px, so a GPU tile" that shaped the first search was an eyeball. The entry keeps three things a fix alone would lose. The guard added against corrupt interpolation could not see the worse corruption, because every comparison against a NaN is false. Three A/B designs returned confident numbers that were the probe's own floor, and only a control told them apart. And the cause is in src/50_geom.js, which both pages call, so this is written down as a source fix somebody should take deliberately. A second entry takes the round's other four results: the wind clock that had been sampling one air at N life clocks since the garden shipped, the establishing heading that was a covariance of origins where it wanted a diameter (and the teleport that follows from a diameter not being continuous in a growing stand), the flat pool of light built and rejected for lighting an overhead magenta, and a plume-scored close-up that loses to a 33-petal corolla which structurally cannot shed. Co-Authored-By: Claude Fable 5 <[email protected]>
Each of these cost real time this session and none of them announces itself. A comparison guard is not NaN-safe -- the one written against corrupt varyings was the single thing that could not see the corruption that produced a NaN, because every comparison against a NaN is false; the fix is to state the band you accept rather than the failure you imagined. Three A/B probes on a page that never stops moving each returned a confident number that was the probe's own floor, and only the control -- the identical build against itself -- told them apart. And a channel drawn at the rasteriser's clamp is invisible in a way no screenshot reports: 320 grains of pollen, all at exactly 1.00 px, zero variance, looking on screen precisely like a channel that works. Co-Authored-By: Claude Fable 5 <[email protected]>
…s to both pages Two of them closed differently from how the list expected, which is the reason to write the answer down rather than tick the box. Pollen being the hero's alone was listed with "the follow-up is cost"; the follow-up was visibility -- every grain was drawn at the rasteriser's 1 px clamp. And the wide shot's heading was fixed (covariance of origins -> the field's plan diameter, 0.584 -> 0.777 of the frame) without answering what the item was actually about, which is recession, so it is marked improved rather than done. 0f-i is the one that matters and it is only partly discharged: AJ watched and reported a defect, which is not the same as anybody sitting through the film. The new item is blade()'s null geometry. flowers.html fixed it downstream and canalisation.html draws the same 7% of zero-area triangles and 2.30% of zero normals into the same bloom chain. It is booked as a source decision with the warning that a guard is what the other page did, and it left the cause in place. 10b also gains the wind clock, fixed on the way past: every garden member was sampling one air field at its own life clock, so a gust could not cross the stand. Co-Authored-By: Claude Fable 5 <[email protected]>
…watching All three are now false. It is six shots and a 24/24 gate, and a person HAS watched the page -- what they saw was two rasterisation bugs whose cause is in src/50_geom.js and is still there, which is the one thing somebody arriving at this file needs to know before they touch either page. The list keeps its shape (short, pointing at flowers/README.md for the story) and gains two entries that are lessons rather than features: the film's one screen-referred rate with the derived transition pace under it, and the fact that a channel drawn at the rasteriser's clamp is invisible in a way no screenshot reports -- which was true of the pollen and, from the other end, of the pool of light on the ground. Co-Authored-By: Claude Fable 5 <[email protected]>
…rson looks flowers_floor.mjs and flowers_motes.mjs produced the load-bearing numbers behind the ground pool and the pollen's angular size, and neither was in tools/README.md -- which for this project means they would have been rewritten from scratch by whoever asked the same question next. Both entries carry the trap that bit while writing them: floor's dMax is the defocus moving at organ silhouettes rather than floor, and motes reports device AND css pixels because at dpr 2 the two readings support opposite conclusions. test/flowers_pollen.mjs was missing from CLAUDE.md's harness list too; run to confirm before adding it (emit 971, edge 919, residual 0). Co-Authored-By: Claude Fable 5 <[email protected]>
The probe count in the JOURNAL entry was ten and it is twelve, six per artefact, and naming them is more useful than the total. The garden section's heading promised four stated numbers and round 4 added five more. And a "parity 20/20" from the round-3 ABC work now reads as a claim about the current gate, which is 24/24 -- the sort of sentence that is true of the code it was written against and false of the code shipping. Co-Authored-By: Claude Fable 5 <[email protected]>
… say the band 45_director.js works the glide at 0.91 u/s off Rout and its landing commit quotes 0.73 off the solved aim distance; both are arithmetic on the same law and neither is measured. Quoting one as if it were the answer is exactly the kind of number this project gets caught on later, so the docs give the band and point at the screen rate, which is the thing tools/flowers_clip.mjs actually measured. Co-Authored-By: Claude Fable 5 <[email protected]>
flDirBlendEye stopped the eye CHORDING across the middle of the stand; what was left is that the blended path's height and radius are still a line through whatever canopy stands between two poses. Caught on film (shots/film/fr06.png: one out-of-focus near blade, no subject, four seconds into a move) and now measured — tools/flowers_trans.mjs walks every transition in a rotation with the real poses and the real blend, against the drawn VERTICES rather than a model of them. Before, on garden=7&seed=21: two of six transitions pass within 0.7 units of tissue and one puts foreground across 46% of the frame. Seed 1337: 0.07 units and 63%. So a transition cranes. `eye.y += A * 4w(1-w)` — zero at both ends because the poses are solved and must not move, and zero in VELOCITY there too since w is already a smoothstep. A is the smallest amplitude for which nothing drawn is within FL_DIR_BRUSH of any sample of the path, which has a closed form: it is flDirClearance's derivation turned on its side, asking how far UP rather than how far ALONG. Solved on the cut and held, like the establishing end, the low shot's gap and the traverse's lane. Three things the measurement decided rather than confirmed: - THE AIM RISES WITH THE EYE. Lifting the eye alone pitches the lens down onto the canopy it just cleared, which is the same wall from above: cover 20% -> 27%, worse. A crane is a translation, not a swing. - F.solid, and an organ in it is a chain of spheres. `drawn` is a plan projection and samples an organ along its own AXIS, so the first version found nothing to clear on a path 0.46 units from a blade. - 6.0 units, swept: 4 leaves the traverse's exit inside a plant, 8 buys nothing and starts craning the two moves that were already clear. And the close-up's entrance was the one straight line left in the film — it is the shipped framer, held back by a world-space lerp. It goes through the same blend now, with the framer's own eye as the far end and the crane as an offset that is taken back off before the framer reads its own state. seed 21, worst interior clearance 0.07 -> 0.27 (and below the lens rather than across it), 2/6 transitions inside 3 units -> 1/6, the traverse's exit 0.70 -> 4.49 with cover 46% -> 24%, four of six transitions untouched. A lifted path is a longer path, so it is re-priced off the polyline it will fly (FL_DIR_VPEAK is unchanged); the rotation goes 126.2 -> 129.7 s. Co-Authored-By: Claude Fable 5 <[email protected]>
`src/60_render.js`'s MESH_FS does `normalize(vN)` with no guard of any kind, on the geometry `blade()` emits, through a comp shader character-for-character the one flowers transliterated. Every ingredient of flowers.html's one-frame black square, and nobody had ever probed it. `tools/nan_check.mjs` is that probe. The ingredient is there and there is MORE of it: the main page's tri stream carries 41,775 zero normals in 565,932 vertices (7.38%) against the flowers stream's 1.70%, with no non-unit normals at all. Measured, over 133,731 frames on ANGLE-on-Metal, headed — solo hero, a garden of seven at seed 1337, three named species including a fully grown Ashfall Spire, 4x the fragment samples at DPR=2, and 18,000 frames parked on a blade base filling the frame: ZERO non-finite texels and zero one-frame holes. So the guard was not ported, and `src/` is untouched. The reason is structural and `node tools/nan_check.mjs geom` reproduces it. At u = 0 a blade's half-width is zero, so all MV+1 grid vertices of that row are the same point, `Pv - P` is the zero vector, and `v3norm` returns an exactly-zero normal. Per quad in that row you get one triangle with TWO zero normals — area identically zero, because two corners coincide — and one with ONE, which is drawn. 13,924 of 13,925 two-zero triangles have area exactly 0 (the last, one needle, has 1.05e-6). So the only locus where the interpolant is exactly zero and a fragment could land on it is a single VERTEX; the edge that would divide by zero over a whole span is collapsed to a point. flowers.html wins that coincidence rarely too — 69 frames of 4,921 at seed 1337, none at seeds 21 or 4242 — and `flowers_zeron.mjs` shows the drawable population is the same triangles species for species, so the difference is framing and rasterisation rather than geometry. MARK=1 is the A/B and it is better than any picture, because the wind never stops and a screenshot differ has a floor far above the artefact: it paints the fragments the guard WOULD discard instead of discarding them. 29,381 frames, including the conifer and the macro blade base, marked zero blocks. Porting the guard would be free and would buy nothing today. Two things worth not relearning, both in the header. The first positive control wrote `normalize(vec3(0.0))`, which is a compile-time constant — ANGLE folded it away and the control came back clean, which would have been read as "the probe works and the page is fine"; it builds the zero vector from a uniform that is never written. And the exit code flips under CONTROL and MARK, because a shipped-shader run is bad if it finds something and a control run is bad if it finds nothing. With the control patched in, canalisation.html draws the black square perfectly, which is the proof that the chain is identical here and only the trigger is missing. node build.js exits 0 and leaves canalisation.html byte-identical; smoke 73/73, views 23/23. Co-Authored-By: Claude Fable 5 <[email protected]>
A result nobody can find is not a result. The 2026-08-12 JOURNAL entry has the frame counts, the geometry table that explains them, the two alternatives that were ruled out mechanically rather than by eye, and the two instrument lessons — including the positive control that could not fire because ANGLE folded a compile-time constant away, which would have been read as a clean page. Also corrects one claim in the probe's own header: the two comp shaders are not character-for-character identical, they differ in exactly one token (`max(c, 0.0)` against `max(c, vec3(0.0))`, a GLSL ES 1.0 requirement). That is now a check anyone can re-run rather than something read off two screens. Co-Authored-By: Claude Fable 5 <[email protected]>
# Conflicts: # docs/JOURNAL.md
… half-rise Three corrections, all from measurement, and each one is a thing the first version got wrong for a reason worth writing down. THE MODEL WAS A PROXY AND THE PROXY WAS WRONG BOTH WAYS. `drawn` samples an organ along its own AXIS, so it could not see a blade the lens was inside; inflating each sample to a sphere of a guessed half-width then over-stated as often as it under-stated — on seed 1337 it asked for a 36-unit crane where the true geometry wanted none. Every specimen's FlowerBuffers holds the vertices that went to the GPU, so `F.solid` is the drawn SURFACE now, subsampled to 8000 points a scan. Same argument as framing a close-up from the drawn reach. THE WINDOW HAS A FLAT TOP. A crane rises, holds and comes down; `4w(1-w)` is at full height for an instant, and dividing a required excess by a window that small blows the amplitude past any ceiling. Two smoothsteps: 24.4 units asked against 21.9 for the same path. AND THE AIM RISES ONLY HALF WAY. Sweeping it on the move that cranes 21.9 units (foreground cover / least ink): no crane 21%/45%, aim 0 20%/44%, aim 0.5 11%/38%, aim 1 10%/25%. At 0 the lens pitches into the canopy it just cleared and the crane buys nothing; at 1 the top of the arc is empty sky, which is three blank frames on film. `ink` is new in the harness and is what caught that — a transition can be a bad two seconds by being empty as well as by being a wall. The ceiling is `hHi + m` (clearing a point AT the skyline means standing a margin above it; a bare `hHi` killed a move on seed 1337 by 5%) and it is a KILL CRITERION: if the crane cannot clear, the director keeps the move it had. Clamping instead flew the eye through the crown of the plant it could not get over — clearance 6.41 -> 1.67, cover 8% -> 18%, worse than the straight path. seed 21, against the same rotation with the crane disabled: bank -> glide cover 21% -> 11%, glide -> dolly 47% -> 25% with clearance 0.23 -> 1.10. seed 1337: glide -> dolly 63% -> 22% and 0.07 -> 2.54, dolly -> close 47% -> 27% and 0.10 -> 9.20 (that one is the arc alone, no crane). Four of six moves untouched on seed 21, three of six on 1337. ⚠ And a knob bug worth the line it cost: `+null` is 0 and 0 is a legal margin, so every unswept run of the page was flying at margin zero while every swept one used the URL. The ladder was right and the default was a different program. Co-Authored-By: Claude Fable 5 <[email protected]>
…ry frame Caught on film, and it predates the crane — it is in both builds. "The short way round" is not a continuous function of two poses: at a half-turn it is a coin flip, and `wantEye` is rebuilt every frame by the orbit, the dolly's travel and the 500 ms field re-measure. `low` and `wide` are very nearly antipodal — one kneeling outside the stand, one ninety units off the far side — so they sit exactly on that flip. tools/flowers_clip.mjs on garden=7&seed=1337 reports `low -> wide` peaking at 191 u/s in the old build and 434 in the new one, against FL_DIR_VPEAK's 26, one 4 Hz sample wide and in the middle of the blend. The track says what it is: the eye's azimuth runs 221 -> 216 -> 204 -> 186 degrees and then jumps to 238 and climbs, and the eye moves eighty units between two frames. The blend reversed its direction of travel mid-move. The difference is still read from the live want every frame — it has to be, the want moves — but on the branch the move STARTED on, so it drifts continuously and can never jump by a turn. Same argument as the frozen heading, the frozen establishing end, the frozen gap and the frozen lane: nothing about a move in progress should be a function of what the plants did while it was being flown. Co-Authored-By: Claude Fable 5 <[email protected]>
…'s hold went Two small things, both of which were assumptions until they were written down. THE SOLVE COULD THREAD ITS OWN SAMPLES. Twenty-one samples on a hundred-unit path is five units apart against a three-unit margin, so a canopy could sit between two of them and be declared cleared by a crane that flies straight through it. The count comes off the path's own length now, at half the margin, measured first with a pass that touches no geometry and capped at 81 because a solve is one pass over F.solid per sample. AND THE LOW SHOT'S DWELL WAS NEVER BEING EATEN. It has been read the other way, so it is worth a note where someone would look: flDirClock ADDS the transition to the hold rather than taking it out, and tools/flowers_clip.mjs measures `low` at 12.3 s of which the move is 3.3, in the build before this work and in the build after it. The only part of the hold the move costs is 40_boot's 0.12-a-frame damping, about 0.15 s. Neither repair someone might reach for is needed, and lengthening the hold would push a rotation the crane has already taken from 126 to 130 s further out still. Co-Authored-By: Claude Fable 5 <[email protected]>
…ses under The requirement was "the eye must be above everything within the margin in PLAN", which asks a move that passes comfortably BENEATH a canopy to get over it. Measured on garden=7&seed=1337, `close -> low` has 6.41 units of clearance to anything drawn and the solve still wanted a 21-unit crane — which took the clearance to 2.95 and the foreground cover from 7% to 20%. It made a clean move worse to satisfy a requirement that move never violated. Passing under a plant is a perfectly good shot. A point pushes the eye only when the eye is INSIDE its sphere now, and then only to the top of it; climbing out of one sphere can put the eye inside a higher one, so the walk repeats. And the amplitude solve is iterated for the same reason one level up: the requirement is evaluated at the height the eye will actually fly, which depends on the amplitude being solved for, so a single pass answers the question the OLD path asked. Without that, seed 1337's traverse exit lifted 18 units and finished with a leaf 0.4 units above the lens. seed 1337, against the same rotation with the crane off: `close -> low` back to 6.41 / 7% / 62% (clearance / foreground cover / least ink) from the 2.95 / 20% / 28% the plan rule gave it, and `glide -> dolly` 0.32 -> 2.54 with cover 39% -> 24%. seed 21: `bank -> glide` 0.92 -> 0.50 with cover 21% -> 11%, `glide -> dolly` 0.23 -> 1.37 with cover 47% -> 23%. Four of six moves untouched on seed 21, five of six on 1337. The rotation goes 126.2 -> 129.9 s. Co-Authored-By: Claude Fable 5 <[email protected]>
…s own solve A transition can be a bad two seconds in two ways and this file only measured one of them. `ink` is the fraction of the frame with any drawn vertex in it at any range, so a crane that rises into blank sky scores near zero on both `cov` and `ink` and reads as a hole rather than as a wall — which is how the aim fraction got measured instead of tasted. Also: the per-sample MODEL column now calls flDirRise rather than the plan rule it replaced, so the harness and the director are asking the same question. Co-Authored-By: Claude Fable 5 <[email protected]>
…w is stale The 3 and 4 rows were measured before the solve stopped craning over canopies the path passes under, so they were describing a different mechanism. Re-taken: 3 lifts 22.0 and 10.2 units for cover 11% and 23%, 4 lifts 23.5 and 9.8 for 10% and 22% — a unit and a half of extra crane for nothing a number can see. The 6 row is left as an upper bound and flagged as such rather than quietly kept. Co-Authored-By: Claude Fable 5 <[email protected]>
…e invariant instead The comment computed 0.91 u/s off F.Rout while commit b178e2d computed 0.73 off the solved aim distance — same law, different distance, and both are arithmetic rather than measurements. The docs pass caught it. The number that does not move with that choice is the measured screen rate, 2.1-2.2 %frame/s against FL_DRIFT's 0.75, so the comment now quotes the band and anchors on the invariant. Co-Authored-By: Claude Fable 5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A garden, in the flowers piece
?garden=N(2..12) grows a whole flowering field inflowers.html: N specimens, dealtwithout replacement from the eight flowering species, each rotating its own floral form off
its own seed, germinating on a stagger, standing in one wind on one ground, shot by a
five-shot director. The URL still fully decides the field.
Nothing about a plant's shape is drawn here either. Placement, camera, ground and air are
the environment category the project already allows — "where a plant stands was never a
simulation result" — and this branch adds no growth code and no organ geometry.
What it cost, measured
0.83 s sqrt(N)— constant areal densityLive, 30 s of real growth at garden=7: gap median 8.7 ms, p95 66.4, worst 117.6, 56.9 fps
measured. Gates:
flowers/build.jsexits 0,flowers/parity.test.mjs20/20,test/smoke.mjs73/73,test/views.mjs23/23, both artifacts rebuilt and not stale.Three findings that outlive the feature
Capture cost was FLORAL, not leaf — and that corrects the shipped app's own diagnosis.
70% of a field capture is floral organs (petal 45.9 ms, sepal 28.2, leaf 25.3, stamen 13.1)
because
20_draw.jspinsdetL = 1.0for every floral organ — the microscope permanentlyon, so a 0.1-unit filament was built as a 75x75 grid for 70 pixels.
flowers/28_lod.jscapsmesh resolution at one quad per pixel: a cap, never a raise, no taste constant in it
(
pxHeightis the drawing buffer,fovis the camera's, one quad per pixel is Nyquist).Do not lower it to buy frames — it is Nyquist, not a dial.
?lod=0is the pre-LOD page.Reach is a statement about a SEED, not a species. Ember Creeper columbine measures maxR
7.4 at seed 21 and 65.9 at seed 31697 — same species, same form, 9x apart — and that
specimen is still climbing linearly at step 3200 (11.9 -> 28.8 -> 44.4 -> 70.9): a creeper
whose axes never arrest. A one-seed table got the middle right and the tail 3x wrong, and
the tail is what collides. No fixed spacing can keep a creeper off its neighbours and none
should try — that is
organBudget's business, not placement's. So 12 is a stated fraction(full clearance of two median bodies, 62% of two median arms), not a clearance guarantee.
A plain palette mean is mud, measurably. Blending seven species' atmospheres gives fog
saturation 0.261 against a member mean of 0.598, and
keyCol0.090 against 0.328 — near-grey.The shipped blend preserves luminance and restores chroma. The hero still leads at 0.35
because a pure field mean gives every garden the same violet sky: mean pairwise fog-hue angle
across 40 fields is 84 deg at lead 1.0, 64 at 0.35, 27 at 0.0.
Also here
flowers/45_director.js— wide (establishing, solved from the field's plant bounds and thelive frame equation), bank (the thicket mid-shot), dolly, close, low.
flowerScore()picks asubject across the whole field; a drag suspends the director for 25 s in a garden.
flowers/25_ground.js— the project's first ground geometry. One sharedFL_SKYso theground and the background are the same sky rather than two that resemble each other, plus a
ground-hugging haze whose optical depth is closed-form. No hard horizon at any camera height
the director reaches (
tools/flowers_horizon.mjsis the check).tools/flowers_shot.mjs,tools/flowers_perf.mjs,tools/flowers_horizon.mjs,test/flowers_capture.mjs(a headless field profiler),test/flowers_pollen.mjs(a censusthat asserts the population balances). The headless profiler caught the shot tool
double-counting the hero — 998 organs reported for 771.
flowers/README.md, the numbers in TUNING, the reasoning and thenegative results in JOURNAL 2026-08-12, the traps in PITFALLS, a ranked debt list in ROADMAP.
What needs your eye, not mine
project's own record ranks as its weakest evidence. That is the first thing to do.
moon-in-black to moon-over-indigo-soil. Taste call. Dials:
soil0/soil1and the meltstart in
25_ground.js, not the fog.5 (dolly), 7 (close), 23 (low). The mechanism is right and what a viewer gets is a scatter
of single pixels. The honest lever is the drawn size — the vein width floor's own
argument applied to a mote, since a real grain is sub-pixel at every distance.
?pol=0.05,12,0.07is the 3.2x A/B, built and shot but not shipped, because looks onthis project are decided by watching.
entirely, so the field floats. Correct per the melt's design, and arguably the piece's
signature void, but it means the frame cannot show you where the floor is.
One bug found and NOT fixed, because it is outside this piece
src/40_plant.jssamples the wind at each plant's ownthis.time, so in a garden amember germinated at world step 900 bends to the air of 900 steps ago. One field, evaluated
at N different times. The pollen deliberately does not copy it (grains drift on the world
clock). Worth its own branch.
Housekeeping
A phase-1 agent ran
ln -s .../node_modules node_modulesfrom the repo root rather than itsworktree, replacing the real directory with a self-referential broken symlink — and committed
it, because
.gitignore'snode_modules/matches a directory. Every Playwright tool thenfailed with
ERR_MODULE_NOT_FOUND. Untracked here, the bare form ignored, incident inPITFALLS.
Round 4 (2026-08-12/13): the film, and the two artifacts
49 further commits, unattended overnight, seven Opus agents across four staged workflows
plus integration and verification between each. The brief: "make this garden as awesome
and aesthetic and amazing to watch as possible... there's these glitchy flashes on
flowers that seem unintentional."
The glitchy flashes: two defects, one cause, both convicted
The flashes were real, caught on camera by a new probe (
tools/flowers_glitch.mjssnapshots the canvas the exact frame a tile spikes, excluding camera-motion frames), and
they turned out to be the same null geometry reaching the screen two ways.
blade()(src/50_geom.js) emits ~7% zero-area triangles at every leaf base, whose vertices also
carry exactly-zero normals.
The bright gridded square: a zero-area triangle has no barycentric denominator, so
the rasteriser hands its odd fragment varyings outside the hull of its own vertex values
—
vC*vE*3measured at 710 against a legitimate ceiling near 3, one HDR texel at 8,863against a frame median of 2.38 — and the bloom chain's sparse blur renders that delta as
a comb: the "grid" was the blur kernel seen naked. Fixed at two causes:
10_capture.jsdrops no-area triangles (scale-free test, counted and reconciled exactly by the parity
gate, extended 20→24 checks), and the tri/petal shaders enforce the interpolation
contract (|vN| ≤ 1 is a theorem for unit normals under convex interpolation; the spike
held 56.10 of the shaded colour above that test and 0.69 below it).
The one-frame black square (found while gating the fix):
normalize(vN)where|vN| == 0 makes a NaN the bloom smears into a 112 px square — 112 derived from the
blur support, not a GPU tile. The first guard couldn't see it because every comparison
against NaN is false; rewritten as the negation of the valid band. Headless: 14,485 of
629,364 tri vertices carry an exactly-zero normal, matching the no-area census species
for species, with nothing between 0 and 1−1e-7.
After both: zero events — 0 flash spikes, 0 holes, 0 non-finite texels across all
probe runs at seeds 21, 1337, 4242, 8888 (tens of thousands of frames). Scene-peak p99
369.5 → 5.2. The look is measurably unchanged (close shot mean pixel −0.4%; controlled
same-frame A/B: 0 finite blocks changed). Twelve purpose-built instruments carry the
diagnosis in
tools/.And canalisation.html was measured, not assumed: it has the same unguarded
normalizeon 3× the zero normals (7.38%) and does not fire — 133,731 frames, 0hits — because there the zero-normal edges live only on triangles with identically
zero area, which are never rasterised. Positive controls prove its chain would draw the
square if fed a NaN.
src/untouched; ROADMAP 0g books the deliberate decision:fix
blade()at the source for both pages, or port the (provably no-op) guard.One air, one clock
PlantsampledwindAtat its own life clock, so garden members bent to moments of thewind up to 2,600 steps apart — a gust could never cross the stand as one event, in
either page's garden.
Plant.windPhase(default 0; solo pages and every harnessbit-identical) anchors each member to the scene's wall clock; a regrown hero inherits it
("the weather outlives the specimen"). Gates: smoke 73/73, stem 82/82, wind 27/27.
The film
The director is now a cinematographer rather than a slideshow:
rate (FL_DRIFT = 0.75% frame width/s — by eye, the uRef category); stationary samples
15.1% → 0.0%.
glidetraverses the stand's widest clear lane(derived from the drawn points' projection), at 3× the drift rate = ~5 cm/s, rack
focus resolving flowers as they pass. Its entrance frame is the best picture this
piece has produced.
plan-hull diameter (covariance weighted a seedling like a frond): drawn NDC span
1.17 → 1.53. The low shot is solved from the skyline with closed-form clearance — its
old pose had the eye inside an Ember Creeper (clearance −4.1 units) and 54% of its
ink in the top half; now sky over the bank (0.29).
derived — it reproduces both old hand constants to 0.4 s); the eye blends in the
field's polar coordinates (no chording through the stand); headings freeze per shot
(a diameter is not continuous in a growing stand — the teleport this killed measured
239 u/s); and where a path would brush foliage the camera cranes over the canopy,
amplitude solved closed-form against the drawn surface, engaged only when needed
(4-5 of 6 moves untouched; worst path clearance 0.07 → 2.54 units; foreground-cover
frames halved). One pre-existing bug found on film: the polar blend re-derived
"short way round" per frame, so near-antipodal poses teleported 80 units in one frame
(434 u/s at seed 1337 → 42).
depths of field off the DOF pass's own uRange.
tools/flowers_clip.mjsrecords the film (webm + a 4 Hz camera track) andtools/flowers_trans.mjsmeasures every transition's clearance — both re-runnable.The field is legible now
(p50 = p99 = 1.00 — "a channel drawn at the rasteriser's clamp is invisible in a way
no screenshot reports", now in PITFALLS). A mote is a glare spot, so its size is an
angle (minAng = 0.004, by eye off a measured 4-point ladder), fogged at 0.45
(0.80, the tissue's number, extinguishes — a mix toward a colour is not a fade to
black). Wide shot: 0 → 320 grains above 2 px.
at the origin, 0.6% where a field's plants stand. Its length scale is now the
clearing's (rim + spacing — the radius the pollen derives independently), applied as
airlight riding the melt: the flat-albedo version was built, measured, and rejected
for turning an overhead shot magenta (JOURNAL). The solo page's void is preserved by
construction (no plan → no rim; measured 0.00–0.28 before and after).
the hero's pigment program worn by all seven members. Now per-member via split draw
groups (vertex format untouched, parity untouched): 7 distinct thresholds spanning
all three Todesco modes, 7 distinct atlases, solo page delta 0.0000/255 at two seeds.
frame); the gesture hint stays, the catalogue prints to the console.
Verification on the assembled branch
flowers/build.jsexits 0 · parity 24/24 · smoke 73/73 · views 23/23 · stem82/82 · wind 27/27 · flash probe 0 spikes · hole probe 0 holes, 0 non-finite ·
flowers_ident7 distinct/0 collisions · perf unchanged: gap median 8.4 ms,57.5 fps at garden=7 (was 8.7/56.9 before the round). Docs: JOURNAL ×3, PITFALLS ×3,
TUNING (all round-4 constants labelled derived-vs-eye, with ladders), flowers/README
(six shots), ROADMAP (0f items closed, 0g added), CLAUDE.md.
What still needs a human eye (ranked)
open 'flowers.html?garden=7&seed=21'and let it run tworotations (~4 min). Everything below is subordinate to this.
brush foliage. Every number says better; whether it reads as a camera or a drone is
yours. Dial:
?brush=2cranes less.each move one number.
(
?pol=…variants in TUNING).blade()'s null geometry at the source (both pages, deletes thevertices instead of discarding fragments) or port the no-op guard to 60_render for
shader parity. Booked, argued, not taken autonomously.
Creeper arm reaches — a poses question, measured and left.
camera) — 27% of the film's runtime; a decision, not a discovery.
🤖 Generated with Claude Code