A single vision model has been wrong in both directions on this project - it missed real defects and disputed frames that were correct - so agreement between different model families is worth more than one confident opinion. judge_screenshot.py now runs two models by default (openai/gpt-4o-mini + qwen/qwen3.7-flash, both served on OpenRouter; claude-3.5-haiku is gone, which the first attempt discovered with a 404). combine() reconciles them: agreement carries the verdict with the mean confidence, disagreement reports 'uncertain' and keeps both reasons rather than letting the more confident model win. --model still forces a single judgement, and every line prints which model said what. comfyui_generate.py generates the same prompt and seed with two checkpoints, scores both by high-frequency detail (mean absolute Laplacian, which catches blurry or flat results), keeps the loser as <out>__<model>.png with its sidecar, and moves the winner onto --out. --alt-model restores the single run. Tests cover the reconciliation - agreement, disagreement, unknown verdicts, and no verdicts at all - and the suite is at 103.
16 KiB
16 KiB
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_retail_detail.py |
Imports the two retail trim meshes into /Game/Hollywood/Frontage with complex-as-simple collision — the trim decorates a solid the player already collides with, so it must not add snag points. Staged by the demo builder as RetailTrim_*. |
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. |
retail_frontage_build.py |
Retail frontage trim for 6800 Lee Drug and 6806–6820 (0.1.6): bronze cornice, mural row, lettering band, stepped Deco parapet and rounded corner tower on Lee Drug; burgundy fascia, pilasters, white-framed windows and banded parapet on 6806–6820. Built in world coordinates from the blockout bounds, one joined mesh per building, JSON part report with the evidence behind every piece. 6822 Budget is not built — no photograph exists. |
retail_frontage_build.py |
Retail frontage trim for 6800 Lee Drug and 6806–6820 (0.1.6): bronze cornice, mural row, lettering band, stepped Deco parapet and rounded corner tower on Lee Drug; burgundy fascia, pilasters, white-framed windows and banded parapet on 6806–6820. Built in world coordinates from the blockout bounds, one joined mesh per building, JSON part report with the evidence behind every piece. 6822 Budget is not built — no photograph exists. |
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 |
hero_iterate.sh |
Full hero-facade loop in one command: chmod the Blender files, build the facade, import it, render the capture set, judge the shot and write a comparison plate. bash Tools/hero_iterate.sh [shot]. |
hero_compare.py |
Side-by-side plate of a reference photograph and a rendered capture, scaled to one height and labelled. --reference, --render, --out. |
ue_clear_simple_collision.py |
Removes the FBX import's auto-generated convex hulls from the site and street meshes and sets them to complex-as-simple, so physics bodies are not pushed out of a 279 m hull. |
ue_vehicle_probe_map.py |
Copies ReviewMap to VehicleProbe, optionally adds one demo mesh and the Chaos pawn, and saves it, to bisect which map content throws a physics body into the air. Set GIGLINE_PROBE_ASSET to stage one mesh; unset for the control. |
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). Judged by two models by default (--models a,b), reconciled: agreement carries the verdict, disagreement reports uncertain with both reasons. Optional, advisory only — 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. |
comfyui_generate.py |
Local ComfyUI driver: list checkpoints, run txt2img through the HTTP API, and write a provenance sidecar (model, prompt, seed, full graph). Two checkpoints by default: both are generated from the same prompt and seed, scored by high-frequency detail, and the better one takes --out while the loser is kept beside it. Local, human-triggered. |
comfyui_character_blueprint.py |
Generates a character blueprint sheet - front, side, back and head views plus a contact sheet - from one description, with fixed seeds and a JSON record. |
character_match.py |
Compares a rendered character against a blueprint: figure aspect, fill and coverage deltas, mean CIE76 colour distance, dominant-palette distance, and a side-by-side montage. |
generate_character_textures.py |
Procedural skin-pore and hair-strand textures with sidecars; no model involved. |
vehicle_build.py |
Models the drivable sedan: three-tier body, four wheels, lamps and bumpers, plus four UCX_ convex collision hulls; saves the .blend, exports an FBX with the ue_metric_v1 preset, and writes a report with every dimension the vehicle config needs. |
ue_vehicle_import.py |
Imports the sedan's six systems into /Game/Hollywood/Vehicle as separate static meshes, plus the two LOD meshes; the body carries the UCX_ hulls as simple collision. |
vehicle_from_template.py |
Converts the owner's hot hatch.blend into UE-ready assets: normalises the scale to a documented 4.05 m, splits systems (body/trim/glass/lights/wheels), adds a UCX_ hull, decimates two LODs, exports FBX per system and writes a report. |
blender_screenshots.py |
Renders review screenshots out of a .blend: one per mesh object (--mode objects) and four turntable angles (--mode scene), framed from the scene's own bounds, into Screenshots/blender/. |
build_comparison_md.py |
Regenerates the Blender vs in-game section of Screenshots/README.md between <!-- blender-comparison:begin/end --> markers: in-game captures beside their Blender renders in one table, plus a signoff table with one row per in-game screen. Pairs by name with an alias map for camera-named shots. Run it after every capture or Blender render. |
shadow_gap.py |
Measures the vertical pixel gap between an object's lowest pixels and the top of its shadow, as a fraction of the object's height, and calls it contact or floating. Objective replacement for asking a vision model whether something floats. |
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.