Chaos drives a skeletal mesh, so the static system split was not drivable on its own. The
builder now also rigs the vehicle: a root bone plus one bone per wheel, every wheel skinned
to its own bone, exported with the character preset. SK_Vehicle_Sedan imports with
SK_Vehicle_Sedan_Skeleton (508 triangles).
Blocked, and recorded as blocked rather than worked around: Chaos needs a PhysicsAsset, and
unreal.PhysicsAssetFactory in 5.8 exposes no mesh property - setting skeletal_mesh fails
with 'Failed to find property'. Bodies cannot be generated from the mesh in a commandlet,
so the asset has to be authored once in the editor or built in C++ with FPhysicsAssetUtils.
Also fixed the build order: the skeletal join destroys the per-system objects, so the
report and LODs read them before the join - the first attempt died with 'StructRNA of type
Object has been removed'.
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.
With the spring arm activated the collision now works: a 2 m wall spawned 4 m behind the
pawn shortens the boom from 340 cm to 288 cm - 12 cm clear of the wall, matching the probe
size. -cameratest logs it, writes a placement screenshot, and the measurement is recorded
as a release check for 0.2.3.
Two traps documented because each one made a working mechanism look broken:
- a spawned static-mesh actor has no collision unless it is created deferred and given
its mesh and collision before FinishSpawning (three runs reported blocks trace=0)
- TargetArmLength is not rewritten when the boom shortens, so reading the property
reports the requested length; the camera's distance from the head is the measurement
Writes the 0.2.3 changelog entry and prunes the section from TODO.md.
The skin was never grey. A red test colour reached the render and the slot was confirmed
holding M_Character_Skin, so the material and the assignment were both fine - the body
was simply standing in building shadow and lit almost entirely by the blue skylight
(intensity 2.6). Warm skin under blue fill reads blue-grey.
Raising the sun from pitch -62 to -74, same camera:
face (99, 98, 100) -> (131, 121, 117)
arms (67, 74, 81) -> (71, 78, 85)
This touches every approved shot, so it sits on the branch for the owner's approval
rather than being folded in. Also records that the Unreal commandlet silently drops
unknown switches, which is why a --unlit-test flag never took effect.
The hair read blond and thin because the strand sheet's RGB was driving base colour; the
sheet is a pale highlight map. Colour is now a dark constant (0.030/0.020/0.014) and the
texture supplies only alpha. Sampled in the render: hair (31, 26, 21).
Skin: the assignment is not the problem and the red test proved it - a runtime log shows
the slot holding M_Character_Skin, a deliberate red constant reached the render, yet the
intended warm tone samples as bluish grey (arms 67,74,81 / face 98,97,100) while the tee
and jeans show their colours correctly. Recorded in TODO.md with the next suspects rather
than guessed at.
The temporary material log is removed again.
Solidify thickness went from 1 cm to 2 cm and the shell is fattened 8 mm along its own
normals afterwards. That closed the shoulders, hips and knees where the body was
showing through the cloth.
Evaluation against the blueprint, same shot and box before and after:
mean deltaE 31.9 -> 21.8
palette deltaE 25.4 -> 19.7
aspect delta 1.24 -> 1.22
Also fixes a crash I introduced: binding the materials in Tick used
ConstructorHelpers::FObjectFinder, which is fatal outside a constructor - the game died
with 'FObjectFinders can't be used outside of constructors' and the render run failed
with signal 11. The binding now lives in BeginPlay and uses LoadObject.
Not fixed, and recorded: the arms and face still render neutral grey, so
M_Character_Skin itself is suspect rather than the assignment.
Follows the character-artist clothing workflow literally: duplicate the body's own
faces for the garment base, solidify for cloth thickness, and keep each piece a
separate mesh. Attached as leader-pose components beside the hair, so no second rig.
- Tools/character_clothing_build.py builds three garments from the body mesh, trimmed
geometrically (height band plus a width limit) instead of by dominant bone: the bone
filter left holes wherever a face's strongest bone was outside the allowed list.
- Tools/ue_character_clothing_import.py imports them as CharacterTee / CharacterJeans /
CharacterShoes, each arriving with its own single material slot.
- The pawn attaches all three, hidden with the body in first person.
Three traps recorded in MEMORY.md, each of which cost a build: MPFB weights its helper
geometry to groups that dominate every vertex, Blender refuses to apply a modifier to a
mesh with shape keys, and object-mode polygon selection is discarded on entering edit
mode - the delete wiped the whole garment.
Known defects, in TODO.md: the garments have coverage holes at shoulders, hips and
knees, and the hair reads light and wispy since its material was repointed to the
masked strand sheet.
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.
The first card build anchored every card on a fixed outward vector, so the silhouette
was a spiked crown. Each card is now oriented from the scalp normal at its anchor: the
length runs along the downward tangent, the width across it, and a small tip lift gives
the cut volume. Three overlapping rows (7/9/10) replace the single ring; overlap is
what stops cards reading as separate spikes.
26 cards, 401 verts, 224 quads, 468 tris, still all quads on one head bone.
Recorded as still open: the skin renders darker than the baked diffuse suggests, and
the generated pore and strand textures remain unwired because the material-editing
tool segfaults in the headless commandlet.
Follows the character-artist skill on two points it is explicit about: hair for a
game character is a card system, and swappable parts stay separate meshes.
- Tools/character_hair_build.py builds SM_Char_Hair: a scalp cap plus 18 cards in
three rings, quads only, every vertex weighted 1.0 to the head bone, UV-mapped for
the strand sheet from Tools/generate_character_textures.py. 321 verts / 404 tris.
- Tools/ue_character_hair_import.py imports it as its own skeletal mesh, which is the
point: joined into the body, Unreal collapses the second material slot and the hair
renders with the skin material.
- The pawn carries it as a second USkeletalMeshComponent on SetLeaderPoseComponent, so
it follows the head bone with no second rig and hides with the body in first person.
Still wrong and recorded: the cards point outward, so the silhouette reads as a spiked
crown rather than a short cut. The next pass orients them tangentially along the
skull with a second overlapping row. Tools/ue_character_materials.py also segfaults in
the headless commandlet when editing a material graph, so the generated textures are
not wired yet and the hair keeps its Blender base colour.
The stride shot regressed twice for two different reasons, and only the second was
the clip. The walk cycle was authored on a sine, which is zero at both the start and
the middle of the cycle - exactly the two frames where the legs pass together - so a
deterministic capture at any phase near those frames read as a still. The probe
proved it: frames 0 and 15 carried identical hip rotations while 7 and 22 were the
stride.
The cycle now starts at maximum stride (cosine). Length, amplitudes, keys and loop
are unchanged; only the phase moved. demo-third-person-walk is back as shot 8 and
shows a genuine stride with the curled hands from 0.2.1.
8/8 shots verify.
Two defects from the 0.2.0 review, both fixed and both measured.
- Fingers splayed because the bind is an A-pose hand. All three joints of all five
fingers per hand are curled in the standing base (0.28/0.34/0.22 rad), with the
curl axis probed on the rig first. The front shot now shows relaxed hands.
- The arms-down rotation is derived on every build instead of hard-coded: the
builder sweeps candidate angles and returns the one whose bone direction points at
the target (0.60 rad, direction dot 1.000 this build). The old 0.55 was a
hand-swept constant.
- Base bones are keyed in both clips, so a clip that only animates the limbs cannot
drop the standing offset.
Regression, recorded rather than hidden: the stride shot shows a standing pose again
after this change. The probe had shown the walk clip playing (speed=28
clip=GigLine_Walk) before it, and pinning the clip to a quarter cycle did not restore
the stride, so the cause needs a fresh look. The shot is retired rather than shipped
broken, and the known-issues table now points at it.
Shot 8 shows the body mid-stride in profile: legs apart, arms counter-swinging. The
traversal release check for 0.2.0 is closed with that evidence.
The reason earlier attempts froze on one pose was not the clip. A probe of the
single-node instance showed the clip advancing normally (assetTime 0.19 -> 1.03,
rate 1.0, playing) while the pawn's speed stayed 0 and the clip stayed Idle: the
call that enables walking sat in the shot sequencer's BeginPlay, which runs before
the player pawn exists, so the cast found nothing and silently did nothing. Moved
into the per-frame follow update, the same run logs walk=1 speed=28 clip=GigLine_Walk
and the stride appears.
Also: the walk expectation was missing from visual_targets.json after the earlier
retirement, so --verify was checking 7 shots while 8 were captured. Restored; 8/8
verify.
The shot actor can now photograph a moving pawn: a per-shot ShotFollow flag re-places
the camera every frame at a fixed offset in the pawn's frame, and the walk is driven
per shot (SetReviewWalk) with the pawn returned to spawn after a follow shot, so the
fixed-camera shots later in the list still find it in place. With that, the capture
stopped depending on where a 4.5 m/s pawn happened to be.
Two further bugs fixed on the way:
- the engine's screenshot folder is cleared before each run. The collect step copies
every review-*.png it finds in Saved/, so a shot retired from the camera list kept
reappearing with byte-identical content from an older run - which is how a walk shot
that was never captured looked like a captured one.
- UpdateBodyAnimation no longer calls SetAnimation every frame. Re-setting the same
clip restarts it, which freezes the body on frame 0.
What the deterministic capture then exposed: the walk clip is applied but does not
advance - every walking frame shows the same pose (arms forward, legs together). The
stride shot is retired again rather than shipped, and TODO.md names the next step:
log GetCurrentAssetTime across frames and check the imported PlayRate. The harness
stays wired for when the clip plays.
Shots 6 and 7 were re-rendered after these changes and still pass; 7/7 verify.
Screenshots 6 and 7 are approved, so the remaining 0.2.0 work is the traversal
release check. This adds the piece of harness it needs - `-walk` holds forward input
from the first frame, so the pawn actually crosses the sidewalk while the third-person
rig follows it - and records what that check still lacks.
A stride shot was attempted three times and removed: a fixed review camera cannot
catch a pawn moving at 4.5 m/s. Each attempt framed it at a different distance and
the last two missed it entirely, so shipping it would have been a broken shot with a
green verification. The TODO item now says the blocker is the capture harness (a
camera that tracks the pawn), not the game, and names where the shot goes back in.
The flag stays because it is what the traversal check will drive.
The character now stands naturally: arms at its sides with a soft elbow bend, hands
at the hips, feet flat. Both character shots confirm it, front and back.
The root cause was not the pose. The FBX importer had been silently refusing to
replace the existing assets: after changing the arm base from 1.55 rad to 0.55 rad
and rebuilding, the imported clip returned the IDENTICAL bone quaternion
(+0.006, +0.002, -0.179, -0.984). replace_existing=True was set and delete_asset
returned False even after chmod u+w, because the pawn's constructor hard-references
the clips. Importing under a new name (CharacterV2) created fresh assets and the
same probe then returned (+0.439, +0.898) — the rotation had landed. Every earlier
attempt had been invisible for this reason.
The rotation itself is measured, not inherited: sweeping it in Blender gives 0.55 rad
for arms down and clear of the body, 0.65 for straight down against the hip, 0.75 for
past vertical and into the torso. It lives in the clamp keys, not in the bind: every
bind experiment was impossible on this shape-keyed MPFB mesh or left Unreal showing
the old bind.
Remaining, for 0.2.2: the fingers splay from the bind, and the walk clip has still
not been captured in motion.
The character now stands with its arms at its sides instead of in a bind A-pose,
and there is a second camera to prove it: the third-person shot only ever showed
the pawn's back, so a front-facing pose defect was invisible in review.
demo-third-person-front camera on the road, ~6 m in front, looking back north
at the pawn; embedded in Screenshots/README.md as shot 7.
The pose work itself is a chain of measured findings, all recorded in TODO.md:
- clip data is exported relative to the REST pose, so arms-down offsets held only in
the keys arrived as a ±20 degree swing around an A bind;
- a rest-pose bake alone does not move the mesh: the MPFB mesh has shape keys, the
armature modifier cannot be applied while they exist, and the vertices stayed in
the A shape (X span unchanged at 1.051 m). Clearing the shape keys, baking the
deformation into the vertices and re-binding is what made bind and clips agree.
Not settled, and left as an open item rather than a claim: the base pose uses
inverted signs to compensate for the FBX axis mapping, the upper arms clip into the
torso, and the fingers splay. An aim-based pose module was written and then removed
- applied without the vertex bake it produced arms raised in a V, and a half-working
shared module is worse than none. The proper solve is 0.2.2: apply the standing pose
at character creation, then author clips against that bind.
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.
What the first pass got wrong, and what changed:
- The mural plates were placed 0.20 m INSIDE the solid blockout volume, so they
never rendered. Nothing can be recessed into a solid we do not cut: the inset is
now faked by a proud frame with a near-flush plate, and the row reads.
- Detail sat 0.05 m off the wall, under a pixel of depth at 40 m. Trim planes now
stand 0.10-0.20 m proud, and the parapet became two tiers with deeper reveals.
- 6806's parapet started at 0.62 H and left a 4.6 m blank band; the window storey
and parapet moved up to follow the photograph.
- Security bars on the upper windows, twelve fluted fins on the corner tower.
- The trim's stucco/white/glass slots are mapped onto the demo's look materials, so
the walls sit in the same noise/texture family as the rest of the block; the
colour slots sampled from the photograph stay as authored.
- Review camera pulled back and widened to 66 deg so the east end is in frame.
- The scene audit caught duplicate RetailTrim_ actors: the demo builder's clear list
did not include the new label, so every rebuild stacked another copy.
Still open and not fixable by geometry: wall lettering. A plate at 0.10 m does not
read at 40 m, so it moves to the 0.9.0 decal route (TODO.md, Screenshots/README.md).
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.
Adds the five remaining verified south-side buildings to the demo block
(6922, 6902, 6838 El Capitan Theatre, 6822 Budget, 6806-6820, 6800 Lee Drug),
exported in place from the OSM blockout by Tools/export_block_frontage.py,
imported to /Game/Hollywood/Frontage/ and staged by the demo builder with the
look material set (stone for the theatres, stucco for the retail run).
Adding neighbours exposed a pre-existing placement bug that was invisible while
the hero facade stood alone in an empty field:
- UE south is +Y here (south sidewalk Y +718..+1447), but the hero sat at
Y -1450 on the NORTH side of the street.
- It was also at X -12024..-9420, 26 m east of the 6840 footprint; the frontage
gap between 6902 and 6838 is X -14591..-12082.
- The two north-side buildings were on the SOUTH side, because their exporter
mirrored Y.
Fixes: hero moved to X -13336 / Y +1307, inside its frontage gap; the frontage
and north-building exporters no longer apply a Y mirror (the FBX preset already
lands Blender -Y south on UE +Y south); player start and demo cameras moved to
the north sidewalk looking south; sun raised to pitch -62 so both frontages read
instead of the north side silhouetting.
Result: the demo is a street - continuous south frontage with the hero in it,
both sidewalks, the road with the designated crossing, and the north-side
buildings across the road.
Screenshots re-rendered and approved by the owner.
Squash of branch frontage-block-0.1.0 (PR #6).
Completes "Materials and look development for all exported assets". Every
exported surface previously carried a flat Constant3Vector fill, which read as
plastic. ensure_material() in Tools/ue_build_demo_map.py now builds a
procedural material per surface:
- base colour blended between two shades by world-space noise
(WorldPosition -> Multiply -> Noise -> Lerp -> BaseColor) for large-scale
variation,
- an explicit roughness and specular so the surface type is readable.
M_Asphalt 0.92, M_Concrete 0.88, M_KerbConcrete 0.85, M_Ground 0.98,
M_RoadPaint 0.62, M_FacadeStone 0.72, M_Stucco 0.86
Site meshes take their material by mesh name; the hero facade and the two
north-side buildings take theirs by actor label, so they are covered even
though other scripts stage them. Materials are deleted and rebuilt on every
run, so a look change cannot be masked by a stale asset.
Result: the facade reads as warm stone with surface variation, asphalt as dark
asphalt with grain, concrete and kerbs lighter and matte, and the north
buildings in stucco instead of flat grey.
Screenshots re-rendered and approved by the owner. The richer texture-based
pass (baked/procedural brick_facade, seamless_concrete, glass, material
instances) stays in the 0.1.4 facade patch.
Squash of branch materials-lookdev-0.1.0 (PR #5).
Completes "Geographic attribution display" for 0.1.0. The demo is built from
OpenStreetMap data (ODbL 1.0) and needs attribution wherever the derived work
is shown, so the credit now lives in the running game, not just the documents.
- AGigLineHUD (C++): plain AHUD::DrawText in the bottom-left draws
"(c) OpenStreetMap contributors" and "Data (c) OpenStreetMap contributors,
ODbL 1.0 - openstreetmap.org/copyright", with a shadow pass so it stays
legible over bright pavement and sky. No UMG widget asset is involved, so a
cook cannot drop the attribution through a missing widget reference.
- AGigLineGameModeBase: HUDClass = AGigLineHUD, so the credit is on by default
in both the DemoMap and the ReviewMap.
- AGigLineShotActor: screenshots are requested with the UI shown, so the credit
appears in every capture under Screenshots/ - the obligation is provable from
the images rather than only described.
Docs:
- THIRD_PARTY_NOTICES records that the attribution is displayed in-game and
where the code lives.
- Screenshots/README documents the overlay.
- TODO marks the item done and narrows the remaining ODbL question to the
shipped-derivative-database posture.
Squash of branch gameplay-attribution-0.1.0 (PR #4).
Completes "Fixed daytime lighting and basic ambience" for 0.1.0. All 14
screenshots approved by the owner.
Lighting rig (Tools/ue_build_demo_map.py):
- Sun pitch -50 / yaw 120, intensity 4, warm (255/245/230), atmosphere sun light
- Sky light 2.6 with real-time capture, sky atmosphere, height fog (density 0.008)
- Unbound post-process volume with fixed exposure (min == max brightness) so a
shot cannot wash out or crush to black depending on how much sky it frames
- Dynamic-only: r.AllowStaticLighting=False plus MOVABLE lights and mesh
components, so the editor's "LIGHTING NEEDS TO BE REBUILT" warning no longer
fires and nothing needs baking
Bugs found and fixed on the way:
- Blender diffuse colours do not survive the FBX -> Unreal import: every site
material arrived white and the terrain arrived with WorldGridMaterial, which
is what actually made the scene look blown out. The demo builder now creates
and assigns M_Asphalt, M_Concrete, M_Ground and M_RoadPaint.
- Only one of the three crossings reached the game: each zebra band was its own
object, so Blender renamed the duplicates .001/.002 and exact-name site
assembly skipped them. All bands are now one Crossing_Markings mesh.
- Demo cameras: street-level sat inside the facade body (rendering its unlit
back face) and the crossing camera aimed at the s=36.6 crossing instead of
the designated s=130.1 (UE X -14956).
- The terrain had no material and imported as a bright default grid.
- Cleared a committed Android File Server SecurityToken (plugin disabled and
shipping-excluded; the value must stay empty).
Docs:
- Screenshots/README.md: one consolidated table for all 14 shots with map,
camera view and description; all approved.
- MEMORY.md: records the material-import, exposure, light_color struct,
band-naming, camera-bounds and lighting-build traps.
- TODO.md: lighting item marked done.
Squash of branch gameplay-lighting-0.1.0 (PR #3).
Root cause of the empty review frames: EditorLevelLibrary.load_level/new_level
are deprecated no-ops in -nullrhi commandlets, so staged actors landed in the
current world and got saved over the wrong map. Switched both map builders to
the LevelEditorSubsystem and reordered the review builder to save ReviewMap
before the DemoMap section (the switch-back to a map that only exists in
memory silently failed, writing demo content over ReviewMap).
Rebuilt both maps, re-rendered every screenshot; user visual check passed.
Screenshots/README.md cube naming corrected (A grounded, B floating), approval
states updated; TODO.md visual-review notes closed. MEMORY.md records the
deprecated-call and save-order traps.
- street_mesh.py: continental zebra markings (0.5 m bars, 0.5 m gaps, 3 m
band) at all three signalised crossings, triangles not quads (FBX SDK
refuses to triangulate the crown-twisted quads), stripes +6 cm above the
DEM so they stay above the road surface; winding flipped so every street
surface faces up (road/sidewalks/markings were 100% down-facing and
invisible in engine once the terrain stopped covering them).
- terrain_from_dem.py: cut a 10.7 m corridor around the Hollywood Blvd
centreline so road, kerbs and sidewalks are not buried under the terrain;
sidewalks themselves cover the transition band, walk-check stays at 0 holes.
- Blender builders save with a writability guard + fatal failure: *.blend
is LFS-lockable and checks out read-only; a failed save previously left
downstream steps building from stale files.
- ue_import_site.py: Crossing_Markings imports with no collision (paint on
the road); ue_build_demo_map.py stages it and chmods the lockable .umap
before saving (save_map silently returned False on read-only maps).
- ue_build_review_map.py: demo-crossing camera moved to the real crossing
(s=130.1 m -> UE -24291,-1340) and fixed the ensure_writable path (was
missing the .umap suffix, so the chmod never fired).
- New C++ AGigLineNPCPerson: reusable NPC/pedestrian prefab built on the MPFB
human. ACharacter capsule half-height 83 cm (total 1.66 m, head top ~1.66 m
so a 1.60 m camera doesn't clip), the character skeletal mesh on the built-in
mesh as the visible body (feet on Z 0), AutoPossessAI for 0.5.0 pedestrians.
Staged at Z 0 in the review scene; captured as review-npc-prefab.png.
- AGigLineShotActor now names screenshots by stable camera name (no list-index
prefix): inserting or reordering cameras no longer renumbers files or orphans
the old ones. Re-rendered all review/demo shots with stable names.
- ue_build_review_map.py: chmod the target .umap before save_map (git marks maps
444 on checkout, silently making save_map return False and dropping stages).
- Screenshots/README.md, ROADMAP, TODO updated for the prefab + stable names.
- DemoMap.umap rebuilt by the render pipeline (north buildings + demo cameras).
- TCL Chinese Theatre (6925) and Madame Tussaud's (6933) exported from the
verified OSM blockout with ue_metric_v1 (export_north_buildings.py), with a
Y-mirror so they land north of the road, across from the hero facade.
- ue_import_north_buildings.py imports + stages them in the DemoMap as
North_TCL_Chinese / North_Madame_Tussauds (TCL Y 1802..9431, Tussaud's
1888..7586; hero at Y -1450, road centreline ~0).
- render_screenshots.sh now also runs the DemoMap capture pass; added a
demo-north-buildings camera, so the new buildings get a review shot.
- README/roadmap updated; flat facade materials pending the 0.1.4 patch.
Tools/ue_build_demo_map.py builds /Game/Hollywood/DemoMap: sun, sky, the hero
facade on the building line, six site meshes imported with collision, and a
player start on the north sidewalk. Tools/ue_import_site.py imports the site
FBXs; terrain, road and sidewalks use complex-as-simple so a simple hull cannot
ramp over the kerbs. Art/blender/site/export_site_fbx.py exports one FBX per
object so each can carry its own collision.
Reset (R) and pause (Escape) join the character and the input context: reset
restores the spawn transform and clears momentum, pause is a real game pause that
releases the cursor. DefaultInput.ini captures the mouse on launch.
Three placement defects found by measuring the imported actors rather than
trusting the plan: the spawn was on the wrong sidewalk because the FBX flip
inverts north, a mirror I added to the exporter double-corrected an already
correct sidewalk, and the street meshes only carry part of the north walk - the
rest is terrain.
The portal detail shot failed for a modelling reason, not a camera reason: the
doors were 0.1 m panels on a solid plinth, so they read as shading. The plinth is
now four piers plus a lintel band, with a 1.2 m reveal, stone jambs and lintels
standing 0.10 m proud and three mullions per opening. review-06 frames them from
8 m at eye height.
Two defects fixed while doing it: the jamb boxes hung 0.15 m below the pavement,
so the facade imported with min Z -15 cm; and a rebuild run against a read-only
.blend silently skipped the save, which is why screenshots kept showing the old
geometry.
Tools/sibling_asset_audit.py reads the sibling security-game project's prop
library straight from the GLB headers: 20 street props, 11.8k triangles, every
one with a Blender source. The 50 .blend sources are copied to
Art/props/blender_src/ with a manifest; nothing is imported into Unreal yet. Its
building meshes are deliberately not taken - they are Godot GLBs built around a
29.6 m facade that this project measured at 25.75 m.
The facade proportions were wrong against the photograph: 5.7 m of blank wall
above the capitals. Entablature is now 3.4 m, parapet 2.2 m, with architrave and
cornice bands and square pedestals, so the elevation reads as layered stone.
Tools/hero_facade_build.py builds the north elevation parametrically and writes
a JSON report naming the evidence behind every part: the 25.75 m frontage and
14.5 m height are measured, the 4.96 m column bay comes from the sibling notes
and matches that frontage, the three portal flights follow the photographs (five
risers, overturning the inherited eight-step, 12 m wide claim), and the rest is
labelled assumption.
Tools/ue_hero_facade_import.py imports it with the ue_metric_v1 preset
(2604 x 1257 x 1450 cm, 1892 tris, 4 materials) and places it on the measured
building line of /Game/Hollywood/DemoMap.
Two new review shots at review-04 and review-05, both pending approval. Camera
placement cost four renders: the first two sat inside the facade body, and the
frontage faces Unreal -Y, so a camera has to stand at Y < -1450 cm, not north of
the building line in project terms.