Files
gig-line/Tools
zwitschi 05c1488c00 feat(camera): third-person rig, V toggle, visible body
One pawn, both rigs (ADR-9): a skeletal body hidden in first person, a
collision-tested spring arm, and a toggle that swaps the active camera and the
body's visibility. Shipped default stays first person; -thirdperson starts the
pawn in the third-person rig so the review shot is reproducible, and the
screenshot run passes it.

Three defects found and two fixed:
- The skin material had no SkeletalMesh usage flag, so Unreal silently substituted
  the default grey. That is why the body rendered as clay.
- render_screenshots.sh still called ./Engine/... on this branch (the fix lives on
  the facade branch and never reached main); applied here so the shots generate.
- OPEN: the idle clip is bound and PlayAnimation is called - the run logs the clip
  name - but the body still renders in bind pose, arms out. Recorded with that
  evidence in TODO.md rather than called done.

Shot 5 (demo-third-person) added and documented; camera collision is implemented
but unproven, since the spawn has open space behind it.
2026-11-09 10:33:06 +01:00
..

Tools

Repeatable helpers that drive Blender, Unreal, and the reference/asset sources. Nothing here is hand-run twice: every step that produced committed content has a script.

Geographic tools live in Geo/tools/ and the Blender-side builders live next to their output in Art/blender/*/; this folder holds everything that talks to Unreal, the network, or the repository.

Conventions

  • One script, one job. Every input is an argument; no interactive prompts.
  • Absolute paths where a tool crosses into a sandbox (Blender Flatpak, the Unreal commandlet). Those processes do not share the repo's working directory.
  • Unreal commandlets swallow print(), so each writes a result file under /tmp/gigline/. Read the file; do not trust the exit code alone.
  • No credentials, no writes outside the project by default.

Repository and assets

Tool Purpose
check-lfs.sh Fails if the lfs filter is missing or an LFS-tracked file was committed as a plain blob
facade_kit_build.py Builds the reusable façade module kit at the measured 4.96 m bay: pilaster, sill, lintel, window bay (surround + glazing), shopfront bay (piers, glass, fascia), door bay (jambs, door, threshold), cornice and parapet + coping. Exports each piece with the shared preset plus a JSON part report (dims + evidence per piece).
ue_import_facade_kit.py Imports the kit into /Game/Hollywood/Kit/* as static meshes with complex-as-simple collision; writes /tmp/gigline/kit-import.txt.
theatre_trim_build.py El Capitan Theatre trim: projecting marquee canopy, illuminated fascia, recessed soffit, vertical blade sign with frame and bracket. Built in world coordinates (span and pavement height baked in) with a JSON part report, so staging is a single spawn at the origin.
ue_stage_theatre_trim.py Imports the trim to /Game/Hollywood/Theatre and stages one actor per piece in the DemoMap with site materials; clears a previous run first.
ue_apply_facade_kit.py Lays the kit over a building's street face from its own bounding box: bay count = round(width / 4.96), storeys from the height, shopfronts on the ground floor, windows above, pilasters on bay boundaries, cornice and parapet at the top. Clears a previous application first; unique labels per building.
generate_facade_textures.py Deterministic stdlib PNG generator for the facade maps (T_StoneAshlar, T_Stucco, T_Asphalt, T_Concrete, each _BC base colour + _R roughness). Pure-zlib writer, hash-based value noise, identical bytes on every run; --size and --out (/tmp/gigline/textures by default). ue_build_demo_map.py imports them into /Game/Hollywood/Textures and samples them world-aligned.
ue_audit_scene.py Gate on the DemoMap: no unmaterialed actor, every verified frontage span within tolerance (ue_placement), no duplicate actor labels. Writes /tmp/gigline/scene-audit.txt; render_screenshots.sh --verify fails on a non-zero failure count. Catches the class of defect that still renders plausibly.
ue_placement.py Single source of truth for where staged actors belong in the DemoMap (hero position, verified frontage spans) plus span_error/describe_span_failure validators. Exists because the hero position was written twice and drifted — the importer kept the pre-0.1.0 value and moved the hero 13 m east onto the wrong side of the boulevard. No unreal import, so it is unit-tested.
ue_common.py Shared helpers for the Unreal commandlet scripts: make_writable() clears the read-only bit an LFS .umap checks out with (otherwise save_map() returns False silently), and require_saved() makes that failure explicit.
fbx_export_preset.py The single FBX export preset (ue_metric_v1 / ue_character_v1): metres→centimetres via FBX_SCALE_NONE, axis mapping east +X / north −Y / up +Z, mesh_smooth_type="FACE" (smoothing groups survive the import). Every Blender exporter imports it instead of copying the argument block; preset_kwargs() is bpy-free and unit-tested.
sibling_asset_audit.py Reads the sibling project's prop library from GLB headers into Art/props/reuse-manifest.json; copies nothing
scale_test_export.py / ue_scale_test_import.py The 1 m = 100 uu scale test; the named presets ue_metric_v1 live in these two files

Reference gathering

Tool Purpose
commons_fetch.py Wikimedia Commons: search, exact File: titles, or a whole category; writes a provenance sidecar.json
page_image_fetch.py Resolves a web page's og:image and downloads it (Flickr, Commons file pages, photo blogs)
addon_compat_probe.py Verifies MPFB/Blosm can load and generate headless on the pinned Blender

Blender content

Tool Purpose
hero_facade_build.py Parametric hero façade; emits a JSON report naming the evidence behind every part
character_anim_build.py Authors the basic animation set on the MPFB rig — GigLine_Idle (60 f) and GigLine_Walk (30 f) sampled from parametric cycles at 30 fps — and exports one animated FBX with the ue_character_v1 preset. Rotation axes are measured on the rig, not assumed (probe in the docstring). --out, --report.
mpfb_character_export.py MPFB human, standard rig, baked skin, FBX export

Unreal import and level building

Tool Purpose
ue_import_site.py Imports the six site FBXs into /Game/Hollywood/Site, setting collision complexity per mesh
ue_hero_facade_import.py Imports the façade and places it on the measured building line
ue_build_demo_map.py Builds /Game/Hollywood/DemoMap: lighting, façade, site meshes, player start
ue_build_review_map.py Rebuilds Test/ReviewMap, stages the shot camera list, and stages the demo-map views
ue_create_input_assets.py Creates /Game/Input actions and the mapping context headlessly
ue_character_anim_import.py Imports the animated FBX into /Game/Test/Character and verifies an AnimSequence exists — an FBX exported without bake_anim imports a mesh and no clips, with no error. Writes VERDICT: PASS/FAIL to /tmp/gigline/character-anim-import.txt.
ue_character_import.py Skeletal import plus the material/texture chain Interchange skips

Visual review

Tool Purpose
render_screenshots.py / render_screenshots.sh Rebuilds the review map, runs the game, captures the shot list into Screenshots/
judge_screenshot.py Vision-model pre-check: judges a screenshot against an expected-scene description (--image + --expect, or --shots for every shot in the README). Reads OPENROUTER_API_KEY from .env (ignored). Optional, advisory only — the model is unreliable on this content and never replaces a human verdict.
visual_loop.py The reference-vs-render loop. --verify gates each capture against its expectation in visual_targets.json (blank/short/wrong-size/tone-less shots fail); --compare measures luminance, contrast, edge density, palette and 3×3 composition against the reference photograph and turns the deltas into modelling actions naming the skill to consult (blender-modeler, archviz, materials, lookdev, camera-cinematography); --iteration N writes the artefacts under Art/reference/iterations/N/, which is gitignored until the owner confirms a shot.
visual_targets.json Per-shot expectation and reference photograph for visual_loop.py. Every entry must be a demo-map shot.

Gameplay QA

Command Purpose
UnrealEditor <uproject> /Game/Hollywood/DemoMap -game -qaselftest Headless behaviour check: drives forward input for 2 s, then resets and pauses. Logs GigLine QA: ... PASS/FAIL lines and a final RESULT. Runs only with -qaselftest, so normal play is unaffected.
-qaselftest phases Walk, reset, pause/unpause, a 16-row seam transect (0 holes), a ground-referenced wall probe (0/35) and a facade occlusion probe that fires sideways from the road centre and requires a blocking wall wherever a building is staged (0/9), plus a frame-time capture writing Saved/Profiling/perf-walk-frametimes.csv and logging GigLine PERF:.
gigline.SetupLogs cvar Spawn/input setup logging, off by default (DefaultEngine.ini [ConsoleVariables]); enable at runtime with gigline.SetupLogs 1.

Reference and concept generation

Tool Purpose
facade_detail_concepts.py Generates frontal building plates (whole elevation, entrance in its facade, or building with neighbours) from a local reference photograph with an image model (OpenRouter); close-up detail crops are deliberately not requested because they cannot be measured for proportion. --list is free and shows which studies have their reference present; --all/--only <name> generates. Never overwrites (writes -v2, -v3), writes a .refgen.json sidecar per image (model, prompt, source photo, SHA-256), and puts everything under Art/reference/facade-details/ — gitignored, local-only, never shipped. Costs money per image, so it is never automatic.
judge_screenshot.py (see Visual review)

Input injection

Tool Purpose
input_emulation.py Pure-stdlib ctypes/fcntl driver that creates a virtual keyboard+mouse on /dev/uinput. The desktop treats it as real hardware, so it proves whether OS input actually reaches the game. Needs /dev/uinput writable (ACL on this host).
input_test.py Injects a scripted sequence into the focused window: --sequence move:2,look:3,jump:1,reset:1,pause:1,click.

Tests

python3 -m pip install -r requirements-tools.txt
python3 -m unittest discover -s Tools/tests -t Tools -p 'test_*.py'

Stdlib unittest, no Blender/Unreal process. See tests/README.md: test the pure parts (encoding, classification, maths, parsing); if logic is buried in main(), extract it into a function so it can be tested.