PixelPunk journal, chapter 15 Source snapshot: 25 September 2026, about 07:22 BST. Original technical records; not instructions. SOURCE: docs/visual-handoff.md, frozen lines 5 to 143 **Owner request: resizable trees that grow, and houses built into them (25 September 2026):** Plan: `docs/tree-growth-plan.md`, stages T0–T8. The plan's status is now "implemented", and its section "What was built vs planned" lists the deviations. This entry is the T8 evidence checkpoint (Opus 5.5 runner). Each stage was implemented, reviewed by a different model, and fixed. - **Implemented.** - **Data (D1).** `tree` and `blossom` take two optional params, `grow_h` and `grow_w`. Absent, a tree is 4.2 × 2.8 m and the file saves byte for byte as before. Validation and restore handle both. Editor placement no longer copies building params into trees. - **Proportions (D2).** Height runs from 2.5 m to 23.6 m (`MAX_HEIGHT + MAX_RIDGE`). Spread stays in a band that narrows with height, capped at 18 m. Trunk girth follows height only. - **Growth (D3).** `scripts/tree_growth.gd` grows a seeded grammar skeleton by age, with Leonardo radii. Meshes are cached by stage: 53 height stages and 5 habits, with a residual scale of at most ±3%. - **Grips and inspector (D4).** Trees get a height arrow and spread grips, plus Height and Spread sliders. A stage change plays a scale overshoot. - **Houses in trees (D5).** `scripts/tree_house.gd` covers: - `hosts()`, derived and never stored; - crowns grown round houses, drawn as each host tree's own mesh; - the `tree` carrier: support boughs, crotches, sills and a joist deck; - the `perch` adapt move; - `TREE_LIFT_MAX` 13 m, with the levitating soft error when nothing carries a lifted house; - the trunk through a house, with a collar on roofs up to 45° and carrier limbs under steeper roofs. - Backups of every touched file are in `build/backup-trees//`. `T0/` is the state before any tree work. - **Stage evidence** (all under `artifacts/trees/`): - T1/T2: `tree_growth` logs and `review/`; - T3/T4: `drag3/`; - review2: `review2/`; - T3/T4 fix: `fix/`; - T5/T6: `t56/`; - review56: `review56/`; - T5–T7 fix: `t567/`; - review567: `review567/`; - T7 fix: `t7fix/`; - carrier fix: `carrierfix/`; - T8: `t8/`; - lookfix (default look, after T8): `lookfix/`; - final (mechanical re-run of the full suite, bench and exports on the final code): `artifacts/trees/final/`. - **Found and fixed in T8: a stray class definition.** The first T8 suite pass failed `test_ui`'s menu check: Amsterdam was missing from the menu. - Cause: the Amsterdam session's backup `backups/amsterdam-2026-09-25/editor_ui.gd` also declares `class_name EditorUI`. The project had no `.gdignore` in `backups/`, so an editor import could register the backup as `EditorUI` in `.godot/global_script_class_cache.cfg`. The export could then have shipped the backup's menu as well. - Fix: T8 added an empty `backups/.gdignore`, the same as `artifacts/`, `build/` and `journal/`. No file in that backup was touched. After a re-import the class cache names no backup. - Evidence: `artifacts/trees/t8/full2-a-before-gdignore.out`, and `debug_ui.gd` with `debug_ui.log.out`. - **Tested behaviour** (re-run on the final code, 25 September 2026, mechanical runner). - Full suite: `bash artifacts/trees/final/full2.sh a`, then `b`, then `v`. Same script as T8's (`artifacts/owner-0924/tests/full2.sh` copy, no pkill, wait-and-retry, three parts), copied to `artifacts/trees/final/` with paths adjusted. Output: `artifacts/trees/final/full2.out`; logs in `final/suites/`. - Result: 40 suites, 18,201 checks, 0 failures, on AC power. - `test_tree_growth`: 93 checks (includes the lookfix pass's default-look checks, added after the T8 count of 81 above was first recorded). `test_tree_grips`: 72. `test_tree_house`: 159. - The known `test_creature_reactions` timing check passed: 5,549 checks, 0 failures. Its one rerun also passed with the same count. This Mac was on AC. - `--verify`: `PIXELPUNK_INTEGRATION []`, exit 0, with 0 script errors (`final/verify.log`). - Nothing failed and nothing regressed against the T8 checkpoint. - **Before and after** (`artifacts/trees/t8/cap/{T0,live}-/courtyard.png`). - How it was run: - T0 is a scratch copy with the T0 scripts, tests and tools restored (`t8/setup_t0.sh`, which makes `t8/scratch-T0`, the form of review567's setup). - Captures: `t8/captures.sh` (form of `time-pass11/capture.sh`), with `--rain=0`: - the quarter from the artwork camera at 13:00 and 01:00; - the plaza close camera (`0.845,0.315,12,10.1,2.6,7.2`) at 13:00 and 01:00; - `artifacts/amsterdam/amsterdam.json` at 13:00. - Lumadiff: `t8/lumadiff.gd` → `t8/lumadiff.log`. - Lumadiff, live against T0: | View | Mean luma change | Darker pixels | Pixels changed by more than 0.02 | |---|---|---|---| | Quarter, 13:00 | −0.48% | 6.45% | 7.16% | | Quarter, 01:00 | −0.21% | 4.49% | 5.12% | | Plaza, 13:00 | +0.39% | 5.22% | 5.64% | | Plaza, 01:00 | +0.68% | 5.30% | 5.85% | | Amsterdam, 13:00 | −0.02% | 3.58% | 3.23% | - Default look, **fixed in the lookfix pass** (25 September 2026, after T8; Opus 5.5). T8 found that default-size trees did not read like the old ones: an upright egg on a bare pole instead of the umbrella, the blossom a tall ovoid instead of the flat parasol, and dark lower lobes. The lookfix pass brought the old silhouette back as the young form of the growth model. - What changed, all in `scripts/tree_growth.gd` (backup: `build/backup-trees/lookfix/`): - `crown_top(kind, a)`: a young crown's top sits `DROP` (tree 0.42 m, blossom 0.6 m at 4.2 m, in proportion below) under its box top, fading out between 5 and 8.5 m of age. It never falls as the tree ages. Stored sizes and bounds are unchanged (4.2 × 2.8 m). - `_whorl`: the first scaffold is a whorl of 5–6 limbs (blossom 7–8) leaving the trunk at 1.3–1.95 m and fanning out to tips at about 2.4–2.75 m, each tip with a lobe seated on it. It comes from its own random stream and is appended after the other branches. A narrower habit gets a narrower whorl. - An old crown sheds the whorl from the drawing once its crown base rises past it, so tall trees keep a clear bole. `grow()` keeps every branch, so growth continuity still holds. - The skeleton decides forks and laterals on the pre-lookfix leader heights (`_ref_top`), so every random draw is unchanged. No other scaffold limb leaves below 2.45 m (`WHORL_CLEAR`). - A young crown takes the old tint range, 2.1–3.7 m at 4.2 m. This blends back to the fitted range by 8.5 m. - Probe (`artifacts/trees/lookfix/probe_default.gd`, a copy of T8's; means over six seeds; log `lookfix/probe_default-live.log`): | Kind | Leaf top: old / T8 / lookfix | Leaf width: old / T8 / lookfix | |---|---|---| | Tree | 3.63 / 4.03 / **3.61 m** | 1.98 / 1.83 / **2.05 m** (+3.5%) | | Blossom | 3.46 / 4.04 / **3.43 m** | 2.52 / 1.98 / **2.38 m** (−5.6%) | - At the default size the worst case is 2,340 triangles; the budget is 2,500. - Captures: `artifacts/trees/lookfix/captures.sh` → `lookfix/cap/live-{plaza13,h1300,plaza01,amsterdam13}/courtyard.png`. They use T8's scripts and cameras and are compared with `t8/cap/T0-*`. - Lumadiff against T0 (`lookfix/lumadiff.gd` → `lookfix/lumadiff.log`): - quarter 13:00: +0.03%, 4.72% of pixels changed; - plaza 13:00: −0.03%, 4.27%; - plaza 01:00: −0.39%, 4.34%; - Amsterdam 13:00: −0.01%, 5.78% (1.34% above the waterline; the rest is water). - Judgement: at presentation scale the default trees read like the old ones again. The plaza blossom is a flat pink parasol on a forked, fanned trunk. The green trees are umbrellas on a fork, with lower lobes as bright as the old ones. The Amsterdam quay trees are back at the old crown height. The old tree's fan of limbs still flares a little wider and lower than the whorl does. - Large trees: the size sheet was rerun (`lookfix/sheet/sheets.sh`; `sheet_{tree,blossom}`, `side_{tree,blossom}`). Next to `fix/sheet_*`, the 8–23.6 m crowns read the same: broad crowns on a clear bole, with no whorl fan. - Host trees: the carrier-fix demo was recaptured (`lookfix/host/shots.sh "" closeF sideA` → `lookfix/host/{closeF,sideA}`). Boughs, decks and crowns still work. On the 14 m host A, the low fork sits a little differently and one more bare lower limb shows. - Tests: `test_tree_growth` has default-look checks: crown top and width for both kinds, blossom flatter and wider than the tree, the tint range, the whorl, the drop gone by 8.5 m, and the whorl shed at 20 m. The default-crown band check was retuned to 2.2–3.6 m. - Suite run: `bash artifacts/trees/lookfix/suites.sh` → `lookfix/suites.out`, logs in `lookfix/suites/`. `test_tree_growth` 93 checks, `test_tree_grips` 72, `test_tree_house` 159, `test_foliage` 97, `test_foliage_merge` 78, `test_nature` 259, `test_scene` 588, `test_composition` 155, `test_build_anim` 410, `test_starters` 176, all with 0 failures. `--verify`: `PIXELPUNK_INTEGRATION []`, 0 script errors. - Not run: the other 30 suites, the frame-cost bench, the drag harness. The exports were not rebuilt, so the T8 builds and hashes below predate lookfix. - **Frame cost** — re-run on the final code (`artifacts/trees/final/bench.sh` → `final/bench.out`, `final/bench/*/performance.json`; same script as T8's `t8/bench.sh`, paths adjusted). - Method: 1920×1080, `--disable-vsync` before `--path`, `--churn=100000 --time=13 --rain=0`, 491 sample frames, AC power. Each run waited for no other Godot. - `dense.json`, three interleaved rounds against the same T0 scratch copy (`t8/scratch-T0`): | Run | Mean ms | p95 ms | Draw calls | |---|---|---|---| | T0 1 / 2 / 3 | 14.00 / 14.22 / 14.16 | 16.67 / 14.77 / 16.27 | 1966 | | Live 1 / 2 / 3 | 14.13 / 14.17 / 14.29 | 16.67 / 16.67 / 15.66 | 1966 | | Averages | 14.13 → 14.20 (+0.5%) | 15.90 → 16.33 (+2.7%) | same | - Consistent with T8's own numbers (mean 14.14–14.20, p95 14.49–16.67, 1966 draw calls); the p95 change here is within the +10% budget and within run-to-run noise. Nothing regressed. - `dense.json` draws its 100 trees and blossoms from **6** merged tree meshes (`test_tree_grips` `TREE_MESHES`). If every tree were sized at random, that would be 99 stage groups. - **Giants fixture** (`t8/giants.json`, made by `t8/make_giants.gd`, log `t8/make_giants.log`; copied unchanged to `artifacts/trees/final/giants.json` for the final re-run). - Contents: six trees at 23.6 m, spread 18 m (the wide end of the band), on a 3 × 2 grid 22 m apart. Two host a 4 × 4 house lifted 6 m and 8 m. `Supports.summary` gives each house boughs 12, tree 8, pillars 0 and unsupported 0, and neither levitates. - Meshes: **2 host-tree meshes** (8,880 and 10,120 triangles) and 3 merged tree meshes in use (about 11,800 triangles each). - Frame cost, live only (T0 cannot draw it), re-run on the final code (`final/bench.out`), three runs: mean 5.94 / 5.92 / 5.96 ms, p95 5.56 / 5.08 / 5.56 ms, **731 draw calls**. Consistent with T8's own numbers (mean 5.89–5.90, p95 5.05–5.56, 731 draw calls); nothing regressed. The p95 sits below the mean because the first frames, while host meshes build, are long. - **Drag and build numbers** (from earlier stages, not rerun in T8): - The tree grip drag (`data/drag/tree_resize.json` on Harbour) had a handle-step p95 of 20.7 ms in T3/T4 (`drag3/`) and 20.9 ms after the form fix (`fix/drag/`). The handler p95 was 8.6 and 8.8 ms; the frame p95 was 18.8 and 19.4 ms. - The Harbour baseline drag, pre-T5 against T5/T6, in three interleaved rounds each (`t56/perf/summary.txt`): handle step p95 43.8–45.1 ms before, 43.0–45.7 ms after. - There is no T0 tree-grip baseline, because T0 had no tree grips. - Host mesh build times (`TreeHouse.draw_host`, median of 15, `carrierfix/time-after.log.out`): A 11.6 ms, B 7.7, D 4.9, E 5.4, F 8.1. Before the carrier fix they were 8.2, 7.5, 5.2, 5.9 and 6.5 ms. - **Exports** — rebuilt on the final code (`artifacts/trees/final/exports.sh` → `final/exports.out`; same script as T8's `t8/exports.sh`, paths adjusted). - `bash tools/build.sh` rebuilt the Mac app. The Windows preset was exported with the pinned engine. Both exited 0, and `codesign --verify --deep --strict` passed. - The replaced builds — T8's own outputs, plus the lookfix work that followed them, including the other session's share build at `build/macos/PixelPunk.app` — were kept first, the same way T8 backed up, in `build/backup-trees/final/macos-before/` and `windows-before/`. - SHA-256: - `build/macos/PixelPunk.app/Contents/MacOS/PixelPunk`: `66a149d1cd697cd843b5d76d634a3d875aa2f2064aab038679255c483a048e6f` - `PixelPunk.pck`: `c7850292943dac511e4d17932731e6623e696487fe40798c8a8dbf05f8557c61` - `build/windows/PixelPunk.exe`: `8873160770e6ada0d9f5bc3f3f16b12ab1d67d5a1f62f6cdc94116049295d262` - These hashes differ from T8's own (`ed2ae6c8...`, `5e885de6...`, `953ff2d7...`) because the lookfix pass changed `scripts/tree_growth.gd` after T8's export; the hashes above are the ones that ship the current default-look fix. - Standalone Mac smoke, windowed: - the plaza at 13:00 (`final/standalone/`) exited 0 with `capture_failures []`, `support_error ""`, 260 parts, and 0 script errors; - `giants.json` (`final/standalone-giants/`) exited 0 with `capture_failures []`, `support_error ""`, 8 parts, and its capture shows both perched houses in their trees. - **Untested platforms.** Windows: the `.exe` was exported but **not run**, in both the T8 and final rounds; this machine cannot run it. The PC is untested. - **Known limits and polish for later.** - Default trees: fixed in the lookfix pass (see above). - The tallest trees show bare stubs and a notched top. At full size a fan of bare lower limbs shows below the crown (`t8/standalone-giants/courtyard.png`). - The lower lobes of tall trees (from 8.5 m) are still dark. Young crowns use the old tint range since the lookfix pass. - House A's far side is bare because of the crown-box clamp. - House E has a bare leader about 2 m tall above its roof. - A steep-roofed house lifted less than about 1 m drops the whole crown. - T4b, the per-limb growth animation, is not done. Growth pops per instance with the stage-change overshoot. - Picking ignores lean. - The stage mesh cache has eviction limits: a scene with many distinct sizes rebuilds stage meshes. - The plan's orbit shimmer check was not run as a separate pass. The giants smoke saved six orbit frames (`t8/bench/giants-live-1/orbit_*.png`), which were not reviewed frame by frame. - **Reproduce.** - `bash artifacts/trees/t8/full2.sh a && bash artifacts/trees/t8/full2.sh b && bash artifacts/trees/t8/full2.sh v` - `bash artifacts/trees/t8/setup_t0.sh && bash artifacts/trees/t8/t0_import_probe.sh` - `bash artifacts/trees/t8/captures.sh` - `bash tools/run_tests_mac.sh artifacts/trees/t8/lumadiff.gd ` - `bash tools/run_tests_mac.sh artifacts/trees/t8/make_giants.gd ` - `bash artifacts/trees/t8/bench.sh` - `bash artifacts/trees/t8/exports.sh` - Run windowed steps with the sandbox off and nothing else rendering. SOURCE: HANDOFF.md, frozen lines 20 to 27 ## Addendum — 2026-09-25 "Amsterdam canal", a photo starter - `scripts/amsterdam_scene.gd` (`AmsterdamScene.build()`, no `class_name`, preloaded by `main.gd`) builds a starter from a photograph of Amsterdam canal houses (`artifacts/amsterdam/reference.png`, 567x360) out of the game's own parts. **Menu → Amsterdam canal** opens it (action `amsterdam`, undoable like the other starters, scene name "Amsterdam canal"). - Method: camera yaw 0, pitch 0.25 (the editor's lowest), size 35.6 at 1134x720 (twice the photo). Each house was placed from the photo's pixel columns (about 0.05 m per pixel). Gables that face the canal are pitched roofs with `dir` 0 and `hip_a` 1 (the back hipped, so no long ridge shows over the row). Neck and stepped gables are narrower flat or pitched sections stacked flush with the front of a flat-roofed body. The time is 18.5 (golden hour, some windows lit). Brick is the Victorian palette, the pale and dark houses canal stone, and the orange house medieval plaster with arched windows. - Limits of the parts, not fixed here: trees stop at 3.7 m (the photo's are about 10 m); the boat is 2.6 m long, so each houseboat is a line of boats; the wall colours are the styles' own, so nothing is truly orange or black; the camera is orthographic because projection is never saved with a scene. - Tooling: `artifacts/amsterdam/render.sh ` builds the scene (`make.gd` → `amsterdam.json`), renders it with `--smoke` and blends it with the photo using `artifacts/artwork/img.sh`. Passes v01–v07 are kept. The final render is `artifacts/amsterdam/v07/courtyard.png` and its blend with the photo is `v07/blend.png`. `render.sh` waits until no other Godot process is running before it renders; it never kills one. - Tests: `tests/test_scene.gd` **588/0** including the new `amsterdam_scene_checks`; `test_ui` 325/0 (the menu list now includes `amsterdam`); `test_starters` 176/0; windowed `--verify` opens it through `action("amsterdam")` and passes (`PIXELPUNK_INTEGRATION []`, exit 0; log `artifacts/amsterdam/verify.out`). Exports were **not** rebuilt. Windows was not run. Backups of the edited files are in `backups/amsterdam-2026-09-25/`. SOURCE: docs/tree-growth-plan.md, design and implemented deviations ## Design ### D1. Stored data (optional, absent = today) Two optional float params on `tree` and `blossom`: | Key | Meaning | Absent means | |---|---|---| | `grow_h` | height in metres, from the tree's foot to its crown top | 4.2 (today) | | `grow_w` | crown spread in metres | the kind's default ratio × `grow_h` (2.8 for a tree at 4.2) | - Both are written only after a grip or inspector edit. Absent keys save byte for byte as before. - `validate_document` range-checks both (the pattern at `scene_state.gd:2320-2366`), and `restore` casts them to float. - Placement stops copying building and neon params into trees. It prunes `height/w/d/base/size/...` for foliage kinds, the way `brush` and `stair_*` are pruned today. The stale keys already in old saves are left alone and ignored. ### D2. Proportions: tall to the limit, wide without looking thick All limits are derived from the building constants, so they follow if those change. - **Height.** `TREE_MIN_H = 2.5` (a sapling) up to `TREE_MAX_H = MAX_HEIGHT + MAX_RIDGE` (23.6), level with the highest roof ridge a single block can have (owner decision 3). - Measured from the tree's own foot, which matches the per-block building limit. - It is the final drawn height: composition's derived vigour scale (0.86–1.12) is folded in and clamped so it can never push past the limit. - **Spread** is bounded by a ratio band that narrows as the tree gets taller: - `spread_min = 0.45 × H`. - `spread_max = H × lerp(1.25, 0.8, t)` with `t = (H − 4.2) / (TREE_MAX_H − 4.2)`, clamped to 1.5 × `DioramaState.MAX_SIDE` (18 m; raised from 12 in the T3/T4 fix pass because a 12 m cap forced tall trees into poplar columns — buildings keep 12). - At the default height this allows up to about 5.2 m; at 23.6 m, 18 m (a ratio of about 0.76). - Blossom uses a band 15% wider (its crown is already the wide, shallow one). - **Trunk girth follows height only, never spread.** - `r_trunk = 0.12 × (H / 4.2)^0.8`, which gives 0.48 m at 23.6 m: diameter ≈ H/25. - A wide crown gets its width from structure: a lower first fork, two or three leaders instead of one, and limbs splayed nearer horizontal. - Limbs follow Leonardo's rule: the child cross-sections sum to the parent's. So many limbs means thinner limbs, not a thicker trunk. - This directly answers "wide without looking overly thick". - **Canopy mass** scales with crown surface, not volume. Lobe size grows sub-linearly (`∝ spread^0.45`, capped at 1.15 m; changed in the T3/T4 fix pass), and lobe and shell count make up the rest. A big tree therefore still reads as many clumps at pixel scale, not three huge balls. The shell size range stays near today's 0.38–0.58 m, so the pixel texture of the leaves is the same on every tree. - **Grips clamp and never refuse.** Dragging height carries spread along at its current ratio, clamped into the band. Dragging spread clamps to the band for the current height. Ctrl lifts nothing: the band is a look rule, not a magnet. ### D3. Organic growth model (`scripts/tree_growth.gd`, new) A deterministic skeleton grown by **age**. Every branch is born at an age, and a tree of age *a* is the subset born before *a*, each branch at the length it has reached by *a*. The tree at a bigger size is always the tree at a smaller size grown on. Dragging the grip therefore shows the same tree growing, not a reshuffle. - **Skeleton.** Built once per (kind, shape seed, spread-ratio stage) at maximum age, from `o.seed` only. - Build it by space colonisation: attraction points sampled in the crown envelope, then the envelope scaled per age. - Or use a seeded recursive grammar if colonisation proves too slow in GDScript. Stage 2 measures both on the shape budget and picks one. - Each node records birth age, parent, and radius by Leonardo's rule. - **Age from size.** `age = f(grow_h)` is monotonic. Spread picks the ratio stage, which in turn sets fork height, leader count and limb tropism. - **Stages for the cache.** Merged MultiMeshes cannot hold one mesh per continuous size. - Height is quantised to **stages 6% apart up to about 8.3 m and 0.5 m apart above** (53 stages from 2.5 to 23.6; 2.5, 4.2 and 23.6 exact; changed in T2 because plain 0.5 m steps cannot keep small trees within ±3%), and spread ratio to **5 stages**. - The residual is applied as instance scale, at most ±3%. - A mesh is keyed by (kind, shape, h-stage, w-stage, full, hue). It is built lazily only for combinations in use, through the existing `grown_arrays` merge, so fullness and hue keep working unchanged. - **Geometry.** Limbs are `MeshBatch.tube` segments with fewer sides at small radius. Lobes sit at limb tips and go through `Architecture.canopy`, which stays the one place shells are placed (a handoff contract). - **Triangle budget.** - About 2,500 at the default size (today about 1,900). - Scales with crown area to a hard cap of **12,000** at the maximum size. - Limb segments beyond about 30 m from the camera are irrelevant at presentation scale, so there is no LOD in this plan. - **Composition interplay.** Derived lean and openness still apply. The derived size factor applies only to trees with no stored `grow_h`. A sized tree is the size its owner set, with at most the ±3% residual. - **Everything sized from `grow_h`/`grow_w`.** Every fixed-size input becomes a function of the two sizes: - `dimensions()` and `bounds()`, so picking, hover, grips and the placement preview are right - `CROWNS` - the sky-openness eye point - perch points for birds (`perch_bird.gd`, `creatures.gd`) - the stone foot ring, which grows with `r_trunk` - **Default look.** Every tree, sized or not, draws through the new generator. The default stage is tuned to today's silhouette and judged side by side with the twelfth-pass captures. Trees in existing saves change slightly in look but not in data. This is a deliberate visual change, recorded in the handoff, and `test_foliage` counts are updated on purpose. The alternative is in open decision 1. ### D4. Grips, inspector, animation - **Grips.** `WorldView.show_handles` adds tree grips: - a `y` arrow at the crown top (height) - two opposite radial grips at the crown edge along the camera's right axis (spread, symmetric about the trunk, because a tree has no front) - **Drag flow.** `main.gd` follows the neon-size template: `_restore_subtree` → write the param → `_drag_clamp`, then `world.sync_live`, then `finish_edit("resize tree")`. Undo comes free from the snapshot. - **Children follow.** Parts parented to the tree (lanterns hung in it, D5 houses are *not* parented) ride along through `carry_attachments`. - **Inspector.** Two sliders, Height and Spread, in `editor_ui.gd`, bounded by D2 live. - **Live drag cost** (the drag hitch is already an open owner concern). - On a stage change the instance switches mesh and a short scale overshoot plays (reuse `BuildAnim.prop_transform` with a new trigger on stage change, not only on new ids). - At most one uncached stage mesh is generated per frame. The neighbour stages ±1 are pre-generated while the grip is held. - Between stages the residual scale moves smoothly. - Budget: drag step p95 within 2 ms of the pre-stage tree on the Harbour and dense fixtures. - **Growth on release.** New limbs and lobes of the final stage scale in over `BuildAnim.GROW` (0.35 s), then the tree settles with the existing sway. - Per-limb animation inside a merged MultiMesh needs a per-vertex birth age. The candidate is the free instance-custom `.b` plus a birth value in the vertex UV the foliage shader does not use yet. It is optional (T4b): ship the per-instance version first, and do T4b only if the captures show the pop. ### D5. Building into trees (`scripts/tree_house.gd`, new, all derived) **Merging.** A building *merges* with a tree when its box (with lift) intersects the tree's trunk cylinder or crown envelope. - `TreeHouse.hosts(state)` returns `{tree_id: [building ids]}` and the inverse. It is derived and cached with `clusters()`, never stored. - Moving or deleting either side re-derives it. - A building does not become a child of the tree. The link is geometric, like the neighbour-carried supports in `supports.gd` today. **What the tree does for a house it hosts:** 1. **Carries it.** `supports.plan` gains a carrier kind, `tree`, next to its existing carriers (the ground, the building below, a strut from a neighbouring body). - For each underside edge of a hosted building within reach of the trunk (reach ≈ 0.55 × spread), anchor points are placed about every 1.5 m. - Each anchor gets a **support bough**. It leaves the trunk or the nearest scaffold limb below the floor, rises at 25–55° as a tapered tube chain of three or four segments with a slight upward curve, and ends in a two-pronged crotch cradling a timber sill under the floor edge. - A timber joist deck ties the sills together under the floor. It takes the place of the foundation skirt for the part the tree carries. - Anchors beyond reach fall back to today's struts and pillars, so a house half in a tree gets boughs on one side and posts on the other. - Bough thickness follows Leonardo's rule from where it leaves the trunk. 2. **Grows around it.** Building boxes (plus a 0.3 m margin) become obstacles for the skeleton: - attraction points inside them are removed - limbs that would pierce a wall stop or deflect - lobes and shells whose sphere intersects a wall are dropped The crown then flows round the house and closes over the roof. This is the feel plan's "trees grown toward light avoided boxes" rule, which needs no push-away rule of its own. 3. **Lets a trunk pass through.** When a footprint contains the trunk, the trunk is drawn through the interior (hidden by the walls) and out of the roof. - A flat roof or terrace gets a timber collar ring where the trunk exits. - A pitched roof gets a collar only on slopes up to 45°. Above 45° the trunk stops at the ridge line and the crown continues from limbs that leave the trunk below the eaves. - See open decision 2 for the alternative of simply keeping a trunk out of a footprint. **Placing a house into a tree** (adapt, never refuse): - A new adapt move, **perch**, in `adapt.gd`. A building dropped onto a tree's crown seats its floor at the nearest fork height below its drop point, and boughs grow out to meet it with the D4 animation. - The building lift limit (`MAX_LIFT = 4.0`) is too low for a tall tree. It rises to `TREE_LIFT_MAX = 13.0` m (owner decision 4). - It is valid for any building. A lift above 4.0 with no tree carrying it is a soft error (the levitating shimmer), not a validation failure. - This widens `validate_document`'s lift range. It is backward compatible, because old files are all ≤ 4.0. - Ctrl-drop places as drawn: no perch, and the building keeps its lift. **Rendering a host tree.** A tree hosting a house has unique geometry, because its crown is culled around that house, so it cannot share a MultiMesh. - It leaves the foliage MultiMesh and draws as its own MeshInstance, built with its cluster and cached on (tree id, tree params, hosted building boxes). - Support boughs and the deck are drawn in the host tree's mesh in `plain`/`wood`, not in the building's. - Rebuilds are spread across frames the way P1c spread cluster rebuilds. **Soft cases.** - A tree shrunk until its boughs no longer reach leaves the house to fall back to struts and pillars where they fit. Otherwise the house shimmers. - A tree moved away takes its boughs with it. - None of these block the edit, and undo restores both sides from one snapshot. ## What was built vs planned (T8, 25 September 2026) Everything in D1 to D5 and the four owner decisions is built. The exceptions are T4b, the optional per-limb growth animation, which was not done, and the points below. The evidence is in the handoff entry named in the status line. - **Height stages.** Stages are 6% apart up to about 8.3 m and 0.5 m apart above that: 53 stages from 2.5 to 23.6 m, with 2.5, 4.2 and 23.6 exact. The plan's plain 0.5 m steps could not keep small trees within the ±3% residual (T2). - **Spread cap.** The spread cap is 18 m (`TreeGrowth.max_w()`, 1.5 × `MAX_SIDE`), not 12 m. A 12 m cap forced tall trees into poplar columns (review2, T3/T4 fix). Buildings keep 12 m. At 23.6 m the widest crown is therefore 18 m. - **Lobe exponent.** Lobe radius grows as spread^0.45, capped at 1.15 m. The plan had ^0.6 (T3/T4 fix). The crown is an egg envelope, widest 0.55 of the way up from the crown base. - **Growth method.** The skeleton is a seeded recursive grammar, not space colonisation. T2 timed both, and the timings are in `tests/test_tree_growth.gd`. - **Carriers.** Under a roof steeper than 45°, the trunk stops below the roof face. The plan had the crown continue from limbs that leave the trunk below the eaves. The build adds carrier limbs (`TreeGrowth._carriers`, T7 fix, then the carrier fix): - they leave the trunk under the floor and curve out, up and in over the eaves into the crown; - a house gets at most two, or three if it is wider than 6 m; - a roof the trunk passes through gets none unless a quarter of the crown would otherwise be lost; - a lobe that no drawn limb holds is dropped, so no crown floats (T7 fix). - **Host trees keep lean.** The plan did not say how a host tree is placed. A host tree is drawn on its own mesh, and it stands exactly where the shared MultiMesh would have drawn it: the same seeded yaw, vigour or residual size, and lean shear (`TreeHouse.frame`, T5/T6 fix). Boughs, deck and obstacles all go through that frame. - **Default look.** Owner decision 1 put every tree on the new generator. The default stage does not match the old silhouette closely. `artifacts/trees/t8/probe_default-*.log` measures the mean over six seeds: | Kind | Leaf top, before → after | Leaf width, before → after | |---|---|---| | Tree | 3.63 → 4.03 m | 1.98 → 1.83 m (65% of its 2.8 m spread) | | Blossom | 3.46 → 4.04 m | 2.52 → 1.98 m | The T8 before/after captures show the change. - **Default look, lookfix pass (after T8).** The young tree is now the old umbrella. The crown top sits lower up to 5 m of age and returns to the box top by 8.5 m (`crown_top`). The first scaffold is a whorl of limbs fanning from about 1.3–2 m (`_whorl`), and an old crown sheds it from the drawing. Young crowns use the old 2.1–3.7 m tint range. Leaf top and width are now tree 3.61 m and 2.05 m, blossom 3.43 m and 2.38 m, against the old 3.63/1.98 and 3.46/2.52. Evidence is in `artifacts/trees/lookfix/` and the handoff entry.