Built the map-content bisect the task asked for: Tools/ue_vehicle_probe_map.py duplicates ReviewMap,
optionally adds one demo mesh and the pawn, and saves it as VehicleProbe.
control (no extra mesh) Z 300 -> 297 rests on the review floor
+ Terrain_DEM Z 300 -> 498 thrown upward
So Terrain_DEM alone reproduces the fault in a map that works. Two fixes were tried:
- Tools/ue_clear_simple_collision.py cleared the auto-generated convex hulls from all 76 site/street
meshes - the terrain had ONE convex element over 279 m - and set them to complex-as-simple. Still rises.
- The terrain component set to QueryOnly, so traces hit it and physics ignores it. Still rises.
Neither changed the behaviour, so the ejection is neither the convex hull nor the collision channel. The
next probe is the complex triangles themselves: give Terrain_DEM no collision at all in the probe map and
see whether the pawn falls. If it falls, the triangle winding is pushing bodies out of the ground mesh -
the same inverted-winding family that made the sidewalk unstandable earlier.
Both tools have README rows; the pawn is parked again.
Tools/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. It
classifies a shadow by per-channel scaling - a shadow darkens every channel by the same
proportion, an object does not - which separates the two where an absolute delta cannot. Four
unit tests cover contact, floating, an empty box and the border median.
On the real spawn view it returns 'unknown': the crop contains two surfaces (pale wall behind,
dark road below) and one global background cannot classify both. Recorded as the next step, not
presented as working.
The spawn height is measured and cleared: tracing down from 30 m, the topmost surface under the
player is Z 366 and the capsule's feet are at 369 - 3 cm, the capsule skin. If the character
still reads as floating, the gap is between the rendered pavement and its collision surface, not
the spawn Z, and that is what to measure next.
Merged COMPARISON.md into the review README as a generated section between
<!-- blender-comparison:begin/end --> markers, so there is one place to look and one place
to sign off. Tools/build_comparison_md.py rewrites only that block - the hand-written
sections around it are untouched - and formats the file with prettier afterwards, because a
generated table is never byte-identical to prettier's output and the repo gates on
--check.
Run it after every capture or Blender render; it is the routine that keeps the page current
with new images rather than a one-off export.
Two tools for the comparison the review was missing.
- Tools/blender_screenshots.py renders out of a .blend: one image per mesh object, and
four turntable angles of the whole scene, framed from the scene's own bounds so adding
geometry never silently crops a shot. Output lands in Screenshots/blender/, beside the
in-game captures.
- Tools/build_comparison_md.py writes Screenshots/COMPARISON.md: a table with the in-game
capture and its Blender render side by side per row, then a signoff table with one row
per in-game screen. Unmatched shots show an em dash rather than disappearing, because a
missing Blender render is information too.
The first run already earns its keep: the Blender render of the owner's hot hatch is red
with clear glazing, while the in-game capture of the same asset is near-black, so the
material colour is being lost on import - visible in one glance in the new page.
The invisible-vehicle defect is closed. Shot 9 now shows the car instead of an empty road.
Tools/vehicle_from_template.py converts Art/blender/vehicle/hot hatch.blend into UE-ready
assets, following two skills:
vehicle-artist - systems split (body 113k tris, trim 10.5k, glass 20.5k, lights 18.6k,
wheels 47.8k), one UCX_ hull 1.45 x 3.94 x 0.76 m, two decimated LODs at 39.7k and 13.6k.
unreal-export - scale normalised to a documented 4.05 m hot hatch before export (the
template measures 7.26 m, factor 0.5579), then verified after import: body bounds
1.51 x 4.02 x 1.22 m against the 4.05 m target.
Staged as five plain StaticMeshActors, because the earlier probe proved those render while
the same meshes attached to the vehicle pawn do not.
Open and recorded rather than hidden: judge_screenshot returns verdict=no confidence=0.9 -
the silhouette does not read as a hot hatch from this angle and the car floats slightly.
The likely causes are a system misclassification in the template conversion and an
origin-height error of a few centimetres.
Follows the vehicle-artist object split, which the first build violated: body, glass,
front and rear lights, mirrors and a single wheel mesh are now separate objects and
separate assets in /Game/Hollywood/Vehicle. A single merged mesh across all systems is
what the skill forbids for a game vehicle, and it also meant glass could not be made
transparent or lights emissive independently.
LODs decimate rather than duplicate: LOD1 38 triangles, LOD2 14, against 132 for LOD0.
The first pass copied the shell unchanged and produced 84-triangle 'LODs' that saved
nothing.
Imports verified with bounds: body 4.76 x 1.76 x 1.28 m, wheel 0.22 x 0.66 x 0.66 m,
which match the modelled dimensions.
Open, recorded: LOD group wiring needs StaticMeshEditorSubsystem.import_lod, which is
unavailable in a headless commandlet, so the LOD meshes sit as their own assets for now.
First 0.3.0 asset. Tools/vehicle_build.py builds a 4.72 x 1.80 x 1.44 m sedan - 2.78 m
wheelbase, 1.62 m track, 0.33 m wheels - as a three-tier silhouette: sill slab, bonnet
and boot shoulders, cabin greenhouse, plus glazing, lamps, bumpers and mirrors. 252
triangles.
Collision is four UCX_-prefixed convex hulls (body, cabin, two bumpers) per the
collision-proxy convention, hidden from render so the preview reads as the car rather
than the colliders.
Two corrections from the preview, both visible in the saved image: the first build was
one tall slab plus a floating cabin and read as a box truck, and the wheels were buried
inside a full-width sill - the sill is now 1.56 m so the wheels stand proud.
Reports every dimension to JSON so the Chaos vehicle config can read the wheelbase, track
and radius instead of restating them.
Two tools that turn character work into something measurable.
- Tools/comfyui_character_blueprint.py generates a four-view sheet (front, side, back,
head) plus a contact sheet from one description, with fixed seeds and a JSON record
of prompts and prompt ids. First sheet: 'GigLine walker', lean pedestrian in a navy
tee, jeans and sneakers.
- Tools/character_match.py compares a rendered character against a sheet: figure
aspect, fill and coverage deltas, mean CIE76 colour distance, dominant-palette
distance, and a side-by-side montage. The deltas stay separate on purpose - a single
similarity score between a photoreal sheet and a low-detail body would be a number
with no meaning.
- comfyui_generate.py grew wait_for_image() so both tools share the polling path
instead of duplicating it.
- 13 unit tests for the colour maths, masks and palette; 95 tool tests pass.
First real result, recorded rather than smoothed over: aspect delta 1.24, fill delta
0.12, mean deltaE 31.9, palette deltaE 25.4. The montage shows the reason - the model
is a nude body and the blueprint wears a tee and jeans, so clothing dominates every
metric. Figure segmentation is explicit (--render-box): auto-detection picked backdrop
blobs on every attempt, and a background plate is the proper fix.
Conflicts resolved by union, not by picking a side:
- AGENTS.md keeps the stronger visual-review rule (README updated with every visual
change; a stale section is a defect).
- render_screenshots.sh keeps the absolute engine path from main and adds
-thirdperson from this branch - both were needed and neither alone worked.
- Screenshots/README.md now lists six shots, retail and third-person both embedded;
the known-issues tables merged, with the animation defect and unproven camera
collision added to main's rows.
- visual_targets.json carries both expectations.
- Architecture docs: ADR-8 (realistic buildings) and ADR-9 (third-person body) both
present and in numeric order; ADR-8 was mis-placed before the Quality Requirements
heading in the combined document by an earlier edit, now fixed.
- Binary conflicts resolved to main's content, since the retail trim only exists
there.
Checks: 82 tool tests, prettier clean, six embeds resolve.
Two parametric cycles authored at 30 fps instead of hand-keyed poses, so the loop
closes by construction: GigLine_Idle (60 f, breathing sway, feet planted) and
GigLine_Walk (30 f, one stride, hip 0.45 rad / knee 0.55 / arm 0.32, arms
counter-phased, torso counter-rotation).
The limb axes were measured, not assumed: a probe rotated upperleg01.L by 0.5 rad
on each axis and read foot.L, which moved -0.022 -> +0.362 in Y on X, twisted on Y
and abducted on Z. Using the wrong axis here would have produced a character
scissoring sideways, which no import error would have caught.
The character export preset now bakes animation. Without bake_anim the exporter
writes a static bind pose and Unreal imports a mesh with no AnimSequence and no
error, so the importer asserts a clip exists and prints VERDICT: PASS/FAIL.
Verified in /Game/Test/Character: Idle 1.967 s and Walk 0.967 s, both AnimSequences,
skeleton intact. Playback on a possessed pawn is not yet verified: the demo pawn has
no visible body, so that check belongs with the third-person camera item.
Models the two retail buildings beside the hero from their own photograph rather
than the generic kit: bronze cornice, mural row, lettering band, stepped Deco
parapet and rounded corner tower on 6800 Lee Drug; burgundy fascia, pilasters,
white-framed windows and banded parapet on 6806-6820. Reference analysis written
before any geometry (Art/blender/retail/ref-analysis.md), every unmeasured
proportion carried into the part report as an assumption.
6822 Budget is deferred, not guessed: no photograph of it exists and the 6.0 x 5.5 m
sliver cannot be read out of the neighbour shot.
Pipeline fixes found on the way:
- render_screenshots.sh called ./Engine/... for the capture step while every
commandlet used the absolute path; set -e killed the script at 127 *after* the
maps were rebuilt, so screenshots silently stayed a day old.
- the facade kit applies to every Frontage_*/North_* actor, so it pasted its own
language over the new modelled facades; it now skips them, and the trim stages as
RetailTrim_*.
- pure bay arithmetic moved to Tools/_common.py so it is unit-testable without
Blender (4 new tests, 82 total pass).
Known gaps recorded in Screenshots/README.md, not hidden: the parapet reads as a
blank band, murals and lettering do not resolve at review distance, the tower is a
plain cylinder, and the review camera crops the east end.
The reported fall-through was a misdiagnosis: the player never fell. The
imported Terrain_DEM had only a bounding-box simple collision proxy, so a
downward trace anywhere in the block hit the terrain at Z 819 (the mesh
bbox max) and the street sat under an invisible 8 m lid. Standing on that
box looked plausible, which is why it read as working.
- add UGigLineAssetTools (C++): force CTF_UseComplexAsSimple on a static
mesh. UE 5.8 dropped bUseComplexAsSimpleCollision and Python cannot set
the flag; the builder also saves the asset, since the flag lives there
and a map-only save silently reverts it.
- spawn (-13336, -1150, 600): inside the carved corridor (the cut is a
10.7 m half-width around the road centreline), lands on the road at 456.
- fix ResetToStart returning to the mid-air PlayerStart captured in
BeginPlay: StartLocation is now captured on the first grounded frame.
- HighResShot 1280x720 instead of "1280 720", which is a multiplier.
- add -qaselftest: scripted walk/reset/pause on a core ticker plus a
swept ground transect. Results: walk 746 cm/2 s with Z steady, reset
0 cm from spawn, pause/unpause 1/0, transect 84 samples 0 holes.
- rebase onto main, keeping the frontage materials and placement fixes.
- Tools/judge_screenshot.py: judges a screenshot against an expected-scene
description via OpenRouter multimodal image understanding, reading
OPENROUTER_API_KEY from the ignored .env. --image+--expect for one shot,
--shots to run every review shot in Screenshots/README.md. Returns a
{verdict, confidence, reason, missing} JSON.
- Documented in AGENTS.md (Visual review pre-check step) and Tools/README.md;
recorded the OpenRouter payload format gotcha in MEMORY.md.
README rewritten around the actual state (walkable DemoMap, blockout facades,
planned patches); Tools/README now lists all seventeen tools by role; build.md
gains the content pipeline table and the two enforced rules (one unit conversion,
collision decided at import). Setup gains a content-pipeline row, the toolchain
state notes the demo level exercises the engine path, release checks record the
Blender pre-check that backs traversal, PLAN points at ROADMAP/MEMORY/Screenshots,
and THIRD_PARTY_NOTICES gains rows for the hero facade and the candidate props.
Also fixed the last stale reference to Geo/scripts in MEMORY.md - a sweep found
old paths in six documents after the tool move, which the memory entry now warns
about.
Geo/tools/ is now every geographic script, with Geo/README.md documenting each
one; the Blender-side builders moved beside their outputs in Art/blender/*/ and
import their helpers from Geo/tools/osm_common.py.
That module gains to_unreal_local() and to_blender_local(): the only place in the
repository that multiplies metres by 100, so a second silent scale cannot be
introduced. block_height_report.py adds height provenance for the block — 3 of
15 buildings have no height data and render at a placeholder 6.4 m.
asset_metadata.py writes and audits <asset>.geo.json sidecars; four now exist
(hero-facade, terrain, street, blockout) and the audit recomputes each centre from
the origin, failing anything that disagrees.
- AGigLineShotActor (dev-only): walks a fixed camera list in -game mode,
captures via FScreenshotRequest, quits. Test content only.
- ue_build_review_map.py builds /Game/Test/ReviewMap from current content
(gray floor, key light, sky, MPFB character, 1 m scale cubes).
- render_screenshots.sh: build map -> run game -> collect PNGs into
gitignored Screenshots/.
- Screenshots/README.md (committed) describes each capture with approval
state; AGENTS.md gains the visual-review instructions.
common_fetch was out of material, so an OpenRouter web-search call (gpt-5.2,
openrouter:web_search) returned candidate photo pages and page_image_fetch.py
resolved each og:image and downloaded it, recording page URL and hash locally.
Four frames kept, including the clearest entrance view yet.
The frames overturn the inherited 'eight stone steps, 12 m wide': the entrance
is three portals with individual short flights. Recorded as a resolved conflict
with the photographs as evidence.
A text search had stopped surfacing new material, so commons_fetch.py gained
--category. Category:Hollywood Masonic Temple and Category:El Capitan Theater
yielded 32 more frames locally, including 4-5k px frontal façades, a 2019
street-level shot of both buildings together, the 1922 postcard, and the first
oblique evidence for the 6806-6822 retail run. 68 local images total, none
committed. A clean sidewalk-level frame of the hero entry is still absent.
Five usable sets added locally (hero daytime façades, the El Capitan Theatre
block, night premiere staging, Ovation Hollywood, and the Lee Drug neighbour) —
41 images total, none committed. Records what remains missing: a sidewalk-level
frame of the hero entry, the 6806-6822 retail run, and a night frame of 6840
itself rather than its neighbour.
commons_fetch.py now survives a throttled download instead of discarding the
rest of the batch, and takes --delay.
site-2026-11-06.blend gathers terrain, street and blockout into categorised
collections: 281 objects, ~58k triangles. A downward raycast on a 1 m grid along
both sidewalks and the designated crossing finds no holes, so nothing drops the
player on either side. Two steps are flagged: 15.8 cm at the sidewalk's outer
terrain seam and 10-13 cm ramps at the crossing flush transitions, steeper than
a real 8% ramp.
Sampling is projected through PROJ: placing points with the OSM spherical
approximation put them metres off the UTM meshes and reported 91 phantom holes.
Tools/street_mesh.py builds the corridor in the project frame from the measured
profile: carriageway -10.5..+10.5 m, kerbs 0.15 m (0.13 m as built), sidewalks
4.0 m south and 5.3 m north, all elevations sampled from the DEM. Kerbs flush at
the three signalised crossings. Crown at mid-block reproduces the cross-sections
to the centimetre.
Tools/blosm_blockout.py imports the committed extract in 3Dsimple mode and
reports per-building dimensions so a units failure is visible in the log. The
result (87 meshes) is committed as the blockout baseline: hero 25.68 x 54.88 x
14.50 m against 25.75 x 54.88 m of OSM geometry, so the import is 1:1 metres.
Two headless defects needed working around: the asset-directory check that
fires before mode is honoured, and an empty default-level table sending the
add-on down a broken relative import. Member fetching stays off so the import
does not depend on Overpass.
The lfs filter is inactive until 'git lfs install' writes filter.lfs.*, and a
matched file is then stored as a plain blob without any warning. Document that
instead of the wrong claim that git add fails, and add a guard script.
Create Game/, Art/, Geo/, Tools/, Docs/ with LFS and ignore rules, record the
workstation audit, toolchain pins, setup checklist, release checks, and
licensing policy. ROADMAP 0.1.0 environment tasks marked to match verified state.