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.
This commit is contained in:
1 parent
50e023f15d
commit
d0f94dff4e
1 file changed
+29
-86
@@ -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
|
## 2026-11-09 — quality priority flipped: realism first, performance last
|
||||||
|
|
||||||
- Owner decision: realism leads; performance closes the quality list. Applied in
|
- 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).
|
||||||
`Docs/Architecture/01_introduction_and_goals.md`, `10_quality_requirements.md`,
|
- Ordering used: 1 visual realism, 2 geographic fidelity, 3 walkability/usability, 4 build reproducibility/portability, 5 provenance, 6 performance.
|
||||||
`04_solution_strategy.md`, the combined `architecture.md`, `Docs/release-checks.md`
|
- **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.
|
||||||
(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
|
## 2026-11-09 — 0.1.5 start: retail reference + kit layout maths
|
||||||
|
|
||||||
- Commons holds exactly **one** usable reference for the retail run:
|
- 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.
|
||||||
`Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg` (Gb321, CC BY-SA
|
- **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.
|
||||||
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
|
## 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
|
- 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.
|
||||||
(120 pieces) rather than boolean cuts — the eye reads them in silhouette and
|
- **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.
|
||||||
grazing light, booleans would cost far more for that. Motto band is now a real
|
- 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).
|
||||||
recess: surround 6 cm proud, panel 12 cm behind (was an 8 cm dark stripe).
|
- Lesson to keep: **duplicated placement constants drift silently.** The metric that caught it was the composition correlation, not a visual review.
|
||||||
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
|
## 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
|
- 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.
|
||||||
(363 fps)**, p50 2.73, p95 3.17, p99 3.64, max 4.62 — against `0.1.0`'s 2.42 ms
|
- **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.
|
||||||
(414 fps). The 625 kit actors + 8 generated textures cost about **12 %** and leave
|
- Series committed at `Docs/perf/0.1.5-kit-walk-frametimes.csv`; comparison table in `Docs/release-checks.md`.
|
||||||
~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
|
## 2026-11-09 — 604 unmaterialed actors: material rebuild was delete+recreate
|
||||||
|
|
||||||
- The "blockout cleaning pass" turned out to need **material and placement hygiene,
|
- 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.
|
||||||
not more geometry**. `Tools/ue_audit_scene.py` (new) found **604 of 620 mesh actors
|
- **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.
|
||||||
with no material**: kit pieces and theatre trim all untextured.
|
- **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.
|
||||||
- **Cause:** `ensure_material` in `ue_build_demo_map.py` deleted and recreated each
|
- **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**.
|
||||||
`/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).
|
- 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
|
- 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.
|
||||||
duplicate labels). Packaged QA still PASS; spawn-view reference score 0.72.
|
|
||||||
|
|
||||||
## 2026-11-09 — v0.1.5 released
|
## 2026-11-09 — v0.1.5 released
|
||||||
|
|
||||||
- All actionable `0.1.5` work is done: hero fluting + recessed motto band, kit layout
|
- 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.
|
||||||
for wide/tall buildings, placement single-source-of-truth, 1440p re-measure
|
- 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.
|
||||||
(363 fps), and the material/placement hygiene that the "blockout cleaning" task
|
- 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.
|
||||||
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
|
## 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_*`.
|
- **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.
|
- **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.
|
||||||
Reference in new issue
Block a user