Merged PR #9: close the reference-photography task Co-authored-by: Zwitschi <zwitschi82@gmail.com>
274 lines
39 KiB
Markdown
274 lines
39 KiB
Markdown
# 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](TODO.md); development direction lives in [ROADMAP.md](ROADMAP.md); formal setup, pins, and licensing live in [Docs/](Docs/).
|
||
|
||
---
|
||
|
||
## 2026-11-08 — Documentation toolchain installed; asciidoc→markdown conversion
|
||
|
||
- **pandoc 3.13** installed at `~/.local/bin/pandoc` from the [GitHub release tarball](https://github.com/jgm/pandoc/releases/tag/3.13) (statically linked, 161 MB). The Fedora repos carry only `ghc-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` (skips `Binaries/`, `*.uasset`, `.agents/`).
|
||
- **Conversion chain:** `.adoc` → `asciidoctor -b docbook5` → `.xml` → `pandoc -f docbook -t gfm` → `.md` → `prettier --write`. Pandoc's built-in `asciidoc` reader 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 `.adoc` files removed.** Markdown is the canonical format. Architecture doc lives in `Docs/Architecture/` as 13 section `.md` files + combined `architecture.md`.
|
||
- All `.md` in the repo passes `prettier --check` (23 files formatted, `.agents/` excluded as vendored content).
|
||
|
||
## 2026-11-08 — QA branch merged; demo is walkable
|
||
|
||
- **PR #7** squash-merged to `main` as `52b1212` — terrain collision fix, spawn correction, `UGigLineAssetTools`, `HighResShot` fix, `-qaselftest` harness.
|
||
- **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.0` branch deleted (already merged); `gig-line-judge` worktree removed.
|
||
- `qa-input-collision-fix-0.1.0` branch still exists locally/remote (squash merge has no shared ancestry, `git branch -d` refuses; delete with `-D` after release tag).
|
||
- `GigLine SIM` / `GigLine GROUND` TEMP diagnostic logging still in `GigLineCharacter.cpp` — must be removed before `v0.1.0` tag.
|
||
|
||
## 2026-11-08 — Reset was returning to mid-air; QA self-test switch
|
||
|
||
- **`ResetToStart()` restored a `StartLocation` captured in `BeginPlay`**, 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: capture `StartLocation` on 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.
|
||
- **`-qaselftest`** drives the checks headlessly and logs `GigLine 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_DEM` had only a **bounding-box simple collision proxy** (Interchange default). A line trace down at _every_ Y through the block hit `Demo_Terrain_DEM` at 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 `LineTraceSingleByChannel` profile at Y −1912…+1447 reported `top=819 actor=Demo_Terrain_DEM` for the entire street. After the fix it reports `Sidewalk_North 395`, `Road_Hollywood 366`, `Sidewalk_South 317`.
|
||
- **Fix:** force complex-as-simple collision on the terrain. Python **cannot** set it (`StaticMesh`/`BodySetup` expose no such property; `dir()` shows only `lod_for_collision`). Added `UGigLineAssetTools::SetComplexCollisionAsSimple` in the C++ module (`BodySetup->CollisionTraceFlag = CTF_UseComplexAsSimple`, UE 5.8 no longer has `bUseComplexAsSimpleCollision`).
|
||
- **Second gotcha:** the flag lives on the **asset**, not the map. Setting it in a builder session and saving only the `.umap` loses it — `EditorAssetLibrary.save_loaded_asset()` is required, otherwise the next run reloads the bbox proxy.
|
||
- **Third gotcha:** `HighResShot 1280 720` means **multiplier 1280** (UE warns "the high resolution screenshot multiplier is too large for your system"). The correct form is `HighResShot 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.toml` identifies its code as GPL-3.0-or-later; its versioned `LICENSE.md` explicitly 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-42a29fe2` contains those private files and must not be pushed; `/tmp` may be cleared by the system.
|
||
- `git ls-remote --refs origin` succeeded on 2026-11-07 with no advertised refs; `git ls-remote --exit-code origin HEAD` returned 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 fsck` passed 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, `unzip` exit 0). CL `58902052`, `++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 with `HOME=/tmp/uehome XDG_CONFIG_HOME=/tmp/uehome/.config` reached `LogInit: Display: Engine is initialized.`
|
||
- **Vulkan RHI is still unproven.** The successful run used `-NullRHI`. Untested: `UnrealEditor -vulkan` from 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`: `sudo` is blocked (`no new privileges`), `flatpak run` and `podman` cannot create instances, `/dev/nvidia*` is absent so `nvidia-smi` always 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 into `Docs/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.org` throttles 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 shops` returned a Münster street, `Hollywood Masonic Temple entrance` a Seattle card index. Check the hit list before accepting a set.
|
||
- **OpenRouter web search is expensive.** One `gpt-5.2` + `openrouter:web_search` call 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.py` turns results into files by reading `og: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 run` does not work here.** Call the binary inside the app tree: `B=/var/lib/flatpak/app/org.blender.Blender/x86_64/stable/active/files`, then `LD_LIBRARY_PATH="$B/blender/lib" "$B/blender/blender" -b …` with `BLENDER_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`:
|
||
1. it refuses to import until an asset directory is set, and the check fires before the mode is honoured;
|
||
2. 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. `loadMissingMembers` is 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.py` handles 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. `/tmp` is 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 install` the lfs filter is inactive and `git add` does not warn** — it stores the file as a plain blob. `Tools/check-lfs.sh` is the guard.
|
||
- **Never commit reference images or `.env`.** `.gitignore` covers `/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.083` in 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.V7`** to share the installed editor's build environment; V5 is rejected with "modifies the values of properties … not allowed".
|
||
- **`UnrealEditor-Cmd -run=pythonscript` swallows Python `print()`** with `-stdout` in 5.8 — write results to a file instead (see `Tools/ue_scale_test_import.py`).
|
||
- **`unreal.Box` has `min`/`max`, not `origin`/`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` + `FbxImportUI` is the pattern (Interchange pipeline under the hood).
|
||
- **Flatpak Blender headless needs `XDG_CONFIG_HOME=$HOME/.var/app/org.blender.Blender/config`** for extensions: `bpy.utils.user_resource('EXTENSIONS')` otherwise resolves to `~/.config/blender/5.2/extensions` (empty) and `bl_ext.blender_org.mpfb` is undiscoverable. Blosm unaffected (scripts/addons via `BLENDER_USER_SCRIPTS`). Found via `Tools/addon_compat_probe.py`; documented in `Art/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 with `sm.set_editor_property('materials', [SkeletalMaterial])` — the struct field is `material_interface`, not `material`, and `SkeletalMesh.set_material()` does not exist. See `Tools/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 when `image.filepath` (not just `filepath_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 log` and `git status` before committing, and never `git add -A` blindly: it once swept generated `Game/GigLine/Build/**` logs into a commit. Stages the source files deliberately.
|
||
- **`Build/` under a project is generated**, not source — `FileOpenOrder/*.log` and packaging output. `.gitignore` now covers it alongside `Binaries/`, `Intermediate/`, `Saved/`, `DerivedDataCache/`.
|
||
- **Scope boundary for `0.1.0`:** do not tag `v0.1.0` without 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 after `load_map` in a commandlet every `spawn_actor` call 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 `-game` mode and a tiny C++ actor (`AGigLineShotActor`) walks camera setups via `SetViewTarget` + `FScreenshotRequest::RequestScreenshot`. In 5.8 `FScreenshotRequest` has no resolution argument (window size via `-ResX/-ResY`) and no `IsFinished()` — poll `!IsScreenshotRequested()`. Lives in `UnrealClient.h`, not `Misc/ScreenshotManager.h`.
|
||
- **Commandlet-only quirks**: bulk render-target readback (`read_render_target_raw`, `export_render_target`) silently returns empty in commandlets; single-pixel `read_render_target_uv` works. Game targets must use `BuildSettingsVersion.V7`.
|
||
|
||
## 2026-11-07 — Enhanced Input from Python (UE 5.8)
|
||
|
||
- **`unreal.InputCoreTypes` does not exist** in the 5.8 Python bindings, and `unreal.Key` takes no constructor argument. Build a key with `k = unreal.Key(); k.import_text("SpaceBar")` — the export text is the key name (`MouseX`, `Gamepad_Left2D`).
|
||
- **Factories are `unreal.InputAction_Factory` and `unreal.InputMappingContext_Factory`**, not `InputActionFactory`. `create_asset(..., factory=None)` also works.
|
||
- **Do not delete an asset and recreate it in the same commandlet run.** `create_asset` returns `None` for a path deleted earlier in that process. Load the existing asset and overwrite it instead (`Tools/ue_create_input_assets.py: fresh()`).
|
||
- **Modifiers on `EnhancedActionKeyMapping` do not survive saving.** Assigning `InputModifierNegate` to the mapping's `modifiers` reads back correctly in memory, even after a `gc.collect()`, but the saved asset reads `mods=[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 `.umap` silently blocks `save_map`.** `EditorLoadingAndSavingUtils.save_map` returned `False` with 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=pythonscript` still swallows `print()`, so write results to a file; `/tmp` is 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..1450` cm, so to put the front plane on the building line the actor goes at `Y = line + 7 cm`, and `X = east edge + half width`.
|
||
- **`save_map` returns False, without an error, when the packed `.umap` is read-only.** The review map silently keeps its old camera list. `chmod u+w` the file before rebuilding; a `umap` that keeps rendering four shots after a sixth was added is this, not a caching problem.
|
||
- **`StaticMaterial` is a struct**: `material.get_editor_property("material_interface")`, no `get_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-game` holds 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 at `Art/props/reuse-manifest.json`; the 50 `.blend` sources are copied to `Art/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.py` in that project is the OpenRouter image endpoint pattern** (`POST /api/v1/images`, `b64_json`, optional `reference_path` for image-to-image on the chat endpoint) and it already has procedural `brick_facade` / `seamless_concrete` / `asphalt` generators. Worth porting for our facade materials.
|
||
- **Its AI concept frames are restaged photograph references** (`.refgen.json` sidecars 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 ships `setup/neoclassical.py` as 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_local` is the only metres→centimetres conversion; every exporter calls it. `os_common` also needed an explicit `import os` at module level — a helper defined with a function-local import list crashed with `NameError: os` when called from a fresh interpreter.
|
||
- **`asset_metadata.py audit` must recurse.** Sidecars sit next to their `.blend`, which is one level below `Art/blender/`; a flat `glob` found 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 via `os.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.py` lists 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 `.blend` makes Blender skip the save without failing the run.** `Tools/hero_facade_build.py` printed its part list and "bounds" as normal, then went straight to the export: the `.blend` and 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+w` the `.blend` first, then confirm the timestamp moved.
|
||
- **Model bottom must land on Z 0.** The hero portal jambs were sized `PORTAL_H + 0.3` and centred at `PORTAL_H / 2`, so they hung 0.15 m below the pavement and the whole facade imported with `min z = -15 cm`. Always check the asset's `bounds_m.min.z` in 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_South` is the offset band (-14.47..-7.18 m) and `Sidewalk_North` is 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_South` was 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_consistent` needs a mesh context**; in object mode it fails with `poll() failed, context is incorrect`. Flip winding by reversing `polygon.vertices` instead.
|
||
- **`StaticMesh` has no trace-flag property in Python.** Collision complexity is set through `mesh.get_editor_property("body_setup").set_editor_property("collision_trace_flag", …)`; `StaticMeshEditorSubsystem.get_collision_complexity` reads it back but there is no setter.
|
||
- **A `-game` run 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 into `Art/blender/*/` left stale paths in six documents and two tool READMEs. Grep for old paths (`.md` and `.py`) after any move, not just the ones that error at runtime.
|
||
- **Every new tool needs a row in `Tools/README.md` and `Docs/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 Cycles `bpy.ops.object.bake` does update `img.pixels` correctly (verified in-memory px were warm). Writing the PNG ourselves from `img.pixels` is required.
|
||
- **A per-pixel `struct.pack` loop over `img.pixels` is catastrophic in headless Blender** (256² took 25 s). Blender ships numpy: read `img.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 `.umap` silently makes `save_map` return `False` with no error** (already recorded); re-hit in the review pipeline — `chmod u+w` the 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. Reads `OPENROUTER_API_KEY` from the repo `.env` (ignored). Base64 data-URI image under `image_url.url`; OpenAI-compatible body → `POST /api/v1/chat/completions`. `openai/gpt-4o-mini` returns 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 `skill` tool**, only readable as files. The loader resolves `<skills-root>/<pack>/SKILL.md` one 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 though `archviz/SKILL.md` exists.
|
||
- **Fix applied:** 94 symlinks in `~/.agents/skills/<pack>` → `<repo>/.agents/skills/blender-skills/<pack>/`. The catalog then lists all of them (verified by calling `skill` on `collision-proxy`: base dir resolves through the link). `references/` stays unlinked — it is a shared folder with no `SKILL.md`, not a pack.
|
||
- **The AGENTS.md "MCP-driven" caveat is overstated.** 41 of 94 SKILL.md mention MCP, mostly only in the `description` line; 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 commit` that included `MEMORY.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 check `git log -S` for 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 in `strip()`, `kerb_faces()` and `crossing_band()`.
|
||
- **`*.blend` and `*.umap` are LFS-lockable in `.gitattributes`** — Git LFS checks them out read-only (444). `bpy.ops.wm.save_as_mainfile` then fails ("path is not writable") and `EditorLoadingAndSavingUtils.save_map` silently 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_level` are deprecated no-ops in `-nullrhi` commandlets** (they return without switching worlds, so staged actors land in whatever world is current and get saved over the wrong map). Use the `LevelEditorSubsystem`: `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 final `save_map` wrote 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_RoadPaint` via `Constant3Vector`) and assign them per actor in `Tools/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 `PostProcessVolume` with `AEM_HISTOGRAM` and `auto_exposure_min_brightness == auto_exposure_max_brightness` (fixed exposure). Do not use `AEM_MANUAL` + `auto_exposure_bias` — a bias of 11 blew the whole frame out.
|
||
- **`DirectionalLightComponent.light_color` takes a `Color` struct, not `LinearColor`** (`unreal.Color(r=255,g=245,b=230,a=255)`); passing `LinearColor` raises a nativize error.
|
||
- **One band per object loses geometry downstream.** `crossing_band()` created `Crossing_Markings` each call, so Blender renamed the second and third to `.001`/`.002`; `site_assembly.py` appends 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 is `r.AllowStaticLighting=False` in `Config/DefaultEngine.ini` plus `set_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 `SecurityToken` kept reappearing in `Config/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, so `UnrealEditor-Cmd` on a new worktree exits "abnormally (error code: 1)" before running the Python script, with no Python error in the log. Run `Engine/Build/BatchFiles/Linux/Build.sh GigLineEditor Linux Development -project=<worktree>/Game/GigLine/GigLine.uproject` first.
|
||
- `/tmp/gigline/*.txt` result files are **shared across worktrees**. Reading `/tmp/gigline/demo-map.txt` after 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_North` is Y −1912..−1053 and `Sidewalk_South` Y +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.py` mirrored 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.sh` opened `/Engine/Maps/Templates/OpenWorld` (empty engine template) instead of the demo, so nothing was interactive. `GameDefaultMap` was in `DefaultGame.ini` and not applied; it must live in `DefaultEngine.ini[/Script/EngineSettings.GameMapsSettings]` with `GlobalDefaultGameMode`. 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/Input` were only soft-referenced at runtime, so the cooker excluded them (cook `ReferencedSet.txt` had zero `/Game/Input` lines -> all inputs nil at runtime). Fix: hard-reference via `ConstructorHelpers::FObjectFinder` in the character ctor (cooker follows hard refs) + `+DirectoriesToAlwaysCook`. Packaged log then showed `ctx=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 `-nullrhi` commandlet 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.py` tool 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-game` for reference
|
||
material vs our `Art/reference/`. Result: its **photo set** (`docs/screens/refs/
|
||
hollywood/`, 9 files) is already fully duplicated in our `Art/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 into
|
||
`Art/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.json` still 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 and `concepts/joke_shop/` are its
|
||
own engine/venue renders — not relevant to our 6840 modeling.
|