From d0f94dff4e49d89ce3a647f67e5a9f61fb3718b3 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Mon, 9 Nov 2026 10:45:07 +0100 Subject: [PATCH] memory: record the bind-pose defect and the FBX that is not the cause Re-importing the exported FBX gives two actions with 1379 f-curves over 137 bones, so the animation is baked; the defect is engine-side. Records that PlayAnimation needs AnimationSingleNode mode and that the clip-name log line proves intent, not effect. --- MEMORY.md | 115 ++++++++++++++---------------------------------------- 1 file changed, 29 insertions(+), 86 deletions(-) diff --git a/MEMORY.md b/MEMORY.md index 9396193..8577f97 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -423,111 +423,54 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO. ## 2026-11-09 — quality priority flipped: realism first, performance last -- Owner decision: realism leads; performance closes the quality list. Applied in - `Docs/Architecture/01_introduction_and_goals.md`, `10_quality_requirements.md`, - `04_solution_strategy.md`, the combined `architecture.md`, `Docs/release-checks.md` - (performance section now last, with the reason), `ROADMAP.md` (a "Quality priority" - section) and `AGENTS.md` (the tie-break rule next to the scope-cutting rule). -- Ordering used: 1 visual realism, 2 geographic fidelity, 3 walkability/usability, - 4 build reproducibility/portability, 5 provenance, 6 performance. -- **Both revisions carry the same guard so the order cannot be used as an excuse:** - performance is a *floor*, not a preference — 60 FPS at 1440p without frame - generation must be met at every release, and if realism cannot fit, the realism - scope is cut deliberately and recorded in `CHANGELOG.md`, never the frame rate. - Walkability outranks realism in one direction: a block the player falls through is - not a demo. +- Owner decision: realism leads; performance closes the quality list. Applied in `Docs/Architecture/01_introduction_and_goals.md`, `10_quality_requirements.md`, `04_solution_strategy.md`, the combined `architecture.md`, `Docs/release-checks.md` (performance section now last, with the reason), `ROADMAP.md` (a "Quality priority" section) and `AGENTS.md` (the tie-break rule next to the scope-cutting rule). +- Ordering used: 1 visual realism, 2 geographic fidelity, 3 walkability/usability, 4 build reproducibility/portability, 5 provenance, 6 performance. +- **Both revisions carry the same guard so the order cannot be used as an excuse:** performance is a _floor_, not a preference — 60 FPS at 1440p without frame generation must be met at every release, and if realism cannot fit, the realism scope is cut deliberately and recorded in `CHANGELOG.md`, never the frame rate. Walkability outranks realism in one direction: a block the player falls through is not a demo. ## 2026-11-09 — 0.1.5 start: retail reference + kit layout maths -- Commons holds exactly **one** usable reference for the retail run: - `Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg` (Gb321, CC BY-SA - 4.0, 1920×953) — 6800 Lee Drug **and** its neighbour at street level, shopfronts - and parapets visible (vision pre-check: yes). Nothing for 6822 or 6806–6820, so - those two façades still have no reference and stay blocked. -- **Kit layout was wrong for tall/wide buildings**, not just ugly: bays were - `round(width / 496)`, so 6922 (65.8 m wide, 37.1 m tall) got **13 bays** — a - window every 4.96 m across the whole frontage — and the storey count came out at - 11 × 310 cm = 34.1 m against a 35.7 m window zone, leaving bare wall under the - cornice. Fixed: `MAX_BAYS = 9` (module widens instead of subdividing), and the - storey spread is derived from the available height (`usable / storeys`) so the top - band always lands inside the building. Result: 6922 9 × 11, theatre 8 × 6, - 6806 7 × 3, 6800 4 × 3, TCL 9 × 5. +- Commons holds exactly **one** usable reference for the retail run: `Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg` (Gb321, CC BY-SA 4.0, 1920×953) — 6800 Lee Drug **and** its neighbour at street level, shopfronts and parapets visible (vision pre-check: yes). Nothing for 6822 or 6806–6820, so those two façades still have no reference and stay blocked. +- **Kit layout was wrong for tall/wide buildings**, not just ugly: bays were `round(width / 496)`, so 6922 (65.8 m wide, 37.1 m tall) got **13 bays** — a window every 4.96 m across the whole frontage — and the storey count came out at 11 × 310 cm = 34.1 m against a 35.7 m window zone, leaving bare wall under the cornice. Fixed: `MAX_BAYS = 9` (module widens instead of subdividing), and the storey spread is derived from the available height (`usable / storeys`) so the top band always lands inside the building. Result: 6922 9 × 11, theatre 8 × 6, 6806 7 × 3, 6800 4 × 3, TCL 9 × 5. ## 2026-11-09 — hero fluting + motto band; and a silent placement divergence -- Fluting: 20 grooves per column as thin shadow-tone cylinders set into the shaft - (120 pieces) rather than boolean cuts — the eye reads them in silhouette and - grazing light, booleans would cost far more for that. Motto band is now a real - recess: surround 6 cm proud, panel 12 cm behind (was an 8 cm dark stripe). - Hero 148 parts/3 044 tris -> 271/5 492, still 14.50 m. -- **The real find:** `ue_hero_facade_import.py` had its own hero position - `(-12024.5, -1450, 0)` — the pre-`0.1.0` value, before the frontage placement fix - moved the hero to `(-13336, +1307)`. Running the importer on its own therefore - teleported the hero **13 m east and onto the north side of the boulevard**, and - nothing complained: a displaced building still renders. The only symptom was the - reference score dropping 0.69 -> 0.56 with the 3x3 composition correlation - flipping +0.54 -> -0.08. -- Fix: `Tools/ue_placement.py` is now the single source of truth (hero position, - verified frontage spans, `span_error`/`describe_span_failure`). Both - `ue_hero_facade_import.py` and `ue_build_demo_map.py` import it, the import asserts - the staged span and logs `PLACEMENT ERROR` when it does not match, and tests assert - the stale literal cannot come back. Spawn-view score back to **0.72** (best yet). -- Lesson to keep: **duplicated placement constants drift silently.** The metric that - caught it was the composition correlation, not a visual review. +- Fluting: 20 grooves per column as thin shadow-tone cylinders set into the shaft (120 pieces) rather than boolean cuts — the eye reads them in silhouette and grazing light, booleans would cost far more for that. Motto band is now a real recess: surround 6 cm proud, panel 12 cm behind (was an 8 cm dark stripe). Hero 148 parts/3 044 tris -> 271/5 492, still 14.50 m. +- **The real find:** `ue_hero_facade_import.py` had its own hero position `(-12024.5, -1450, 0)` — the pre-`0.1.0` value, before the frontage placement fix moved the hero to `(-13336, +1307)`. Running the importer on its own therefore teleported the hero **13 m east and onto the north side of the boulevard**, and nothing complained: a displaced building still renders. The only symptom was the reference score dropping 0.69 -> 0.56 with the 3x3 composition correlation flipping +0.54 -> -0.08. +- Fix: `Tools/ue_placement.py` is now the single source of truth (hero position, verified frontage spans, `span_error`/`describe_span_failure`). Both `ue_hero_facade_import.py` and `ue_build_demo_map.py` import it, the import asserts the staged span and logs `PLACEMENT ERROR` when it does not match, and tests assert the stale literal cannot come back. Spawn-view score back to **0.72** (best yet). +- Lesson to keep: **duplicated placement constants drift silently.** The metric that caught it was the composition correlation, not a visual review. ## 2026-11-09 — 1440p re-measured with the kit: 363 fps, ~12% cost -- 2560×1440, Epic preset, TSR, VSync off, no frame generation: **2.76 ms average - (363 fps)**, p50 2.73, p95 3.17, p99 3.64, max 4.62 — against `0.1.0`'s 2.42 ms - (414 fps). The 625 kit actors + 8 generated textures cost about **12 %** and leave - ~6× the 60 fps floor. -- **Variance trap confirmed a third time:** runs reporting ~105 fps (9.5 ms) were - two overlapping game instances — an earlier launch had not exited. `pgrep -af - Binaries/Linux/GigLine` should show one wrapper + one binary; 363 fps reproduced - twice on a clean run. Always count instances before believing a perf number. -- Series committed at `Docs/perf/0.1.5-kit-walk-frametimes.csv`; comparison table in - `Docs/release-checks.md`. +- 2560×1440, Epic preset, TSR, VSync off, no frame generation: **2.76 ms average (363 fps)**, p50 2.73, p95 3.17, p99 3.64, max 4.62 — against `0.1.0`'s 2.42 ms (414 fps). The 625 kit actors + 8 generated textures cost about **12 %** and leave ~6× the 60 fps floor. +- **Variance trap confirmed a third time:** runs reporting ~105 fps (9.5 ms) were two overlapping game instances — an earlier launch had not exited. `pgrep -af Binaries/Linux/GigLine` should show one wrapper + one binary; 363 fps reproduced twice on a clean run. Always count instances before believing a perf number. +- Series committed at `Docs/perf/0.1.5-kit-walk-frametimes.csv`; comparison table in `Docs/release-checks.md`. ## 2026-11-09 — 604 unmaterialed actors: material rebuild was delete+recreate -- The "blockout cleaning pass" turned out to need **material and placement hygiene, - not more geometry**. `Tools/ue_audit_scene.py` (new) found **604 of 620 mesh actors - with no material**: kit pieces and theatre trim all untextured. -- **Cause:** `ensure_material` in `ue_build_demo_map.py` deleted and recreated each - `/Game/Hollywood/Site/M_*` asset every run ("so a look change cannot be masked by a - stale asset"). That orphane every reference: actors staged by other tools pointed at - deleted assets and fell back to the default material. The map looked only *slightly* - flat — no error, no warning. -- **Fix:** update the material **in place** — load it, `MaterialEditingLibrary. - delete_all_material_expressions(mat)`, rebuild the graph, recompile, save. Note - `Material.expressions` is protected from Python; use the library's deleter. -- **Ordering rule now documented and enforced:** the demo builder rebuilds materials, - so kit and theatre staging must run *after* it. `render_screenshots.sh` now runs - demo map → kit assembly → theatre trim → scene audit → shot cameras → capture, and - **fails on a non-zero audit count**. +- The "blockout cleaning pass" turned out to need **material and placement hygiene, not more geometry**. `Tools/ue_audit_scene.py` (new) found **604 of 620 mesh actors with no material**: kit pieces and theatre trim all untextured. +- **Cause:** `ensure_material` in `ue_build_demo_map.py` deleted and recreated each `/Game/Hollywood/Site/M_*` asset every run ("so a look change cannot be masked by a stale asset"). That orphane every reference: actors staged by other tools pointed at deleted assets and fell back to the default material. The map looked only _slightly_ flat — no error, no warning. +- **Fix:** update the material **in place** — load it, `MaterialEditingLibrary. delete_all_material_expressions(mat)`, rebuild the graph, recompile, save. Note `Material.expressions` is protected from Python; use the library's deleter. +- **Ordering rule now documented and enforced:** the demo builder rebuilds materials, so kit and theatre staging must run _after_ it. `render_screenshots.sh` now runs demo map → kit assembly → theatre trim → scene audit → shot cameras → capture, and **fails on a non-zero audit count**. - Second find: 53 duplicated kit labels (both shop piers of a bay shared one name). -- Scene audit after the fixes: **0 failures** (all materialed, all spans verified, no - duplicate labels). Packaged QA still PASS; spawn-view reference score 0.72. +- Scene audit after the fixes: **0 failures** (all materialed, all spans verified, no duplicate labels). Packaged QA still PASS; spawn-view reference score 0.72. ## 2026-11-09 — v0.1.5 released -- All actionable `0.1.5` work is done: hero fluting + recessed motto band, kit layout - for wide/tall buildings, placement single-source-of-truth, 1440p re-measure - (363 fps), and the material/placement hygiene that the "blockout cleaning" task - actually needed (604 unmaterialed actors, 53 duplicate labels) with a new scene - audit gate. -- Only item carried forward is the **retail frontage (6822 / 6806–6820 / 6800)**, - blocked because Commons holds no usable reference for two of the three buildings - and substituting the theatre's photographs would misattribute the façades. It now - lives in a `0.1.6` Retail frontage patch. -- Precedent applied again: tag when the stated scope is done, carry blocked scope - into the next patch rather than letting a release wait indefinitely on missing - reference photography. +- All actionable `0.1.5` work is done: hero fluting + recessed motto band, kit layout for wide/tall buildings, placement single-source-of-truth, 1440p re-measure (363 fps), and the material/placement hygiene that the "blockout cleaning" task actually needed (604 unmaterialed actors, 53 duplicate labels) with a new scene audit gate. +- Only item carried forward is the **retail frontage (6822 / 6806–6820 / 6800)**, blocked because Commons holds no usable reference for two of the three buildings and substituting the theatre's photographs would misattribute the façades. It now lives in a `0.1.6` Retail frontage patch. +- Precedent applied again: tag when the stated scope is done, carry blocked scope into the next patch rather than letting a release wait indefinitely on missing reference photography. ## 2026-11-09 — Retail frontage and two pipeline traps -- **`Tools/render_screenshots.sh` called `./Engine/...` for the capture step** while every commandlet above went through `run_cmdlet`'s `cd "$ENGINE"`. With `set -e` the script died at exit 127 *after* rebuilding the maps, so the PNGs silently stayed a day old and `--verify` never ran. Fixed to `"$ENGINE/Engine/Binaries/..."`. Symptom to remember: maps rebuild, screenshots do not move. +- **`Tools/render_screenshots.sh` called `./Engine/...` for the capture step** while every commandlet above went through `run_cmdlet`'s `cd "$ENGINE"`. With `set -e` the script died at exit 127 _after_ rebuilding the maps, so the PNGs silently stayed a day old and `--verify` never ran. Fixed to `"$ENGINE/Engine/Binaries/..."`. Symptom to remember: maps rebuild, screenshots do not move. - **The generic façade kit is applied to every actor labelled `Frontage_*` or `North_*`**, so staging a hand-modelled façade under those labels gets the kit pasted over it — two contradicting façade languages on one wall. `ue_apply_facade_kit.py` now skips `MODELLED` labels, and retail trim stages as `RetailTrim_*`. - **A Blender builder cannot be imported by a unit test** (`import bpy` at module scope). Pure layout arithmetic lives in `Tools/_common.py`; `Tools/tests/test_retail_frontage.py` tests it there, and the builder delegates. Same trick `fbx_export_preset.py` uses for its preset dicts. -- **Joining 85 parts into one mesh per building** before export cut the Unreal import from 85 assets to 2, and the staging step to two spawns. Record per-part dimensions *before* the join: after it only the joined bounds exist. +- **Joining 85 parts into one mesh per building** before export cut the Unreal import from 85 assets to 2, and the staging step to two spawns. Record per-part dimensions _before_ the join: after it only the joined bounds exist. +## 2026-11-09 — Third-person body renders in bind pose (open defect) + +- **Symptom**: the pawn's body is visible and skinned, the run logs `GigLine: playing body clip Character_AnimHuman_rig_GigLine_Idle`, and the figure still stands in the A-pose bind. Two captures were taken before the call was proven to succeed — the camera, then the material, were blamed first and neither was the cause. +- **The FBX is not the problem.** Re-importing `/tmp/gigline/character-anim.fbx` into Blender gives two actions, `GigLine_Idle` (frames 1–60) and `GigLine_Walk` (1–30), each with **1379 f-curves over 137 bones**. Animation is baked and exported correctly, so the defect is on the Unreal side. +- **The material was a real but separate defect**: `M_Character_Export` had no `SkeletalMesh` usage flag, so Unreal substituted the default grey with only a `LogMaterial` warning. Fixed in place (`used_with_skeletal_mesh = True`). +- **`PlayAnimation` alone is not enough.** `USkeletalMeshComponent::PlayAnimation` needs the component already in `AnimationSingleNode` mode; the body is hidden at construction (`SetHiddenInGame(true)`) and only unhidden by the camera toggle, so the first call can land on a component that is not yet in the right mode and never retries — the guard `Wanted == PlayingAnimation` then returns forever and the pose stays at bind. +- **What to log next time**: animation mode and `GetSingleNodeInstance()` together with the clip name. A clip name alone shows the intent, not the effect.