The body stood in an A-pose while the log said the clip was playing. Two independent defects, found by querying the imported asset instead of guessing: 1. PlayAnimation needs the component in AnimationSingleNode with a live single-node instance. The body is hidden at construction and only unhidden by the camera toggle, so the first call could land before the component was ready, and the Wanted == PlayingAnimation guard then returned forever. UpdateBodyAnimation now sets the mode, calls SetAnimation + Play, and re-asserts while the instance is missing. 2. The arms-down offsets lived only in the clip keys. The exporter writes clip data relative to the rest pose, so they arrived as a ±20 degree swing around an A-posed bind - which is exactly what the pose probe measured. apply_base_pose now bakes them into the rest pose before any clip is authored, the same trick mpfb_character_export.py --pose idle uses. The table is applied with inverted signs: as written, the FBX axis mapping produced arms raised in a V. Shot 6 confirms it: the arms hang at the sides. Still open and recorded - the idle pose reads awkwardly (arms forward, hands splayed) and the walk clip has no shot in motion; both are marked in TODO.md and Screenshots/README.md. Instrumentation removed; the diagnostics that remain are behind gigline.SetupLogs.
75 KiB
75 KiB
MEMORY.md
Shared working memory between sessions and collaborators. Short, dated entries with evidence: the command, the log line, or the file path.
Scope: facts and traps that cost time to establish. Planned work lives in TODO.md; development direction lives in ROADMAP.md; formal setup, pins, and licensing live in Docs/.
2026-11-08 — Documentation toolchain installed; asciidoc→markdown conversion
- pandoc 3.13 installed at
~/.local/bin/pandocfrom the GitHub release tarball (statically linked, 161 MB). The Fedora repos carry onlyghc-pandoc(Haskell library), not the CLI. - asciidoctor 2.0.26 layered via
rpm-ostree install rubygem-asciidoctor— pending reboot to activate. As a workaround, a distrobox Fedora container (distrobox enter arch-convert) runs asciidoctor without rebooting. - prettier 3.9.10 installed globally via npm (Node v24.20.0). Project config:
.prettierrc(proseWrap: never,tabWidth: 2),.prettierignore(skipsBinaries/,*.uasset,.agents/). - Conversion chain:
.adoc→asciidoctor -b docbook5→.xml→pandoc -f docbook -t gfm→.md→prettier --write. Pandoc's built-inasciidocreader cannot handle Asciidoctor tables or attributes — it must go through docbook5. - Post-processing fixes: glossary
cols="e,2e"emphasis leaks italic into cell content (strip*...*wrapping),®from the R key binding becomes(R), anchor IDs{#...}stripped, escaped quotes/brackets normalised. - All
.adocfiles removed. Markdown is the canonical format. Architecture doc lives inDocs/Architecture/as 13 section.mdfiles + combinedarchitecture.md. - All
.mdin the repo passesprettier --check(23 files formatted,.agents/excluded as vendored content).
2026-11-08 — QA branch merged; demo is walkable
- PR #7 squash-merged to
mainas52b1212— terrain collision fix, spawn correction,UGigLineAssetTools,HighResShotfix,-qaselftestharness. - QA self-test results (from merged main): walk 750 cm / 2 s (Z steady 456), reset 0 cm from spawn, pause/unpause pass, transect 84 samples / 0 holes (ground Z 317..395).
- Traversal release check closed: the swept transect measures walkability rather than assuming it.
gameplay-crossing-0.1.0branch deleted (already merged);gig-line-judgeworktree removed.qa-input-collision-fix-0.1.0branch still exists locally/remote (squash merge has no shared ancestry,git branch -drefuses; delete with-Dafter release tag).GigLine SIM/GigLine GROUNDTEMP diagnostic logging still inGigLineCharacter.cpp— must be removed beforev0.1.0tag.
2026-11-08 — Reset was returning to mid-air; QA self-test switch
ResetToStart()restored aStartLocationcaptured inBeginPlay, which is the PlayerStart position above grade (Z 600) so the capsule never spawns embedded. Every reset therefore dropped the player 1.4 m back into the air. Fix: captureStartLocationon the first grounded frame (repeating 0.25 s timer, cleared once captured) — not a one-shot timer, which can fire after the player has already started walking and record a mid-stride position.-qaselftestdrives the checks headlessly and logsGigLine QA: ... PASS/FAIL. It runs on a core ticker, not a world timer: pausing the game stops world timers, so a timer-driven test freezes exactly when it proves pause works.- Current results: walk 746 cm / 2 s with Z steady, reset 0 cm from spawn, pause
IsGamePaused()1, unpause 0, transect 84 samples / 0 holes (ground Z 317..395).
2026-11-08 — The fall-through was an 8 m invisible box (QA branch)
- Symptom: player "spawned above ground but never fell", sat at Z 909,
fall=0,mode=1(Walking), no fall-through, view looked plausible so it was mistaken for working. - Cause:
Terrain_DEMhad only a bounding-box simple collision proxy (Interchange default). A line trace down at every Y through the block hitDemo_Terrain_DEMat exactly 819 — the mesh bbox max Z. The whole street was under an invisible lid; the pawn stood on the box, 8 m above the road. - Proof: a debug
LineTraceSingleByChannelprofile at Y −1912…+1447 reportedtop=819 actor=Demo_Terrain_DEMfor the entire street. After the fix it reportsSidewalk_North 395,Road_Hollywood 366,Sidewalk_South 317. - Fix: force complex-as-simple collision on the terrain. Python cannot set it (
StaticMesh/BodySetupexpose no such property;dir()shows onlylod_for_collision). AddedUGigLineAssetTools::SetComplexCollisionAsSimplein the C++ module (BodySetup->CollisionTraceFlag = CTF_UseComplexAsSimple, UE 5.8 no longer hasbUseComplexAsSimpleCollision). - Second gotcha: the flag lives on the asset, not the map. Setting it in a builder session and saving only the
.umaploses it —EditorAssetLibrary.save_loaded_asset()is required, otherwise the next run reloads the bbox proxy. - Third gotcha:
HighResShot 1280 720means multiplier 1280 (UE warns "the high resolution screenshot multiplier is too large for your system"). The correct form isHighResShot 1280x720. - Resulting spawn:
(-13336, -1150, 600)— inside the carved corridor, lands on the road at Z 456 (feet 368 on road top 366).
2026-11-07 — Publication audit
- MPFB 2.0.18's installed
blender_manifest.tomlidentifies its code as GPL-3.0-or-later; its versionedLICENSE.mdexplicitly puts bundled mesh, texture, and rig assets under CC0 1.0. The earlier CC0 claim for the add-on code was wrong. - Blender inspection found no packed images or linked libraries in the tracked scenes. The character scene references local MPFB textures and a temporary baked texture, so those paths are not portable to a fresh checkout.
- The 2026-11-07 publication rewrite removed the approved private documents and bundled skills from all reachable refs; local copies remain ignored. The original history backup at
/tmp/gig-line-private-history-backup-42a29fe2contains those private files and must not be pushed;/tmpmay be cleared by the system. git ls-remote --refs originsucceeded on 2026-11-07 with no advertised refs;git ls-remote --exit-code origin HEADreturned 2 because no HEAD was advertised. This does not verify LFS upload support.- Gitleaks 8.28.0 found an Android File Server token in historical
Game/GigLine/Config/DefaultEngine.ini. The token was removed from reachable history and the unused server disabled; the old private backup and earlier temporary sanitized clone still contain it and must not be published. - The first Gitea push uploaded 26 LFS objects. An independent clone fetched all 26 and
git lfs fsckpassed for the full history; the server also advertised LFS locking support.
2026-11-07 — Unreal Engine 5.8.4
- Extracted to
/run/media/system/DATA/Engines/UE_5.8.4(73 GiB,unzipexit 0). CL58902052,++UE5+Release-5.8. - Gotcha: the editor needs a writable
~/.config/Epic. In the agent shell it crashed with signal 11 twice and looked like a broken install. The log named it:create dir('/home/zwitschi/.config/Epic/') failed: errno=30 (Read-only file system)→Fatal error: Failed to create Intermediate directory '…/.config/Epic/UnrealEngine/5.8/Intermediate/'. Rerunning withHOME=/tmp/uehome XDG_CONFIG_HOME=/tmp/uehome/.configreachedLogInit: Display: Engine is initialized. - Vulkan RHI is still unproven. The successful run used
-NullRHI. Untested:UnrealEditor -vulkanfrom a desktop terminal. - A packaged game does not need
~/.config/Epic, so this failure does not follow the build.
2026-11-07 — Agent shell limits
- The agent shell has a read-only home and read-only
/run:sudois blocked (no new privileges),flatpak runandpodmancannot create instances,/dev/nvidia*is absent sonvidia-smialways fails there. None of this is a broken machine; verify GPU things from a normal terminal. - Anything outside the session workspace (
/run/media/system/DATA/Engines,/tmp) needs the wider sandbox with an approval prompt.
2026-11-07 — Reference photography
- Licensing is not a gate for local development. Reference images live in the gitignored
Art/reference/and are never committed. What each set establishes is written intoDocs/reference-notes-hollywood.md. - Commons text search runs dry; categories do not.
Tools/commons_fetch.py --category "Category:Hollywood Masonic Temple"surfaced 15 files a search never showed, including 4–5k px façades. upload.wikimedia.orgthrottles hard. Batches of six or fewer with--delay 3; a throttled file is skipped and recorded rather than killing the run (HTTP 429).- Unqualified queries return junk.
Hollywood Boulevard shopsreturned a Münster street,Hollywood Masonic Temple entrancea Seattle card index. Check the hit list before accepting a set. - OpenRouter web search is expensive. One
gpt-5.2+openrouter:web_searchcall ran 17 searches and cost $0.38; two of its eight URLs were Commons files already held. Use it for a known gap, not a sweep.Tools/page_image_fetch.pyturns results into files by readingog:image. - The inherited "eight stone steps, 12 m wide" is wrong. Photographs show three portals, each with its own short flight. Do not copy that number into geometry.
2026-11-06 — Blender and Blosm, headless
flatpak rundoes not work here. Call the binary inside the app tree:B=/var/lib/flatpak/app/org.blender.Blender/x86_64/stable/active/files, thenLD_LIBRARY_PATH="$B/blender/lib" "$B/blender/blender" -b …withBLENDER_USER_SCRIPTS="$HOME/.var/app/org.blender.Blender/config/blender/5.2/scripts".- Blosm needs two headless workarounds, both already in
Art/blender/blockout/blosm_blockout.py:- it refuses to import until an asset directory is set, and the check fires before the mode is honoured;
- in background mode the default level table is empty, and the add-on then takes a path with a broken relative import (
from gui import addDefaultLevels). Seeding one entry avoids it.
- Blosm projects on a sphere, not an ellipsoid. Its frame is about 0.4 % oversized and rotated by meridian convergence — roughly 2 m at the theatre. Fixed with a six-parameter affine into EPSG:26911 (0.0 cm residual).
- Transform roots only. 256 of 275 objects are children; transforming both parent and child applies the correction twice and reads as a 2 m placement error.
- Blosm quietly fetches from Overpass for relation members missing from the extract.
loadMissingMembersis off by default in the tool so imports stay offline and reproducible.
2026-11-06 — Geo data traps
- The 3DEP export is a tiled TIFF, not strips. A strip reader silently scrambles rows and produces fake 0 m / 127 m bands.
Geo/tools/dem_stats.pyhandles tiles. - Sample walk routes through PROJ. Placing samples with the OSM spherical approximation put them metres off the UTM meshes and reported 91 phantom holes.
/tmpis not shared between tool calls here either — regenerate instead of saving intermediate files there. - 3DEP cannot resolve kerbs. Sharpest break near the kerb line is 0.09–0.12 m/m, a gutter transition, not a 0.15 m upstand. Kerb height stays a stated assumption.
- Building interiors are not ground. Points inside the hero footprint returned 114.5–117.1 m — structure, not terrain. Excluded from the relief figure.
- Two measurements independently agree on the carriageway: crossing length 20.9 m and gutter spacing −10 / +10…12 m.
2026-11-06 — Repository traps
- Without
git lfs installthe lfs filter is inactive andgit adddoes not warn — it stores the file as a plain blob.Tools/check-lfs.shis the guard. - Never commit reference images or
.env..gitignorecovers/Art/reference/,.env,*.env. When images slipped in early, history was rewritten to remove them. - The project origin is Hollywood × Highland:
E 376 515.389 / N 3 774 224.083in EPSG:26911. Blender X east, Y north, Z up, metres, Z measured from the origin ground (NAVD88 115.911 m). The whole block sits at negative X.
2026-11-07 — Unreal pipeline findings
- UE 5.8 game targets must use
BuildSettingsVersion.V7to share the installed editor's build environment; V5 is rejected with "modifies the values of properties … not allowed". UnrealEditor-Cmd -run=pythonscriptswallows Pythonprint()with-stdoutin 5.8 — write results to a file instead (seeTools/ue_scale_test_import.py).unreal.Boxhasmin/max, notorigin/get_extent()in 5.8 Python; compute centre/size from the corners.- FBX Blender→UE axis mapping is east→+X, north→−Y, up→+Z with preset
ue_metric_v1; the Y mirror is inherent (RH→LH). Compass math in game code: north = −Y. Verified with the three-cube scale test. - Headless asset import works fine with
-nullrhi;unreal.AssetImportTask+FbxImportUIis the pattern (Interchange pipeline under the hood). - Flatpak Blender headless needs
XDG_CONFIG_HOME=$HOME/.var/app/org.blender.Blender/configfor extensions:bpy.utils.user_resource('EXTENSIONS')otherwise resolves to~/.config/blender/5.2/extensions(empty) andbl_ext.blender_org.mpfbis undiscoverable. Blosm unaffected (scripts/addons viaBLENDER_USER_SCRIPTS). Found viaTools/addon_compat_probe.py; documented inArt/blender/README.md. - UE 5.8 Interchange skips material/texture assets for skeletal FBX imports (static imports do get materials). Finish the chain in Python: import texture, build Material via
MaterialEditingLibrary, assign withsm.set_editor_property('materials', [SkeletalMaterial])— the struct field ismaterial_interface, notmaterial, andSkeletalMesh.set_material()does not exist. SeeTools/ue_character_import.py. - Blender 5.2 FBX exporter removed
armature_nla(passes for 4.x recipes fail). Also: a baked image is only referenced by the FBX exporter whenimage.filepath(not justfilepath_raw) is set and the exporter can map the node — MPFB's v2 skin node-group tree can't be mapped, so a flat export material is required.
2026-11-07 — Collaboration and scope
- Two sessions edit this repo at once. The other session created the Unreal project and the blank package while this one was documenting. Check
git logandgit statusbefore committing, and nevergit add -Ablindly: it once swept generatedGame/GigLine/Build/**logs into a commit. Stages the source files deliberately. Build/under a project is generated, not source —FileOpenOrder/*.logand packaging output..gitignorenow covers it alongsideBinaries/,Intermediate/,Saved/,DerivedDataCache/.- Scope boundary for
0.1.0: do not tagv0.1.0without an explicit go-ahead from the owner. It is a minor release and carries the walking/driving demo promise; the board's release checks decide when it is ready, not the pace of work.
2026-11-07 — Screenshot pipeline
-executePythonScript+ editor is unreliable for captures: the editor quits right after a successful deferred script (async screenshot requests never render), and afterload_mapin a commandlet everyspawn_actorcall returns None — build scenes in the default world instead.- Robust in-game capture recipe: commandlet builds a map (
ue_build_review_map.py), then the game runs in-gamemode and a tiny C++ actor (AGigLineShotActor) walks camera setups viaSetViewTarget+FScreenshotRequest::RequestScreenshot. In 5.8FScreenshotRequesthas no resolution argument (window size via-ResX/-ResY) and noIsFinished()— poll!IsScreenshotRequested(). Lives inUnrealClient.h, notMisc/ScreenshotManager.h. - Commandlet-only quirks: bulk render-target readback (
read_render_target_raw,export_render_target) silently returns empty in commandlets; single-pixelread_render_target_uvworks. Game targets must useBuildSettingsVersion.V7.
2026-11-07 — Enhanced Input from Python (UE 5.8)
unreal.InputCoreTypesdoes not exist in the 5.8 Python bindings, andunreal.Keytakes no constructor argument. Build a key withk = unreal.Key(); k.import_text("SpaceBar")— the export text is the key name (MouseX,Gamepad_Left2D).- Factories are
unreal.InputAction_Factoryandunreal.InputMappingContext_Factory, notInputActionFactory.create_asset(..., factory=None)also works. - Do not delete an asset and recreate it in the same commandlet run.
create_assetreturnsNonefor a path deleted earlier in that process. Load the existing asset and overwrite it instead (Tools/ue_create_input_assets.py: fresh()). - Modifiers on
EnhancedActionKeyMappingdo not survive saving. AssigningInputModifierNegateto the mapping'smodifiersreads back correctly in memory, even after agc.collect(), but the saved asset readsmods=[NoneType]. Keep-alive references, explicit flag setting, and in-place saves all failed to change it. Workaround: bind one action per direction (W and S as separate actions) so no modifier is needed. - A read-only packed
.umapsilently blockssave_map.EditorLoadingAndSavingUtils.save_mapreturnedFalsewith no error until the file's mode was fixed (chmod u+w). Symptom: a rebuilt review map keeps three cameras after a fourth is added. -run=pythonscriptstill swallowsprint(), so write results to a file;/tmpis visible to the commandlets even though it is not shared between agent tool calls.
2026-11-07 — Hero facade build and review cameras
- The review floor was 5 cm thick and centred at Z -50 cm, so anything placed at Z 0 floated ~1.8 m. It is now a 90 m x 90 m x 10 cm slab with its top face at Z 0; a character's bounds should read Z 0..83 cm when it stands on it. The review floor is the only ground in the scene. Cameras 25 m from the building line look past its edge and see the black horizon band under the facade. Place facade cameras so both the facade and enough floor are in frame, or extend the floor.
- The facade's frontage faces Unreal -Y. The FBX preset maps Blender +Y (north) to Unreal -Y, so the elevation looks at negative Y and a straight-on camera must stand at Y < -1450 cm. A camera at positive Y looks at the back wall; cameras inside Y -1443..+250 are inside the building and render flat blue.
- The imported mesh is centre-origin in X but not in Y or Z. Bounds are
x -1302..1302, y -7..1250, z 0..1450cm, so to put the front plane on the building line the actor goes atY = line + 7 cm, andX = east edge + half width. save_mapreturns False, without an error, when the packed.umapis read-only. The review map silently keeps its old camera list.chmod u+wthe file before rebuilding; aumapthat keeps rendering four shots after a sixth was added is this, not a caching problem.StaticMaterialis a struct:material.get_editor_property("material_interface"), noget_name()on the wrapper.- Review screenshots are committed on the owner's instruction (2026-11-07). They are large PNGs, so a review run adds weight to the repo; keep the shot list short and delete retired shots rather than accumulating.
2026-11-07 — Sibling project reuse survey
security-gameholds 20 reusable street props with Blender sources (assets/models/blender_src/*.blend) and Godot GLBs: bollard, bench, trash bin, stanchion, crowd barrier, street lamp, traffic cone, fire hydrant, newspaper box, palm/street trees, sedan, marquee, doors, lanterns. About 727 KB and 11.8k triangles for the set. Audit:Tools/sibling_asset_audit.py, manifest atArt/props/reuse-manifest.json; the 50.blendsources are copied toArt/props/blender_src/.- Their building meshes are not reusable — they are Godot GLBs built to a different key set (hero facade scaled to 29.6 m, a figure this project measured at 25.75 m). Do not import their venue geometry.
tools/generate_textures.pyin that project is the OpenRouter image endpoint pattern (POST /api/v1/images,b64_json, optionalreference_pathfor image-to-image on the chat endpoint) and it already has proceduralbrick_facade/seamless_concrete/asphaltgenerators. Worth porting for our facade materials.- Its AI concept frames are restaged photograph references (
.refgen.jsonsidecars point at photos with a "keep geometry fixed" suffix). Useful as art direction, not as texture sources. - Blosm exposes
setupScript(scene.blosm.setupScript→loadSetupScript) for custom building materials, and shipssetup/neoclassical.pyas an example — the hook for giving OSM buildings a per-building facade style instead of flat colours.
2026-11-07 — Geo tooling layout
- One conversion, one place.
Geo/tools/osm_common.py::to_unreal_localis the only metres→centimetres conversion; every exporter calls it.os_commonalso needed an explicitimport osat module level — a helper defined with a function-local import list crashed withNameError: oswhen called from a fresh interpreter. asset_metadata.py auditmust recurse. Sidecars sit next to their.blend, which is one level belowArt/blender/; a flatglobfound nothing and reported "0 sidecars, 0 problems" — a green result that meant "did not look".- Blender-side builders live in
Art/blender/*/(terrain, street, site, blockout) and reach helpers viaos.path.join(HERE, "..","..","..","Geo","tools"). Moving them breaks the old"..", "Geo", "scripts"path silently until the script is run. - Height provenance: 3 of the 15 block buildings carry no OSM height and Blosm renders them at a placeholder 6.4 m.
Geo/tools/block_height_report.pylists them; they need heights measured off the photographs before the skyline is believable.
2026-11-07 — Blender rebuilds that silently did nothing
- A read-only
.blendmakes Blender skip the save without failing the run.Tools/hero_facade_build.pyprinted its part list and "bounds" as normal, then went straight to the export: the.blendand the report stayed at their old timestamps, and the Unreal re-import brought back the old mesh. Symptom to watch for: the imported bounds do not change after a rebuild. Fix:chmod u+wthe.blendfirst, then confirm the timestamp moved. - Model bottom must land on Z 0. The hero portal jambs were sized
PORTAL_H + 0.3and centred atPORTAL_H / 2, so they hung 0.15 m below the pavement and the whole facade imported withmin z = -15 cm. Always check the asset'sbounds_m.min.zin the build report. - Rebuilding a facade is not enough to make a detail shot work: doors modelled as thin panels on a solid plinth read as shading, not openings. Cutting the plinth into piers with a lintel band is what makes them legible from 8 m.
2026-11-07 — Demo map assembly traps
- Unreal signs, measured not assumed. The FBX preset flips north, so in the demo map the road spans Y -1382..+1047, the north sidewalk Y -1912..-1053, the south sidewalk Y +718..+1447, and the facade sits at Y -1450. A PlayerStart at +1600 is on the south sidewalk behind the building. Read the actor bounds before placing anything.
- Street meshes carry only PART of the sidewalk. In the source
.blend,Sidewalk_Southis the offset band (-14.47..-7.18 m) andSidewalk_Northis the kerb-to-wall band (10.53..19.12 m); the rest of the north walk comes from the terrain. So a sidewalk-looking strip of ground between kerb and wall is the terrain, not a gap. - Do not mirror in the exporter.
Sidewalk_Southwas already correct in the.blend; mirroring both sidewalks at export moved the correct one to the wrong side and produced an "everything on the wrong side" look that took three rebuilds to unpick. Check the source coordinates first. bpy.ops.mesh.normals_make_consistentneeds a mesh context; in object mode it fails withpoll() failed, context is incorrect. Flip winding by reversingpolygon.verticesinstead.StaticMeshhas no trace-flag property in Python. Collision complexity is set throughmesh.get_editor_property("body_setup").set_editor_property("collision_trace_flag", …);StaticMeshEditorSubsystem.get_collision_complexityreads it back but there is no setter.- A
-gamerun with a paused world still exits on timeout, so a smoke test that "hangs" may simply be running.
2026-11-07 — Documentation sweep
- Docs drift silently after a refactor. Moving the geo scripts to
Geo/tools/and the Blender builders intoArt/blender/*/left stale paths in six documents and two tool READMEs. Grep for old paths (.mdand.py) after any move, not just the ones that error at runtime. - Every new tool needs a row in
Tools/README.mdandDocs/build.md, or the next session will not know it exists. Both now list the full content pipeline in order.
2026-11-07 — Character diffuse bake
img.save()writes black for an image created in memory even though Cyclesbpy.ops.object.bakedoes updateimg.pixelscorrectly (verified in-memory px were warm). Writing the PNG ourselves fromimg.pixelsis required.- A per-pixel
struct.packloop overimg.pixelsis catastrophic in headless Blender (256² took 25 s). Blender ships numpy: readimg.pixels[:]once into a float array, reshape(h,w,4), clip→uint8, reverse rows for PNG top-down, then zlib-compress. Whole 1024² encode ≈ 0.3 s. - MPFB's v2-skin node tree does not resolve to a simple base colour, so a Cycles DIFFUSE bake returns black. Since the FBX export material replaces the node tree anyway, synthesize the diffuse directly (
make_diffuse): warm skin body + a darker shorts-suggestive bottom band. - A read-only
.umapsilently makessave_mapreturnFalsewith no error (already recorded); re-hit in the review pipeline —chmod u+wthe maps before rebuilding, otherwise the new character material never appears in shots.
2026-11-07 — screenshot judge tool
Tools/judge_screenshot.py— runs an OpenRouter vision model over a screenshot vs an expected-scene description. ReadsOPENROUTER_API_KEYfrom the repo.env(ignored). Base64 data-URI image underimage_url.url; OpenAI-compatible body →POST /api/v1/chat/completions.openai/gpt-4o-minireturns a{verdict, confidence, reason, missing}JSON. It flags genuinely missing content (e.g.main's pre-warm-skin character shot judged "no"); it is a pre-check, not the human verdict.- Test one:
Tools/judge_screenshot.py --image Screenshots/review-01-character.png --expect "<scene>" --verbose. Bulk:--shots(parses the README rows).
2026-11-07 — Blender skill packs: discovery depth
- The 94 packs were never registered with the
skilltool, only readable as files. The loader resolves<skills-root>/<pack>/SKILL.mdone level down;.agents/skills/blender-skills/is a container of 94 packs, so it was skipped silently (no warning, no error). Symptom:skill("archviz")unresolvable even thougharchviz/SKILL.mdexists. - Fix applied: 94 symlinks in
~/.agents/skills/<pack>→<repo>/.agents/skills/blender-skills/<pack>/. The catalog then lists all of them (verified by callingskilloncollision-proxy: base dir resolves through the link).references/stays unlinked — it is a shared folder with noSKILL.md, not a pack. - The AGENTS.md "MCP-driven" caveat is overstated. 41 of 94 SKILL.md mention MCP, mostly only in the
descriptionline; the bodies are method guides (naming, proportions, validation checklists) that work headless. Do not skip a pack because it advertises MCP. - Skill names in AGENTS.md/TODO.md all resolve except
references, which is the folder above, not a pack. - Symlinks are user-level: a fresh machine needs the same 94 links before the packs are loadable by name.
- A shared checkout can commit your uncommitted edit for you, or lose it. This entry was written but not yet committed when another session ran a
git committhat includedMEMORY.md; it landed inside their commit (54b6239) with a message that does not mention it. Had they overwritten the file instead, the work would have been gone with no reflog to recover from. On a shared worktree, either commit a shared file immediately or checkgit log -Sfor it before assuming the edit is missing.
2026-11-07 — Street visibility and the LFS lockable trap
- Every street surface (road, sidewalks, kerbs, markings) had 100 % downward face normals —
strip()'s natural winding (rows along +s, cols along +normal) faces the ground. Invisible in engine for as long as the terrain covered them; the corridor cut exposed the black trench. Fixed by reversing the winding instrip(),kerb_faces()andcrossing_band(). *.blendand*.umapare LFS-lockable in.gitattributes— Git LFS checks them out read-only (444).bpy.ops.wm.save_as_mainfilethen fails ("path is not writable") andEditorLoadingAndSavingUtils.save_mapsilently returns False. All Blender builders now chmod u+w before saving and treat a failed save as fatal; the UE map builders chmod the .umap first. A failed save used to leave downstream steps building from stale files — that produced a degenerate-mesh FBX (all-same vertex indices from an index-arithmetic bug) that cost an hour to trace.- FBX SDK refuses to triangulate the crown-twisted crossing quads ("unable to triangulate mesh ... will be omitted") — emit triangles from the builder, and make sure per-stripe vertex indices advance (
len(verts)must be captured before the loop, not per-corner). - Demo-map coordinate note: UE Y is south-positive; the facade at Y −1450 and the player start at Y −3500 are on opposite sides of the road (Y −1382..+1047) — arrangement is correct; the Sidewalk_North/South mesh names are swapped relative to their physical sides (documented in export_site_fbx.py).
2026-11-07 — ReviewMap corruption: deprecated level calls + save-order bug
EditorLevelLibrary.load_level/new_levelare deprecated no-ops in-nullrhicommandlets (they return without switching worlds, so staged actors land in whatever world is current and get saved over the wrong map). Use theLevelEditorSubsystem:unreal.get_editor_subsystem(unreal.LevelEditorSubsystem).load_level(path)actually switches. Symptom was ReviewMap containing Demo actors +ShotSequencer.- Map-save ordering bug in
Tools/ue_build_review_map.py: the script saved ReviewMap after the DemoMap section, but the switch back to ReviewMap fails silently because the map only exists in memory at that point — so the finalsave_mapwrote the demo world over ReviewMap. Fix: save ReviewMap first, then stage the demo cameras, and never switch back. Both maps verified by probe (ReviewMap lists Review actors; DemoMap lists Demo actors). - Rebuilt ReviewMap after the fixes: character, both cubes (A grounded, B floating at 2 m), pawn, NPC prefab, hero facade all present; user re-approved the shots. README cube naming corrected (README had A/B swapped vs the script).
2026-11-07 — Materials and lighting in the demo map
- Blender flat diffuse colours do not survive FBX -> Unreal. Every site mesh (road, sidewalks, kerbs, crossing paint) imported with a material asset that had a default white base colour, and the terrain imported with
WorldGridMaterial(UE's bright grey grid). That — not the sun — is what made the demo look blown out and washed white. Fix: create the colours in UE (M_Asphalt,M_Concrete,M_Ground,M_RoadPaintviaConstant3Vector) and assign them per actor inTools/ue_build_demo_map.py. - Auto-exposure makes review shots non-comparable: the same road rendered washed-out in one framing and near-black in another because each camera saw a different amount of sky. Fix: an unbound
PostProcessVolumewithAEM_HISTOGRAMandauto_exposure_min_brightness == auto_exposure_max_brightness(fixed exposure). Do not useAEM_MANUAL+auto_exposure_bias— a bias of 11 blew the whole frame out. DirectionalLightComponent.light_colortakes aColorstruct, notLinearColor(unreal.Color(r=255,g=245,b=230,a=255)); passingLinearColorraises a nativize error.- One band per object loses geometry downstream.
crossing_band()createdCrossing_Markingseach call, so Blender renamed the second and third to.001/.002;site_assembly.pyappends by exact name, so only one crossing ever reached the game. Build all bands into a single mesh instead of one object per band. - Street arc length -> UE X: s=0 is the west end of the block (UE X −27966 cm) and s increases eastward, so the designated mid-block crossing at s=130.1 m is UE X ≈ −14956, not the −24291 used by an earlier camera (that is the s=36.6 crossing).
- Camera placement in the demo map needs the mesh bounds, not intuition. The hero façade body spans UE Y −1457..−200, so a "street level" camera at Y −400 is inside the building and renders its unlit back face. Read
get_bounding_box()on the staged meshes before choosing a camera. - "LIGHTING NEEDS TO BE REBUILT" (Build Lighting Only /
BuildLighting) appears whenever a map has Stationary/Static lights and no baked lightmap. For a fully dynamic scene the clean fix isr.AllowStaticLighting=FalseinConfig/DefaultEngine.iniplusset_mobility(ComponentMobility.MOVABLE)on the directional light, sky light and height fog (and MOVABLE on the staged mesh components). Nothing to build, warning gone. Note the cvar is read at startup, so each new commandlet/game run picks it up. - The Android File Server
SecurityTokenkept reappearing inConfig/DefaultEngine.ini; the plugin is disabled and excluded from shipping, so the value is cleared and must stay empty — it is a credential and must not be committed.
2026-11-07 — Worktrees and the material look pass
- A fresh git worktree has no compiled game binaries.
Binaries/is gitignored, soUnrealEditor-Cmdon a new worktree exits "abnormally (error code: 1)" before running the Python script, with no Python error in the log. RunEngine/Build/BatchFiles/Linux/Build.sh GigLineEditor Linux Development -project=<worktree>/Game/GigLine/GigLine.uprojectfirst. /tmp/gigline/*.txtresult files are shared across worktrees. Reading/tmp/gigline/demo-map.txtafter a run can show another worktree's stale output — check the file mtime, or use a per-worktree result path.- Flat Constant3Vector materials read as plastic. Giving each surface a roughness plus a world-space-noise blend between two shades (WorldPosition → Multiply → Noise → Lerp between two Constant3Vectors → BaseColor) is enough to stop the blockout looking like a toy; no textures required.
2026-11-07 — The Y-side bug (found when the frontage was added)
- UE convention in this project: south = +Y, north = −Y. Ground truth is the street mesh:
Sidewalk_Northis Y −1912..−1053 andSidewalk_SouthY +718..+1447. - The hero facade had been placed at Y −1450 (north side) and X −12024..−9420 — 26 m east of the 6840 footprint and on the wrong side of the street. It only looked right because it stood alone in an empty field; the approved screenshots were of a building with no context.
- The two north-side buildings (TCL, Tussaud's) were similarly on the south side:
export_north_buildings.pymirrored Y, which put them where the frontage belongs. - Fix: hero at X −13336 / Y +1307 (the frontage gap between 6902 and 6838); frontage and north buildings exported without a Y mirror (the FBX preset already maps Blender −Y south to UE +Y south).
- Lesson: place a building only against a neighbour or the street mesh, never in isolation. A single object in an empty map hides any number of placement errors; adding the adjacent frontage immediately exposed this one.
2026-11-07 — walkable demo QA bugs (fix branch qa-input-collision-fix-0.1.0)
- "Stuck" window = wrong default map. A plain
./GigLine.shopened/Engine/Maps/Templates/OpenWorld(empty engine template) instead of the demo, so nothing was interactive.GameDefaultMapwas inDefaultGame.iniand not applied; it must live inDefaultEngine.ini[/Script/EngineSettings.GameMapsSettings]withGlobalDefaultGameMode. Verified: plain launch now loads/Game/Hollywood/DemoMap. Evidence:LogLoad: LoadMap: /Engine/Maps/Templates/OpenWorld. - Input assets were pruned from the pak. The Enhanced Input assets under
/Game/Inputwere only soft-referenced at runtime, so the cooker excluded them (cookReferencedSet.txthad zero/Game/Inputlines -> all inputs nil at runtime). Fix: hard-reference viaConstructorHelpers::FObjectFinderin the character ctor (cooker follows hard refs) ++DirectoriesToAlwaysCook. Packaged log then showedctx=ok move=ok ... jump=ok. - Character fell through the site: site meshes staged MOVABLE + PlayerStart far past the north sidewalk. Fix: STATIC mobility + explicit collision (QUERY_AND_PHYSICS/WorldStatic/BlockAll) and PlayerStart at Y -1200 on the sidewalk. Packaged run:
grounded=1 movement=Walking. - Diagnose packaged runtime via the engine log (
.../Linux/GigLine/Saved/Logs/GigLine.log); a headless-nullrhicommandlet cannot validate physics traces (even a spawned cube returns NO HIT there).
2026-11-08 — reference photography gaps closed (local-only)
- Closed the two missing reference angles for the hero façade, both from Wikimedia Commons (gitignored deliverables under
Art/reference/, provenance in sidecars):el-capitan-entrance/—File:Hollywood El Capitan entrance.jpg(+ detail), Andreas Praefcke, CC BY 3.0. This is the isolated hero (6840) entrance/door recess that the notes doc had claimed Commons didn't hold. Query that found it:--query "El Capitan entrance".el-capitan-night/—File:El Capitan theater with Tron marquee.jpg(CC BY-SA 3.0) + West Side Story premiere (Adam Jones, CC BY 2.0). The theatre is the building NEXT to 6840; treat as proximity evidence only, not the hero.
- Commons throttles hard: HTTP 429 after a batch. Fetch fewer files with
--delay/separate calls and wait ~15-20s between runs (queue.pytool default 1 req/s is insufficient once rate limited). - Task was claimed on branch
ref-photo-0.1.2; delivers only the TODO closure (all actual reference files are gitignored, so no code branch content). Reported to coordinator for merge/cleanup.
2026-11-08 — security-game scan: concepts copied, licensing ignored (owner)
- Scanned
/run/media/system/DATA/Documents/06-games/security-gamefor reference material vs ourArt/reference/. Result: its photo set (docs/screens/refs/ hollywood/, 9 files) is already fully duplicated in ourArt/reference/hollywood/(with SIDECAR/BOARD copies) — nothing new there. - What we lacked: its AI concept renders (
docs/screens/concepts/ hollywood_boulevard/, 12 PNGs +.refgen.json). Copied intoArt/reference/el-capitan-concepts/with a folder README. Owner explicitly said ignore licensing completely — they are gitignored local development reference, never committed or shipped;.refgen.jsonstill records the model and source photo per file for traceability. - Use concepts for mood/lighting intent only; geometry truth stays with the photo refs (facade, entrance, night marquee, TCL/Tussauds, Masonic 1922, etc.).
- The security-game
realism/A/B screenshots andconcepts/joke_shop/are its own engine/venue renders — not relevant to our 6840 modeling.
2026-11-08 — ODbL review closed; remaining active TODO items finished
- Wrote
Docs/odbl-review.md(decision 2026-11-08): the shipped block geometry (streets/footprints/sidewalks) is a Produced Work of the OpenStreetMap database (ODbL 1.0). Obligations held: on-screen attribution via AGigLineHUD ("© OpenStreetMap contributors" + "Data © OSM, ODbL 1.0 — openstreetmap.org/copyright"), ODbL link, and the raw extract committed in Geo/ with a manifest (date, bbox, SHA-256). Share-alike is limited to the data layer; project code/art remain CC0 (Docs/licensing.md decision). 3DEP DEM is US Government public domain, unrelated to ODbL. - Closed both [A] items in TODO: (1) sidewalks/roads/collision — collision verified; remaining kerb radii/aprons/kerb-height explicitly tracked as 0.1.1 patch tasks (Sidewalk outer edge, Crossing ramp gradient, Kerb height); (2) ODbL review — recorded. The [D] Blosm-import (blockout cleaning) item stays deferred (tracked separately).
- Branch: odbl-review-0.1.0 (docs + TODO closure only).
2026-11-08 — placement release check; north-buildings camera was 9.5 km off
- Release check 2 (placement vs references): rendered demo, probed DemoMap world positions, compared to Geo references. Matches: road 24.3 m (ref crossings 20.9-25.9), sidewalk centreline offsets 12.5/13.2 m (ref 12.6/13.6), hero frontage 26.0 m / setback ~13 m (ref 25.75/14.5), six south-frontage buildings
- TCL/Tussaud's in verified OSM positions.
- Bug found: the
demo-north-buildingscamera sat at X -22800 (the buildings' own X), aiming at its own position -> 85%-sky screenshot. Probed real geometry: TCL bbox X -22889..-18442, Y -9431..-1802; Tussaud's X -27071..-22799, Y -7586..-1888; hero at X -13336, Y +1307 (frontage 26 m). Camera now at (X -22750, Y +1600) looking north; sky fraction 85% -> 41%. - Getting actor world bounds: spawn at loc(0,0,0) means static mesh bbox IS the world footprint (FBX carries the geo offset). Probe by loading the level explicitly (load_level) — a fresh commandlet starts on Untitled otherwise.
2026-11-08 — collision/seam audit done; QA self-test extended
-qaselftesttransect widened from 84 samples (3 lane centres) to 1456 samples / 16 rows including kerb/road/sidewalk seam Ys (probed: N side -1912..-1053, kerb -1382..-1053, road -1382..+1047, kerb +718..+1047, S side +718..+1447). 0 holes.- New wall probe: ground-referenced horizontal 30 m trace at +160 cm. First attempt used a fixed Z 160 and started INSIDE the earth (street is at Z ~366) -> every row reported a dist-0 blocker. Always ground the probe first.
- Guard: GigLineShotActor must skip rounds under
-qaselftest, else bQuitWhenDone closes the game before the transect phase (observed: game exited at t=2s with "ShotActor: done (4 shots)" + RequestExit). - Results on packaged build: walk 761cm/2s, reset 0cm, pause/unpause, transect 0 holes, wall 0/35 -> all PASS. Evidence recorded in Docs/release-checks.md.
2026-11-08 — setup logs gated behind a cvar (diagnostics item)
- The remaining spawn/input setup logs (settle-timer "start captured", "GigLine IN:" pushed/bound/errors) are now gated behind
gigline.SetupLogs(TAutoConsoleVariable, default 0 viaDefaultEngine.ini [ConsoleVariables]). Re-enable at runtime:gigline.SetupLogs 1. - The
-qaselftestQA logs stay ungated (deliberate release-check feature per Docs/release-checks.md). Verified packaged build: 0 setup-log lines, QA suite still all PASS (walk/reset/pause/transect 0 holes/wall 0-35). - Branch qa-diagnostics-0.1.0. SIM/GROUND probes were removed earlier in the DefaultKeyMappings fix (PR #8).
2026-11-08 — v0.1.0 released
- All six
0.1.0release checks pass and are recorded inDocs/release-checks.md: traversal (seam transect 0 holes), placement vs references, collision+packaging (wall probe 0/35), 60 fps at 1440p (2.42 ms avg; TSR on, VSync off, no FG), fresh-checkout build, packaged Linux build with controls shown in-game. CHANGELOG.mdgets the0.1.0entry; the completed0.1.0section was removed fromTODO.md(goal stays inROADMAP.md); tagv0.1.0cut onmain.- Owner approved the regenerated screenshot set (PR #15) on 2026-11-08.
- THIRD_PARTY_NOTICES switched to checkbox statuses (
[x]/[P]/[ ]); all tools incl. Blosm, QGIS and Git LFS are now verified as tool-only. - Next work: the
0.1.1collision-detail patch (sidewalk outer edge 15.8 cm step, crossing ramp gradient 10 %, kerb height assumption, traversal regression).
2026-11-08 — v0.1.1 released (collision patch)
- Sidewalk upstand now tapers full-at-kerb → zero at the building edge (removes the 15.8 cm outer ledge). Crossing ramp run derives from
--max-ramp-grade(8 % ⇒ 1.63 m) instead of a fixed 1.5 m/8.7 %. Street + site rebuilt, site re-imported, DemoMap rebuilt, packaged build verified. - Pre-check tool fixes: coverage no longer shrinks with
--sample(it usedrange(int(block_len))), and it reports grade + location of the worst step. - Residual, documented not hidden: 13.4 cm surface step at s≈198 (south) / 8.0 cm at s≈251 (north) — a strip discontinuity near a centreline vertex, below the 45 cm MaxStepHeight, so not blocking.
- Kerb height finalised at 0.15 m modelled / 0.13 m built (6 in LA standard; DEM and photos can't resolve finer).
2026-11-09 — Mapbox building data as a second height source
Geo/tools/mapbox_buildings.py(new, stdliburllib) Tilequeriesmapbox.mapbox-streets-v8layerbuildingat every building centroid in the extract and merges the answer with our OSM height + source. Token comes fromMAPBOX_ACCESS_TOKENin the ignored.env; responses cache toGeo/raw/mapbox-cache/.- Mapbox terms restrict persisting/redistributing their data, so
.gitignorenow covers/Geo/raw/mapbox-cache/and/Geo/verification/*mapbox*. The tool also refuses to cache a response that contains the token. - First run (72 buildings): 58 match, 5 close, 3 disagree, 6 non-building.
- It independently classifies our roof/canopy/shelter volumes as
roof/shelter— corroborating the0.1.2finding that they are not buildings. - Remaining disagreements are untyped
building=yes/apartmentsrear structures where our fallback guess (2 × 3.2 m = 6.4 m) meets Mapbox's 3.0 m. - Every "close" case is a
building:levels × 3.2 mbuilding where Mapbox is 0.6 m lower per 6 levels → Mapbox implies 3.1 m per level, not 3.2. Worth revisitingLEVEL_HEIGHTif a measured storey height ever lands. - Its query API surface:
height,min_height,type,extrudeper polygon.
- It independently classifies our roof/canopy/shelter volumes as
2026-11-09 — Mapbox calibration adopted into the blockout
- Owner decision: adopt Mapbox. Applied:
LEVEL_HEIGHT3.2 → 3.1 m inGeo/tools/block_height_report.py, and the Blosm--level-heightdefault inArt/blender/blockout/blosm_blockout.py.HEIGHT_OVERRIDESin block_height_report: ways 1490910971, 1492058875, 1492747608 → 3.0 m (Mapbox single-storey; the 2-level guess gave 6.4 m).classify_height(tags, level_height, way_id)now takes the way id so overrides apply;mapbox_buildings.pypasses it, so the cross-check agrees with the height table.
- Re-running the cross-check: 66 match, 6 non-building, 0 close/disagree.
- Geometry: blockout regenerated at 3.1 (object names preserved — the frontage exporter maps
element.020/element/element.019/named buildings, all still present, 6/6 exported), frontage re-imported, DemoMap rebuilt, packaged build-qaselftestall PASS. - Blosm adds roof mass on top of
levels × levelHeight: 6922 39.1 m vs table 37.2, El Capitan Theatre 20.5 m vs 18.6 — both ~+1.9 m. That is a roof, not a height error; the table is the tagged height, the mesh is storey + roof. - Gotcha: regenerating the blockout is safe for names, but any future change to the import must re-verify the 6/6 frontage export and the
element.*mapping.
2026-11-09 — hero colonnade proportion fixed (47.6 % → 53.1 %)
- The hero's colonnade (columns 2.00–7.95 + capitals 7.95–8.90) was 6.90 m of the 14.50 m elevation = 47.6 %, below the photographed 50–60 %, because the entablature (3.40 m) and parapet (2.20 m) used 5.60 m of the height.
- Thinned ENTABLATURE_H 3.40 → 3.00 and PARAPET_H 2.20 → 1.80 in
Tools/hero_facade_build.py; the colonnade grows to 7.70 m = 53.1 % while the envelope, motto band and medallions keep their relationships (all positioned relative to those two constants). - Gotcha (cost a bad build):
hero_facade_build.pytakes--outfor the FBX and--save-blendfor the.blend. Passing--out <file>.blendwrites FBX bytes over the blend and Blender then reports "File format is not supported". Correct invocation:--out /tmp/gigline/hero-facade.fbx --save-blend Art/...blend --report Art/...json. - Rebuilt hero → re-imported (
SM_HeroFacade, bounds 2604 × 1257 × 1450 cm) → DemoMap rebuilt → screenshots re-rendered. Noteue_hero_facade_import.pysaves the map, sochmod u+wthe.umapfirst or the import silently does not persist.
2026-11-09 — AI detail plates from reference photos; floating windows fixed
Tools/facade_detail_concepts.pygenerates architectural detail plates from one local reference photograph via OpenRouter image models (defaultgoogle/gemini-2.5-flash-image; the sibling project uses the same route intools/generate_textures.py— chat/completions with a text + image_url content part, image returned asmessage.images[].image_url.url). Eight studies,--listfree,--allgenerates,-vNnever overwrites,.refgen.jsonsidecar per image, all output under gitignoredArt/reference/facade-details/.- Prompt gotcha: the model refuses when the prompt describes features the reference photograph does not show ("the image you sent appears to be a decorative architectural detail, not an entrance with steps"). Prompts must describe the reference subject and only restage light/people/surface.
- Output is real: 1184×864 to 1344×768 PNGs, ~2 MB each.
- Measured defect found and fixed: hero window panes sat at Y −1.98 while the mass front is Y −0.50 and the columns reach −1.02 — i.e. glazing floating 1.44 m in front of the wall and ahead of the colonnade. Now
WINDOW_Y = −0.56(6 cm proud of the wall, behind the columns). Visible as dark openings inreview-hero-facade-front. - Reminder:
.blendis LFS read-only on checkout —chmod u+wor Blender fails with "Cannot save blend file … not writable" (this bit again here).
2026-11-09 — UE export hygiene: smoothing groups + facade collision
- All our FBX exporters used
mesh_smooth_type="OFF", which is why every Unreal import logged "No smoothing group information was found for this mesh". The hero now exports"FACE"and its round parts are shaded withshade_smooth_by_angle(35°)before export: cylinders read as round instead of faceted, and the import warning is gone (6 → 0). - The hero façade had no simple collision (complex-only), so line traces and capsule sweeps passed straight through it — the player could walk through the wall of the demo. Caught by a new
-qaselftestfacade occlusion probe (40 m sideways traces from the road centre at walking height). - Auto-generated convex collision on a 95-part 26 m façade is unusable: it produced coarse hulls that bulged into the street and the wall probe went 0/35 → 2/35. Rejected in favour of
UseComplexAsSimpleviaUGigLineAssetTools.set_complex_collision_as_simple(same reason the terrain uses it), saved on the asset. - Probe expectations must mirror the staged buildings: the facade probe now carries explicit X spans per frontage (and the TCL/Tussaud's spans on the north side) so a gap between buildings is not reported as a defect.
2026-11-09 — one FBX export preset instead of six copies
- The
bpy.ops.export_scene.fbx(...)argument block was duplicated in six scripts (hero, block frontage, north buildings, site, scale test, MPFB character), and all six carriedmesh_smooth_type="OFF"— hence the "No smoothing group information was found" warning on every import. - Now
Tools/fbx_export_preset.pyholdsue_metric_v1andue_character_v1:preset_kwargs()(bpy-free, unit-tested) plusexport_static()/export_character()wrappers that importbpylazily. Every caller imports it and passesselection=True/False; the seven duplicated blocks are gone. - Callers in
Tools/resolve the module withsys.path.insert(0, os.path.dirname(os.path.abspath(__file__))); the Blender-sideArt/blender/site/export_site_fbx.pycomputes the repo root (..,..,..) and joinsTools. Getting that wrong silently insertsTools/Tools— check the import path when adding a caller. - Verified end to end: scale test, hero, frontage (6/6), site (7/7) and north buildings all re-exported through the preset, re-imported with 0 smoothing warnings, DemoMap rebuilt and
-qaselftestall PASS (walk, transect 0 holes, wall probe 0/35, facade probe 0/9). mpfb_character_export.pywas not re-run: it needs the MPFB add-on onBLENDER_USER_SCRIPTS("Add-on not loaded: bl_ext.blender_org.mpfb"), which is pre-existing and unrelated; the character is not in0.1.xgameplay.
2026-11-09 — v0.1.2 released
- Cornice finished: projecting corona over a 52-dentil course (0.18 × 0.30 × 0.22 m at 0.45 m centres, z 12.12–12.34, 0.25 m clear of the corona). Hero is now 3 044 tris across 148 parts, still 14.50 m. Verified: packaged
-qaselftestall PASS (transect 0 holes, wall probe 0/35, facade probe 0/9). CHANGELOG.mdgets the0.1.2entry (added/changed/fixed/verified/limitations), the0.1.2section is removed fromTODO.md,ROADMAP.mdstatus updated, tagv0.1.2cut after the merge.- Assumptions recorded rather than hidden: dentil spacing, base and abacus dimensions are not resolvable in our reference photography.
2026-11-09 — elevation cameras levelled (straight verticals)
- archviz MUST: an elevation must not lean.
hero-facade-frontlooked up ~4° from 4.2 m at a 7.6 m target;demo-spawn-viewanddemo-street-levelwere pitched too. All three now put the camera at the target's height, soFindLookAtRotationyields pitch 0 and roll 0 and verticals are parallel by construction. - Do not try to measure verticality from the rendered PNG with a naive edge scan: banners, windows and the parapet produce different edge sets at different heights and the pairwise match is meaningless (it reported a 418 px "drift"). Verify it geometrically in the camera list instead.
2026-11-09 — 0.1.3 packaging/setup fixes
Tools/ue_common.py:make_writable()clears the read-only bit an LFS.umapchecks out with (otherwisesave_map()returns False with no error and the staged actors vanish), andrequire_saved()makes that failure visible.ue_hero_facade_import.pyandue_import_north_buildings.pynever did this — which is why importing the hero appeared to succeed while saving nothing. Verified by forcingchmod u-won DemoMap first: the run loggedmap writable: chmod u+w … (was 0o444), thensave_map: True, exit 0.- Gotcha while adding it:
ue_hero_facade_import.pyhad noimport os/sys(onlyunreal), so the sys.path line raisedNameError: name 'sys' is not definedinside the commandlet — an exit code of 255 with no obvious cause. - Docs:
build.mdgained the current pipeline table (7 site FBXs, frontage, north, hero, map), theBuilds/GigLineQAarchive, the packaged-log path,-qaselftestusage and a traps table;setup.mdmarks the fresh-checkout build verified and documents the two optional API keys; architecture 07 + combined doc corrected the build-output path.
2026-11-09 — 0.1.4 materials + architecture-camera pass
- Material graphs:
ensure_material()now supports masonry coursing (courses_m) and roughness variation. UE'sWorldPositionis in CENTIMETRES, so a course height in metres must be ×100 — the first attempt divided by 0.55 and produced coursing 100× too fine to see. connect_material_expressionsreturns False instead of raising, so an unknown pin name leaves an input unconnected and the material silently compiles with a default (a mask of 0 is invisible). All links now go through alink()helper that logs failures. There is also noMaterialExpressionLess; useFrac → Divide → OneMinus → Clampfor a soft threshold band, orMaterialExpressionStep/SmoothStepif a hard one is wanted.- Material assignment was slot-0-only, which on the hero is the banner — the stone, shadowed stone and glazing kept their flat Blender materials. Assignment is now per slot, keyed on the imported slot material's name (
glass/banner/stoneshadow/stone/roof/wall). Hero: 4/4 slots. - New materials:
M_FacadeStoneShadow(same coursing, a stop darker),M_FacadeGlass(roughness 0.14, specular 0.62 ≈ IOR 1.5 expressed without a refraction pass),M_FacadeBanner(the one saturated surface). - Camera FOV: levelling the elevation cameras left the building small in frame (39 % sky). Added optional per-shot
ShotFieldOfViewtoAGigLineShotActorand a 4th element on the shot tuples; a long lens (42–55°) fills the frame from across the street while the camera stays level, so verticals stay parallel.review-hero-facade-frontsky 39 → 20 %,demo-spawn-view23 → 16 %.
2026-11-09 — facade texture maps (0.1.4)
Tools/generate_facade_textures.pywrites deterministic 512² base-colour and roughness maps (ashlar stone, stucco, asphalt, concrete) with a pure-zlib PNG encoder and hash-based value noise — no numpy/Pillow, ~14 s for all eight, and byte-identical on a re-run (checked by md5sum).ue_build_demo_map.pyimports them to/Game/Hollywood/Texturesand samples them world-aligned: WorldPosition (cm) → ×(1/tile_cm) → ComponentMask(R,G) → TextureSample UVs.tile_cmper material: stone 440, stucco 260, asphalt 900, concrete 250.- Gotchas:
MaterialExpressionTextureSample's UV input pin is "UVs", and the output pin for colour is "RGB" / roughness "R".ComponentMaskpins are R/G/B/A booleans. A textured material skips the graph coursing on purpose — overlaying both would double the course pattern. - The script needs
ROOTinue_build_demo_map.pyandsysimported; forgetting either fails withNameErrorand an exit code of 0 from the commandlet (always read the result file under /tmp/gigline).
2026-11-09 — façade module kit (0.1.4)
Tools/facade_kit_build.pybuilds the kit at the measured 4.96 m bay and 3.10 m storey: pilaster, sill, lintel, window bay, shopfront bay, door bay, cornice, parapet + coping = 16 meshes, exported per-object withfbx_export_presetplus a JSON part report (dims + evidence per piece).Tools/ue_import_facade_kit.py→/Game/Hollywood/Kit/*, complex-as-simple collision (a convex hull would fence off the shopfront glazing).Tools/ue_apply_facade_kit.pylays the kit out from each building's own bbox:bays = round(width / 496 cm), storeys from height minus the 310 cm ground floor. 638 pieces over six frontage buildings + both north buildings.- Bug worth remembering: the first window piece was modelled entirely at +Y (into the wall), so every window sat inside the solid blockout wall and was invisible in every render while the placement maths looked right. The kit now projects the surround 3 cm proud with the glazing 6 cm behind its face — a solid-wall blockout cannot carry a real recess, so the piece has to project.
- Blender gotcha: clearing the scene including materials invalidates handles already taken ("StructRNA of type Material has been removed"); clear objects and orphan meshes only, and create the material after the clear.
- The kit meshes are spawned at the world origin with their position baked into the mesh (like the frontage exports), so placement uses the bbox directly and must not add the actor location.
2026-11-09 — CRITICAL: blockout must be regenerated with --origin
- Regenerating the blockout in 0.1.2 (for the 3.1 m storey height) was run without
--origin, so Blosm centred on the extract instead of fitting the project frame. The whole frontage shifted about 119 m in X and 2.4 m in Y, and the hero ended up outside the 6902/6838 gap — the "verified placement" was undone silently, and every render still looked like a plausible block. - Correct invocation (README documents it, and the affine fit prints
frame correction residual: max 0.0 cm):blosm_blockout.py --osm … --bounds 34.1006,-118.3422,34.1020,-118.3370 --level-height 3.1 --origin 34.101545,-118.338718 --assets-dir … --out … - After re-export + re-import, the frontage spans are back to the verified values: 6922 −24276..−17700, 6902 −17749..−14591, hero −14644..−12028 between them, theatre −12082..−8210, and the north buildings at −22889..−18442 / −27071..−22799.
ue_import_north_buildings.pyalso lackedimport sys, so its sys.path line raised NameError and the import silently did nothing (exit 255). Same trap as the hero importer: these scripts imported onlyunreal.- Lesson: after any blockout regeneration, probe the staged buildings' world X spans and compare them with
Geo/verification/2026-11-06-el-capitan-block.mdbefore trusting a render. A shift of this size is invisible in a screenshot.
2026-11-09 — kit naming + topology validation (blender-modeler checklist)
- Kit assets now follow the Unreal convention: the Blender object keeps its readable name (
Kit_Pilaster), the exported asset and file areSM_Kit_Pilaster. The kit loader strips theSM_prefix so the layout code still addresses pieces by short name. facade_kit_build.py::validate()runs the blender-modeler cleanup checklist on every piece before handoff — non-manifold edges, loose vertices, triangle count — and records the result in the part report. Current kit: 16 pieces, all manifold, no loose geometry, 12 tris each.ue_import_facade_kit.pynow deletes kit assets that the current build no longer produces, so a rename cannot leave two generations of the same piece in the folder (the rename fromKit_*toSM_Kit_*did exactly that until it did).
2026-11-09 — visual comparison loop; screenshots are demo-only
Tools/visual_loop.pycloses the reference → render → feedback loop.--verifygates captures againstTools/visual_targets.json(blank, tone-less, wrong-size fail);--comparemeasures luma, RMS contrast, edge density, palette distance and a 3x3 luma grid against the reference and prints a 0–1 score;--iteration NwritesArt/reference/iterations/N/{metrics.json,feedback.md}. The feedback names the skill to consult, so the next modelling pass starts from a measurement rather than an opinion.- Iterations are gitignored (
/Art/reference/iterations/); only confirmed shots are promoted intoScreenshots/. A test asserts the ignore rule and that every target is a demo-map shot. Screenshots/now holds demo-map captures only — the review staging map is no longer rendered for review.render_screenshots.sh --verifyrenders the demo map, collects the shots and runs the verification gate in one step.- PIL gotchas:
FIND_EDGESleaves its own border ring at 128 (236/3600 px on a 60x60 frame = 6.5% "detail" on a perfectly flat image), so crop after filtering — cropping before does not help, the new border gets the same treatment. Palette shares must divide by the full-image area, not the cropped one. - First comparison (iteration 2): spawn view 0.70, street level 0.35, crossing 0.41, north buildings 0.34. Edge density is the dominant gap everywhere (0.04–0.11 against 0.20–0.32 in the references) — the render still lacks the relief the photographs carry, which is exactly what the
0.1.4façade work is for.
2026-11-09 — theatre marquee + blade sign
Tools/theatre_trim_build.pybuilds the El Capitan Theatre's signature features in world coordinates: marquee canopy 12.0 × 2.60 m at a 4.60 m soffit, illuminated fascia, recessed soffit, vertical blade sign 1.30 × 5.20 m with frame and bracket.Tools/ue_stage_theatre_trim.pyimports to/Game/Hollywood/Theatreand stages one actor per piece (single spawn at the origin — no placement arithmetic).- Two placement bugs, both invisible in a render:
- Axis sign. Blender +Y is Unreal −Y, so a face at Unreal Y +1001 is Blender Y −10.01. Using +10.01 put the whole marquee on the far side of the boulevard.
- Absolute Z. Trim heights were absolute while the pavement at the theatre is 4.56 m above the project datum, so the canopy hung 5 cm above the pavement — the QA wall probe caught it as a knee-height blocker.
GROUND_Znow offsets every height.
- Wall probe: a scan row at Y 1000 sat ~1 cm from the frontage building line and grazed the theatre wall once the kit's proud shopfronts went in. Rows now sample the street envelope only (−1400, −167, 800, −1382, 718).
2026-11-09 — 0.1.4 signed off; 0.1.5 created for the carried geometry
- Owner reviewed the four demo captures locally and approved the set. Signoff recorded in
Docs/reviews/0.1.4-review.md(7/7 items) and in the per-shotApprovedcolumn ofScreenshots/README.md. - The three geometry items that
0.1.4did not finish — retail neighbours from their own references, hero fluting/motto band, blockout cleaning — move to a new0.1.5Façade finishing patch rather than growing this one, plus a much-needed 1440p performance re-measure (625 kit actors have never been measured;0.1.0measured 414 fps). - Decision recorded because it sets a precedent: finish and sign off a version when its stated scope is done, and carry leftover scope into the next patch — do not let a patch accumulate unmeasured geometry.
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 combinedarchitecture.md,Docs/release-checks.md(performance section now last, with the reason),ROADMAP.md(a "Quality priority" section) andAGENTS.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.
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.pyhad its own hero position(-12024.5, -1450, 0)— the pre-0.1.0value, 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.pyis now the single source of truth (hero position, verified frontage spans,span_error/describe_span_failure). Bothue_hero_facade_import.pyandue_build_demo_map.pyimport it, the import asserts the staged span and logsPLACEMENT ERRORwhen 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/GigLineshould 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 inDocs/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_materialinue_build_demo_map.pydeleted 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. NoteMaterial.expressionsis 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.shnow 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.
2026-11-09 — v0.1.5 released
- All actionable
0.1.5work 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.6Retail 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.shcalled./Engine/...for the capture step while every commandlet above went throughrun_cmdlet'scd "$ENGINE". Withset -ethe script died at exit 127 after rebuilding the maps, so the PNGs silently stayed a day old and--verifynever 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_*orNorth_*, 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.pynow skipsMODELLEDlabels, and retail trim stages asRetailTrim_*. - A Blender builder cannot be imported by a unit test (
import bpyat module scope). Pure layout arithmetic lives inTools/_common.py;Tools/tests/test_retail_frontage.pytests it there, and the builder delegates. Same trickfbx_export_preset.pyuses 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.
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.fbxinto Blender gives two actions,GigLine_Idle(frames 1–60) andGigLine_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_Exporthad noSkeletalMeshusage flag, so Unreal substituted the default grey with only aLogMaterialwarning. Fixed in place (used_with_skeletal_mesh = True). PlayAnimationalone is not enough.USkeletalMeshComponent::PlayAnimationneeds the component already inAnimationSingleNodemode; 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 guardWanted == PlayingAnimationthen 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. - Fixed 2026-11-09, two independent causes. (1)
PlayAnimationneedsAnimationSingleNodeplus a live single-node instance; the body is hidden at construction and only unhidden by the camera toggle, so the first call could land before the component was ready and theWanted == PlayingAnimationguard then returned forever.UpdateBodyAnimationnow sets the mode, callsSetAnimation+Play, and re-asserts whileGetSingleNodeInstance()is null. (2) The arms-down offsets lived only in the clip keys — the exporter writes clip data relative to the rest pose, so they arrived in Unreal as a ±20° swing around an A-posed bind.apply_base_posenow bakes them into the rest pose before any clip is authored, which is whatmpfb_character_export.py --pose idlealready did. - The base pose needs inverted signs when baked to rest. Applied as written, the FBX axis mapping produced arms raised in a V; negating the table gives arms down. Both states were verified on the imported asset, not assumed.
- Unreal bone names use underscores:
upperarm01_L, not Blender'supperarm01.L.GetBoneIndex(TEXT("upperarm01.L"))returns INDEX_NONE,GetBoneTransformthen returns identity, and a probe reading it reports a constant 0.0000 — which looks like "animation not applied" and is actually "bone not found". CheckAnimPose.get_bone_names()before trusting any bone probe. Logverbosity does not reach the capture log,Warningdoes. Two diagnostic runs produced zero lines because the messages wereUE_LOG(LogTemp, Log, ...); the same lines atWarningappeared. Instrument with Warning when the evidence has to survive into/tmp/gigline/render-*.log.