Box.Center was -Extent.Z*0.25 (below the root bone); the box then sat under the ground
and the suspension pushed the chassis up. Raising it to +Extent.Z*0.35 cuts the float
from ~33 m to 7.8 m, confirming the direction. Still not settled - the box centre and/or
the root bone (grounded at Z 0 by normalise_scale) need to move to the car's centre of
mass.
vehicle_from_template.py now authors the physics mesh from the real hot hatch: apply
the mirror/nodes modifiers, derive the 4 wheel centres, add a root + 4 wheel bones,
weight and export vehicle_skeletal.fbx. A rotation orients the car so it imports
unrolled (length +X, width +Y, height +Z); wheel bones land at front +X, rear -X,
left/right +/-Y. The vehicle still floats (rises to 33 m) - recorded in MEMORY.
SM_Vehicle_Body (hot hatch) is 150x400x122 (length along Y); SK_Vehicle_Sedan is
476x228x144 (length along X). They are built by different scripts and do not share
a forward axis - the likely real cause of the reverse drive. MaxTorque sign was
also a no-op. Next: reconcile the two meshes into one asset with one forward axis.
The axis_forward:X / Blender-Z axle fix changed the reference pose but not the drive
(-2737 cm/s), and axis_forward:X also alters the shared character preset. Revert it.
The Chaos solver drives the wheel about the vehicle's Y (not the bone), and the
drive force is positive yet the car moves backward - the inversion is in the engine
torque / drive-force sign. Next: GetEngineTorque() sign or a SetupVehicle override.
Wheel axles were vertical in Unreal (the FBX armature conversion rolled them 90 deg
about X). Fix: axis_forward:X in the character preset and a Blender-Z wheel tail so the
axle lands on Unreal Y. This is correct (reference pose FL roll 180 / FR roll 0) but
does not fix the reverse: -drivetest still -2737 cm/s, and FinalRatio sign is a no-op.
The remaining cause is the Chaos wheel spin-axis convention or engine torque sign.
The export rotates the armature -90 deg about X (wheel axle on Z) while leaving the
mesh unrotated. Head/tail, axis_forward:X, and armature pre-roll all failed to change
the imported bones. Next: post-import skeleton fix or bake_space_transform.
Bone reference poses show root roll 90 and wheel bones rolled +/-90, with wheel
centres at Z +/-0.81 instead of Y. The mesh vertices are unrotated, so the FBX
axis_forward:-Y conversion rotates the armature but not the geometry, leaving the
wheel axle on a rotated axis. Fix is in the bone authoring, verified by the
reference-pose probe.
-reversetest holds negative throttle; forward throttle drives -224.9 km/h and reverse
throttle -99.5 km/h, both backward, so the throttle sign is not the cause - the wheel
spin is inverted relative to the engine torque. Mesh orientation is fine (nose +X).
Next: check the imported wheel-bone reference pose (axle may land on X/Z, not Y).
- TODO.md: drop the 200+ lines of vehicle-investigation prose (evidence, ruled-out
list, bisect results) that MEMORY.md already carries, and point the intro at
MEMORY.md for observations. Mark the three Chaos-rig tasks resolved (plugin
enablement -> 4 wheels; PhysicsType=Simulated -> simulates/settles/drives).
- AGENTS.md: note that observations belong in MEMORY.md, not TODO.md.
Flipping the wheel-bone axles inward->outward and re-importing left the drive
direction unchanged (-2788 vs -2680 cm/s). Reverted. The next probe is
SetThrottleInput(-1) to split throttle-inverted from wheel-spin-inverted.
The physics asset body was missing PhysicsType. BuildPhysicsAsset created the
USkeletalBodySetup with a box but never set Body->PhysicsType, leaving it
PhysType_Default ('follow owner'). The base AWheeledVehiclePawn sets
Mesh->BodyInstance.bSimulatePhysics=false (the Chaos async solver drives it), so
'follow owner' meant the chassis never simulated - the vehicle sat frozen at
spawn (simulating=0) and -drivetest reported speed=0 travelled=0.
Set Body->PhysicsType = EPhysicsType::PhysType_Simulated. Result: simulating=1,
settled at Z 77 cm, and the drive test moves the car (36.3 m). It drives backwards
(-96.5 km/h) - the next bug is the reverse direction, not the physics setup.
The 'To update' note only covered the Blender packs. Add the Unreal Engine and
C++ sources, each with its git source and the commit to record in
skills.manifest.json, and note that the manifest is the install record to keep in
step with the table.
The owner added /.agents/skills/ to .gitignore, so no skill is in the repository:
the UE packs, the cpp router and blender-director are all local-only. Reconcile the
docs that still said 'in the repository':
- AGENTS.md: blender-director and the UE packs now say gitignored, not committed.
- Docs/toolchain.md: drop the stale 'in this repository' row and name the UE and C++
sources in the table.
- MEMORY.md: record the decision.
AGENTS.md only described the Blender packs. Add the two skill sets that shipped
with the project since:
- 31 Unreal Engine packs (MIT, quodsoler/unreal-engine-skills f3742d7) at
.agents/skills/ue-*/, targeting UE 5.8 C++, with ue-project-context as the entry
point and a discipline mapping to this project's work.
- The cpp router (MIT, DiamonDinoia/skill-cpp f2617fb) at .agents/skills/cpp/, for
general C++11-23 against the Core Guidelines; the project is C++20.
The transient settle at Z 78 was the first physics-asset recreation; repeating the
delete + rebuild + assign cycle flipped the pawn to simulating=0 and stuck at spawn,
with the same box centre -18 that had settled. The delete+recreate is the failure,
not the box: delete_asset does not fully release the package, so NewObject reuses
stale state and the rebuilt body stops simulating.
- Plugin enablement (5e328f5) stays the solid fix (4 wheels).
- The physics asset needs a proper fix: set PhysicsType=PhysType_Simulated and update
in place, not delete+recreate.
- ue_vehicle_import.py SOURCE is stale (/tmp/gigline/hatch vs /tmp/gigline/vehicle for
the skeletal FBX), so the wired regeneration is never reached.
- Driving is still untested.
The Chaos vehicle never existed: ChaosVehiclesPlugin is EnabledByDefault=false and
was absent from GigLine.uproject, so FChaosVehicleManager was null and
OnCreatePhysicsState skipped CreateVehicle() (read from the engine source). The
pawn stayed at setups=4 wheels=0 output=null and the mesh fell back to the regular
rigid-body solver, which is what teleported upward.
- Enable ChaosVehiclesPlugin in GigLine.uproject. CreateVehicle() now runs:
'wheels 4', 'output present', 'setups=4 wheels=4'.
- Drop the constructor's SetSimulatePhysics(true) / collision-profile override: the
Chaos movement component drives the mesh through async physics, and forcing the
regular solver kept the body out of the vehicle solver.
The plugin fix turns the teleport into a stable 'float at Z 545' (540 cm above the
floor) in both probe maps - suspension tuning is the next bug.
Rebuilt and re-ran the vehicle probe workflow:
- Control (no terrain) falls and settles (300 -> 78 cm).
- Terrain rises (313 -> 545 cm) with body velocity pinned to 0 and zero overlaps.
- Turning physics OFF at 3 s stops the rise, so the Chaos vehicle solver is
teleporting the body (transform set directly, no velocity) - not an external
SetActorLocation.
- 0 wheels in both runs, so the missing wheels are not the trigger; the terrain is
what flips fall to rise.
Ruled out the asset pipeline with commandlets: the FBX, the imported
SK_Vehicle_Sedan (476x228x144 cm), the physics asset (452x205x40) and Terrain_DEM
(380x210 m) are all correct; the logged '1 cm bounds' is a stale component read,
not a bad mesh.
Next: bisect step 2 - a stock AWheeledVehiclePawn beside AGigLineVehicle in the
terrain probe map.
The only main-side change was the skills documentation (266fc1b), which already
exists on this branch as 19ed4fe with the same content. MEMORY.md conflicted
because the two skills commits were appended to different bases; the branch's
version is a strict superset (it also carries the vehicle/site/tools/hero entries),
so it is kept.
Banner lettering (JIMMY KIMMEL LIVE): new Tools/gen/generate_banner_texture.py
draws the exact title with Pillow + Liberation Sans Bold - an image model would
mangle a trademark string, and Qwen-Image 2.1 is research-only. Gold JIMMY /
KIMMEL / LIVE stacked on a crimson ground. ue_hero_facade_import.py generalises
import_albedo to import_texture and wires the banner into M_Facade_Banner_Ref as
Base Color (tiling 1.0), replacing the flat dark tint with a brighter red per the
owner's 'less dark, more bright'.
Re-rendered all three ReviewMap staging shots (front, oblique, portals) and
updated their Screenshots/README.md status rows.
The hero facade rework is now closed at 'surface detail that reads at 45 m'. The
remaining elevation work - volute capital depth, lattice grille depth, and the
full realism pass - is deferred to 0.9.0 per the owner, and the 0.9.0 hero item
records the hand-off.
The motto band and moulded surround had the same burial as the medallions: offset
0.06 left their front face at Y -0.44, inside the 1.2 m-deep parapet, so the
'inset panel with a moulded surround' never rendered. Only the inscription (moved
to 0.40 in the previous commit) read.
- Band recessed at offset 0.34 (Y -0.16), surround proud at 0.43 (Y -0.07),
inscription stays 0.40 (Y -0.10).
- Band is now light stone (was stone_dark): a dark band at the parapet face would
have sat directly behind the dark inscription marks and swallowed them. The
reference is dark incised lettering on a pale band.
- Removed the dead duplicate 'Hero_MottoBand 0.40' entry - the earlier 0.06 match
won, so the 0.40 was silently ignored.
Rendered and judged; the advisory judge no longer flags the band.
The medallions were not just flat - they were buried. seat_on_front put
Hero_Medallion at offset 0.08 (front Y -0.42) while the 1.2 m-deep parapet stands
at Y -0.15, so both discs sat inside the parapet and never rendered. The motto
band and surround have the same burial (offset 0.06) and are a separate follow-up.
- Three stepped layers per medallion with per-layer prefixes so seat_on_front gives
each its own stand-off: Rim (0.55 m, dark, offset 0.36 -> Y -0.14), Field
(0.38 m, light, offset 0.43 -> Y -0.07), Boss (0.15 m, dark, offset 0.50 -> Y 0.00).
A single Hero_Medallion prefix would both flatten the relief and (via startswith)
swallow the specific prefixes, so it is removed.
- Moved from +/-6.2 (inside the inscription text, which spans +/-6.87) to +/-9.8.
- Fixed Tools/review/hero_iterate.sh, which still had the pre-tools-move
'cd dirname/..' and landed in Tools/ instead of the repo root.
Rendered and judged; the advisory two-model judge (no, 0.93) no longer flags the
medallions. Owner review pending.
The reference's most distinctive architectural feature is three lines of incised capitals across the attic.
inscription() lays 112 letter marks in three lines, in the shadow stone so they read as dark incisions at 45 m
rather than disappearing into a same-colour relief - which is exactly what the first attempt did.
Positioning was the lesson: the parapet is 1.2 m deep and stands in FRONT of the wall plane, so marks seated on
FRONT_Y were hidden behind it and rendered invisible. They are now seated on the parapet face (FRONT_Y + 0.40),
which is where the reference puts the lettering too.
Still open in the architectural pass, recorded in TODO.md: medallion relief (the discs are flush and empty),
banner lettering (needs a texture, not geometry), and depth on the capitals and window grilles.
Rows 4-6 re-rendered and pending.
The owner rejected the previous pass on three counts - too saturated, too dark, and the albedo was wrong for the
building. All three were true, and the reason the colour work kept missing is that the albedo never reached the
material: import_albedo() resolved the repository root one level short after the tools move, so every 'stone'
render was the flat tint alone. That is fixed first.
Then measured instead of guessed: the reference's sunlit stone samples sRGB ~0.49 (linear ~0.19), attic
highlight ~0.75, shaded recess ~0.43, and it is nearly neutral, not warm. New albedo generated for a pale
low-contrast marble-limestone ashlar (two checkpoints compared, AlbedoBase won) and the tint moved to
(0.47, 0.455, 0.43) with the shadow at (0.30, 0.29, 0.27), which lands lit stone around 0.62 sRGB - brighter
than the last pass and close to the photographs.
The elevation now reads as pale, low-saturation concrete/marble with thin-jointed ashlar. Row 4-6 re-rendered
and pending. The architectural pass the owner also asked for - carved inscription, medallions, banner lettering,
capital and grille depth - is untouched and recorded as the next step.
THIRD_PARTY_NOTICES carries the new seed and prompt.
The generated ashlar albedo never showed for two reasons that had nothing to do with the material graph,
which was verified correct the whole time (TextureSampleParameter2D -> Multiply -> Base Color, colour sampler,
texture set, 6x UV tiling):
1. **The map overrode every hero slot.** The generic per-slot material pass in ue_build_demo_map.py assigned
the facade KIT's flat stone to every actor whose slot names looked like stone, banner, glass - including
Hero_Facade. Whatever the import authored was replaced at map build time. The hero is now excluded and gets
its own materials.
2. **Mesh slot assignment does not persist.** Assigning to mesh.get_editor_property('static_materials') returns a
copy; after a reload the mesh still pointed at the original imported materials (measured twice). Component
level overrides do persist in the saved map, so apply_hero_materials() sets them in both builders.
The elevation now renders as coursed, grainy warm limestone with banners, window bays and recessed portals.
Rows 4-6 re-rendered and pending review; the comparison plate is regenerated. Still missing against the
reference: carved inscription, medallion relief, banner lettering, crimson banner tone. The two-model judge
returns no/no (0.90/0.95) - it wants the text and detail that are still absent, which is at least the right
complaint this time.
- New 'Toolchain - end-to-end asset check (done 2026-11-10)' section: what the e2e command runs, the last PASS,
and the seven steps that got it there. Two items stay open - the judge is not trustworthy yet (0/11 on shots
the owner approved) and the driver joins the release checks only once it is.
- Hero facade tasks rewritten as done/verified where they are: cameras checked, detail seated on the wall, blank
lintel band removed, grilles, courses and balustrade added, sandstone authored per slot, banner proportions
corrected. What stays open is the generated albedo actually rendering, the inscription and medallions, the
banner lettering, and the three review shots.
- Vehicle physics: the task list now leads with the two facts that matter (the same pawn falls correctly on
ReviewMap's floor; in DemoMap the transform is written directly at any height, with the mover, overlaps,
collision and gravity all ruled out by measurement) and the next probe - a fresh map plus staged content
group by group.
- 0.3.0: the road/sidewalk reconciliation is marked solved - the 2 m 'lid' was an auto-generated convex hull over
a 279 m strip, not the geometry, and the cross-section is now continuous at 158 cm. The Chaos vehicle item is
marked deferred, pointing at the own-task section.
Records what cost time today, with the evidence:
- the four traps of the folder move: sibling imports (sys.path needs both the tools root AND the script's own
folder), shell scripts resolving REPO relative to themselves, stale result files passing as success, and
Unreal hanging rather than failing when a second editor is alive - plus the pkill-matches-its-own-command-line
gotcha;
- measured proof that LevelEditorSubsystem.new_level() cannot replace a map in one session (create -> delete ->
create returns False, and delete_asset leaves the .umap), so emptying actors is the workable step; and that a
falling actor count (600 -> 409) was the fix that audits clean;
- two-model judging: the reconciliation rule, the 404 that retired claude-3.5-haiku, and the observation that
this judge is consistently pessimistic on our content so the model disagreement is the signal;
- local ComfyUI: it is a Stability Matrix install nothing here starts, the documented graphs are UI format that
/prompt rejects, albedo-only output, no seamless sampler in 0.44, and per-model licence rules;
- hero facade: detail was modelled inside the wall, the hero's materials are not the kit's, Unreal keeps a
material on re-import, materials must be saved themselves, and project_dir() is not the repo root;
- the render chain is build -> import -> render, and e2e_asset_check.py is the command that runs it all.
Investigated the count difference from the e2e run and it is the opposite of a regression. The rebuilt map
holds 409 actors: 72 site meshes, 310 facade-kit pieces (the kit tool reports 'placed 310 kit pieces'), six
frontage buildings, lights, cameras and the player start. The previous 600 carried the duplicated staging that
the prefix-only clear used to leave behind; ue_audit_scene.py on the new map reports 0 failures and no
duplicate actor labels.
Captures were re-rendered against this map.
The e2e asset check now completes: PASS on all six steps - empty maps 9 s, demo build 10 s, review build 9 s,
captures 80 s, judge 388 s, review file. Four breakages from the folder move had to be fixed first:
- ue_placement and ue_common are siblings of the scripts that import them, but the rewrite put only the tools
root on sys.path, so every Unreal commandlet died with ModuleNotFoundError. Both paths are inserted now.
- render_screenshots.sh resolved the repository root one level up, so commandlet paths became Tools/Tools/...
and the capture step failed in 0 s.
- the driver treated a result file left by an earlier run as success, which is how a ten-second 'demo build'
passed without building. Result files are unlinked before each step and child output streams to a per-step
log file rather than a pipe.
- two editors on one project hang instead of failing, so the driver refuses to start while one is running.
Also corrects ue_recreate_maps.py: LevelEditorSubsystem.new_level() refuses a path whose package is still in
memory and delete_asset() leaves the file on disk, measured create->delete->create returning False. The step
now loads each map, destroys every actor, saves, and reports the count it left (0), which is the property the
builders actually need.
Open question recorded in TODO.md: the rebuilt map holds 409 actors and 613 KB against 600 and 886 KB before,
and the ~190 missing ones look like the facade-kit and theatre-trim placements that run as separate steps.
The e2e check recreates the maps as empty levels by design, and the run was cancelled while it was still
rebuilding DemoMap. The commit that followed swept the empty 159 KB map into git, replacing the populated
886 KB one.
Both maps are restored from f681d83, the last commit that had them built. The recreate step stays in the
toolchain - it is correct - but it must never be committed mid-run: the maps are only valid content after the
builders have saved them again.
One directory with sixty scripts stopped being navigable, and the grouping is also the sandbox boundary:
Tools/unreal/ commandlets and the two map builders, with ue_common.py and ue_placement.py as the
shared imports (they need the engine)
Tools/blender/ builders, exporters, the FBX preset, the MPFB character pipeline, blender_screenshots
(they need the Blender Flatpak)
Tools/review/ the visual loop: render_screenshots.sh, judge_screenshot.py, hero_compare.py,
build_comparison_md.py, e2e_asset_check.py, hero_iterate.sh, visual_loop.py,
visual_targets.json
Tools/gen/ image and texture generation: the ComfyUI driver, deterministic texture writers,
reference fetching
Tools/input/ OS-level input injection
root _common.py, check-lfs.sh, addon_compat_probe.py, sibling_asset_audit.py, tests/
Every moved script now resolves the repository root as ../.. and puts the tools root plus its siblings on
sys.path, so 'import ue_common' still works from unreal/. The test suite bootstrap does the same for the five
folders. render_screenshots.sh takes the folder as a third argument to run_cmdlet, and the e2e driver,
hero_iterate.sh and the comparison generator were repointed.
51 README rows now link to their new location, the README gains a Layout section naming each folder's
sandbox, and the architecture notes and Blender README follow the moves.
Verified: 108 tests pass, prettier is clean, judge_screenshot.py and hero_compare.py run from their new
homes, and the e2e run was aborted mid-way (it had been rebuilding an empty DemoMap for 15 minutes) and is
being rerun against the new layout.
Used the workflow integration from the previous step for real: generated a warm limestone ashlar albedo
with two profiles compared (AlbedoBase 17.04 vs Krea-2 13.90, AlbedoBase won), saved it as
Art/textures/generated/T_FacadeAshlar_AI.png beside its provenance sidecar, and imported it to
/Game/Hollywood/Textures.
ue_hero_facade_import.py now imports that albedo and, for the two stone slots, builds a material with
TextureSampleParameter2D -> Multiply(tint) -> Base Color, tiling the UVs 6x, so the map carries the coursing
and the tint keeps the value inside the reference's range.
Two real bugs fixed on the way:
- the albedo path was resolved against unreal.Paths.project_dir() (Game/GigLine), not the repository root, so
the import silently skipped;
- the authored materials were never saved - only the mesh was - so after a reload the slots pointed at
nothing and the elevation fell back to one default colour.
Honest status: the nodes are verified present and saved (Constant3Vector, TextureSampleParameter2D,
TextureCoordinate, Multiply) and the source mesh has UVs, but the render still shows the flat tint. Recorded
in TODO.md with the next probe: read the compiled Base Color pin instead of the node list.
THIRD_PARTY_NOTICES.md gains the AlbedoBase row (commercial use, credit required).
Re-read /home/zwitschi/StabilityMatrix/Data/README.md - it now documents 141 GB of models and four
verified, core-node image/texture graphs - and wired the ones that matter into the toolchain.
comfyui_generate.py gains a 'texture' subcommand that rebuilds the API graph for each documented workflow,
because the files in Workflows/ are ComfyUI UI format and /prompt cannot accept them:
albedobase AlbedoBase XL v3.1, 30 steps, CFG 6.0, dpmpp_2m/karras, baked VAE
krea2 Krea-2 Turbo NVFP4, 8 steps, CFG 1.0, euler/simple, ConditioningZeroOut, texture-fix VAE
qwen21 Qwen-Image 2.1 int8, 25 steps, CFG 1.0, euler/simple, TextEncodeQwenImage21, texture-fix VAE
Default pair is albedobase + krea2 - both commercially usable - generated from the same prompt and seed,
scored by high-frequency detail, and compared before the winner takes --out. Smoke-tested at 512 px: both
rendered, albedobase won 18.91 against 17.51, and the resulting limestone albedo is a plausible material
sheet.
Tools/README.md documents the profiles with their settings, a licence row per profile (Qwen-Image 2.1 is
research-only and must not ship), and the two limits the workflows carry: they produce albedo only, and
ComfyUI 0.44 has no seamless sampler, so tiling has to be checked by offsetting the image 50%. Five tests
cover the graph shapes; the suite is at 108.
Tools/README.md now covers the generative tools next to each other:
- comfyui_generate.py drives the LOCAL ComfyUI at 127.0.0.1:8188, which this repository does not start. A new
subsection says so and says what to do: start Stability Matrix, launch ComfyUI, re-run. It points at
/home/zwitschi/StabilityMatrix/Data/README.md for the package version, the launch arguments and the shared
80 GB model library, and notes that THIRD_PARTY_NOTICES.md must carry the model and licence before a
generated texture ships.
- generate_facade_textures.py and generate_character_textures.py are listed as the deterministic procedural
path: no model, no network, byte-identical on every run.
- The tool's own docstring carries the same Stability Matrix instruction.
The default second checkpoint was sd_xl_base_1.0.safetensors, which is NOT installed; it is now
epicrealismXL_pureFix.safetensors, verified against the running server. The pair was smoke-tested end to end
at 512 px: both checkpoints rendered, both scored, juggernautXL won at 20.37 against 12.93, and the winner
landed on --out with the loser kept beside it.
A single vision model has been wrong in both directions on this project - it missed real defects and
disputed frames that were correct - so agreement between different model families is worth more than one
confident opinion.
judge_screenshot.py now runs two models by default (openai/gpt-4o-mini + qwen/qwen3.7-flash, both served
on OpenRouter; claude-3.5-haiku is gone, which the first attempt discovered with a 404). combine()
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 still forces a single
judgement, and every line prints which model said what.
comfyui_generate.py generates the same prompt and seed with two checkpoints, scores both by
high-frequency detail (mean absolute Laplacian, which catches blurry or flat results), keeps the loser as
<out>__<model>.png with its sidecar, and moves the winner onto --out. --alt-model restores the single
run.
Tests cover the reconciliation - agreement, disagreement, unknown verdicts, and no verdicts at all - and the
suite is at 103.
Iterated the loop five times against Art/reference/hero-masonic/. What took the iterations was the material,
not the geometry:
- M_FacadeStone in /Game/Hollywood/Site is the KIT stone; the hero does not use it, so warming it changed
nothing.
- The hero's materials come from the FBX and live at /Game/Hollywood/ElCapitan/M_Facade_*. Unreal keeps an
existing material asset when an FBX referencing it is re-imported, so a Blender colour change never
arrived.
- Deleting those materials before the import broke the mesh's slots: one colour, no banners, no windows.
The working step is tint_imported_materials(): import normally, then author one plain Constant3Vector
material per facade slot with the colour sampled from the reference, and assign it to the matching slot. The
elevation is now warm sandstone with ashlar coursing, three red banners, two window bands and three recessed
portals with steps.
Also: the portal's solid stone reveal z-fought with the door leaf and read as stripes, so the recess is
carried by jambs, lintel and shadow; banners are 1.35 x 6.2 m hung from 7.2 m instead of 1.55 x 5.4.
Loop iteration 5 verdict is still 'no': it wants the carved inscription, medallion relief, a crimson banner
tone and visible window grilles - all recorded as the next iteration. Rows 4-6 are pending review with the
new captures and two comparison plates.
The 94 symlinks in ~/.agents/skills and the full second copy in
~/.pi/agent/skills/blender-skills were the same packs under two roots, so Pi
reported a collision for every one of them on every start and silently dropped
the loser.
- The 93 upstream packs, the shared references/ notes and the LICENSE now live
once, flat and one level down, in the common agent folder ~/.pi/agent/skills/.
- The repository keeps only the project-specific pack:
.agents/skills/blender-skills/blender-director/. Its ../references/ links now
point at ~/.pi/agent/skills/references/ because it no longer sits beside them.
Its SKILL.md declared metadata.role twice (specialist, orchestrator), which is
fatal YAML - the pack had never actually loaded.
- AGENTS.md and Docs/toolchain.md describe the split and warn against copying the
common packs back into the repository.
Verified by walking all three discovery roots and parsing every frontmatter:
97 skills loaded, 0 malformed, 0 collisions.
Tools/hero_iterate.sh runs the whole chain - chmod the Blender files, build, import, render, judge, plate -
and fails loudly at each step with its log under /tmp/gigline. Tools/hero_compare.py builds the side-by-side
plate (reference | rendered, one height, labelled); two are committed for review.
This pass: the stone is warmer in Blender (0.66/0.60/0.48 against the near-white blockout) and the banners are
1.35 x 6.2 m hung from 7.2 m, matching the reference proportion instead of the 1.55 x 5.4 that sat too low and
too fat.
Iteration 1 judge verdict, confidence 0.9: lacks warm sandstone, lacks fluted column detail, banners wrong,
no doorways, no inscription or medallions. Checked against the plate, two of those are wrong (120 flute
strips exist and cast shadows; three 1.2 m deep doorways with steps exist and read black) and three are real.
The split is recorded in TODO.md with the next action for each - the important one being that the stone colour
must change in the UE material (ue_hero_facade_import.py), not in the Blender material, which the render does
not use.
Every .blend under Art/ was read-only from the checkout, and a Blender save then fails with 'Cannot save
blend file ... is not writable'. In Tools/hero_facade_build.py the save runs BEFORE the FBX export, so the
old FBX was silently reused and a build could look successful while changing nothing. All 20 .blend and
.blend1 files are now writable and 'find Art -name *.blend ! -perm -u+w' returns nothing.
Re-rendered the full capture set afterwards. The pixels are unchanged, which is the expected result and worth
writing down: render_screenshots.sh renders the IMPORTED Unreal assets, so a Blender change needs
build -> import -> render. Chmod alone cannot change an image.
Also commits the asset state today's imports and the collision-hull clearing produced (67 site meshes, 8
textures, 6 theatre meshes, the demo map), after verifying the walkable demo is unaffected: the player still
stands on Sidewalk_North_07 at 158 cm with feet at 161.
Art/reference/hero-masonic/ (15 images, git-ignored) replaces the single Wikimedia photo as the source of
truth. Two front elevations drive this pass: 02-masonictemple01.jpg, a clean street-level frontal, and
06-el-capitan-theater-frontal-fachade.jpg; 05-hollywoodmasonictemple-1922.jpg records the original masonry.
Changes:
- the blank Hero_Stylobate_LintelBand across the portals is removed - the reference has open doorways with
steps, and they now read as three dark openings with banners above them;
- rustication(): five ashlar courses from the pavement to the portal heads, 4 cm joints proud of the wall;
- balustrade(): bottom rail, top rail and 38 balusters along the roof edge, where the reference shows a
cresting and railing.
Still missing against the same references, and recorded: warm sandstone instead of flat pale stone, lattice
grilles in the window bays, the carved inscription, and medallions that do not read at distance.
The blend is read-only after a checkout - chmod u+w first or the save fails silently and the FBX is never
written, which happened once here.
Rows 4-6 re-rendered and pending review.
The three hero captures showed a plain block with six columns because every applied detail sat BEHIND the
wall's front face, inside the solid mass:
Hero_Banner_1 y -1.08..-1.02 (55 cm inside)
Hero_Portal_1 y -2.35..-1.15 (2 m inside)
Hero_Window_1_1 y -0.60..-0.52 (2 cm inside)
The colonnade, cornice and dentils happened to protrude, which is exactly what the shots showed.
The depth is now stated once, in hero_facade_build.py: FRONT_Y is the wall's front face and
seat_on_front() moves every applied part until its street-facing face sits at FRONT_Y + offset - 8 cm for
banners and jambs, 5 cm for entablature and cornice, 3 cm for window panes, 4 cm for mullions, 10 cm for
portal signs. It reports what it moved, 99 parts.
After re-export, re-import and re-render the front elevation now shows the three red banners, the two
window bands, the dentil course and the portals. What is still missing against the reference: rusticated
banding on the stone, lettering on the motto band, grilles in the windows, a balustrade on the parapet, and
the blank lintel band across the portals that the reference does not have.
Rows 4, 5 and 6 are back to pending for review because the images changed.
Measured the Y positions in the source and the imported mesh:
blend scene Y -12.50 .. +0.13
Hero_Mass y = -BODY_D/2 - 0.5, so it spans -12.50 .. -0.50
Hero_Banner_1 -1.08 .. -1.02, i.e. 55 cm BEHIND the wall's front face
imported bounds y -13 .. +1250 cm
So the banners - and by the same construction the window bays and portal surrounds - sit inside the mass and
never render. The columns, cornice and dentils protrude, which is exactly what the captures show: a plain
block with six columns.
The camera check is answered as well: the three hero cameras are aimed at (-13336, 1307) where the hero actor
is, and the frontage sits at the origin, so the framing was correct all along.
Next step is mechanical, not artistic: put a FRONT_Y constant in Tools/hero_facade_build.py, place every
elevation detail relative to it, re-export and re-render. The model does not need remodelling before its
detail can be seen.
Started the hero facade task the way it has to start - by comparing the reference photograph with the
current render rather than with memory of it.
Reference: el-capitan-facade-day-wikimedia-2009-10-27.jpg, north elevation, daylight.
The model (SM_HeroFacade, 4 materials) is much further along than the review captures suggest: six fluted
columns with capitals and abaci, architrave, entablature, cornice with 52 dentils, parapet, motto band with
surround and two medallions, three banners, five window pairs, and three portals with jambs, lintels,
mullions and five steps each.
What it genuinely lacks: surface. Rusticated banding, carved lettering on the motto band, window grilles,
and a balustrade with finials on the parapet.
And one thing to check before any modelling: the three hero captures show a plain block with six columns and
nothing else, while the mesh carries banners, windows and portals and is staged at (-13336, 1307, 0). The
likely explanation is that the cameras frame Frontage_SM_ElCapitanTheatre at the origin, or the wrong side
of the hero - in which case the rejection is describing a camera error. One render aimed at Hero_Facade from
Y -4000 decides it, and three new tasks are recorded with that first.
A shared heading string made one edit land in two sections, so the hero-facade task carried a copy of the
vehicle's bisect evidence. The section is back to its own two steps.
Also re-ran the gates on the tree as it stands after today's ground and placement work:
Tools/ue_audit_scene.py 593 mesh actors, every mesh has a material, building X ranges as expected,
no duplicate actor labels, 0 failures
prettier --check all Markdown clean
unittest 99 tests pass