feat(hero): facade rework — inscription, medallions, motto band, banner lettering #39
@@ -607,3 +607,57 @@ Road_Hollywood_07 158 (was 366)
|
||||
- **Layout now** — shared, one level down and flat so it is discovered: `~/.pi/agent/skills/<93 packs>/`, `~/.pi/agent/skills/references/`, `~/.pi/agent/skills/LICENSE`. Project-specific and versioned: `.agents/skills/blender-skills/blender-director/`, whose `../references/` links were rewritten to `~/.pi/agent/skills/references/…` because it no longer sits beside them. The 94 `~/.agents/skills` symlinks are gone.
|
||||
- **Verified** by walking the three discovery roots (`~/.pi/agent/skills`, `~/.agents/skills`, project `.agents/skills`, dot-dirs skipped), parsing every frontmatter: **97 skills loaded, 0 malformed, 0 collisions**. Backups of the old trees: `/tmp/agent-skills-backup.tgz`, `/tmp/pi-blender-skills-dupe` (now consumed).
|
||||
- Rule: a skill exists in **one** place. Two roots with the same `name` is a warning on every start, and the loser is invisible — check the startup list, not the file tree.
|
||||
|
||||
## 2026-11-10 — Tools restructured, and the four traps it set
|
||||
|
||||
The sixty scripts had grown into one flat directory; they are now grouped by what they talk to (and therefore by sandbox): `Tools/unreal/` (commandlets, both map builders, `ue_common.py`, `ue_placement.py`), `Tools/blender/` (builders, exporters, FBX preset, MPFB pipeline, `blender_screenshots.py`), `Tools/review/` (render/judge/compare/e2e/`visual_targets.json`), `Tools/gen/` (ComfyUI driver, procedural textures, reference fetching), `Tools/input/` (OS input injection). Root keeps `_common.py`, `check-lfs.sh`, `addon_compat_probe.py`, `sibling_asset_audit.py`, `tests/`.
|
||||
|
||||
Four things broke, each with the same shape — a path that used to resolve and now does not:
|
||||
|
||||
- **Sibling imports.** `import ue_placement` / `import ue_common` failed inside every Unreal commandlet (`ModuleNotFoundError`), because the rewrite put only the tools root on `sys.path` while those modules are the script's _siblings_. Both paths are inserted: `sys.path.insert(0, dirname(_HERE))` then `sys.path.insert(0, _HERE)`.
|
||||
- **Shell scripts resolve `REPO` relative to themselves.** `Tools/review/render_screenshots.sh` computed the root as one level up, so every commandlet path became `Tools/Tools/...` and the capture step died in **0 s**. Any script whose own depth changes needs its `REPO="$(cd "$(dirname ...)/.." && pwd)"` re-checked.
|
||||
- **Stale result files pass as success.** The e2e driver accepted `/tmp/gigline/demo-map.txt` left by an earlier run, so a **10-second "demo build"** reported PASS without building. Result files must be unlinked before a step runs, and child output must stream to a per-step log file — a captured pipe hides fifteen minutes of a Unreal build and deadlocks if the engine outlives it.
|
||||
- **Two editors hang, they do not fail.** Unreal does not refuse a second instance on the same project; it waits. The driver now refuses to start while any `UnrealEditor-Cmd` is alive. `pkill -9 -f <name>` in the same shell also matches the shell's own command line — kill in a separate invocation, or by PID.
|
||||
|
||||
## 2026-11-10 — `LevelEditorSubsystem.new_level()` cannot replace a map in one session
|
||||
|
||||
Measured on a throwaway path:
|
||||
|
||||
```
|
||||
new_level fresh -> True
|
||||
delete_asset -> True
|
||||
disk file still present -> True (delete_asset leaves the .umap)
|
||||
new_level again -> False
|
||||
```
|
||||
|
||||
So "delete the map and recreate it" does not work in a commandlet: the package stays in memory and `new_level` refuses the path even after removing the file from disk. What works — and is what the builders actually need — is load, destroy **every** actor, save, and verify the count is 0. That is what `Tools/unreal/ue_recreate_maps.py` does now.
|
||||
|
||||
The related lesson: a prefix-based clear (`clear(("Demo_", "PlayerStart", ...))`) only removes what the builder knows about. Stale actors from experiments survive every rebuild — which is how a stale road mesh and a duplicated vehicle actor were both saved back into the map. Clearing the whole level turned the old **600-actor** map into a **409-actor** one that audits clean (`0 failure(s)`, no duplicate labels): 72 site meshes, 310 facade-kit pieces, six frontage buildings, lights, cameras, player start. **A falling actor count was the fix, not a regression.**
|
||||
|
||||
## 2026-11-10 — Every image is judged by two models; the disagreement is the signal
|
||||
|
||||
`Tools/review/judge_screenshot.py` runs `openai/gpt-4o-mini` and `qwen/qwen3.7-flash` by default and reconciles them: agreement carries the verdict with the mean confidence, disagreement reports `uncertain` and keeps both reasons rather than letting the more confident model win. `--model` forces a single judgement.
|
||||
|
||||
- The second model had to be replaced once already: `anthropic/claude-3.5-haiku` returned **404, no longer served by any provider**. Take the model list from `https://openrouter.ai/api/v1/models` rather than trusting a remembered id.
|
||||
- On this content the judge is **consistently pessimistic**: it reports `0/11` across review shots the owner has approved, and it has been wrong in both directions (missed a real float, disputed correct frames). Treat it as an advisory pre-check, and treat a _model disagreement_ as the thing worth reading.
|
||||
- `Tools/gen/comfyui_generate.py` applies the same rule to generation: two checkpoints by default, same prompt and seed, scored by mean absolute Laplacian (catches blurry/flat output), winner takes `--out`, loser is kept beside it. The score ranks candidates; only the judge understands content.
|
||||
|
||||
## 2026-11-10 — Local ComfyUI, and the workflows worth using
|
||||
|
||||
- The server at `http://127.0.0.1:8188` is a **Stability Matrix** install and nothing in this repository starts it. If a tool cannot connect, start Stability Matrix, launch ComfyUI, re-run. The install documents 141 GB of models in `/home/zwitschi/StabilityMatrix/Data/README.md`.
|
||||
- The documented texture graphs are ComfyUI **UI format**, which `/prompt` cannot accept; a tool must rebuild the equivalent API graph. `Tools/gen/comfyui_generate.py texture` does that with their exact settings: `albedobase` (30 steps, CFG 6, dpmpp_2m/karras), `krea2` (8 steps, CFG 1, euler/simple, `ConditioningZeroOut`, texture-fix VAE), `qwen21` (25 steps, CFG 1, `TextEncodeQwenImage21`, texture-fix VAE).
|
||||
- Two limits the graphs carry and a tool must not hide: they produce **albedo only** (normals/roughness come from a material tool downstream), and ComfyUI 0.44 core has **no seamless sampler**, so "tileable" in a prompt is a hint — offset the image 50 % and check the seam.
|
||||
- Licences differ per workflow and matter before shipping: AlbedoBase allows commercial use with credit, Krea-2 has a community licence, **Qwen-Image 2.1 is research-only**. `THIRD_PARTY_NOTICES.md` carries the row.
|
||||
|
||||
## 2026-11-10 — Hero facade: what actually blocked it
|
||||
|
||||
- **Detail modelled inside the wall.** Every applied part sat behind the mass's front face (`Hero_Banner_1` at Y −1.02 against a wall at −0.50), so nothing rendered but the columns and cornice. `seat_on_front()` now states the plane once (`FRONT_Y`) and seats each part at `FRONT_Y + offset`; it reports every part it moves.
|
||||
- **The hero's materials are not the kit's.** `M_FacadeStone` in `/Game/Hollywood/Site` belongs to the facade _kit_; the hero uses `/Game/Hollywood/ElCapitan/M_Facade_*`, created by the FBX import. Warming the Site material changed nothing for three rebuilds.
|
||||
- **Unreal keeps a material asset on re-import**, so a colour change in the Blender builder never arrived. Deleting the materials before the import broke the mesh's slots (one colour, no banners, no windows). What works: import normally, then **author one plain material per slot** with the reference colour and assign it.
|
||||
- **Save the material itself.** Saving only the mesh left the new materials unsaved; after a reload the slots pointed at nothing and the whole elevation fell back to one default colour.
|
||||
- `unreal.Paths.project_dir()` is `Game/GigLine`, **not** the repository root — textures under `Art/` must be resolved from the script's own location.
|
||||
- Still open: the generated ashlar albedo is imported and the material contains `TextureSampleParameter2D` → `Multiply` → Base Color with UV tiling, but the render still shows the flat tint. Next probe is the compiled Base Color pin, not the node list.
|
||||
|
||||
## 2026-11-10 — Render-to-import chain, in one order
|
||||
|
||||
A visual change needs **build → import → render**, never just a render: `render_screenshots.sh` renders the imported Unreal assets. `Tools/review/e2e_asset_check.py` runs the whole thing (Blender renders → empty maps → both builds → captures → two-model judge → README) and is the command to use instead of remembering four.
|
||||
Reference in new issue
Block a user