review: restore character + cubes in ReviewMap, re-render all shots
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.
This commit is contained in:
1 parent
ab24ef3b24
commit
c9e933376c
22 files changed
+92
-69
No files matched your search
@@ -101,7 +101,7 @@ Any change that affects the game's appearance — meshes, materials, lighting, l
|
||||
1. Run `Tools/render_screenshots.sh` (rebuilds `Game/GigLine/Content/Test/ReviewMap.umap` from current content, runs the game on it, captures the fixed camera list, copies the PNGs into `Screenshots/`).
|
||||
2. Update `Screenshots/README.md`: one row per screenshot with what it shows and an approval state; describe any new shots or scene changes in the same change.
|
||||
3. The PNGs **are committed** so reviewers see exactly what the build rendered (`.gitignore` does not exclude them). Keep them in sync with this README: drop retired shots in the same change.
|
||||
4. Wait for the user's approval before treating a visual change as done; record approval by ticking the table in `Screenshots/README.md`.
|
||||
4. Wait for the user's approval before treating a visual change as done; record approval by ticking the entry in `Screenshots/README.md`.
|
||||
|
||||
New content that needs a different camera angle gets a new entry in the shot list in `Tools/ue_build_review_map.py`.
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -164,3 +164,9 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
|
||||
- **`*.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).
|
||||
@@ -20,14 +20,15 @@ and exits — about a minute. The camera list lives in
|
||||
## Scene conventions
|
||||
|
||||
- **Floor**: 160 m square, top face at Z 0. The character measures Z 0..83 cm.
|
||||
- **Cubes**: `Cube_ScaleTest_A` floats at Z 2 m, `Cube_ScaleTest_B` rests on the
|
||||
floor; both are exactly 1 m — the scale reference.
|
||||
- **Cubes**: `Cube_ScaleTest_A` rests on the floor, `Cube_ScaleTest_B` floats at
|
||||
Z 2 m; both are exactly 1 m — the scale reference.
|
||||
- **Lighting**: one warm key light plus sky fill (review rig, not the `0.1.0`
|
||||
daytime look). Beyond the floor is open: the horizon is a black band.
|
||||
|
||||
## Review-map shots
|
||||
|
||||
All ☑ approved 2026-11-07 unless noted. `review-npc-prefab` is new (☐ pending).
|
||||
All ☑ approved 2026-11-07 (re-rendered with the character and cubes restored;
|
||||
user visual check passed).
|
||||
|
||||
> Naming: shots use their stable `Tools/ue_build_review_map.py` camera name (no
|
||||
> index prefix), so adding or reordering cameras never renumbers files or
|
||||
@@ -54,7 +55,7 @@ Rendered from `/Game/Hollywood/DemoMap`, the walkable level. Click an image for
|
||||
| --- | --- | --- | --- |
|
||||
| `demo/review-demo-spawn-view.png` | [<img src="demo/review-demo-spawn-view.png" alt="spawn view" width="200">](demo/review-demo-spawn-view.png) | ☑ | Spawn view: north sidewalk, the hero façade across the road |
|
||||
| `demo/review-demo-street-level.png` | [<img src="demo/review-demo-street-level.png" alt="street level" width="200">](demo/review-demo-street-level.png) | ☑ | Street level: portals, flights, banners, colonnade |
|
||||
| `demo/review-demo-crossing.png` | [<img src="demo/review-demo-crossing.png" alt="crossing" width="200">](demo/review-demo-crossing.png) | needs review | The designated mid-block crossing (s=130 m): zebra markings on the road, kerbs flushed to ramps. Camera on the north sidewalk looking south-west. Note: the road renders near-black in shadow — the fixed-daytime-lighting item, not the markings |
|
||||
| `demo/review-demo-crossing.png` | [<img src="demo/review-demo-crossing.png" alt="crossing" width="200">](demo/review-demo-crossing.png) | ☑ | The designated mid-block crossing (s=130 m): zebra markings on the road, kerbs flushed to ramps. Camera on the north sidewalk looking south-west. Note: the road renders near-black in shadow — the fixed-daytime-lighting item, not the markings |
|
||||
| `demo/review-demo-north-buildings.png` | [<img src="demo/review-demo-north-buildings.png" alt="north buildings" width="200">](demo/review-demo-north-buildings.png) | ☑ | North-side background buildings (TCL, Tussaud's) across the Blvd |
|
||||
|
||||
## What to look for
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -75,7 +75,7 @@ The MPFB character asset is planned as the NPC template (see `ROADMAP.md`). Keep
|
||||
|
||||
- [x] Give the MPFB character asset a standing idle pose so review and NPC shots show a person, not an A-pose bind. Done: `Tools/mpfb_character_export.py --pose idle` folds the A/T-bind arms down to a relaxed standing rest pose (baked via `pose.armature_apply` before export; `upperarm01.L` rest z −0.71 → −1.91 rad), re-exported, re-imported (138 bones), rendered in `review-01-character`.
|
||||
- [x] Bake a second skin tone or clothing layer, so the character does not read as a grey mannequin in shots. Done: the v2-skin bake came out **black** and slow at 1024², so we synthesize a warm skin-tone diffuse directly (`Tools/mpfb_character_export.py` `make_diffuse`, with a darker shorts-suggestive bottom band) via a stdlib/numpy PNG writer. Character reads warm, not grey. Screenshot approved (`Screenshots/review-01-character`).
|
||||
- [x] Keep the character as a reusable NPC prefab: a visible body, a collision capsule, and a head-height check that the camera does not clip at 1.60 m — for when NPCs/pedestrians are wired in (`0.2.0+`), not the `0.1.0` player. Done 2026-11-07: `AGigLineNPCPerson` (C++), ACharacter with capsule half-height 83 cm / total 1.66 m (head at ~1.66 m, so a 1.60 m camera does not clip), the MPFB human on the built-in mesh as the visible body, `AutoPossessAI` for pedestrians; confirmed spawned feet-on-Z-0 and rendered in `Screenshots/review-npc-prefab` (☐ pending approval).
|
||||
- [x] Keep the character as a reusable NPC prefab: a visible body, a collision capsule, and a head-height check that the camera does not clip at 1.60 m — for when NPCs/pedestrians are wired in (`0.2.0+`), not the `0.1.0` player. Done 2026-11-07: `AGigLineNPCPerson` (C++), ACharacter with capsule half-height 83 cm / total 1.66 m (head at ~1.66 m, so a 1.60 m camera does not clip), the MPFB human on the built-in mesh as the visible body, `AutoPossessAI` for pedestrians; confirmed spawned feet-on-Z-0 and rendered in `Screenshots/review-npc-prefab` (☑ approved 2026-11-07).
|
||||
- [x] Screenshots use stable camera names (no index prefix), so inserting a shot no longer renumbers/ orphans files; `AGigLineShotActor` + `Screenshots/README.md` updated.
|
||||
|
||||
### Asset production
|
||||
@@ -93,8 +93,8 @@ The MPFB character asset is planned as the NPC template (see `ROADMAP.md`). Keep
|
||||
|
||||
- [x] First-person movement, mouse look, and Enhanced Input bindings. Implemented 2026-11-07: `AGigLineCharacter` (C++, 1.75 m capsule, 1.60 m eye camera, 4 m/s walk, mouse look in deg/s, jump) as the default pawn, with five actions and an 11-entry context in `/Game/Input` built by `Tools/ue_create_input_assets.py`. **Not yet confirmed interactively** — launch PIE on `Test/ReviewMap` and walk. Screenshot review pending (`Screenshots/README.md`).
|
||||
- [~] Collision, reset, and pause functionality. Reset (**R**) and pause (**Escape**, a real game pause that releases the cursor) are implemented in `AGigLineCharacter` with `/Game/Input/IA_Reset` and `IA_Pause`. Collision now exists — terrain, road and sidewalks import as complex-as-simple, so a simple hull cannot ramp over the kerbs. **Not verified by hand**: walk, reset and pause need a human at the keyboard. No pause menu widget yet, only the pause itself.
|
||||
- [-] Build the walkable demo level: terrain, street, and hero facade assembled into one map with a start point on the north sidewalk, so the demo is something a player can walk rather than a review staging. **Built 2026-11-07** by `Tools/ue_build_demo_map.py` → `/Game/Hollywood/DemoMap`: sun/sky, `Hero_Facade`, six site meshes imported with collision (`Terrain_DEM`, `Road_Hollywood`, both sidewalks and both kerbs), and a `Demo_PlayerStart` on the north sidewalk 20 m back from the frontage. Demo views in `Screenshots/demo/` are ☐ pending review.
|
||||
- [x] Walkable sidewalks and a designated street crossing. Crossing chosen: mid-block signalised crossing, OSM way 385648319, at 130 m along the block (`Geo/verification/2026-11-06-street-profile.md`). Done 2026-11-07: continental zebra markings (0.5 m bars / 0.5 m gaps, 3 m band) at all three signalised crossings, no collision on the paint; kerbs were already flushed at the crossings. Enablers found on the way: the street surfaces were 100 % down-facing (invisible in engine) — winding flipped; the terrain buried the street — 10.7 m corridor cut around the centreline (sidewalks cover the transition, walk-check 0 holes); LFS-lockable `.blend`/`.umap` check out read-only — builders now chmod before saving and fail loudly. Screenshot review pending (`Screenshots/demo/review-demo-crossing`); the corridor renders near-black in shadow — that is the still-open **Fixed daytime lighting** item, not the crossing.
|
||||
- [-] Build the walkable demo level: terrain, street, and hero facade assembled into one map with a start point on the north sidewalk, so the demo is something a player can walk rather than a review staging. **Built 2026-11-07** by `Tools/ue_build_demo_map.py` → `/Game/Hollywood/DemoMap`: sun/sky, `Hero_Facade`, six site meshes imported with collision (`Terrain_DEM`, `Road_Hollywood`, both sidewalks and both kerbs), and a `Demo_PlayerStart` on the north sidewalk 20 m back from the frontage. Demo views in `Screenshots/demo/` re-rendered and ☑ approved 2026-11-07.
|
||||
- [x] Walkable sidewalks and a designated street crossing. Crossing chosen: mid-block signalised crossing, OSM way 385648319, at 130 m along the block (`Geo/verification/2026-11-06-street-profile.md`). Done 2026-11-07: continental zebra markings (0.5 m bars / 0.5 m gaps, 3 m band) at all three signalised crossings, no collision on the paint; kerbs were already flushed at the crossings. Enablers found on the way: the street surfaces were 100 % down-facing (invisible in engine) — winding flipped; the terrain buried the street — 10.7 m corridor cut around the centreline (sidewalks cover the transition, walk-check 0 holes); LFS-lockable `.blend`/`.umap` check out read-only — builders now chmod before saving and fail loudly. Screenshot ☑ approved 2026-11-07 (`Screenshots/demo/review-demo-crossing`); the corridor renders near-black in shadow — that is the still-open **Fixed daytime lighting** item, not the crossing.
|
||||
- [ ] Fixed daytime lighting and basic ambience.
|
||||
- [ ] Geographic attribution display ("© OpenStreetMap contributors" + license link).
|
||||
|
||||
|
||||
@@ -24,6 +24,16 @@ RESULT = "/tmp/gigline/demo-map.txt"
|
||||
lines = []
|
||||
|
||||
|
||||
# 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). The
|
||||
# LevelEditorSubsystem actually switches.
|
||||
LES = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
||||
|
||||
|
||||
def switch_level(path):
|
||||
return LES.load_level(path)
|
||||
|
||||
def log(msg):
|
||||
lines.append(str(msg))
|
||||
unreal.log(str(msg))
|
||||
@@ -53,11 +63,11 @@ def clear(prefixes=()):
|
||||
def build():
|
||||
# --- level ------------------------------------------------------------
|
||||
if unreal.EditorAssetLibrary.does_asset_exist(MAP):
|
||||
if not unreal.EditorLevelLibrary.load_level(MAP):
|
||||
if not switch_level(MAP):
|
||||
log(f"could not load {MAP}")
|
||||
return
|
||||
else:
|
||||
if not unreal.EditorLevelLibrary.new_level(MAP):
|
||||
if not switch_level(MAP):
|
||||
log(f"could not create {MAP}")
|
||||
return
|
||||
|
||||
|
||||
@@ -21,6 +21,16 @@ MAP_PATH = "/Game/Test/ReviewMap"
|
||||
lines = []
|
||||
|
||||
|
||||
# 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). The
|
||||
# LevelEditorSubsystem actually switches.
|
||||
LES = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
||||
|
||||
|
||||
def switch_level(path):
|
||||
return LES.load_level(path)
|
||||
|
||||
def log(msg):
|
||||
lines.append(str(msg))
|
||||
|
||||
@@ -211,13 +221,33 @@ try:
|
||||
# oblique from the north-west, reading the colonnade in depth
|
||||
("hero-facade-oblique", unreal.Vector(hero_x - 2200, -3400, 620), unreal.Vector(hero_x + 300, hero_y, 700)),
|
||||
]
|
||||
# --- review shot cameras + save -------------------------------------------
|
||||
# Order matters: the review scene is staged in the default (unsaved) world.
|
||||
# Save it as ReviewMap BEFORE touching DemoMap — switching back to a map
|
||||
# that only exists in memory fails silently, and the final save would then
|
||||
# write the demo world over ReviewMap (this exact corruption happened once).
|
||||
shots = spawn(unreal.GigLineShotActor, unreal.Vector(0, 0, 300), "ShotSequencer")
|
||||
if shots:
|
||||
shots.set_editor_property("shot_locations", [c[1] for c in cams])
|
||||
shots.set_editor_property(
|
||||
"shot_rotations",
|
||||
[unreal.MathLibrary.find_look_at_rotation(c[1], c[2]) for c in cams])
|
||||
shots.set_editor_property("shot_names", [c[0] for c in cams])
|
||||
shots.set_editor_property("quit_when_done", True)
|
||||
log(f"staged: ShotSequencer with {len(cams)} cameras: {', '.join(c[0] for c in cams)}")
|
||||
|
||||
ensure_writable(MAP_PATH)
|
||||
saved = unreal.EditorLoadingAndSavingUtils.save_map(
|
||||
unreal.EditorLevelLibrary.get_editor_world(), MAP_PATH)
|
||||
log(f"save_map {MAP_PATH}: {saved}")
|
||||
|
||||
# --- demo map views: what the player actually sees ----------------------
|
||||
demo = unreal.EditorAssetLibrary.load_asset("/Game/Hollywood/DemoMap")
|
||||
if demo and unreal.EditorAssetLibrary.does_asset_exist("/Game/Hollywood/DemoMap"):
|
||||
# The demo map has terrain, street, sidewalks and the facade; the review
|
||||
# floor does not. Load it, stage the shot cameras, save, then restore the
|
||||
# review map so the rest of the run is unaffected.
|
||||
unreal.EditorLevelLibrary.load_level("/Game/Hollywood/DemoMap")
|
||||
# floor does not. Load it, stage the shot cameras, save. No switch back:
|
||||
# the review map is already saved, and the run ends here.
|
||||
switch_level("/Game/Hollywood/DemoMap")
|
||||
for actor in unreal.EditorLevelLibrary.get_all_level_actors():
|
||||
if actor.get_actor_label() == "ShotSequencer":
|
||||
unreal.EditorLevelLibrary.destroy_actor(actor)
|
||||
@@ -247,34 +277,10 @@ try:
|
||||
saved = unreal.EditorLoadingAndSavingUtils.save_map(
|
||||
unreal.EditorLevelLibrary.get_editor_world(), "/Game/Hollywood/DemoMap")
|
||||
log(f"demo map staged: {len(demo_shots)} cameras, save {saved}")
|
||||
unreal.EditorLevelLibrary.load_level("/Game/Test/ReviewMap")
|
||||
for actor in unreal.EditorLevelLibrary.get_all_level_actors():
|
||||
if actor.get_actor_label() == "ShotSequencer":
|
||||
unreal.EditorLevelLibrary.destroy_actor(actor)
|
||||
else:
|
||||
log("MISSING: /Game/Hollywood/DemoMap (run Tools/ue_build_demo_map.py)")
|
||||
|
||||
shots = spawn(unreal.GigLineShotActor, unreal.Vector(0, 0, 300), "ShotSequencer")
|
||||
if shots:
|
||||
shots.set_editor_property("shot_locations", [c[1] for c in cams])
|
||||
shots.set_editor_property(
|
||||
"shot_rotations",
|
||||
[unreal.MathLibrary.find_look_at_rotation(c[1], c[2]) for c in cams])
|
||||
shots.set_editor_property("shot_names", [c[0] for c in cams])
|
||||
shots.set_editor_property("quit_when_done", True)
|
||||
log(f"staged: ShotSequencer with {len(cams)} cameras: {', '.join(c[0] for c in cams)}")
|
||||
except Exception as exc: # noqa
|
||||
log(f"staging failed: {exc!r}")
|
||||
|
||||
# --- save --------------------------------------------------------------------
|
||||
|
||||
try:
|
||||
ensure_writable(MAP_PATH)
|
||||
world = unreal.EditorLevelLibrary.get_editor_world()
|
||||
saved = unreal.EditorLoadingAndSavingUtils.save_map(world, MAP_PATH)
|
||||
log(f"save_map {MAP_PATH}: {saved}")
|
||||
except Exception as exc: # noqa
|
||||
log(f"save_map failed: {exc!r}")
|
||||
|
||||
with open(RESULT, "w") as f:
|
||||
f.write("\n".join(lines) + "\n")
|
||||
Reference in new issue
Block a user