Compare commits
10
Commits
v0.1.3
..
304c1e9afd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
304c1e9afd | ||
|
|
05c1488c00 | ||
|
|
96238bdb6a | ||
|
|
817313aabd | ||
|
|
73aca9db80 | ||
|
|
743a290ac9 | ||
|
|
71c7ca734b | ||
|
|
b8209ce82b | ||
|
|
a1d52aa583 | ||
|
|
5e2c5e29c0 |
No files matched your search
@@ -47,6 +47,10 @@ Thumbs.db
|
|||||||
/Geo/raw/mapbox-cache/
|
/Geo/raw/mapbox-cache/
|
||||||
/Geo/verification/*mapbox*
|
/Geo/verification/*mapbox*
|
||||||
|
|
||||||
|
# Visual-loop iterations: generated comparisons stay uncommitted until the owner
|
||||||
|
# confirms a shot. Only Screenshots/ is committed.
|
||||||
|
/Art/reference/iterations/
|
||||||
|
|
||||||
# Local, private reference material — never committed (see Docs/reference-notes-hollywood.md)
|
# Local, private reference material — never committed (see Docs/reference-notes-hollywood.md)
|
||||||
/Art/reference/
|
/Art/reference/
|
||||||
/Docs/data-inventory.md
|
/Docs/data-inventory.md
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ Rules for `TODO.md`:
|
|||||||
- When **all** steps in a version section are done: write a short descriptive entry to `CHANGELOG.md`, remove the version section from `TODO.md`, and leave its goal in `ROADMAP.md`.
|
- When **all** steps in a version section are done: write a short descriptive entry to `CHANGELOG.md`, remove the version section from `TODO.md`, and leave its goal in `ROADMAP.md`.
|
||||||
- Release checks for each version are listed at the end of its `TODO.md` section; `Docs/release-checks.md` defines the checks themselves. If a check fails, do not tag the release; open a patch task in `TODO.md` and report the failing check in `CHANGELOG.md`.
|
- Release checks for each version are listed at the end of its `TODO.md` section; `Docs/release-checks.md` defines the checks themselves. If a check fails, do not tag the release; open a patch task in `TODO.md` and report the failing check in `CHANGELOG.md`.
|
||||||
- Finish each milestone's release checks **before** expanding scope. Cut map size and simulation complexity before compromising the core walking/driving experience.
|
- Finish each milestone's release checks **before** expanding scope. Cut map size and simulation complexity before compromising the core walking/driving experience.
|
||||||
|
- **Realism leads, performance is a floor.** When a realism improvement costs frame time, realism wins and the cost is paid — but 60 FPS at 1440p without frame generation must still be met at every release. If realism cannot fit the budget, reduce the realism scope deliberately and record it in `CHANGELOG.md`; never ship below the floor without the owner's approval. Walkability outranks realism in one direction only: a block the player falls through is not a demo.
|
||||||
|
|
||||||
## Git workflow
|
## Git workflow
|
||||||
|
|
||||||
@@ -209,8 +210,8 @@ 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/`).
|
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. Optional pre-check with a vision model: `Tools/judge_screenshot.py --image <shot.png> --expect "<what it should show>"` (or `--shots` to run every shot in the README) flags likely-missing content before a human looks. It reads `OPENROUTER_API_KEY` from `.env` (ignored). Small helper only — it never replaces the human verdict.
|
2. Optional pre-check with a vision model: `Tools/judge_screenshot.py --image <shot.png> --expect "<what it should show>"` (or `--shots` to run every shot in the README) flags likely-missing content before a human looks. It reads `OPENROUTER_API_KEY` from `.env` (ignored). Small helper only — it never replaces the human verdict.
|
||||||
3. 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. Update `Screenshots/README.md` **in the same change as any visual work** — every change that affects appearance, not only new shots: the image is **embedded inline** (``) with its view, expected content, verification and approval state, plus one row in the status table at the top. The review happens in the file; a path alone is not a review, and a shot whose README section is stale is a defect.
|
||||||
4. 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. 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.
|
||||||
5. Wait for the user's approval before treating a visual change as done; record approval by ticking the table in `Screenshots/README.md`.
|
5. Wait for the user's approval before treating a visual change as done; record approval by ticking both the shot's section and its row in the status table 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`.
|
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.
@@ -37,6 +37,15 @@
|
|||||||
],
|
],
|
||||||
"evidence": "6 columns at 4.96 m centres (sibling notes x measured frontage)"
|
"evidence": "6 columns at 4.96 m centres (sibling notes x measured frontage)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"object": "Hero_Flute_x120",
|
||||||
|
"dims_m": [
|
||||||
|
0.064,
|
||||||
|
0.064,
|
||||||
|
6.53
|
||||||
|
],
|
||||||
|
"evidence": "photographs: fluted shafts; 20 grooves per column modelled as shallow cylinders in the shadow tone rather than boolean cut grooves"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"object": "Hero_ColumnBase_x6",
|
"object": "Hero_ColumnBase_x6",
|
||||||
"dims_m": [
|
"dims_m": [
|
||||||
@@ -104,10 +113,10 @@
|
|||||||
"object": "Hero_MottoBand",
|
"object": "Hero_MottoBand",
|
||||||
"dims_m": [
|
"dims_m": [
|
||||||
22.75,
|
22.75,
|
||||||
0.08,
|
0.1,
|
||||||
0.85
|
0.85
|
||||||
],
|
],
|
||||||
"evidence": "photographs: motto panel above the colonnade"
|
"evidence": "photographs: motto panel above the colonnade, now an inset panel with a moulded surround rather than a flat stripe"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"object": "Hero_Medallion_x2",
|
"object": "Hero_Medallion_x2",
|
||||||
|
|||||||
+82
-1
@@ -8,6 +8,85 @@ Format: [Keep a Changelog](https://keepachangelog.com/) style, versions follow [
|
|||||||
|
|
||||||
Nothing yet.
|
Nothing yet.
|
||||||
|
|
||||||
|
## [0.1.5] — 2026-11-09
|
||||||
|
|
||||||
|
Façade finishing. The kit gets the hero detail it was missing, and a class of silent defect is found and gated: the map had been rendering with **604 of 620 mesh actors unmaterialed** without a single error.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Scene audit** (`Tools/ue_audit_scene.py`): fails when any staged mesh has no material, when a frontage building is more than ±1 m off its verified footprint, or when two actors share a label. Wired into `Tools/render_screenshots.sh`, which now runs the whole staging chain in the correct order and stops on an audit failure.
|
||||||
|
- **`Tools/ue_placement.py`**: one source of truth for staged positions and the verified frontage spans, with `span_error` / `describe_span_failure` validators.
|
||||||
|
- **Hero fluting**: 20 grooves per column as shallow shadow-tone cylinders (120 pieces) rather than boolean cuts.
|
||||||
|
- **Presence of references for the retail run**: `Art/reference/retail-neighbours/` (6800 Lee Drug and its neighbour). Local only, not committed.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Motto band is a real recess** now — moulded surround 6 cm proud, panel 12 cm behind — instead of an 8 cm dark stripe. Hero: 148 parts / 3 044 tris → 271 / 5 492, still 14.50 m.
|
||||||
|
- **Kit layout for wide and tall buildings**: bays are capped at 9 with the module widened (6922 was 13 bays across 65.8 m — a window every 4.96 m on a 37 m block), and the storey spread is derived from the available height so the top band always lands inside the volume.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **604 of 620 mesh actors rendered untextured.** `ensure_material` deleted and recreated each site material on every run, orphaning every reference held by the kit pieces and the theatre trim; the map looked only slightly flat. Materials are now updated in place.
|
||||||
|
- **The hero could be teleported 13 m east onto the wrong side of the boulevard.** `ue_hero_facade_import.py` carried its own copy of the pre-`0.1.0` hero position; running the importer alone moved the building, invisible in a render and visible only as a reference-score drop. Placement now has one definition and the import asserts the staged span (`span ok: X -14644..-12028`).
|
||||||
|
- **53 duplicated kit actor labels** (both shop piers of a bay shared one name).
|
||||||
|
|
||||||
|
### Verified
|
||||||
|
|
||||||
|
- Scene audit: **0 failures** — every mesh materialed, all 7 verified spans within tolerance, no duplicate labels.
|
||||||
|
- Packaged `-qaselftest`: walk, reset, pause/unpause, seam transect 1456 samples / 0 holes, wall probe 0/35, façade probe 0/9 — all PASS.
|
||||||
|
- **1440p re-measured with the kit**: 2.76 ms average (**363 fps**), p99 3.64 ms, against `0.1.0`'s 2.42 ms (414 fps) — the 625 kit actors and 8 textures cost about 12 % and leave roughly six times the 60 fps floor. Series at `Docs/perf/0.1.5-kit-walk-frametimes.csv`.
|
||||||
|
- 78 tool tests pass.
|
||||||
|
|
||||||
|
### Known limitations
|
||||||
|
|
||||||
|
- **Retail neighbours 6822 and 6806–6820 are not modelled**: Commons holds no usable reference for either, and substituting the El Capitan Theatre's photographs would misattribute their façades. Tracked in `TODO.md`.
|
||||||
|
- Cornice dentil spacing, abacus and plinth dimensions remain recorded assumptions.
|
||||||
|
- Textures are generated, not authored — no decals, signage text or weathering maps.
|
||||||
|
- The vision pre-check stays advisory; it has rejected shots the owner approved.
|
||||||
|
|
||||||
|
## [0.1.4] — 2026-11-09
|
||||||
|
|
||||||
|
Building façades. The block goes from flat extruded boxes with flat colours to coursed, textured surfaces assembled from a reusable module kit, and the El Capitan Theatre gets the marquee and blade sign that make it read as itself.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Façade module kit** at the measured **4.96 m bay** / **3.10 m storey**: pilaster, sill, lintel, window bay, shopfront bay, door bay, cornice, parapet and coping — 16 `SM_` meshes, each validated for manifold topology (12 tris each) and assembled onto **eight buildings, 625 pieces**, from each building's own world bounds.
|
||||||
|
- **Generated surface textures** — deterministic stdlib PNGs for ashlar stone, stucco, asphalt and concrete (base colour + roughness), sampled world-aligned with per-material tiling; byte-identical on re-run.
|
||||||
|
- **Materials**: masonry coursing and roughness variation in the material graphs; `M_FacadeStoneShadow`, `M_FacadeGlass` (specular 0.62 ≈ IOR 1.5) and `M_FacadeBanner`. Material assignment is now **per slot**, keyed on the imported slot material, instead of slot 0 only.
|
||||||
|
- **El Capitan Theatre trim**: projecting marquee (12.0 × 2.60 m canopy at a 4.60 m soffit), illuminated fascia, recessed soffit, and a vertical blade sign (1.30 × 5.20 m) with frame and wall bracket.
|
||||||
|
- **Visual review loop** (`Tools/visual_loop.py` + `visual_targets.json`): verifies every capture against an expectation (missing, blank, tone-less or wrong-size shots fail) and measures each render against its reference photograph, emitting feedback that names the discipline to consult. Iterations are written under `Art/reference/iterations/`, which is gitignored until a shot is confirmed.
|
||||||
|
- **`Tools/fbx_export_preset.py`** — one export preset replacing six copies, and it exports smoothing groups (`FACE`), so round geometry is no longer faceted.
|
||||||
|
- **`Tools/ue_common.py`** — clears the read-only bit an LFS `.umap` checks out with and makes a refused save loud.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Elevation cameras** are levelled (pitch 0, so verticals stay parallel) and use a 42–55° lens to fill the frame from across the street.
|
||||||
|
- **`Screenshots/` holds demo-map captures only**; the review staging map is no longer rendered for review. Each shot carries its expected content and an approval state.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Blockout frame**: the `0.1.2` regeneration had run without `--origin`, shifting the whole frontage ≈119 m in X and 2.4 m in Y and dropping the hero out of the 6902/6838 gap. Regenerated with the affine fit (residual 0.0 cm) and every span re-verified against the block verification notes.
|
||||||
|
- **Kit placed ~12 km from its buildings** — it used the mesh-local bounding box, which excludes the actor offset.
|
||||||
|
- **Marquee mirrored across the boulevard** (Blender +Y is Unreal −Y) and **hanging 5 cm above the pavement** (absolute Z instead of pavement-relative).
|
||||||
|
- **Window bays invisible** inside the solid wall; the surround now projects with glazing inset.
|
||||||
|
- **Glazing was not glass**: kit pieces kept the grey material the FBX import creates.
|
||||||
|
- **Hero windows floated 1.44 m in front of the wall**; parapet medallions were flat discs on the coping.
|
||||||
|
- **Imports could report success while saving nothing** (read-only LFS map), and the north-buildings import failed silently on a missing `import sys`.
|
||||||
|
|
||||||
|
### Verified
|
||||||
|
|
||||||
|
- Packaged build `-qaselftest`: walk 761 cm, reset, pause/unpause, seam transect 1456 samples / 0 holes, wall probe 0/35, façade occlusion probe 0/9 — all PASS. Run twice; the harness is time-based and one loaded run reported a false failure.
|
||||||
|
- All eight buildings probed against the verified world spans.
|
||||||
|
- 67 tool tests pass.
|
||||||
|
|
||||||
|
### Known limitations
|
||||||
|
|
||||||
|
- Kit pieces are boxes; cornice dentil spacing, abacus and plinth dimensions are recorded assumptions.
|
||||||
|
- Textures are generated, not authored: no decals, signage text or weathering maps.
|
||||||
|
- The vision pre-check remains advisory — it is unreliable on this content.
|
||||||
|
- Retail neighbours, hero fluting and the blockout cleaning pass move to `0.1.5`.
|
||||||
|
- Performance has not been re-measured since `0.1.0`; 625 kit actors are unmeasured.
|
||||||
|
|
||||||
## [0.1.3] — 2026-11-09
|
## [0.1.3] — 2026-11-09
|
||||||
|
|
||||||
Packaging and setup fixes. No gameplay or visual scope; this patch makes the pipeline and its documentation tell the truth about what `0.1.x` taught.
|
Packaging and setup fixes. No gameplay or visual scope; this patch makes the pipeline and its documentation tell the truth about what `0.1.x` taught.
|
||||||
@@ -129,7 +208,9 @@ The first playable release: a walkable exterior demo of the **6840 Hollywood Blv
|
|||||||
- Kerb height (0.13 m as built vs 0.15 m assumed), kerb radii, driveway aprons and graded crossing ramps are deferred to `0.1.1`; crossing ramps are kerb flushes.
|
- Kerb height (0.13 m as built vs 0.15 m assumed), kerb radii, driveway aprons and graded crossing ramps are deferred to `0.1.1`; crossing ramps are kerb flushes.
|
||||||
- One block only; no vehicles, NPCs, audio, or settings UI yet.
|
- One block only; no vehicles, NPCs, audio, or settings UI yet.
|
||||||
|
|
||||||
[Unreleased]: https://git.allucanget.biz/zwitschi/gig-line/compare/v0.1.3...main
|
[Unreleased]: https://git.allucanget.biz/zwitschi/gig-line/compare/v0.1.5...main
|
||||||
|
[0.1.5]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.5
|
||||||
|
[0.1.4]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.4
|
||||||
[0.1.3]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.3
|
[0.1.3]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.3
|
||||||
[0.1.2]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.2
|
[0.1.2]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.2
|
||||||
[0.1.1]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.1
|
[0.1.1]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.1
|
||||||
|
|||||||
@@ -20,11 +20,14 @@ Full requirements and progress are in [TODO.md](../../TODO.md); version directio
|
|||||||
|
|
||||||
| Priority | Quality Goal | Scenario |
|
| Priority | Quality Goal | Scenario |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| 1 | **Performance efficiency** | 60 FPS at 2560×1440 on RTX 5070 Ti, documented preset, no frame generation |
|
| 1 | **Visual realism** | Renders read as the reference photographs: façade identity, proportion, material and light — judged side by side, shot against photo |
|
||||||
| 2 | **Terrain walkability** | Zero raycast holes across both sidewalks and the carriageway (84-sample transect passes) |
|
| 2 | **Geographic fidelity** | Building placement and street scale match the measured data and the photographs |
|
||||||
| 3 | **Geographic fidelity** | Building placement, street scale, and façade identity match reference photography |
|
| 3 | **Terrain walkability** | Zero raycast holes across both sidewalks and the carriageway (84-sample transect passes) |
|
||||||
| 4 | **Build reproducibility** | Fresh checkout + documented engine path produces a runnable Linux build |
|
| 4 | **Build reproducibility** | Fresh checkout + documented engine path produces a runnable Linux build |
|
||||||
| 5 | **Provenance** | Every committed asset has recorded provenance |
|
| 5 | **Provenance** | Every committed asset has recorded provenance |
|
||||||
|
| 6 | **Performance efficiency** | 60 FPS at 2560×1440 on RTX 5070 Ti, documented preset, no frame generation |
|
||||||
|
|
||||||
|
**How the order is used.** It decides trade-offs. When realism and performance disagree, realism wins and the cost is paid — but never by silently shipping a frame rate below the floor. The 60 FPS figure is a **budget that must still be met**, so the response to "realism costs too much frame time" is to cut the realism feature deliberately, record the cut, and keep the floor — not to let frames slip. Walkability outranks realism in one direction only: a beautiful block the player falls through is not a demo.
|
||||||
|
|
||||||
## Stakeholders
|
## Stakeholders
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ All modelling and geographic processing live outside the game project in `Art/`,
|
|||||||
|
|
||||||
## Quality achievement
|
## Quality achievement
|
||||||
|
|
||||||
- **Performance**: dynamic-only lighting (no build step, no static shadow maps), world-space-noise materials (cheap procedural variation), minimal overdraw (simple street-level geometry).
|
- **Realism first**: reference photography and measured data drive every façade decision; renders are compared against the photographs with `Tools/visual_loop.py`, and a façade is built from the shared 4.96 m module kit so the street wall reads consistently. Materials are authored in Unreal with world-aligned generated textures, and lighting is fixed daytime so a shot is comparable between runs.
|
||||||
|
- **Performance as a budget, not a driver**: dynamic-only lighting (no build step, no static shadow maps), world-space-noise materials (cheap procedural variation), minimal overdraw for street-level geometry, and a 60 FPS at 1440p floor that every release must meet. It closes the quality list because it constrains how much realism fits, not what the block should look like.
|
||||||
|
|
||||||
- **Walkability**: complex-as-simple collision on terrain (C++ helper forces the flag on the asset), block-all collision on every site mesh, swept transect QA verifies zero raycast holes.
|
- **Walkability**: complex-as-simple collision on terrain (C++ helper forces the flag on the asset), block-all collision on every site mesh, swept transect QA verifies zero raycast holes.
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ C++ classes in `Game/GigLine/Source/GigLine/`:
|
|||||||
| `AGigLineShotActor` | Screenshot camera walker | Drives cameras in `-game` mode, `FScreenshotRequest`, quits |
|
| `AGigLineShotActor` | Screenshot camera walker | Drives cameras in `-game` mode, `FScreenshotRequest`, quits |
|
||||||
| `UGigLineAssetTools` | C++ collision helpers | `SetComplexCollisionAsSimple` on `StaticMesh` assets |
|
| `UGigLineAssetTools` | C++ collision helpers | `SetComplexCollisionAsSimple` on `StaticMesh` assets |
|
||||||
|
|
||||||
Input assets in `/Game/Input/`: `IA_Move`, `IA_MoveBack`, `IA_Strafe`, `IA_StrafeLeft`, `IA_Look`, `IA_Jump`, `IA_Reset`, `IA_Pause`, bound in `IMC_Default`.
|
Input assets in `/Game/Input/`: `IA_Move`, `IA_MoveBack`, `IA_Strafe`, `IA_StrafeLeft`, `IA_Look`, `IA_Jump`, `IA_Reset`, `IA_Pause`, `IA_ToggleCamera`, bound in `IMC_Default`.
|
||||||
|
|
||||||
|
Player-facing bindings: **WASD** move, **mouse** look, **Space** jump, **R** reset, **Escape** pause, **V** swap first/third person. The camera and body work landed with `0.2.0` (see ADR-9): the shipped default view stays first person, and `-thirdperson` starts the pawn in the third-person rig — used by the screenshot run so the review is reproducible.
|
||||||
|
|
||||||
### Site
|
### Site
|
||||||
|
|
||||||
@@ -56,6 +58,7 @@ Python scripts in `Tools/` drive the content pipeline:
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `export_*`, `hero_facade_build` | Blender headless export (FBX) |
|
| `export_*`, `hero_facade_build` | Blender headless export (FBX) |
|
||||||
| `ue_import_*`, `ue_hero_facade_import` | Unreal headless import + placement |
|
| `ue_import_*`, `ue_hero_facade_import` | Unreal headless import + placement |
|
||||||
|
| `character_anim_build`, `ue_character_anim_import` | Character animation clips (`GigLine_Idle`, `GigLine_Walk`) and their import |
|
||||||
| `ue_build_demo_map`, `ue_build_review_map` | Map assembly (lighting, meshes, player start) |
|
| `ue_build_demo_map`, `ue_build_review_map` | Map assembly (lighting, meshes, player start) |
|
||||||
| `ue_create_input_assets` | Enhanced Input actions + mapping context |
|
| `ue_create_input_assets` | Enhanced Input actions + mapping context |
|
||||||
| `render_screenshots.sh` | Screenshot capture pipeline |
|
| `render_screenshots.sh` | Screenshot capture pipeline |
|
||||||
|
|||||||
@@ -55,3 +55,11 @@
|
|||||||
- **Decision**: All Blender work driven by Python scripts in `Tools/`. Skill packs used as method guides (checklists, naming, proportions) only.
|
- **Decision**: All Blender work driven by Python scripts in `Tools/`. Skill packs used as method guides (checklists, naming, proportions) only.
|
||||||
|
|
||||||
- **Consequences**: Scripts are version-controlled and repeatable. No live Blender session needed for CI.
|
- **Consequences**: Scripts are version-controlled and repeatable. No live Blender session needed for CI.
|
||||||
|
|
||||||
|
## ADR-9: Third-person body is an attachment of the first-person pawn, not a second pawn
|
||||||
|
|
||||||
|
- **Context**: `0.2.0` needs a visible body and a third-person camera, while `0.1.0` shipped a first-person pawn with no body and the owner's decision was first person for that release. Two separate pawn classes would duplicate movement, input and reset logic, which is the code most likely to drift.
|
||||||
|
|
||||||
|
- **Decision**: one pawn, both rigs. `AGigLineCharacter` carries a skeletal mesh (hidden in first person), a collision-tested spring-arm camera, and a **V** toggle that swaps the active camera and the body's visibility. The shipped default view stays first person; `-thirdperson` starts the pawn in the third-person rig and is what the screenshot run passes, so the review shot is reproducible without a human pressing a key.
|
||||||
|
|
||||||
|
- **Consequences**: movement, reset, pause and the QA self-test stay in one place. The body's animation state is driven from `Tick` by speed rather than from input, so being pushed or falling animates correctly. The cost is that the camera swap is not a separate actor, so a future cinematic camera needs its own path rather than reusing the toggle.
|
||||||
@@ -2,24 +2,34 @@
|
|||||||
|
|
||||||
## Quality Requirements Overview
|
## Quality Requirements Overview
|
||||||
|
|
||||||
| Category | Requirement | Priority |
|
Ordered by priority: realism first, performance last. The order decides design trade-offs; it does not license dropping a lower item silently (see the trade-off rule below the table).
|
||||||
|
|
||||||
|
| Priority | Category | Requirement |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Performance efficiency | 60 FPS at 2560×1440, documented preset, no frame generation | 1 |
|
| 1 | Realism / Fidelity | Façade identity, proportion, material and lighting read like the reference photographs when compared side by side |
|
||||||
| Reliability / Terrain | Zero raycast holes across sidewalks and carriageway (84-sample transect) | 1 |
|
| 2 | Realism / Fidelity | Building placement matches the measured footprints and the photographs (within ~1 m) |
|
||||||
| Accuracy / Fidelity | Building placement matches reference photography within ~1 m | 2 |
|
| 2 | Realism / Fidelity | Street scale (lane width, sidewalk width) within ~10 cm of the measured profile |
|
||||||
| Accuracy / Fidelity | Street scale (lane width, sidewalk width) within ~10 cm of measured profile | 2 |
|
| 3 | Reliability / Terrain | Zero raycast holes across sidewalks and carriageway (84-sample transect) |
|
||||||
| Usability | WASD + mouse look, jump, reset (R), pause (Escape) — no tutorial needed | 2 |
|
| 3 | Usability | WASD + mouse look, jump, reset (R), pause (Escape) — no tutorial needed |
|
||||||
| Maintainability | Fresh checkout builds with documented engine installation | 2 |
|
| 4 | Maintainability | Fresh checkout builds with the documented engine installation |
|
||||||
| Portability | Linux build packages and runs; Windows build ships | 3 |
|
| 4 | Portability | Linux build packages and runs; Windows build ships |
|
||||||
|
| 5 | Provenance | Every committed asset has a recorded source and licence |
|
||||||
|
| 6 | Performance efficiency | 60 FPS at 2560×1440, documented preset, no frame generation |
|
||||||
|
|
||||||
|
**Trade-off rule.** Realism leads: when a realism improvement costs frame time, the improvement wins. Performance is still a **floor, not a preference** — 60 FPS at 1440p without frame generation must be met at every release. If realism cannot fit inside that budget, reduce the realism scope deliberately and record the reduction in `CHANGELOG.md`; never ship below the floor without the owner's explicit approval. Walkability outranks realism in one direction: a block the player falls through is not a demo, whatever it looks like.
|
||||||
|
|
||||||
## Quality Scenarios
|
## Quality Scenarios
|
||||||
|
|
||||||
| ID | Source/Stimulus | Response | Metric |
|
| ID | Source/Stimulus | Response | Metric |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| QS-1 | Player walks both sides of the demo | No fall-through, character stays at ground Z | 0 raycast holes in 84-sample transect |
|
| QS-1 | Screenshot is compared against its reference photograph | The render reads as the same building | Similarity score and visual match confirmed by the owner; `Tools/visual_loop.py` reports the deltas |
|
||||||
| QS-2 | Player holds W for 2 s on flat road | Character moves forward | ≥ 200 cm displacement, Z constant within ±5 cm |
|
| QS-2 | A façade is placed from measured data | It sits on its verified footprint | World span probed against `Geo/verification/` within ~1 m |
|
||||||
| QS-3 | Player presses R | Character teleports to spawn | < 100 cm from `StartLocation` |
|
| QS-3 | Player walks both sides of the demo | No fall-through, character stays at ground Z | 0 raycast holes in 84-sample transect |
|
||||||
| QS-4 | Player presses Escape | Game pauses, cursor appears | `IsGamePaused()` = true |
|
| QS-4 | Player holds W for 2 s on flat road | Character moves forward | ≥ 200 cm displacement, Z constant within ±5 cm |
|
||||||
| QS-5 | Player presses Escape while paused | Game unpauses, cursor hides | `IsGamePaused()` = false |
|
| QS-5 | Player presses R | Character teleports to spawn | < 100 cm from `StartLocation` |
|
||||||
| QS-6 | Game runs on dev workstation at 1440p | Frame rate stable | ≥ 60 FPS, 99th percentile ≥ 55 FPS |
|
| QS-6 | Player presses Escape | Game pauses, cursor appears | `IsGamePaused()` = true |
|
||||||
| QS-7 | Fresh checkout + engine install | Build succeeds | Exit code 0, runnable binary produced |
|
| QS-7 | Player presses Escape while paused | Game unpauses, cursor hides | `IsGamePaused()` = false |
|
||||||
|
| QS-8 | Fresh checkout + engine install | Build succeeds | Exit code 0, runnable binary produced |
|
||||||
|
| QS-9 | Game runs on dev workstation at 1440p | Frame rate holds the floor | ≥ 60 FPS, 99th percentile ≥ 55 FPS |
|
||||||
|
|
||||||
|
> QS-9 is last on purpose. Performance is a floor that every release must meet, not the goal that shapes the content: realism decides what gets built, the frame budget decides how much of it.
|
||||||
@@ -27,11 +27,14 @@ Full requirements and progress are in [TODO.md](../../TODO.md); version directio
|
|||||||
|
|
||||||
| Priority | Quality Goal | Scenario |
|
| Priority | Quality Goal | Scenario |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| 1 | **Performance efficiency** | 60 FPS at 2560×1440 on RTX 5070 Ti, documented preset, no frame generation |
|
| 1 | **Visual realism** | Renders read as the reference photographs: façade identity, proportion, material and light — judged side by side, shot against photo |
|
||||||
| 2 | **Terrain walkability** | Zero raycast holes across both sidewalks and the carriageway (84-sample transect passes) |
|
| 2 | **Geographic fidelity** | Building placement and street scale match the measured data and the photographs |
|
||||||
| 3 | **Geographic fidelity** | Building placement, street scale, and façade identity match reference photography |
|
| 3 | **Terrain walkability** | Zero raycast holes across both sidewalks and the carriageway (84-sample transect passes) |
|
||||||
| 4 | **Build reproducibility** | Fresh checkout + documented engine path produces a runnable Linux build |
|
| 4 | **Build reproducibility** | Fresh checkout + documented engine path produces a runnable Linux build |
|
||||||
| 5 | **Provenance** | Every committed asset has recorded provenance |
|
| 5 | **Provenance** | Every committed asset has recorded provenance |
|
||||||
|
| 6 | **Performance efficiency** | 60 FPS at 2560×1440 on RTX 5070 Ti, documented preset, no frame generation |
|
||||||
|
|
||||||
|
**How the order is used.** It decides trade-offs. When realism and performance disagree, realism wins and the cost is paid — but never by silently shipping a frame rate below the floor. The 60 FPS figure is a **budget that must still be met**, so the response to "realism costs too much frame time" is to cut the realism feature deliberately, record the cut, and keep the floor — not to let frames slip. Walkability outranks realism in one direction only: a beautiful block the player falls through is not a demo.
|
||||||
|
|
||||||
## Stakeholders
|
## Stakeholders
|
||||||
|
|
||||||
@@ -129,7 +132,9 @@ All modelling and geographic processing live outside the game project in `Art/`,
|
|||||||
|
|
||||||
## Quality achievement
|
## Quality achievement
|
||||||
|
|
||||||
- **Performance**: dynamic-only lighting (no build step, no static shadow maps), world-space-noise materials (cheap procedural variation), minimal overdraw (simple street-level geometry).
|
- **Realism**: reference photography drives every façade decision, measured data fixes placement and scale, and renders are compared against the photographs (`Tools/visual_loop.py`, `Screenshots/README.md`). One module kit at the 4.96 m bay keeps the street wall coherent; materials are world-aligned and lit by a fixed daytime rig so a shot is comparable between runs.
|
||||||
|
|
||||||
|
- **Performance holds the floor, it does not set the agenda**: dynamic-only lighting (no build step, no static shadow maps), cheap procedural materials, minimal overdraw for street-level geometry, and 60 FPS at 1440p as a budget every release must meet.
|
||||||
|
|
||||||
- **Walkability**: complex-as-simple collision on terrain (C++ helper forces the flag on the asset), block-all collision on every site mesh, swept transect QA verifies zero raycast holes.
|
- **Walkability**: complex-as-simple collision on terrain (C++ helper forces the flag on the asset), block-all collision on every site mesh, swept transect QA verifies zero raycast holes.
|
||||||
|
|
||||||
@@ -164,7 +169,9 @@ C++ classes in `Game/GigLine/Source/GigLine/`:
|
|||||||
| `AGigLineShotActor` | Screenshot camera walker | Drives cameras in `-game` mode, `FScreenshotRequest`, quits |
|
| `AGigLineShotActor` | Screenshot camera walker | Drives cameras in `-game` mode, `FScreenshotRequest`, quits |
|
||||||
| `UGigLineAssetTools` | C++ collision helpers | `SetComplexCollisionAsSimple` on `StaticMesh` assets |
|
| `UGigLineAssetTools` | C++ collision helpers | `SetComplexCollisionAsSimple` on `StaticMesh` assets |
|
||||||
|
|
||||||
Input assets in `/Game/Input/`: `IA_Move`, `IA_MoveBack`, `IA_Strafe`, `IA_StrafeLeft`, `IA_Look`, `IA_Jump`, `IA_Reset`, `IA_Pause`, bound in `IMC_Default`.
|
Input assets in `/Game/Input/`: `IA_Move`, `IA_MoveBack`, `IA_Strafe`, `IA_StrafeLeft`, `IA_Look`, `IA_Jump`, `IA_Reset`, `IA_Pause`, `IA_ToggleCamera`, bound in `IMC_Default`.
|
||||||
|
|
||||||
|
Player-facing bindings: **WASD** move, **mouse** look, **Space** jump, **R** reset, **Escape** pause, **V** swap first/third person. The camera and body work landed with `0.2.0` (see ADR-9): the shipped default view stays first person, and `-thirdperson` starts the pawn in the third-person rig — used by the screenshot run so the review is reproducible.
|
||||||
|
|
||||||
### Site
|
### Site
|
||||||
|
|
||||||
@@ -195,6 +202,7 @@ Python scripts in `Tools/` drive the content pipeline:
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `export_*`, `hero_facade_build` | Blender headless export (FBX) |
|
| `export_*`, `hero_facade_build` | Blender headless export (FBX) |
|
||||||
| `ue_import_*`, `ue_hero_facade_import` | Unreal headless import + placement |
|
| `ue_import_*`, `ue_hero_facade_import` | Unreal headless import + placement |
|
||||||
|
| `character_anim_build`, `ue_character_anim_import` | Character animation clips (`GigLine_Idle`, `GigLine_Walk`) and their import |
|
||||||
| `ue_build_demo_map`, `ue_build_review_map` | Map assembly (lighting, meshes, player start) |
|
| `ue_build_demo_map`, `ue_build_review_map` | Map assembly (lighting, meshes, player start) |
|
||||||
| `ue_create_input_assets` | Enhanced Input actions + mapping context |
|
| `ue_create_input_assets` | Enhanced Input actions + mapping context |
|
||||||
| `render_screenshots.sh` | Screenshot capture pipeline |
|
| `render_screenshots.sh` | Screenshot capture pipeline |
|
||||||
@@ -450,27 +458,37 @@ Rule: place a building only against a neighbour or street mesh, never in isolati
|
|||||||
|
|
||||||
## Quality Requirements Overview
|
## Quality Requirements Overview
|
||||||
|
|
||||||
| Category | Requirement | Priority |
|
Ordered by priority: realism first, performance last. The order decides design trade-offs; it does not license dropping a lower item silently (see the trade-off rule below the table).
|
||||||
|
|
||||||
|
| Priority | Category | Requirement |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Performance efficiency | 60 FPS at 2560×1440, documented preset, no frame generation | 1 |
|
| 1 | Realism / Fidelity | Façade identity, proportion, material and lighting read like the reference photographs when compared side by side |
|
||||||
| Reliability / Terrain | Zero raycast holes across sidewalks and carriageway (84-sample transect) | 1 |
|
| 2 | Realism / Fidelity | Building placement matches the measured footprints and the photographs (within ~1 m) |
|
||||||
| Accuracy / Fidelity | Building placement matches reference photography within ~1 m | 2 |
|
| 2 | Realism / Fidelity | Street scale (lane width, sidewalk width) within ~10 cm of the measured profile |
|
||||||
| Accuracy / Fidelity | Street scale (lane width, sidewalk width) within ~10 cm of measured profile | 2 |
|
| 3 | Reliability / Terrain | Zero raycast holes across sidewalks and carriageway (84-sample transect) |
|
||||||
| Usability | WASD + mouse look, jump, reset (R), pause (Escape) — no tutorial needed | 2 |
|
| 3 | Usability | WASD + mouse look, jump, reset (R), pause (Escape) — no tutorial needed |
|
||||||
| Maintainability | Fresh checkout builds with documented engine installation | 2 |
|
| 4 | Maintainability | Fresh checkout builds with the documented engine installation |
|
||||||
| Portability | Linux build packages and runs; Windows build ships | 3 |
|
| 4 | Portability | Linux build packages and runs; Windows build ships |
|
||||||
|
| 5 | Provenance | Every committed asset has a recorded source and licence |
|
||||||
|
| 6 | Performance efficiency | 60 FPS at 2560×1440, documented preset, no frame generation |
|
||||||
|
|
||||||
|
**Trade-off rule.** Realism leads: when a realism improvement costs frame time, the improvement wins. Performance is still a **floor, not a preference** — 60 FPS at 1440p without frame generation must be met at every release. If realism cannot fit inside that budget, reduce the realism scope deliberately and record the reduction in `CHANGELOG.md`; never ship below the floor without the owner's explicit approval. Walkability outranks realism in one direction: a block the player falls through is not a demo, whatever it looks like.
|
||||||
|
|
||||||
## Quality Scenarios
|
## Quality Scenarios
|
||||||
|
|
||||||
| ID | Source/Stimulus | Response | Metric |
|
| ID | Source/Stimulus | Response | Metric |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| QS-1 | Player walks both sides of the demo | No fall-through, character stays at ground Z | 0 raycast holes in 84-sample transect |
|
| QS-1 | Screenshot is compared against its reference photograph | The render reads as the same building | Similarity score and visual match confirmed by the owner; `Tools/visual_loop.py` reports the deltas |
|
||||||
| QS-2 | Player holds W for 2 s on flat road | Character moves forward | ≥ 200 cm displacement, Z constant within ±5 cm |
|
| QS-2 | A façade is placed from measured data | It sits on its verified footprint | World span probed against `Geo/verification/` within ~1 m |
|
||||||
| QS-3 | Player presses R | Character teleports to spawn | < 100 cm from `StartLocation` |
|
| QS-3 | Player walks both sides of the demo | No fall-through, character stays at ground Z | 0 raycast holes in 84-sample transect |
|
||||||
| QS-4 | Player presses Escape | Game pauses, cursor appears | `IsGamePaused()` = true |
|
| QS-4 | Player holds W for 2 s on flat road | Character moves forward | ≥ 200 cm displacement, Z constant within ±5 cm |
|
||||||
| QS-5 | Player presses Escape while paused | Game unpauses, cursor hides | `IsGamePaused()` = false |
|
| QS-5 | Player presses R | Character teleports to spawn | < 100 cm from `StartLocation` |
|
||||||
| QS-6 | Game runs on dev workstation at 1440p | Frame rate stable | ≥ 60 FPS, 99th percentile ≥ 55 FPS |
|
| QS-6 | Player presses Escape | Game pauses, cursor appears | `IsGamePaused()` = true |
|
||||||
| QS-7 | Fresh checkout + engine install | Build succeeds | Exit code 0, runnable binary produced |
|
| QS-7 | Player presses Escape while paused | Game unpauses, cursor hides | `IsGamePaused()` = false |
|
||||||
|
| QS-8 | Fresh checkout + engine install | Build succeeds | Exit code 0, runnable binary produced |
|
||||||
|
| QS-9 | Game runs on dev workstation at 1440p | Frame rate holds the floor | ≥ 60 FPS, 99th percentile ≥ 55 FPS |
|
||||||
|
|
||||||
|
> QS-9 is last on purpose. Performance is a floor that every release must meet, not the goal that shapes the content: realism decides what gets built, the frame budget decides how much of it.
|
||||||
|
|
||||||
# Risks and Technical Debts
|
# Risks and Technical Debts
|
||||||
|
|
||||||
|
|||||||
@@ -86,8 +86,13 @@ The hero block and the demo level are built by a fixed sequence. Each step write
|
|||||||
| North import | `Tools/ue_import_north_buildings.py` | `/Game/Hollywood/North/*` |
|
| North import | `Tools/ue_import_north_buildings.py` | `/Game/Hollywood/North/*` |
|
||||||
| Hero import | `Tools/ue_hero_facade_import.py` | `/Game/Hollywood/ElCapitan/SM_HeroFacade` |
|
| Hero import | `Tools/ue_hero_facade_import.py` | `/Game/Hollywood/ElCapitan/SM_HeroFacade` |
|
||||||
| Demo map | `Tools/ue_build_demo_map.py` | `/Game/Hollywood/DemoMap` |
|
| Demo map | `Tools/ue_build_demo_map.py` | `/Game/Hollywood/DemoMap` |
|
||||||
|
| Façade kit assembly | `Tools/ue_apply_facade_kit.py` | kit actors in `DemoMap` |
|
||||||
|
| Theatre trim | `Tools/ue_stage_theatre_trim.py` | marquee + blade sign actors |
|
||||||
|
| Scene audit | `Tools/ue_audit_scene.py` | materials, placement spans, labels (gate) |
|
||||||
| Review shots | `Tools/ue_build_review_map.py` + `Tools/render_screenshots.sh` | `Screenshots/` |
|
| Review shots | `Tools/ue_build_review_map.py` + `Tools/render_screenshots.sh` | `Screenshots/` |
|
||||||
|
|
||||||
|
**Order matters.** `ue_build_demo_map.py` rebuilds the site materials, so any actor staged by another tool must be (re)staged _after_ it, or it keeps a reference to a material that no longer exists — that is how 604 actors once ended up untethered from their materials while the map only looked slightly flat. `Tools/render_screenshots.sh` runs the full chain in the right order and gates on `ue_audit_scene.py`.
|
||||||
|
|
||||||
Every Blender exporter calls [`Tools/fbx_export_preset.py`](../Tools/fbx_export_preset.py) (`ue_metric_v1` / `ue_character_v1`) instead of repeating the FBX argument block — one place for the scale conversion, the axis mapping and `mesh_smooth_type="FACE"` (without which every Unreal import warns about missing smoothing groups and round geometry arrives faceted).
|
Every Blender exporter calls [`Tools/fbx_export_preset.py`](../Tools/fbx_export_preset.py) (`ue_metric_v1` / `ue_character_v1`) instead of repeating the FBX argument block — one place for the scale conversion, the axis mapping and `mesh_smooth_type="FACE"` (without which every Unreal import warns about missing smoothing groups and round geometry arrives faceted).
|
||||||
|
|
||||||
Two rules that the pipeline enforces rather than documents:
|
Two rules that the pipeline enforces rather than documents:
|
||||||
|
|||||||
@@ -0,0 +1,728 @@
|
|||||||
|
frame,ms
|
||||||
|
0,2.599
|
||||||
|
1,2.877
|
||||||
|
2,2.709
|
||||||
|
3,2.646
|
||||||
|
4,3.168
|
||||||
|
5,2.729
|
||||||
|
6,2.524
|
||||||
|
7,2.726
|
||||||
|
8,2.639
|
||||||
|
9,2.814
|
||||||
|
10,2.819
|
||||||
|
11,2.708
|
||||||
|
12,2.822
|
||||||
|
13,2.703
|
||||||
|
14,2.365
|
||||||
|
15,2.796
|
||||||
|
16,2.899
|
||||||
|
17,2.972
|
||||||
|
18,2.908
|
||||||
|
19,2.356
|
||||||
|
20,2.781
|
||||||
|
21,2.574
|
||||||
|
22,3.148
|
||||||
|
23,2.306
|
||||||
|
24,2.668
|
||||||
|
25,2.855
|
||||||
|
26,2.588
|
||||||
|
27,3.321
|
||||||
|
28,3.419
|
||||||
|
29,3.121
|
||||||
|
30,3.391
|
||||||
|
31,2.355
|
||||||
|
32,2.679
|
||||||
|
33,3.463
|
||||||
|
34,2.614
|
||||||
|
35,2.922
|
||||||
|
36,2.429
|
||||||
|
37,2.830
|
||||||
|
38,2.842
|
||||||
|
39,2.782
|
||||||
|
40,3.252
|
||||||
|
41,2.731
|
||||||
|
42,2.577
|
||||||
|
43,2.363
|
||||||
|
44,2.786
|
||||||
|
45,2.547
|
||||||
|
46,2.594
|
||||||
|
47,2.652
|
||||||
|
48,2.836
|
||||||
|
49,2.792
|
||||||
|
50,2.552
|
||||||
|
51,2.792
|
||||||
|
52,2.718
|
||||||
|
53,3.333
|
||||||
|
54,2.165
|
||||||
|
55,2.880
|
||||||
|
56,2.613
|
||||||
|
57,2.460
|
||||||
|
58,2.919
|
||||||
|
59,2.770
|
||||||
|
60,2.551
|
||||||
|
61,2.622
|
||||||
|
62,2.712
|
||||||
|
63,2.818
|
||||||
|
64,3.155
|
||||||
|
65,2.472
|
||||||
|
66,2.861
|
||||||
|
67,2.651
|
||||||
|
68,2.573
|
||||||
|
69,2.892
|
||||||
|
70,2.689
|
||||||
|
71,2.729
|
||||||
|
72,2.668
|
||||||
|
73,2.753
|
||||||
|
74,2.812
|
||||||
|
75,2.606
|
||||||
|
76,2.686
|
||||||
|
77,2.842
|
||||||
|
78,2.660
|
||||||
|
79,2.903
|
||||||
|
80,2.486
|
||||||
|
81,2.591
|
||||||
|
82,3.626
|
||||||
|
83,2.455
|
||||||
|
84,2.475
|
||||||
|
85,2.922
|
||||||
|
86,2.603
|
||||||
|
87,2.728
|
||||||
|
88,3.057
|
||||||
|
89,2.731
|
||||||
|
90,3.632
|
||||||
|
91,1.950
|
||||||
|
92,2.694
|
||||||
|
93,3.020
|
||||||
|
94,2.230
|
||||||
|
95,2.796
|
||||||
|
96,2.707
|
||||||
|
97,2.807
|
||||||
|
98,2.658
|
||||||
|
99,2.697
|
||||||
|
100,2.453
|
||||||
|
101,3.051
|
||||||
|
102,2.758
|
||||||
|
103,2.662
|
||||||
|
104,2.599
|
||||||
|
105,2.733
|
||||||
|
106,2.751
|
||||||
|
107,2.894
|
||||||
|
108,2.501
|
||||||
|
109,2.753
|
||||||
|
110,2.873
|
||||||
|
111,2.439
|
||||||
|
112,2.634
|
||||||
|
113,2.682
|
||||||
|
114,2.913
|
||||||
|
115,2.692
|
||||||
|
116,2.735
|
||||||
|
117,2.758
|
||||||
|
118,2.726
|
||||||
|
119,2.907
|
||||||
|
120,2.568
|
||||||
|
121,2.764
|
||||||
|
122,2.815
|
||||||
|
123,2.530
|
||||||
|
124,2.684
|
||||||
|
125,2.719
|
||||||
|
126,2.846
|
||||||
|
127,2.879
|
||||||
|
128,2.647
|
||||||
|
129,2.770
|
||||||
|
130,2.477
|
||||||
|
131,2.657
|
||||||
|
132,2.675
|
||||||
|
133,2.917
|
||||||
|
134,2.700
|
||||||
|
135,2.493
|
||||||
|
136,2.678
|
||||||
|
137,2.786
|
||||||
|
138,3.004
|
||||||
|
139,2.697
|
||||||
|
140,2.642
|
||||||
|
141,2.624
|
||||||
|
142,2.684
|
||||||
|
143,2.717
|
||||||
|
144,2.743
|
||||||
|
145,2.740
|
||||||
|
146,2.825
|
||||||
|
147,2.849
|
||||||
|
148,2.991
|
||||||
|
149,2.297
|
||||||
|
150,3.084
|
||||||
|
151,2.459
|
||||||
|
152,3.214
|
||||||
|
153,2.260
|
||||||
|
154,2.550
|
||||||
|
155,2.621
|
||||||
|
156,2.660
|
||||||
|
157,2.739
|
||||||
|
158,2.574
|
||||||
|
159,3.078
|
||||||
|
160,2.735
|
||||||
|
161,2.607
|
||||||
|
162,2.793
|
||||||
|
163,2.798
|
||||||
|
164,2.584
|
||||||
|
165,2.637
|
||||||
|
166,2.627
|
||||||
|
167,2.726
|
||||||
|
168,2.656
|
||||||
|
169,2.788
|
||||||
|
170,2.674
|
||||||
|
171,2.782
|
||||||
|
172,2.766
|
||||||
|
173,2.613
|
||||||
|
174,2.724
|
||||||
|
175,2.770
|
||||||
|
176,2.709
|
||||||
|
177,2.700
|
||||||
|
178,2.600
|
||||||
|
179,2.686
|
||||||
|
180,2.747
|
||||||
|
181,2.841
|
||||||
|
182,2.601
|
||||||
|
183,2.666
|
||||||
|
184,2.846
|
||||||
|
185,2.599
|
||||||
|
186,2.806
|
||||||
|
187,3.126
|
||||||
|
188,2.521
|
||||||
|
189,2.634
|
||||||
|
190,2.716
|
||||||
|
191,2.584
|
||||||
|
192,2.714
|
||||||
|
193,2.923
|
||||||
|
194,2.277
|
||||||
|
195,2.706
|
||||||
|
196,2.606
|
||||||
|
197,2.758
|
||||||
|
198,2.747
|
||||||
|
199,2.958
|
||||||
|
200,2.798
|
||||||
|
201,2.387
|
||||||
|
202,2.596
|
||||||
|
203,2.861
|
||||||
|
204,2.801
|
||||||
|
205,2.521
|
||||||
|
206,2.869
|
||||||
|
207,2.884
|
||||||
|
208,2.515
|
||||||
|
209,2.592
|
||||||
|
210,3.723
|
||||||
|
211,2.474
|
||||||
|
212,2.736
|
||||||
|
213,2.774
|
||||||
|
214,2.769
|
||||||
|
215,3.007
|
||||||
|
216,2.768
|
||||||
|
217,2.716
|
||||||
|
218,2.694
|
||||||
|
219,2.722
|
||||||
|
220,2.862
|
||||||
|
221,2.587
|
||||||
|
222,2.833
|
||||||
|
223,2.792
|
||||||
|
224,2.746
|
||||||
|
225,2.585
|
||||||
|
226,2.741
|
||||||
|
227,2.585
|
||||||
|
228,2.802
|
||||||
|
229,2.674
|
||||||
|
230,2.742
|
||||||
|
231,2.718
|
||||||
|
232,2.881
|
||||||
|
233,2.707
|
||||||
|
234,2.585
|
||||||
|
235,2.810
|
||||||
|
236,2.948
|
||||||
|
237,2.718
|
||||||
|
238,2.773
|
||||||
|
239,2.571
|
||||||
|
240,2.788
|
||||||
|
241,2.702
|
||||||
|
242,2.634
|
||||||
|
243,2.788
|
||||||
|
244,2.678
|
||||||
|
245,2.914
|
||||||
|
246,2.527
|
||||||
|
247,2.637
|
||||||
|
248,2.877
|
||||||
|
249,2.693
|
||||||
|
250,2.644
|
||||||
|
251,2.670
|
||||||
|
252,2.700
|
||||||
|
253,2.877
|
||||||
|
254,2.638
|
||||||
|
255,2.617
|
||||||
|
256,2.774
|
||||||
|
257,2.660
|
||||||
|
258,2.731
|
||||||
|
259,2.671
|
||||||
|
260,2.838
|
||||||
|
261,2.726
|
||||||
|
262,2.671
|
||||||
|
263,2.744
|
||||||
|
264,2.735
|
||||||
|
265,2.684
|
||||||
|
266,3.850
|
||||||
|
267,2.120
|
||||||
|
268,2.669
|
||||||
|
269,2.729
|
||||||
|
270,2.712
|
||||||
|
271,2.817
|
||||||
|
272,2.926
|
||||||
|
273,2.678
|
||||||
|
274,2.786
|
||||||
|
275,2.927
|
||||||
|
276,2.493
|
||||||
|
277,2.846
|
||||||
|
278,2.610
|
||||||
|
279,2.644
|
||||||
|
280,2.577
|
||||||
|
281,2.939
|
||||||
|
282,2.727
|
||||||
|
283,2.595
|
||||||
|
284,2.819
|
||||||
|
285,2.778
|
||||||
|
286,2.774
|
||||||
|
287,2.628
|
||||||
|
288,2.711
|
||||||
|
289,2.699
|
||||||
|
290,2.715
|
||||||
|
291,2.763
|
||||||
|
292,2.710
|
||||||
|
293,2.892
|
||||||
|
294,2.721
|
||||||
|
295,2.575
|
||||||
|
296,3.608
|
||||||
|
297,2.811
|
||||||
|
298,2.739
|
||||||
|
299,2.751
|
||||||
|
300,2.608
|
||||||
|
301,2.913
|
||||||
|
302,3.052
|
||||||
|
303,2.862
|
||||||
|
304,2.653
|
||||||
|
305,2.727
|
||||||
|
306,2.646
|
||||||
|
307,2.888
|
||||||
|
308,3.338
|
||||||
|
309,2.743
|
||||||
|
310,2.639
|
||||||
|
311,2.681
|
||||||
|
312,2.846
|
||||||
|
313,2.766
|
||||||
|
314,3.184
|
||||||
|
315,2.834
|
||||||
|
316,2.728
|
||||||
|
317,2.581
|
||||||
|
318,2.770
|
||||||
|
319,3.351
|
||||||
|
320,3.157
|
||||||
|
321,2.508
|
||||||
|
322,2.835
|
||||||
|
323,2.754
|
||||||
|
324,2.411
|
||||||
|
325,3.398
|
||||||
|
326,2.802
|
||||||
|
327,2.611
|
||||||
|
328,3.058
|
||||||
|
329,2.407
|
||||||
|
330,2.640
|
||||||
|
331,3.645
|
||||||
|
332,2.729
|
||||||
|
333,2.580
|
||||||
|
334,2.902
|
||||||
|
335,2.523
|
||||||
|
336,3.005
|
||||||
|
337,3.173
|
||||||
|
338,2.840
|
||||||
|
339,2.610
|
||||||
|
340,2.576
|
||||||
|
341,2.706
|
||||||
|
342,3.405
|
||||||
|
343,2.849
|
||||||
|
344,2.678
|
||||||
|
345,2.796
|
||||||
|
346,2.782
|
||||||
|
347,2.636
|
||||||
|
348,3.475
|
||||||
|
349,2.798
|
||||||
|
350,2.745
|
||||||
|
351,2.564
|
||||||
|
352,2.792
|
||||||
|
353,2.714
|
||||||
|
354,3.496
|
||||||
|
355,2.841
|
||||||
|
356,2.465
|
||||||
|
357,2.708
|
||||||
|
358,2.692
|
||||||
|
359,2.934
|
||||||
|
360,3.304
|
||||||
|
361,2.561
|
||||||
|
362,2.689
|
||||||
|
363,2.749
|
||||||
|
364,2.780
|
||||||
|
365,2.940
|
||||||
|
366,3.025
|
||||||
|
367,2.706
|
||||||
|
368,2.881
|
||||||
|
369,2.619
|
||||||
|
370,2.716
|
||||||
|
371,3.133
|
||||||
|
372,2.874
|
||||||
|
373,2.816
|
||||||
|
374,2.414
|
||||||
|
375,2.737
|
||||||
|
376,2.797
|
||||||
|
377,2.651
|
||||||
|
378,2.920
|
||||||
|
379,3.032
|
||||||
|
380,2.606
|
||||||
|
381,2.710
|
||||||
|
382,2.649
|
||||||
|
383,2.700
|
||||||
|
384,3.469
|
||||||
|
385,2.224
|
||||||
|
386,2.567
|
||||||
|
387,2.733
|
||||||
|
388,2.511
|
||||||
|
389,2.950
|
||||||
|
390,3.979
|
||||||
|
391,2.798
|
||||||
|
392,2.487
|
||||||
|
393,2.740
|
||||||
|
394,2.741
|
||||||
|
395,2.718
|
||||||
|
396,3.291
|
||||||
|
397,2.769
|
||||||
|
398,2.697
|
||||||
|
399,2.796
|
||||||
|
400,2.641
|
||||||
|
401,2.652
|
||||||
|
402,3.151
|
||||||
|
403,2.673
|
||||||
|
404,2.741
|
||||||
|
405,2.606
|
||||||
|
406,2.665
|
||||||
|
407,2.859
|
||||||
|
408,2.739
|
||||||
|
409,2.586
|
||||||
|
410,2.910
|
||||||
|
411,2.745
|
||||||
|
412,2.698
|
||||||
|
413,2.670
|
||||||
|
414,2.797
|
||||||
|
415,2.829
|
||||||
|
416,2.643
|
||||||
|
417,2.776
|
||||||
|
418,2.772
|
||||||
|
419,2.530
|
||||||
|
420,2.567
|
||||||
|
421,2.826
|
||||||
|
422,2.782
|
||||||
|
423,2.738
|
||||||
|
424,2.640
|
||||||
|
425,2.967
|
||||||
|
426,2.600
|
||||||
|
427,3.020
|
||||||
|
428,2.502
|
||||||
|
429,2.642
|
||||||
|
430,2.625
|
||||||
|
431,2.770
|
||||||
|
432,2.659
|
||||||
|
433,2.717
|
||||||
|
434,2.698
|
||||||
|
435,2.780
|
||||||
|
436,2.992
|
||||||
|
437,2.686
|
||||||
|
438,2.560
|
||||||
|
439,3.048
|
||||||
|
440,2.721
|
||||||
|
441,2.507
|
||||||
|
442,2.694
|
||||||
|
443,2.695
|
||||||
|
444,2.892
|
||||||
|
445,3.443
|
||||||
|
446,2.633
|
||||||
|
447,2.908
|
||||||
|
448,2.823
|
||||||
|
449,2.346
|
||||||
|
450,2.599
|
||||||
|
451,2.914
|
||||||
|
452,2.774
|
||||||
|
453,3.391
|
||||||
|
454,2.384
|
||||||
|
455,2.585
|
||||||
|
456,2.667
|
||||||
|
457,2.704
|
||||||
|
458,2.674
|
||||||
|
459,2.755
|
||||||
|
460,2.718
|
||||||
|
461,2.804
|
||||||
|
462,2.583
|
||||||
|
463,2.694
|
||||||
|
464,2.797
|
||||||
|
465,3.079
|
||||||
|
466,2.434
|
||||||
|
467,2.723
|
||||||
|
468,3.671
|
||||||
|
469,2.211
|
||||||
|
470,2.567
|
||||||
|
471,2.701
|
||||||
|
472,2.717
|
||||||
|
473,2.657
|
||||||
|
474,2.581
|
||||||
|
475,2.889
|
||||||
|
476,3.006
|
||||||
|
477,2.595
|
||||||
|
478,2.703
|
||||||
|
479,2.498
|
||||||
|
480,2.694
|
||||||
|
481,2.915
|
||||||
|
482,2.655
|
||||||
|
483,2.686
|
||||||
|
484,2.833
|
||||||
|
485,2.717
|
||||||
|
486,2.723
|
||||||
|
487,2.451
|
||||||
|
488,2.913
|
||||||
|
489,2.784
|
||||||
|
490,2.684
|
||||||
|
491,2.651
|
||||||
|
492,2.809
|
||||||
|
493,2.819
|
||||||
|
494,2.676
|
||||||
|
495,2.851
|
||||||
|
496,2.775
|
||||||
|
497,2.638
|
||||||
|
498,2.520
|
||||||
|
499,2.824
|
||||||
|
500,2.930
|
||||||
|
501,2.757
|
||||||
|
502,2.608
|
||||||
|
503,2.808
|
||||||
|
504,2.508
|
||||||
|
505,2.819
|
||||||
|
506,2.654
|
||||||
|
507,2.748
|
||||||
|
508,2.705
|
||||||
|
509,2.798
|
||||||
|
510,2.559
|
||||||
|
511,2.694
|
||||||
|
512,2.821
|
||||||
|
513,2.706
|
||||||
|
514,2.612
|
||||||
|
515,2.928
|
||||||
|
516,2.475
|
||||||
|
517,2.830
|
||||||
|
518,2.658
|
||||||
|
519,2.784
|
||||||
|
520,2.572
|
||||||
|
521,2.935
|
||||||
|
522,2.688
|
||||||
|
523,2.600
|
||||||
|
524,2.675
|
||||||
|
525,2.828
|
||||||
|
526,2.774
|
||||||
|
527,2.848
|
||||||
|
528,2.764
|
||||||
|
529,2.379
|
||||||
|
530,2.962
|
||||||
|
531,2.989
|
||||||
|
532,2.528
|
||||||
|
533,2.639
|
||||||
|
534,2.906
|
||||||
|
535,2.543
|
||||||
|
536,2.582
|
||||||
|
537,2.949
|
||||||
|
538,2.813
|
||||||
|
539,2.504
|
||||||
|
540,2.754
|
||||||
|
541,2.638
|
||||||
|
542,2.695
|
||||||
|
543,2.662
|
||||||
|
544,2.922
|
||||||
|
545,2.616
|
||||||
|
546,2.810
|
||||||
|
547,2.675
|
||||||
|
548,2.551
|
||||||
|
549,2.894
|
||||||
|
550,2.754
|
||||||
|
551,2.632
|
||||||
|
552,2.748
|
||||||
|
553,2.827
|
||||||
|
554,2.751
|
||||||
|
555,2.759
|
||||||
|
556,2.684
|
||||||
|
557,2.766
|
||||||
|
558,2.948
|
||||||
|
559,2.461
|
||||||
|
560,2.621
|
||||||
|
561,2.772
|
||||||
|
562,2.985
|
||||||
|
563,2.582
|
||||||
|
564,2.897
|
||||||
|
565,2.574
|
||||||
|
566,2.661
|
||||||
|
567,2.742
|
||||||
|
568,2.797
|
||||||
|
569,2.632
|
||||||
|
570,2.755
|
||||||
|
571,2.878
|
||||||
|
572,2.759
|
||||||
|
573,3.812
|
||||||
|
574,2.651
|
||||||
|
575,2.862
|
||||||
|
576,2.577
|
||||||
|
577,2.634
|
||||||
|
578,3.173
|
||||||
|
579,2.418
|
||||||
|
580,2.714
|
||||||
|
581,2.746
|
||||||
|
582,2.794
|
||||||
|
583,2.774
|
||||||
|
584,2.583
|
||||||
|
585,3.659
|
||||||
|
586,3.222
|
||||||
|
587,2.489
|
||||||
|
588,2.868
|
||||||
|
589,2.558
|
||||||
|
590,2.586
|
||||||
|
591,2.599
|
||||||
|
592,2.930
|
||||||
|
593,2.401
|
||||||
|
594,2.540
|
||||||
|
595,2.889
|
||||||
|
596,2.733
|
||||||
|
597,2.664
|
||||||
|
598,2.877
|
||||||
|
599,2.588
|
||||||
|
600,2.677
|
||||||
|
601,2.897
|
||||||
|
602,2.672
|
||||||
|
603,2.780
|
||||||
|
604,2.740
|
||||||
|
605,2.780
|
||||||
|
606,2.493
|
||||||
|
607,2.794
|
||||||
|
608,2.631
|
||||||
|
609,2.768
|
||||||
|
610,2.870
|
||||||
|
611,2.626
|
||||||
|
612,2.862
|
||||||
|
613,2.602
|
||||||
|
614,2.868
|
||||||
|
615,2.627
|
||||||
|
616,2.638
|
||||||
|
617,2.702
|
||||||
|
618,2.904
|
||||||
|
619,2.918
|
||||||
|
620,2.487
|
||||||
|
621,3.225
|
||||||
|
622,2.350
|
||||||
|
623,2.820
|
||||||
|
624,2.837
|
||||||
|
625,2.408
|
||||||
|
626,2.593
|
||||||
|
627,3.526
|
||||||
|
628,2.554
|
||||||
|
629,2.746
|
||||||
|
630,2.684
|
||||||
|
631,2.729
|
||||||
|
632,2.762
|
||||||
|
633,2.768
|
||||||
|
634,3.034
|
||||||
|
635,2.514
|
||||||
|
636,2.732
|
||||||
|
637,2.739
|
||||||
|
638,2.692
|
||||||
|
639,2.832
|
||||||
|
640,2.810
|
||||||
|
641,2.484
|
||||||
|
642,2.632
|
||||||
|
643,3.284
|
||||||
|
644,2.311
|
||||||
|
645,3.079
|
||||||
|
646,2.656
|
||||||
|
647,2.683
|
||||||
|
648,2.622
|
||||||
|
649,2.668
|
||||||
|
650,2.779
|
||||||
|
651,2.796
|
||||||
|
652,2.874
|
||||||
|
653,2.427
|
||||||
|
654,2.644
|
||||||
|
655,2.872
|
||||||
|
656,2.867
|
||||||
|
657,2.404
|
||||||
|
658,2.917
|
||||||
|
659,2.773
|
||||||
|
660,2.857
|
||||||
|
661,2.789
|
||||||
|
662,2.443
|
||||||
|
663,2.651
|
||||||
|
664,2.839
|
||||||
|
665,2.814
|
||||||
|
666,2.575
|
||||||
|
667,2.796
|
||||||
|
668,2.709
|
||||||
|
669,2.873
|
||||||
|
670,2.670
|
||||||
|
671,2.936
|
||||||
|
672,2.496
|
||||||
|
673,2.608
|
||||||
|
674,2.641
|
||||||
|
675,2.793
|
||||||
|
676,2.765
|
||||||
|
677,2.736
|
||||||
|
678,2.679
|
||||||
|
679,2.987
|
||||||
|
680,2.884
|
||||||
|
681,2.469
|
||||||
|
682,2.774
|
||||||
|
683,2.948
|
||||||
|
684,2.598
|
||||||
|
685,3.478
|
||||||
|
686,2.856
|
||||||
|
687,4.623
|
||||||
|
688,3.038
|
||||||
|
689,2.507
|
||||||
|
690,2.717
|
||||||
|
691,2.682
|
||||||
|
692,2.807
|
||||||
|
693,2.778
|
||||||
|
694,2.699
|
||||||
|
695,2.802
|
||||||
|
696,2.605
|
||||||
|
697,2.650
|
||||||
|
698,2.789
|
||||||
|
699,2.791
|
||||||
|
700,3.015
|
||||||
|
701,2.539
|
||||||
|
702,2.790
|
||||||
|
703,2.698
|
||||||
|
704,2.637
|
||||||
|
705,2.657
|
||||||
|
706,2.800
|
||||||
|
707,2.720
|
||||||
|
708,2.702
|
||||||
|
709,2.845
|
||||||
|
710,2.583
|
||||||
|
711,2.780
|
||||||
|
712,2.782
|
||||||
|
713,2.593
|
||||||
|
714,2.695
|
||||||
|
715,2.747
|
||||||
|
716,2.871
|
||||||
|
717,2.671
|
||||||
|
718,2.676
|
||||||
|
719,2.991
|
||||||
|
720,2.727
|
||||||
|
721,2.576
|
||||||
|
722,2.539
|
||||||
|
723,2.770
|
||||||
|
724,2.712
|
||||||
|
725,2.818
|
||||||
|
726,2.615
|
||||||
|
+29
-11
@@ -13,7 +13,19 @@ Record results in the table at the end of this file, with the date, the exact bu
|
|||||||
3. **Licensing.** Every new asset has provenance in `THIRD_PARTY_NOTICES.md`, or is recorded as unresolved and kept out of the build.
|
3. **Licensing.** Every new asset has provenance in `THIRD_PARTY_NOTICES.md`, or is recorded as unresolved and kept out of the build.
|
||||||
4. **Version discipline.** Patch releases contain fixes, optimisation, docs, or tuning only — no new scope.
|
4. **Version discipline.** Patch releases contain fixes, optimisation, docs, or tuning only — no new scope.
|
||||||
|
|
||||||
## Performance check
|
## `0.1.0` checks
|
||||||
|
|
||||||
|
1. **Realism first.** Building placement, street width, sidewalk depth, material read and site identity match the reference photography. Compare side by side and note every deliberate deviation. "Looks right" is not evidence.
|
||||||
|
2. Player traverses both the south-side frontage and the north-side frontage of the demo without falling through geometry, including the designated crossing. Walk the route twice: once normally, once with collision debug visualisation enabled.
|
||||||
|
3. No blocking collision, no invisible walls, no gaps at sidewalk/road seams.
|
||||||
|
4. Fresh checkout builds against the documented external engine installation.
|
||||||
|
5. Linux build packaged and launched; controls documented in `Docs/` and shown in-game.
|
||||||
|
6. **Performance last.** 60 FPS at 1440p per the performance check below — a floor every release meets, not the goal that shapes the content.
|
||||||
|
7. Tag `v0.1.0`, write the `CHANGELOG.md` entry, then remove the completed section from `TODO.md` (its goal stays in `ROADMAP.md`).
|
||||||
|
|
||||||
|
## Performance check (last)
|
||||||
|
|
||||||
|
Performance is the **last** check on purpose: realism leads the design and the frame budget only decides how much of it fits. It is still a floor — a release is not tagged below 60 FPS at 1440p without the owner's explicit approval.
|
||||||
|
|
||||||
Measured at 1440p on the development workstation: Bazzite, Ryzen 7 7700X, 64 GB RAM, RTX 5070 Ti 16 GB.
|
Measured at 1440p on the development workstation: Bazzite, Ryzen 7 7700X, 64 GB RAM, RTX 5070 Ti 16 GB.
|
||||||
|
|
||||||
@@ -22,16 +34,6 @@ Measured at 1440p on the development workstation: Bazzite, Ryzen 7 7700X, 64 GB
|
|||||||
- Target: 60 FPS at 1440p without frame generation.
|
- Target: 60 FPS at 1440p without frame generation.
|
||||||
- Capture a frame-time graph over a fixed traversal route, not a single sample. State the route.
|
- Capture a frame-time graph over a fixed traversal route, not a single sample. State the route.
|
||||||
|
|
||||||
## `0.1.0` checks
|
|
||||||
|
|
||||||
1. Player traverses both the south-side frontage and the north-side frontage of the demo without falling through geometry, including the designated crossing. Walk the route twice: once normally, once with collision debug visualisation enabled.
|
|
||||||
2. Building placement, street width, sidewalk depth, and site identity match the reference photography. Compare side by side and note every deliberate deviation. "Looks right" is not evidence.
|
|
||||||
3. No blocking collision, no invisible walls, no gaps at sidewalk/road seams.
|
|
||||||
4. 60 FPS at 1440p per the performance check above.
|
|
||||||
5. Fresh checkout builds against the documented external engine installation.
|
|
||||||
6. Linux build packaged and launched; controls documented in `Docs/` and shown in-game.
|
|
||||||
7. Tag `v0.1.0`, write the `CHANGELOG.md` entry, then remove the completed section from `TODO.md` (its goal stays in `ROADMAP.md`).
|
|
||||||
|
|
||||||
## Check-record template
|
## Check-record template
|
||||||
|
|
||||||
| Version | Check | Result | Evidence | Date |
|
| Version | Check | Result | Evidence | Date |
|
||||||
@@ -125,6 +127,22 @@ Run on the packaged build (`GigLineQA`, Development, Vulkan SM5) with `-qaselfte
|
|||||||
|
|
||||||
Committed frame-time series: `Docs/perf/0.1.0-walk-frametimes.csv`.
|
Committed frame-time series: `Docs/perf/0.1.0-walk-frametimes.csv`.
|
||||||
|
|
||||||
|
### `0.1.5` re-measure with the façade kit in the map
|
||||||
|
|
||||||
|
Same route, same preset, packaged Development build, **2560×1440**:
|
||||||
|
|
||||||
|
| Metric | `0.1.0` | `0.1.5` (kit + textures) |
|
||||||
|
| ------- | ----------------: | -----------------------: |
|
||||||
|
| Average | 2.42 ms (414 fps) | **2.76 ms (363 fps)** |
|
||||||
|
| p50 | 2.38 ms | 2.73 ms |
|
||||||
|
| p95 | 2.71 ms | 3.17 ms |
|
||||||
|
| p99 | 3.01 ms | 3.64 ms |
|
||||||
|
| Max | 4.00 ms | 4.62 ms |
|
||||||
|
|
||||||
|
The **625 kit actors and 8 generated textures cost about 12 %** of frame time and still leave **363 fps average, p99 3.64 ms** — roughly six times the 60 fps floor. Frame-time series: `Docs/perf/0.1.5-kit-walk-frametimes.csv`.
|
||||||
|
|
||||||
|
**Measurement trap, again:** runs that reported ~105 fps were two game instances overlapping (a previous launch had not exited). Confirm a single instance (`pgrep -af Binaries/Linux/GigLine`) before trusting a figure — 363 fps reproduced twice on a clean run.
|
||||||
|
|
||||||
## `0.1.0` — Fresh checkout build — record (2026-11-08)
|
## `0.1.0` — Fresh checkout build — record (2026-11-08)
|
||||||
|
|
||||||
Followed the documented steps in [`Docs/build.md`](build.md) exactly, into a clean directory with no prior state:
|
Followed the documented steps in [`Docs/build.md`](build.md) exactly, into a clean directory with no prior state:
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# `0.1.4` façade review — signoff
|
||||||
|
|
||||||
|
Four demo-map captures from the `facades-0.1.4` branch, after the module kit, the textured materials and the theatre marquee went in. **All seven items signed off 2026-11-09** (owner review of the local renders).
|
||||||
|
|
||||||
|
Reference photographs to compare against: `Art/reference/hollywood/` and `Art/reference/el-capitan-*` (local, not committed). Regenerate the set with `Tools/render_screenshots.sh --verify`; measure it with `python3 Tools/visual_loop.py --compare --iteration 5`.
|
||||||
|
|
||||||
|
## Signoff
|
||||||
|
|
||||||
|
- [x] **1. Block from the north sidewalk** — hero between its neighbours, sidewalk and road read correctly
|
||||||
|
- [x] **2. Street level** — colonnade, shopfronts and window rhythm hold up at eye height
|
||||||
|
- [x] **3. Crossing** — zebra bars, kerb line and the frontage behind
|
||||||
|
- [x] **4. North buildings** — TCL and Tussaud's framed across the boulevard
|
||||||
|
- [x] **5. Materials** — stone reads as coursed stone, glazing as glass, not flat colour
|
||||||
|
- [x] **6. Theatre trim** — marquee and blade sign proportions believable at street level
|
||||||
|
- [x] **7. Approve** `v0.1.4` (kit + materials + theatre trim); remaining geometry moves to the next version
|
||||||
|
|
||||||
|
## 1. Block from the north sidewalk
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Expected: the neoclassical hero façade between its neighbours, both sidewalks and the road in the foreground. _Metric: 0.68 similarity, edges 0.28× reference._
|
||||||
|
|
||||||
|
## 2. Street level
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Expected: the hero colonnade with adjacent frontage buildings either side, shopfronts at pavement level, 4.96 m bay rhythm. _Metric: 0.33, edges 0.27×._
|
||||||
|
|
||||||
|
## 3. The designated crossing
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Expected: continental zebra bars on the asphalt at s=130 m, south frontage and colonnade behind. _Metric: 0.42, edges 0.34×._
|
||||||
|
|
||||||
|
## 4. North-side buildings
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Expected: TCL Chinese Theatre and Madame Tussaud's across Hollywood Blvd, full height in frame. _Metric: 0.34, edges 0.14×._
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix — what was checked, and what was broken
|
||||||
|
|
||||||
|
### Verified
|
||||||
|
|
||||||
|
- **Behaviour**, packaged build, `-qaselftest`: walk 761 cm in 2 s (Z steady 456), reset 0 cm, pause/unpause, seam transect 1456 samples / **0 holes**, wall probe **0/35**, façade occlusion probe **0/9** — all PASS. Run twice: the harness is time-based and one loaded run reported a false failure (`walk 246 cm`, `pause FAIL`), an immediate re-run passed — re-run before believing a single FAIL.
|
||||||
|
- **Placement**, probed from the live map against `Geo/verification/2026-11-06-el-capitan-block.md`: 6922 −24276…−17700, 6902 −17749…−14591, **hero −14644…−12028 between them**, theatre −12082…−8210, 6822 / 6806 / 6800 −7854…−1437, TCL −22889…−18442, Tussaud's −27071…−22799. Blockout affine residual **0.0 cm**.
|
||||||
|
- **Budgets**: hero 148 parts / 3 044 tris · kit 16 meshes × 12 tris · 625 kit actors ≈ 7 500 tris · 8 × 512² textures (196 KB) · 67 tool tests pass.
|
||||||
|
- **Topology**: every kit piece manifold, no loose geometry.
|
||||||
|
|
||||||
|
### Defects found this cycle
|
||||||
|
|
||||||
|
Every one of these rendered convincingly; the placement ones were caught by coordinate probes, not by looking.
|
||||||
|
|
||||||
|
| Sev | Defect | Root cause |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Blocker | Frontage shifted ≈119 m X / 2.4 m Y, hero outside its gap | Blockout regenerated without `--origin` |
|
||||||
|
| Blocker | Kit placed ~12 km from its buildings | Used mesh-local bbox; actors carry the offset |
|
||||||
|
| Blocker | Marquee on the far side of the boulevard | Blender +Y is Unreal −Y |
|
||||||
|
| Major | Marquee canopy 5 cm above the pavement | Absolute Z; pavement is 4.56 m above datum |
|
||||||
|
| Major | Window bays invisible, inside the solid wall | Piece modelled entirely at +Y |
|
||||||
|
| Major | Glazing wasn't glass | Grey `M_Kit_*` from the FBX import, never overridden |
|
||||||
|
| Major | Hero windows floated 1.44 m in front of the wall | Wrong Y in the builder |
|
||||||
|
| Major | Parapet medallions were discs on the coping | Built with the Z-axis cylinder helper |
|
||||||
|
| Major | Hero had no usable collision | Complex-only; traces use simple |
|
||||||
|
| Minor | "No smoothing group information" on every import | `mesh_smooth_type="OFF"` in six exporters |
|
||||||
|
| Minor | `save_map()` returned False silently | LFS `.umap` read-only, importer never chmod'd |
|
||||||
|
| Minor | North-buildings import silently no-op | Missing `import sys` |
|
||||||
|
| Minor | Kit folder held 24 assets (8 orphan materials) | No stale-asset sweep |
|
||||||
|
| Minor | Edge-density metric counted PIL's border ring | `FIND_EDGES` leaves a 128 ring |
|
||||||
|
| Minor | QA wall probe grazed the theatre wall | Scan row 1 cm from the building line |
|
||||||
|
|
||||||
|
### Limitations worth knowing
|
||||||
|
|
||||||
|
- Kit pieces are boxes; dentil spacing, abacus and plinth dimensions are recorded assumptions, not measurements.
|
||||||
|
- Textures are generated, not authored — no decals, signage text or weathering.
|
||||||
|
- The vision pre-check stays advisory: it has rejected shots the owner approved.
|
||||||
|
- The low edge ratios in the metrics are partly structural — a wide block view against a tight façade crop always scores low. Weigh the images, not the scores.
|
||||||
|
- Performance has not been re-measured since `0.1.0`; the 625 kit actors are unmeasured (tracked in `TODO.md` under `0.1.5`).
|
||||||
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.
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.
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.
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,9 @@
|
|||||||
#include "GigLineCharacter.h"
|
#include "GigLineCharacter.h"
|
||||||
|
|
||||||
|
#include "Animation/AnimSequence.h"
|
||||||
#include "Camera/CameraComponent.h"
|
#include "Camera/CameraComponent.h"
|
||||||
|
#include "Components/SkeletalMeshComponent.h"
|
||||||
|
#include "GameFramework/SpringArmComponent.h"
|
||||||
#include "Components/CapsuleComponent.h"
|
#include "Components/CapsuleComponent.h"
|
||||||
#include "EnhancedInputComponent.h"
|
#include "EnhancedInputComponent.h"
|
||||||
#include "EnhancedInputSubsystems.h"
|
#include "EnhancedInputSubsystems.h"
|
||||||
@@ -28,7 +31,7 @@ static TAutoConsoleVariable<int32> CVarGigLineSetupLogs(
|
|||||||
|
|
||||||
AGigLineCharacter::AGigLineCharacter()
|
AGigLineCharacter::AGigLineCharacter()
|
||||||
{
|
{
|
||||||
PrimaryActorTick.bCanEverTick = false;
|
PrimaryActorTick.bCanEverTick = true;
|
||||||
|
|
||||||
// 1.75 m tall, 0.42 m wide, human scale in Unreal units (1 m = 100 uu).
|
// 1.75 m tall, 0.42 m wide, human scale in Unreal units (1 m = 100 uu).
|
||||||
GetCapsuleComponent()->InitCapsuleSize(42.0f, 87.5f);
|
GetCapsuleComponent()->InitCapsuleSize(42.0f, 87.5f);
|
||||||
@@ -51,9 +54,59 @@ AGigLineCharacter::AGigLineCharacter()
|
|||||||
FirstPersonCamera->SetRelativeLocation(FVector(0.0f, 0.0f, 72.5f)); // eye height 1.60 m
|
FirstPersonCamera->SetRelativeLocation(FVector(0.0f, 0.0f, 72.5f)); // eye height 1.60 m
|
||||||
FirstPersonCamera->bUsePawnControlRotation = true;
|
FirstPersonCamera->bUsePawnControlRotation = true;
|
||||||
|
|
||||||
// An eye-level mesh is not needed for 0.1.0; the body is a capsule with a camera.
|
// Visible body (0.2.0). The animated import carries its own skeleton, so the
|
||||||
GetMesh()->SetHiddenInGame(true);
|
// clips resolve without a retarget step; it is hidden while the first-person
|
||||||
|
// camera is active and shown in third person.
|
||||||
|
static ConstructorHelpers::FObjectFinder<USkeletalMesh> BodyFinder(
|
||||||
|
TEXT("/Game/Test/Character/Character_Anim.Character_Anim"));
|
||||||
|
if (BodyFinder.Succeeded())
|
||||||
|
{
|
||||||
|
GetMesh()->SetSkeletalMesh(BodyFinder.Object);
|
||||||
|
}
|
||||||
|
// The animated import carries no material of its own; without this the body is
|
||||||
|
// grey clay. The warm skin material was created by the character import.
|
||||||
|
static ConstructorHelpers::FObjectFinder<UMaterialInterface> SkinFinder(
|
||||||
|
TEXT("/Game/Test/Character/M_Character_Export.M_Character_Export"));
|
||||||
|
if (SkinFinder.Succeeded())
|
||||||
|
{
|
||||||
|
GetMesh()->SetMaterial(0, SkinFinder.Object);
|
||||||
|
}
|
||||||
|
static ConstructorHelpers::FObjectFinder<UAnimSequence> IdleFinder(
|
||||||
|
TEXT("/Game/Test/Character/Character_AnimHuman_rig_GigLine_Idle.Character_AnimHuman_rig_GigLine_Idle"));
|
||||||
|
if (IdleFinder.Succeeded())
|
||||||
|
{
|
||||||
|
IdleAnimation = IdleFinder.Object;
|
||||||
|
}
|
||||||
|
static ConstructorHelpers::FObjectFinder<UAnimSequence> WalkFinder(
|
||||||
|
TEXT("/Game/Test/Character/Character_AnimHuman_rig_GigLine_Walk.Character_AnimHuman_rig_GigLine_Walk"));
|
||||||
|
if (WalkFinder.Succeeded())
|
||||||
|
{
|
||||||
|
WalkAnimation = WalkFinder.Object;
|
||||||
|
}
|
||||||
|
GetMesh()->SetRelativeLocation(FVector(0.0f, 0.0f, -87.5f));
|
||||||
|
GetMesh()->SetRelativeRotation(FRotator(0.0f, -90.0f, 0.0f));
|
||||||
GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||||
|
GetMesh()->SetHiddenInGame(true);
|
||||||
|
|
||||||
|
// Third-person boom. bDoCollisionTest is the camera-collision feature: the arm
|
||||||
|
// shortens when something is between the pivot and the desired camera position.
|
||||||
|
ThirdPersonArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("ThirdPersonArm"));
|
||||||
|
ThirdPersonArm->SetupAttachment(GetCapsuleComponent());
|
||||||
|
ThirdPersonArm->SetRelativeLocation(FVector(0.0f, 0.0f, 60.0f));
|
||||||
|
ThirdPersonArm->TargetArmLength = ThirdPersonBoomLength;
|
||||||
|
ThirdPersonArm->bUsePawnControlRotation = true;
|
||||||
|
ThirdPersonArm->bDoCollisionTest = true;
|
||||||
|
ThirdPersonArm->ProbeSize = 12.0f;
|
||||||
|
ThirdPersonArm->ProbeChannel = ECollisionChannel::ECC_Camera;
|
||||||
|
ThirdPersonArm->bEnableCameraLag = true;
|
||||||
|
ThirdPersonArm->CameraLagSpeed = 8.0f;
|
||||||
|
ThirdPersonArm->SetAutoActivate(false);
|
||||||
|
|
||||||
|
ThirdPersonCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("ThirdPersonCamera"));
|
||||||
|
ThirdPersonCamera->SetupAttachment(ThirdPersonArm, USpringArmComponent::SocketName);
|
||||||
|
ThirdPersonCamera->bUsePawnControlRotation = false;
|
||||||
|
ThirdPersonCamera->SetAutoActivate(false);
|
||||||
|
ThirdPersonCamera->SetFieldOfView(90.0f);
|
||||||
|
|
||||||
// Hard-reference the Enhanced Input assets with ConstructorHelpers so the
|
// Hard-reference the Enhanced Input assets with ConstructorHelpers so the
|
||||||
// cooker always includes them and they load during CDO construction. Runtime
|
// cooker always includes them and they load during CDO construction. Runtime
|
||||||
@@ -78,6 +131,8 @@ AGigLineCharacter::AGigLineCharacter()
|
|||||||
TEXT("/Game/Input/IA_Jump.IA_Jump"));
|
TEXT("/Game/Input/IA_Jump.IA_Jump"));
|
||||||
static ConstructorHelpers::FObjectFinder<UInputAction> ResetF(
|
static ConstructorHelpers::FObjectFinder<UInputAction> ResetF(
|
||||||
TEXT("/Game/Input/IA_Reset.IA_Reset"));
|
TEXT("/Game/Input/IA_Reset.IA_Reset"));
|
||||||
|
static ConstructorHelpers::FObjectFinder<UInputAction> CamF(
|
||||||
|
TEXT("/Game/Input/IA_ToggleCamera.IA_ToggleCamera"));
|
||||||
static ConstructorHelpers::FObjectFinder<UInputAction> PauseF(
|
static ConstructorHelpers::FObjectFinder<UInputAction> PauseF(
|
||||||
TEXT("/Game/Input/IA_Pause.IA_Pause"));
|
TEXT("/Game/Input/IA_Pause.IA_Pause"));
|
||||||
|
|
||||||
@@ -89,6 +144,7 @@ AGigLineCharacter::AGigLineCharacter()
|
|||||||
LookAction = LookF.Object;
|
LookAction = LookF.Object;
|
||||||
JumpAction = JumpF.Object;
|
JumpAction = JumpF.Object;
|
||||||
ResetAction = ResetF.Object;
|
ResetAction = ResetF.Object;
|
||||||
|
ToggleCameraAction = CamF.Object;
|
||||||
PauseAction = PauseF.Object;
|
PauseAction = PauseF.Object;
|
||||||
|
|
||||||
if (!MappingContext || !MoveAction || !MoveBackAction || !StrafeAction || !StrafeLeftAction
|
if (!MappingContext || !MoveAction || !MoveBackAction || !StrafeAction || !StrafeLeftAction
|
||||||
@@ -138,6 +194,23 @@ void AGigLineCharacter::BeginPlay()
|
|||||||
StartLocation = GetActorLocation();
|
StartLocation = GetActorLocation();
|
||||||
StartRotation = GetActorRotation();
|
StartRotation = GetActorRotation();
|
||||||
|
|
||||||
|
// `-thirdperson` starts the pawn in the third-person rig. The first-person view
|
||||||
|
// stays the default (owner decision for 0.1.0), but the screenshot run needs the
|
||||||
|
// body visible without a human pressing V, so the review is reproducible.
|
||||||
|
if (CVarGigLineSetupLogs.GetValueOnGameThread())
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Log,
|
||||||
|
TEXT("GigLine: BeginPlay thirdperson=%d body=%s idle=%s walk=%s"),
|
||||||
|
FParse::Param(FCommandLine::Get(), TEXT("thirdperson")) ? 1 : 0,
|
||||||
|
GetMesh()->GetSkeletalMeshAsset() ? TEXT("ok") : TEXT("nil"),
|
||||||
|
IdleAnimation ? TEXT("ok") : TEXT("nil"),
|
||||||
|
WalkAnimation ? TEXT("ok") : TEXT("nil"));
|
||||||
|
}
|
||||||
|
if (FParse::Param(FCommandLine::Get(), TEXT("thirdperson")))
|
||||||
|
{
|
||||||
|
ToggleCamera();
|
||||||
|
}
|
||||||
|
|
||||||
// The PlayerStart sits above grade so the capsule cannot spawn embedded, so
|
// The PlayerStart sits above grade so the capsule cannot spawn embedded, so
|
||||||
// the BeginPlay position is mid-air. Re-capture once the pawn has landed,
|
// the BeginPlay position is mid-air. Re-capture once the pawn has landed,
|
||||||
// otherwise Reset drops the player 1.4 m back into the air every press.
|
// otherwise Reset drops the player 1.4 m back into the air every press.
|
||||||
@@ -379,7 +452,11 @@ void AGigLineCharacter::StartQASelfTest()
|
|||||||
// trace at a fixed Z (e.g. 160) is wrong: the street is at Z ~366
|
// trace at a fixed Z (e.g. 160) is wrong: the street is at Z ~366
|
||||||
// there, so a 160 cm probe starts inside the earth (dist 0).
|
// there, so a 160 cm probe starts inside the earth (dist 0).
|
||||||
int32 Walls = 0, WallSamples = 0;
|
int32 Walls = 0, WallSamples = 0;
|
||||||
static const float TraceYs[] = {-1400.0f, -167.0f, 1000.0f, -1382.0f, 1047.0f};
|
// Sample the street envelope only: the carriageway plus the walkable
|
||||||
|
// part of each sidewalk. Y 1000 used to be in this list and sits ~1 cm
|
||||||
|
// from the frontage building line, so it grazed the theatre wall once
|
||||||
|
// the kit's proud shopfronts went in - a probe artefact, not a blocker.
|
||||||
|
static const float TraceYs[] = {-1400.0f, -167.0f, 800.0f, -1382.0f, 718.0f};
|
||||||
for (float WallY : TraceYs)
|
for (float WallY : TraceYs)
|
||||||
{
|
{
|
||||||
for (float X0 = -19500.0f; X0 <= -3000.0f; X0 += 2500.0f)
|
for (float X0 = -19500.0f; X0 <= -3000.0f; X0 += 2500.0f)
|
||||||
@@ -553,6 +630,10 @@ void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputCo
|
|||||||
{
|
{
|
||||||
Input->BindAction(PauseAction, ETriggerEvent::Started, this, &AGigLineCharacter::TogglePause);
|
Input->BindAction(PauseAction, ETriggerEvent::Started, this, &AGigLineCharacter::TogglePause);
|
||||||
}
|
}
|
||||||
|
if (ToggleCameraAction)
|
||||||
|
{
|
||||||
|
Input->BindAction(ToggleCameraAction, ETriggerEvent::Started, this, &AGigLineCharacter::ToggleCamera);
|
||||||
|
}
|
||||||
if (JumpAction)
|
if (JumpAction)
|
||||||
{
|
{
|
||||||
Input->BindAction(JumpAction, ETriggerEvent::Started, this, &AGigLineCharacter::StartJump);
|
Input->BindAction(JumpAction, ETriggerEvent::Started, this, &AGigLineCharacter::StartJump);
|
||||||
@@ -640,6 +721,68 @@ void AGigLineCharacter::Look(const FInputActionValue& Value)
|
|||||||
AddControllerPitchInput(Axis.Y * LookSensitivity * (bInvertLookY ? -1.0f : 1.0f));
|
AddControllerPitchInput(Axis.Y * LookSensitivity * (bInvertLookY ? -1.0f : 1.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AGigLineCharacter::Tick(float DeltaSeconds)
|
||||||
|
{
|
||||||
|
Super::Tick(DeltaSeconds);
|
||||||
|
// Speed-driven animation state: correct whether the player walked, was pushed,
|
||||||
|
// or fell, and independent of which input produced the movement.
|
||||||
|
UpdateBodyAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGigLineCharacter::ToggleCamera()
|
||||||
|
{
|
||||||
|
// Both rigs exist from construction; the toggle only swaps which one is active
|
||||||
|
// and whether the body is drawn. Deactivating the other camera matters: with
|
||||||
|
// two active cameras the view is whichever was registered last.
|
||||||
|
bThirdPerson = !bThirdPerson;
|
||||||
|
FirstPersonCamera->SetActive(!bThirdPerson);
|
||||||
|
ThirdPersonCamera->SetActive(bThirdPerson);
|
||||||
|
GetMesh()->SetHiddenInGame(!bThirdPerson);
|
||||||
|
if (bThirdPerson)
|
||||||
|
{
|
||||||
|
UpdateBodyAnimation();
|
||||||
|
}
|
||||||
|
if (CVarGigLineSetupLogs.GetValueOnGameThread())
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine: camera %s"), bThirdPerson ? TEXT("third person") : TEXT("first person"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGigLineCharacter::UpdateBodyAnimation()
|
||||||
|
{
|
||||||
|
if (!bThirdPerson || !GetMesh())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bool bMoving = GetVelocity().Size2D() > WalkAnimationThreshold;
|
||||||
|
UAnimSequence* Wanted = bMoving ? WalkAnimation.Get() : IdleAnimation.Get();
|
||||||
|
if (!Wanted)
|
||||||
|
{
|
||||||
|
// Logged once: a missing clip presents as an A-posed body, which looks like
|
||||||
|
// an animation problem rather than a missing asset reference.
|
||||||
|
static bool bWarnedMissingClip = false;
|
||||||
|
if (!bWarnedMissingClip)
|
||||||
|
{
|
||||||
|
bWarnedMissingClip = true;
|
||||||
|
UE_LOG(LogTemp, Warning,
|
||||||
|
TEXT("GigLine: body clip missing (idle=%s walk=%s) - body stays in bind pose"),
|
||||||
|
IdleAnimation ? TEXT("ok") : TEXT("nil"),
|
||||||
|
WalkAnimation ? TEXT("ok") : TEXT("nil"));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Wanted == PlayingAnimation)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PlayingAnimation = Wanted;
|
||||||
|
if (CVarGigLineSetupLogs.GetValueOnGameThread())
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine: playing body clip %s"), *Wanted->GetName());
|
||||||
|
}
|
||||||
|
GetMesh()->PlayAnimation(Wanted, true);
|
||||||
|
}
|
||||||
|
|
||||||
void AGigLineCharacter::ResetToStart()
|
void AGigLineCharacter::ResetToStart()
|
||||||
{
|
{
|
||||||
// Reset is a level-restart in miniature: put the body back where it spawned,
|
// Reset is a level-restart in miniature: put the body back where it spawned,
|
||||||
|
|||||||
@@ -7,9 +7,11 @@
|
|||||||
#include "InputActionValue.h"
|
#include "InputActionValue.h"
|
||||||
#include "GigLineCharacter.generated.h"
|
#include "GigLineCharacter.generated.h"
|
||||||
|
|
||||||
|
class UAnimSequence;
|
||||||
class UCameraComponent;
|
class UCameraComponent;
|
||||||
class UInputAction;
|
class UInputAction;
|
||||||
class UInputMappingContext;
|
class UInputMappingContext;
|
||||||
|
class USpringArmComponent;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,6 +31,7 @@ public:
|
|||||||
|
|
||||||
virtual void BeginPlay() override;
|
virtual void BeginPlay() override;
|
||||||
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
|
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
|
||||||
|
virtual void Tick(float DeltaSeconds) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/** Loads the Enhanced Input assets from /Game/Input; called from BeginPlay. */
|
/** Loads the Enhanced Input assets from /Game/Input; called from BeginPlay. */
|
||||||
@@ -38,6 +41,23 @@ protected:
|
|||||||
void ResetToStart();
|
void ResetToStart();
|
||||||
void TogglePause();
|
void TogglePause();
|
||||||
|
|
||||||
|
/** Swaps between the first-person and third-person rigs (V). */
|
||||||
|
void ToggleCamera();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays the idle or walk clip on the visible body.
|
||||||
|
*
|
||||||
|
* Called from the movement tick rather than from input: an animation state
|
||||||
|
* driven by speed is correct whether the player walked, was pushed, or fell.
|
||||||
|
* Only switches when the clip changes, because PlayAnimation restarts a clip
|
||||||
|
* and calling it every frame would freeze the cycle at its first key.
|
||||||
|
*/
|
||||||
|
void UpdateBodyAnimation();
|
||||||
|
|
||||||
|
/** Velocity above which the walk clip plays, in cm/s (0.1 m/s tolerance). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation")
|
||||||
|
float WalkAnimationThreshold = 10.0f;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TEMP QA: scripted walk / reset / pause verification.
|
* TEMP QA: scripted walk / reset / pause verification.
|
||||||
*
|
*
|
||||||
@@ -109,6 +129,39 @@ protected:
|
|||||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
|
||||||
TObjectPtr<UCameraComponent> FirstPersonCamera;
|
TObjectPtr<UCameraComponent> FirstPersonCamera;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Third-person boom. Collision testing is on, which is what stops the camera
|
||||||
|
* from ending up inside a wall: the arm shortens until the camera is clear
|
||||||
|
* instead of clipping through geometry.
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
|
||||||
|
TObjectPtr<USpringArmComponent> ThirdPersonArm;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
|
||||||
|
TObjectPtr<UCameraComponent> ThirdPersonCamera;
|
||||||
|
|
||||||
|
/** Boom length in cm: far enough to see the body, close enough to read the street. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
|
||||||
|
float ThirdPersonBoomLength = 340.0f;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UAnimSequence> IdleAnimation;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UAnimSequence> WalkAnimation;
|
||||||
|
|
||||||
|
/** True while the third-person rig is the active view. */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "Camera")
|
||||||
|
bool bThirdPerson = false;
|
||||||
|
|
||||||
|
/** Clip currently playing on the body, so UpdateBodyAnimation can diff it. */
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UAnimSequence> PlayingAnimation;
|
||||||
|
|
||||||
|
/** Toggle-camera action (V). */
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
|
||||||
|
TObjectPtr<UInputAction> ToggleCameraAction;
|
||||||
|
|
||||||
/** Degrees per second per unit of mouse input; defaults to Unreal's usual 45. */
|
/** Degrees per second per unit of mouse input; defaults to Unreal's usual 45. */
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera")
|
||||||
float LookSensitivity = 45.0f;
|
float LookSensitivity = 45.0f;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "GigLineShotActor.h"
|
#include "GigLineShotActor.h"
|
||||||
|
|
||||||
#include "Camera/CameraActor.h"
|
#include "Camera/CameraActor.h"
|
||||||
|
#include "Camera/CameraComponent.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
#include "GameFramework/PlayerController.h"
|
#include "GameFramework/PlayerController.h"
|
||||||
#include "UnrealClient.h"
|
#include "UnrealClient.h"
|
||||||
@@ -83,6 +84,15 @@ void AGigLineShotActor::NextShot()
|
|||||||
Params.ObjectFlags |= RF_Transient;
|
Params.ObjectFlags |= RF_Transient;
|
||||||
CurrentCamera = GetWorld()->SpawnActor<ACameraActor>(
|
CurrentCamera = GetWorld()->SpawnActor<ACameraActor>(
|
||||||
ACameraActor::StaticClass(), ShotLocations[CurrentShot], ShotRotations[CurrentShot], Params);
|
ACameraActor::StaticClass(), ShotLocations[CurrentShot], ShotRotations[CurrentShot], Params);
|
||||||
|
if (ShotFieldOfView.IsValidIndex(CurrentShot) && ShotFieldOfView[CurrentShot] > 0.0f)
|
||||||
|
{
|
||||||
|
if (UCameraComponent* Cam = CurrentCamera->GetCameraComponent())
|
||||||
|
{
|
||||||
|
// Long lens for elevations: fills the frame from across the street
|
||||||
|
// without pitching the camera, so verticals stay parallel.
|
||||||
|
Cam->SetFieldOfView(ShotFieldOfView[CurrentShot]);
|
||||||
|
}
|
||||||
|
}
|
||||||
PlayerController->SetViewTarget(CurrentCamera);
|
PlayerController->SetViewTarget(CurrentCamera);
|
||||||
|
|
||||||
SettleCountdown = SettleSeconds;
|
SettleCountdown = SettleSeconds;
|
||||||
|
|||||||
@@ -36,6 +36,16 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
TArray<FString> ShotNames;
|
TArray<FString> ShotNames;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional per-shot field of view (degrees), same index as ShotLocations.
|
||||||
|
* Zero or missing entries keep the engine default (90 deg). Elevation shots
|
||||||
|
* taken from across the street need a long lens: a level camera 45 m out with
|
||||||
|
* the default FOV leaves the building small in frame, and tilting it up to fill
|
||||||
|
* the frame is what breaks the verticals.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
TArray<float> ShotFieldOfView;
|
||||||
|
|
||||||
/** Seconds to let the view target settle before the capture. Resolution comes from the runner's -ResX/-ResY. */
|
/** Seconds to let the view target settle before the capture. Resolution comes from the runner's -ResX/-ResY. */
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
float SettleSeconds = 0.35f;
|
float SettleSeconds = 0.35f;
|
||||||
|
|||||||
@@ -239,289 +239,287 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
|
|||||||
|
|
||||||
## 2026-11-08 — reference photography gaps closed (local-only)
|
## 2026-11-08 — reference photography gaps closed (local-only)
|
||||||
|
|
||||||
- Closed the two missing reference angles for the hero façade, both from Wikimedia
|
- Closed the two missing reference angles for the hero façade, both from Wikimedia Commons (gitignored deliverables under `Art/reference/`, provenance in sidecars):
|
||||||
Commons (gitignored deliverables under `Art/reference/`, provenance in sidecars):
|
- `el-capitan-entrance/` — `File:Hollywood El Capitan entrance.jpg` (+ detail), Andreas Praefcke, CC BY 3.0. This is the isolated hero (6840) entrance/door recess that the notes doc had claimed Commons didn't hold. Query that found it: `--query "El Capitan entrance"`.
|
||||||
- `el-capitan-entrance/` — `File:Hollywood El Capitan entrance.jpg` (+ detail),
|
- `el-capitan-night/` — `File:El Capitan theater with Tron marquee.jpg` (CC BY-SA 3.0) + West Side Story premiere (Adam Jones, CC BY 2.0). The theatre is the building NEXT to 6840; treat as proximity evidence only, not the hero.
|
||||||
Andreas Praefcke, CC BY 3.0. This is the isolated hero (6840) entrance/door
|
- Commons throttles hard: HTTP 429 after a batch. Fetch fewer files with `--delay`/separate calls and wait ~15-20s between runs (`queue.py` tool default 1 req/s is insufficient once rate limited).
|
||||||
recess that the notes doc had claimed Commons didn't hold. Query that found it:
|
- Task was claimed on branch `ref-photo-0.1.2`; delivers only the TODO closure (all actual reference files are gitignored, so no code branch content). Reported to coordinator for merge/cleanup.
|
||||||
`--query "El Capitan entrance"`.
|
|
||||||
- `el-capitan-night/` — `File:El Capitan theater with Tron marquee.jpg`
|
|
||||||
(CC BY-SA 3.0) + West Side Story premiere (Adam Jones, CC BY 2.0). The theatre is
|
|
||||||
the building NEXT to 6840; treat as proximity evidence only, not the hero.
|
|
||||||
- Commons throttles hard: HTTP 429 after a batch. Fetch fewer files with
|
|
||||||
`--delay`/separate calls and wait ~15-20s between runs (`queue.py` tool default
|
|
||||||
1 req/s is insufficient once rate limited).
|
|
||||||
- Task was claimed on branch `ref-photo-0.1.2`; delivers only the TODO closure (all
|
|
||||||
actual reference files are gitignored, so no code branch content). Reported to
|
|
||||||
coordinator for merge/cleanup.
|
|
||||||
|
|
||||||
## 2026-11-08 — security-game scan: concepts copied, licensing ignored (owner)
|
## 2026-11-08 — security-game scan: concepts copied, licensing ignored (owner)
|
||||||
|
|
||||||
- Scanned `/run/media/system/DATA/Documents/06-games/security-game` for reference
|
- Scanned `/run/media/system/DATA/Documents/06-games/security-game` for reference material vs our `Art/reference/`. Result: its **photo set** (`docs/screens/refs/ hollywood/`, 9 files) is already fully duplicated in our `Art/reference/hollywood/` (with SIDECAR/BOARD copies) — nothing new there.
|
||||||
material vs our `Art/reference/`. Result: its **photo set** (`docs/screens/refs/
|
- What we lacked: its **AI concept renders** (`docs/screens/concepts/ hollywood_boulevard/`, 12 PNGs + `.refgen.json`). Copied into `Art/reference/el-capitan-concepts/` with a folder README. **Owner explicitly said ignore licensing completely** — they are gitignored local development reference, never committed or shipped; `.refgen.json` still records the model and source photo per file for traceability.
|
||||||
hollywood/`, 9 files) is already fully duplicated in our `Art/reference/hollywood/`
|
- Use concepts for mood/lighting intent only; geometry truth stays with the photo refs (facade, entrance, night marquee, TCL/Tussauds, Masonic 1922, etc.).
|
||||||
(with SIDECAR/BOARD copies) — nothing new there.
|
- The security-game `realism/` A/B screenshots and `concepts/joke_shop/` are its own engine/venue renders — not relevant to our 6840 modeling.
|
||||||
- What we lacked: its **AI concept renders** (`docs/screens/concepts/
|
|
||||||
hollywood_boulevard/`, 12 PNGs + `.refgen.json`). Copied into
|
|
||||||
`Art/reference/el-capitan-concepts/` with a folder README. **Owner explicitly
|
|
||||||
said ignore licensing completely** — they are gitignored local development
|
|
||||||
reference, never committed or shipped; `.refgen.json` still records the model
|
|
||||||
and source photo per file for traceability.
|
|
||||||
- Use concepts for mood/lighting intent only; geometry truth stays with the photo
|
|
||||||
refs (facade, entrance, night marquee, TCL/Tussauds, Masonic 1922, etc.).
|
|
||||||
- The security-game `realism/` A/B screenshots and `concepts/joke_shop/` are its
|
|
||||||
own engine/venue renders — not relevant to our 6840 modeling.
|
|
||||||
|
|
||||||
## 2026-11-08 — ODbL review closed; remaining active TODO items finished
|
## 2026-11-08 — ODbL review closed; remaining active TODO items finished
|
||||||
|
|
||||||
- Wrote `Docs/odbl-review.md` (decision 2026-11-08): the shipped block geometry
|
- Wrote `Docs/odbl-review.md` (decision 2026-11-08): the shipped block geometry (streets/footprints/sidewalks) is a Produced Work of the OpenStreetMap database (ODbL 1.0). Obligations held: on-screen attribution via AGigLineHUD ("© OpenStreetMap contributors" + "Data © OSM, ODbL 1.0 — openstreetmap.org/copyright"), ODbL link, and the raw extract committed in Geo/ with a manifest (date, bbox, SHA-256). Share-alike is limited to the data layer; project code/art remain CC0 (Docs/licensing.md decision). 3DEP DEM is US Government public domain, unrelated to ODbL.
|
||||||
(streets/footprints/sidewalks) is a Produced Work of the OpenStreetMap database
|
- Closed both [A] items in TODO: (1) sidewalks/roads/collision — collision verified; remaining kerb radii/aprons/kerb-height explicitly tracked as 0.1.1 patch tasks (Sidewalk outer edge, Crossing ramp gradient, Kerb height); (2) ODbL review — recorded. The [D] Blosm-import (blockout cleaning) item stays deferred (tracked separately).
|
||||||
(ODbL 1.0). Obligations held: on-screen attribution via AGigLineHUD ("©
|
|
||||||
OpenStreetMap contributors" + "Data © OSM, ODbL 1.0 — openstreetmap.org/copyright"),
|
|
||||||
ODbL link, and the raw extract committed in Geo/ with a manifest (date, bbox,
|
|
||||||
SHA-256). Share-alike is limited to the data layer; project code/art remain CC0
|
|
||||||
(Docs/licensing.md decision). 3DEP DEM is US Government public domain, unrelated
|
|
||||||
to ODbL.
|
|
||||||
- Closed both [A] items in TODO: (1) sidewalks/roads/collision — collision
|
|
||||||
verified; remaining kerb radii/aprons/kerb-height explicitly tracked as 0.1.1
|
|
||||||
patch tasks (Sidewalk outer edge, Crossing ramp gradient, Kerb height);
|
|
||||||
(2) ODbL review — recorded. The [D] Blosm-import (blockout cleaning) item stays
|
|
||||||
deferred (tracked separately).
|
|
||||||
- Branch: odbl-review-0.1.0 (docs + TODO closure only).
|
- Branch: odbl-review-0.1.0 (docs + TODO closure only).
|
||||||
|
|
||||||
## 2026-11-08 — placement release check; north-buildings camera was 9.5 km off
|
## 2026-11-08 — placement release check; north-buildings camera was 9.5 km off
|
||||||
|
|
||||||
- Release check 2 (placement vs references): rendered demo, probed DemoMap world
|
- Release check 2 (placement vs references): rendered demo, probed DemoMap world positions, compared to Geo references. Matches: road 24.3 m (ref crossings 20.9-25.9), sidewalk centreline offsets 12.5/13.2 m (ref 12.6/13.6), hero frontage 26.0 m / setback ~13 m (ref 25.75/14.5), six south-frontage buildings
|
||||||
positions, compared to Geo references. Matches: road 24.3 m (ref crossings
|
- TCL/Tussaud's in verified OSM positions.
|
||||||
20.9-25.9), sidewalk centreline offsets 12.5/13.2 m (ref 12.6/13.6), hero
|
- **Bug found:** the `demo-north-buildings` camera sat at X -22800 (the buildings' own X), aiming at its own position -> 85%-sky screenshot. Probed real geometry: TCL bbox X -22889..-18442, Y -9431..-1802; Tussaud's X -27071..-22799, Y -7586..-1888; hero at X -13336, Y +1307 (frontage 26 m). Camera now at (X -22750, Y +1600) looking north; sky fraction 85% -> 41%.
|
||||||
frontage 26.0 m / setback ~13 m (ref 25.75/14.5), six south-frontage buildings
|
- Getting actor world bounds: spawn at loc(0,0,0) means static mesh bbox IS the world footprint (FBX carries the geo offset). Probe by loading the level explicitly (load_level) — a fresh commandlet starts on Untitled otherwise.
|
||||||
+ TCL/Tussaud's in verified OSM positions.
|
|
||||||
- **Bug found:** the `demo-north-buildings` camera sat at X -22800 (the buildings'
|
|
||||||
own X), aiming at its own position -> 85%-sky screenshot. Probed real geometry:
|
|
||||||
TCL bbox X -22889..-18442, Y -9431..-1802; Tussaud's X -27071..-22799,
|
|
||||||
Y -7586..-1888; hero at X -13336, Y +1307 (frontage 26 m). Camera now at
|
|
||||||
(X -22750, Y +1600) looking north; sky fraction 85% -> 41%.
|
|
||||||
- Getting actor world bounds: spawn at loc(0,0,0) means static mesh bbox IS the
|
|
||||||
world footprint (FBX carries the geo offset). Probe by loading the level
|
|
||||||
explicitly (load_level) — a fresh commandlet starts on Untitled otherwise.
|
|
||||||
|
|
||||||
## 2026-11-08 — collision/seam audit done; QA self-test extended
|
## 2026-11-08 — collision/seam audit done; QA self-test extended
|
||||||
|
|
||||||
- `-qaselftest` transect widened from 84 samples (3 lane centres) to **1456
|
- `-qaselftest` transect widened from 84 samples (3 lane centres) to **1456 samples / 16 rows** including kerb/road/sidewalk seam Ys (probed: N side -1912..-1053, kerb -1382..-1053, road -1382..+1047, kerb +718..+1047, S side +718..+1447). 0 holes.
|
||||||
samples / 16 rows** including kerb/road/sidewalk seam Ys (probed: N side
|
- **New wall probe:** ground-referenced horizontal 30 m trace at +160 cm. First attempt used a fixed Z 160 and started INSIDE the earth (street is at Z ~366) -> every row reported a dist-0 blocker. Always ground the probe first.
|
||||||
-1912..-1053, kerb -1382..-1053, road -1382..+1047, kerb +718..+1047, S side
|
- **Guard:** GigLineShotActor must skip rounds under `-qaselftest`, else bQuitWhenDone closes the game before the transect phase (observed: game exited at t=2s with "ShotActor: done (4 shots)" + RequestExit).
|
||||||
+718..+1447). 0 holes.
|
- Results on packaged build: walk 761cm/2s, reset 0cm, pause/unpause, transect 0 holes, wall 0/35 -> all PASS. Evidence recorded in Docs/release-checks.md.
|
||||||
- **New wall probe:** ground-referenced horizontal 30 m trace at +160 cm. First
|
|
||||||
attempt used a fixed Z 160 and started INSIDE the earth (street is at Z ~366)
|
|
||||||
-> every row reported a dist-0 blocker. Always ground the probe first.
|
|
||||||
- **Guard:** GigLineShotActor must skip rounds under `-qaselftest`, else
|
|
||||||
bQuitWhenDone closes the game before the transect phase (observed: game exited
|
|
||||||
at t=2s with "ShotActor: done (4 shots)" + RequestExit).
|
|
||||||
- Results on packaged build: walk 761cm/2s, reset 0cm, pause/unpause, transect
|
|
||||||
0 holes, wall 0/35 -> all PASS. Evidence recorded in Docs/release-checks.md.
|
|
||||||
|
|
||||||
## 2026-11-08 — setup logs gated behind a cvar (diagnostics item)
|
## 2026-11-08 — setup logs gated behind a cvar (diagnostics item)
|
||||||
|
|
||||||
- The remaining spawn/input setup logs (settle-timer "start captured", "GigLine
|
- The remaining spawn/input setup logs (settle-timer "start captured", "GigLine IN:" pushed/bound/errors) are now gated behind `gigline.SetupLogs` (TAutoConsoleVariable, default **0** via `DefaultEngine.ini [ConsoleVariables]`). Re-enable at runtime: `gigline.SetupLogs 1`.
|
||||||
IN:" pushed/bound/errors) are now gated behind `gigline.SetupLogs`
|
- The `-qaselftest` QA logs stay ungated (deliberate release-check feature per Docs/release-checks.md). Verified packaged build: 0 setup-log lines, QA suite still all PASS (walk/reset/pause/transect 0 holes/wall 0-35).
|
||||||
(TAutoConsoleVariable, default **0** via `DefaultEngine.ini [ConsoleVariables]`).
|
- Branch qa-diagnostics-0.1.0. SIM/GROUND probes were removed earlier in the DefaultKeyMappings fix (PR #8).
|
||||||
Re-enable at runtime: `gigline.SetupLogs 1`.
|
|
||||||
- The `-qaselftest` QA logs stay ungated (deliberate release-check feature per
|
|
||||||
Docs/release-checks.md). Verified packaged build: 0 setup-log lines, QA suite
|
|
||||||
still all PASS (walk/reset/pause/transect 0 holes/wall 0-35).
|
|
||||||
- Branch qa-diagnostics-0.1.0. SIM/GROUND probes were removed earlier in the
|
|
||||||
DefaultKeyMappings fix (PR #8).
|
|
||||||
|
|
||||||
## 2026-11-08 — v0.1.0 released
|
## 2026-11-08 — v0.1.0 released
|
||||||
|
|
||||||
- All six `0.1.0` release checks pass and are recorded in `Docs/release-checks.md`:
|
- All six `0.1.0` release checks pass and are recorded in `Docs/release-checks.md`: traversal (seam transect 0 holes), placement vs references, collision+packaging (wall probe 0/35), 60 fps at 1440p (2.42 ms avg; TSR on, VSync off, no FG), fresh-checkout build, packaged Linux build with controls shown in-game.
|
||||||
traversal (seam transect 0 holes), placement vs references, collision+packaging
|
- `CHANGELOG.md` gets the `0.1.0` entry; the completed `0.1.0` section was removed from `TODO.md` (goal stays in `ROADMAP.md`); tag `v0.1.0` cut on `main`.
|
||||||
(wall probe 0/35), 60 fps at 1440p (2.42 ms avg; TSR on, VSync off, no FG),
|
|
||||||
fresh-checkout build, packaged Linux build with controls shown in-game.
|
|
||||||
- `CHANGELOG.md` gets the `0.1.0` entry; the completed `0.1.0` section was removed
|
|
||||||
from `TODO.md` (goal stays in `ROADMAP.md`); tag `v0.1.0` cut on `main`.
|
|
||||||
- Owner approved the regenerated screenshot set (PR #15) on 2026-11-08.
|
- Owner approved the regenerated screenshot set (PR #15) on 2026-11-08.
|
||||||
- THIRD_PARTY_NOTICES switched to checkbox statuses (`[x]`/`[P]`/`[ ]`); all tools
|
- THIRD_PARTY_NOTICES switched to checkbox statuses (`[x]`/`[P]`/`[ ]`); all tools incl. Blosm, QGIS and Git LFS are now verified as tool-only.
|
||||||
incl. Blosm, QGIS and Git LFS are now verified as tool-only.
|
- Next work: the `0.1.1` collision-detail patch (sidewalk outer edge 15.8 cm step, crossing ramp gradient 10 %, kerb height assumption, traversal regression).
|
||||||
- Next work: the `0.1.1` collision-detail patch (sidewalk outer edge 15.8 cm step,
|
|
||||||
crossing ramp gradient 10 %, kerb height assumption, traversal regression).
|
|
||||||
|
|
||||||
## 2026-11-08 — v0.1.1 released (collision patch)
|
## 2026-11-08 — v0.1.1 released (collision patch)
|
||||||
|
|
||||||
- Sidewalk upstand now tapers full-at-kerb → zero at the building edge (removes the
|
- Sidewalk upstand now tapers full-at-kerb → zero at the building edge (removes the 15.8 cm outer ledge). Crossing ramp run derives from `--max-ramp-grade` (8 % ⇒ 1.63 m) instead of a fixed 1.5 m/8.7 %. Street + site rebuilt, site re-imported, DemoMap rebuilt, packaged build verified.
|
||||||
15.8 cm outer ledge). Crossing ramp run derives from `--max-ramp-grade` (8 % ⇒
|
- Pre-check tool fixes: coverage no longer shrinks with `--sample` (it used `range(int(block_len))`), and it reports grade + location of the worst step.
|
||||||
1.63 m) instead of a fixed 1.5 m/8.7 %. Street + site rebuilt, site re-imported,
|
- Residual, documented not hidden: 13.4 cm surface step at s≈198 (south) / 8.0 cm at s≈251 (north) — a strip discontinuity near a centreline vertex, below the 45 cm MaxStepHeight, so not blocking.
|
||||||
DemoMap rebuilt, packaged build verified.
|
- Kerb height finalised at 0.15 m modelled / 0.13 m built (6 in LA standard; DEM and photos can't resolve finer).
|
||||||
- Pre-check tool fixes: coverage no longer shrinks with `--sample` (it used
|
|
||||||
`range(int(block_len))`), and it reports grade + location of the worst step.
|
|
||||||
- Residual, documented not hidden: 13.4 cm surface step at s≈198 (south) / 8.0 cm
|
|
||||||
at s≈251 (north) — a strip discontinuity near a centreline vertex, below the
|
|
||||||
45 cm MaxStepHeight, so not blocking.
|
|
||||||
- Kerb height finalised at 0.15 m modelled / 0.13 m built (6 in LA standard; DEM
|
|
||||||
and photos can't resolve finer).
|
|
||||||
|
|
||||||
## 2026-11-09 — Mapbox building data as a second height source
|
## 2026-11-09 — Mapbox building data as a second height source
|
||||||
|
|
||||||
- `Geo/tools/mapbox_buildings.py` (new, stdlib `urllib`) Tilequeries
|
- `Geo/tools/mapbox_buildings.py` (new, stdlib `urllib`) Tilequeries `mapbox.mapbox-streets-v8` layer `building` at every building centroid in the extract and merges the answer with our OSM height + source. Token comes from `MAPBOX_ACCESS_TOKEN` in the ignored `.env`; responses cache to `Geo/raw/mapbox-cache/`.
|
||||||
`mapbox.mapbox-streets-v8` layer `building` at every building centroid in the
|
- **Mapbox terms restrict persisting/redistributing their data**, so `.gitignore` now covers `/Geo/raw/mapbox-cache/` and `/Geo/verification/*mapbox*`. The tool also refuses to cache a response that contains the token.
|
||||||
extract and merges the answer with our OSM height + source. Token comes from
|
|
||||||
`MAPBOX_ACCESS_TOKEN` in the ignored `.env`; responses cache to
|
|
||||||
`Geo/raw/mapbox-cache/`.
|
|
||||||
- **Mapbox terms restrict persisting/redistributing their data**, so `.gitignore`
|
|
||||||
now covers `/Geo/raw/mapbox-cache/` and `/Geo/verification/*mapbox*`. The tool
|
|
||||||
also refuses to cache a response that contains the token.
|
|
||||||
- **First run (72 buildings): 58 match, 5 close, 3 disagree, 6 non-building.**
|
- **First run (72 buildings): 58 match, 5 close, 3 disagree, 6 non-building.**
|
||||||
- It independently classifies our roof/canopy/shelter volumes as `roof`/
|
- It independently classifies our roof/canopy/shelter volumes as `roof`/ `shelter` — corroborating the `0.1.2` finding that they are not buildings.
|
||||||
`shelter` — corroborating the `0.1.2` finding that they are not buildings.
|
- Remaining disagreements are untyped `building=yes`/`apartments` rear structures where our fallback guess (2 × 3.2 m = 6.4 m) meets Mapbox's 3.0 m.
|
||||||
- Remaining disagreements are untyped `building=yes`/`apartments` rear
|
- Every "close" case is a `building:levels × 3.2 m` building where Mapbox is **0.6 m lower per 6 levels** → Mapbox implies **3.1 m per level**, not 3.2. Worth revisiting `LEVEL_HEIGHT` if a measured storey height ever lands.
|
||||||
structures where our fallback guess (2 × 3.2 m = 6.4 m) meets Mapbox's 3.0 m.
|
|
||||||
- Every "close" case is a `building:levels × 3.2 m` building where Mapbox is
|
|
||||||
**0.6 m lower per 6 levels** → Mapbox implies **3.1 m per level**, not 3.2.
|
|
||||||
Worth revisiting `LEVEL_HEIGHT` if a measured storey height ever lands.
|
|
||||||
- Its query API surface: `height`, `min_height`, `type`, `extrude` per polygon.
|
- Its query API surface: `height`, `min_height`, `type`, `extrude` per polygon.
|
||||||
|
|
||||||
## 2026-11-09 — Mapbox calibration adopted into the blockout
|
## 2026-11-09 — Mapbox calibration adopted into the blockout
|
||||||
|
|
||||||
- Owner decision: adopt Mapbox. Applied:
|
- Owner decision: adopt Mapbox. Applied:
|
||||||
- `LEVEL_HEIGHT` 3.2 → **3.1 m** in `Geo/tools/block_height_report.py`, and the
|
- `LEVEL_HEIGHT` 3.2 → **3.1 m** in `Geo/tools/block_height_report.py`, and the Blosm `--level-height` default in `Art/blender/blockout/blosm_blockout.py`.
|
||||||
Blosm `--level-height` default in `Art/blender/blockout/blosm_blockout.py`.
|
- `HEIGHT_OVERRIDES` in block_height_report: ways 1490910971, 1492058875, 1492747608 → 3.0 m (Mapbox single-storey; the 2-level guess gave 6.4 m).
|
||||||
- `HEIGHT_OVERRIDES` in block_height_report: ways 1490910971, 1492058875,
|
- `classify_height(tags, level_height, way_id)` now takes the way id so overrides apply; `mapbox_buildings.py` passes it, so the cross-check agrees with the height table.
|
||||||
1492747608 → 3.0 m (Mapbox single-storey; the 2-level guess gave 6.4 m).
|
|
||||||
- `classify_height(tags, level_height, way_id)` now takes the way id so
|
|
||||||
overrides apply; `mapbox_buildings.py` passes it, so the cross-check agrees
|
|
||||||
with the height table.
|
|
||||||
- Re-running the cross-check: **66 match, 6 non-building, 0 close/disagree**.
|
- Re-running the cross-check: **66 match, 6 non-building, 0 close/disagree**.
|
||||||
- Geometry: blockout regenerated at 3.1 (object names preserved — the frontage
|
- Geometry: blockout regenerated at 3.1 (object names preserved — the frontage exporter maps `element.020`/`element`/`element.019`/named buildings, all still present, 6/6 exported), frontage re-imported, DemoMap rebuilt, packaged build `-qaselftest` all PASS.
|
||||||
exporter maps `element.020`/`element`/`element.019`/named buildings, all still
|
- **Blosm adds roof mass** on top of `levels × levelHeight`: 6922 39.1 m vs table 37.2, El Capitan Theatre 20.5 m vs 18.6 — both ~+1.9 m. That is a roof, not a height error; the table is the tagged height, the mesh is storey + roof.
|
||||||
present, 6/6 exported), frontage re-imported, DemoMap rebuilt, packaged build
|
- Gotcha: regenerating the blockout is safe for names, but any future change to the import must re-verify the 6/6 frontage export and the `element.*` mapping.
|
||||||
`-qaselftest` all PASS.
|
|
||||||
- **Blosm adds roof mass** on top of `levels × levelHeight`: 6922 39.1 m vs table
|
|
||||||
37.2, El Capitan Theatre 20.5 m vs 18.6 — both ~+1.9 m. That is a roof, not a
|
|
||||||
height error; the table is the tagged height, the mesh is storey + roof.
|
|
||||||
- Gotcha: regenerating the blockout is safe for names, but any future change to
|
|
||||||
the import must re-verify the 6/6 frontage export and the `element.*` mapping.
|
|
||||||
|
|
||||||
## 2026-11-09 — hero colonnade proportion fixed (47.6 % → 53.1 %)
|
## 2026-11-09 — hero colonnade proportion fixed (47.6 % → 53.1 %)
|
||||||
|
|
||||||
- The hero's colonnade (columns 2.00–7.95 + capitals 7.95–8.90) was 6.90 m of the
|
- The hero's colonnade (columns 2.00–7.95 + capitals 7.95–8.90) was 6.90 m of the 14.50 m elevation = **47.6 %**, below the photographed 50–60 %, because the entablature (3.40 m) and parapet (2.20 m) used 5.60 m of the height.
|
||||||
14.50 m elevation = **47.6 %**, below the photographed 50–60 %, because the
|
- Thinned ENTABLATURE_H 3.40 → 3.00 and PARAPET_H 2.20 → 1.80 in `Tools/hero_facade_build.py`; the colonnade grows to **7.70 m = 53.1 %** while the envelope, motto band and medallions keep their relationships (all positioned relative to those two constants).
|
||||||
entablature (3.40 m) and parapet (2.20 m) used 5.60 m of the height.
|
- **Gotcha (cost a bad build):** `hero_facade_build.py` takes `--out` for the **FBX** and `--save-blend` for the `.blend`. Passing `--out <file>.blend` writes FBX bytes over the blend and Blender then reports "File format is not supported". Correct invocation: `--out /tmp/gigline/hero-facade.fbx --save-blend Art/...blend --report Art/...json`.
|
||||||
- Thinned ENTABLATURE_H 3.40 → 3.00 and PARAPET_H 2.20 → 1.80 in
|
- Rebuilt hero → re-imported (`SM_HeroFacade`, bounds 2604 × 1257 × 1450 cm) → DemoMap rebuilt → screenshots re-rendered. Note `ue_hero_facade_import.py` saves the map, so `chmod u+w` the `.umap` first or the import silently does not persist.
|
||||||
`Tools/hero_facade_build.py`; the colonnade grows to **7.70 m = 53.1 %** while the
|
|
||||||
envelope, motto band and medallions keep their relationships (all positioned
|
|
||||||
relative to those two constants).
|
|
||||||
- **Gotcha (cost a bad build):** `hero_facade_build.py` takes `--out` for the
|
|
||||||
**FBX** and `--save-blend` for the `.blend`. Passing `--out <file>.blend` writes
|
|
||||||
FBX bytes over the blend and Blender then reports "File format is not supported".
|
|
||||||
Correct invocation: `--out /tmp/gigline/hero-facade.fbx --save-blend Art/...blend
|
|
||||||
--report Art/...json`.
|
|
||||||
- Rebuilt hero → re-imported (`SM_HeroFacade`, bounds 2604 × 1257 × 1450 cm) →
|
|
||||||
DemoMap rebuilt → screenshots re-rendered. Note `ue_hero_facade_import.py` saves
|
|
||||||
the map, so `chmod u+w` the `.umap` first or the import silently does not persist.
|
|
||||||
|
|
||||||
## 2026-11-09 — AI detail plates from reference photos; floating windows fixed
|
## 2026-11-09 — AI detail plates from reference photos; floating windows fixed
|
||||||
|
|
||||||
- `Tools/facade_detail_concepts.py` generates architectural detail plates from one
|
- `Tools/facade_detail_concepts.py` generates architectural detail plates from one local reference photograph via OpenRouter image models (default `google/gemini-2.5-flash-image`; the sibling project uses the same route in `tools/generate_textures.py` — chat/completions with a text + image_url content part, image returned as `message.images[].image_url.url`). Eight studies, `--list` free, `--all` generates, `-vN` never overwrites, `.refgen.json` sidecar per image, all output under gitignored `Art/reference/facade-details/`.
|
||||||
local reference photograph via OpenRouter image models (default
|
- **Prompt gotcha:** the model refuses when the prompt describes features the reference photograph does not show ("the image you sent appears to be a decorative architectural detail, not an entrance with steps"). Prompts must describe the _reference subject_ and only restage light/people/surface.
|
||||||
`google/gemini-2.5-flash-image`; the sibling project uses the same route in
|
|
||||||
`tools/generate_textures.py` — chat/completions with a text + image_url content
|
|
||||||
part, image returned as `message.images[].image_url.url`). Eight studies,
|
|
||||||
`--list` free, `--all` generates, `-vN` never overwrites, `.refgen.json` sidecar
|
|
||||||
per image, all output under gitignored `Art/reference/facade-details/`.
|
|
||||||
- **Prompt gotcha:** the model refuses when the prompt describes features the
|
|
||||||
reference photograph does not show ("the image you sent appears to be a
|
|
||||||
decorative architectural detail, not an entrance with steps"). Prompts must
|
|
||||||
describe the *reference subject* and only restage light/people/surface.
|
|
||||||
- **Output is real:** 1184×864 to 1344×768 PNGs, ~2 MB each.
|
- **Output is real:** 1184×864 to 1344×768 PNGs, ~2 MB each.
|
||||||
- **Measured defect found and fixed:** hero window panes sat at Y −1.98 while the
|
- **Measured defect found and fixed:** hero window panes sat at Y −1.98 while the mass front is Y −0.50 and the columns reach −1.02 — i.e. glazing floating 1.44 m in front of the wall and ahead of the colonnade. Now `WINDOW_Y = −0.56` (6 cm proud of the wall, behind the columns). Visible as dark openings in `review-hero-facade-front`.
|
||||||
mass front is Y −0.50 and the columns reach −1.02 — i.e. glazing floating 1.44 m
|
- Reminder: `.blend` is LFS read-only on checkout — `chmod u+w` or Blender fails with "Cannot save blend file … not writable" (this bit again here).
|
||||||
in front of the wall and ahead of the colonnade. Now `WINDOW_Y = −0.56`
|
|
||||||
(6 cm proud of the wall, behind the columns). Visible as dark openings in
|
|
||||||
`review-hero-facade-front`.
|
|
||||||
- Reminder: `.blend` is LFS read-only on checkout — `chmod u+w` or Blender fails
|
|
||||||
with "Cannot save blend file … not writable" (this bit again here).
|
|
||||||
|
|
||||||
## 2026-11-09 — UE export hygiene: smoothing groups + facade collision
|
## 2026-11-09 — UE export hygiene: smoothing groups + facade collision
|
||||||
|
|
||||||
- **All our FBX exporters used `mesh_smooth_type="OFF"`**, which is why every Unreal
|
- **All our FBX exporters used `mesh_smooth_type="OFF"`**, which is why every Unreal import logged "No smoothing group information was found for this mesh". The hero now exports `"FACE"` and its round parts are shaded with `shade_smooth_by_angle(35°)` before export: cylinders read as round instead of faceted, and the import warning is gone (6 → 0).
|
||||||
import logged "No smoothing group information was found for this mesh". The hero
|
- **The hero façade had no simple collision** (complex-only), so line traces and capsule sweeps passed straight through it — the player could walk through the wall of the demo. Caught by a new `-qaselftest` **facade occlusion probe** (40 m sideways traces from the road centre at walking height).
|
||||||
now exports `"FACE"` and its round parts are shaded with
|
- **Auto-generated convex collision on a 95-part 26 m façade is unusable**: it produced coarse hulls that bulged into the street and the wall probe went 0/35 → 2/35. Rejected in favour of `UseComplexAsSimple` via `UGigLineAssetTools.set_complex_collision_as_simple` (same reason the terrain uses it), saved on the asset.
|
||||||
`shade_smooth_by_angle(35°)` before export: cylinders read as round instead of
|
- Probe expectations must mirror the staged buildings: the facade probe now carries explicit X spans per frontage (and the TCL/Tussaud's spans on the north side) so a gap between buildings is not reported as a defect.
|
||||||
faceted, and the import warning is gone (6 → 0).
|
|
||||||
- **The hero façade had no simple collision** (complex-only), so line traces and
|
|
||||||
capsule sweeps passed straight through it — the player could walk through the
|
|
||||||
wall of the demo. Caught by a new `-qaselftest` **facade occlusion probe** (40 m
|
|
||||||
sideways traces from the road centre at walking height).
|
|
||||||
- **Auto-generated convex collision on a 95-part 26 m façade is unusable**: it
|
|
||||||
produced coarse hulls that bulged into the street and the wall probe went
|
|
||||||
0/35 → 2/35. Rejected in favour of `UseComplexAsSimple` via
|
|
||||||
`UGigLineAssetTools.set_complex_collision_as_simple` (same reason the terrain
|
|
||||||
uses it), saved on the asset.
|
|
||||||
- Probe expectations must mirror the staged buildings: the facade probe now carries
|
|
||||||
explicit X spans per frontage (and the TCL/Tussaud's spans on the north side) so
|
|
||||||
a gap between buildings is not reported as a defect.
|
|
||||||
|
|
||||||
## 2026-11-09 — one FBX export preset instead of six copies
|
## 2026-11-09 — one FBX export preset instead of six copies
|
||||||
|
|
||||||
- The `bpy.ops.export_scene.fbx(...)` argument block was duplicated in six scripts
|
- The `bpy.ops.export_scene.fbx(...)` argument block was duplicated in six scripts (hero, block frontage, north buildings, site, scale test, MPFB character), and all six carried `mesh_smooth_type="OFF"` — hence the "No smoothing group information was found" warning on every import.
|
||||||
(hero, block frontage, north buildings, site, scale test, MPFB character), and
|
- Now `Tools/fbx_export_preset.py` holds `ue_metric_v1` and `ue_character_v1`: `preset_kwargs()` (bpy-free, unit-tested) plus `export_static()` / `export_character()` wrappers that import `bpy` lazily. Every caller imports it and passes `selection=True/False`; the seven duplicated blocks are gone.
|
||||||
all six carried `mesh_smooth_type="OFF"` — hence the "No smoothing group
|
- Callers in `Tools/` resolve the module with `sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))`; the Blender-side `Art/blender/site/export_site_fbx.py` computes the repo root (`..`, `..`, `..`) and joins `Tools`. Getting that wrong silently inserts `Tools/Tools` — check the import path when adding a caller.
|
||||||
information was found" warning on every import.
|
- Verified end to end: scale test, hero, frontage (6/6), site (7/7) and north buildings all re-exported through the preset, re-imported with **0 smoothing warnings**, DemoMap rebuilt and `-qaselftest` all PASS (walk, transect 0 holes, wall probe 0/35, facade probe 0/9).
|
||||||
- Now `Tools/fbx_export_preset.py` holds `ue_metric_v1` and `ue_character_v1`:
|
- `mpfb_character_export.py` was not re-run: it needs the MPFB add-on on `BLENDER_USER_SCRIPTS` ("Add-on not loaded: bl_ext.blender_org.mpfb"), which is pre-existing and unrelated; the character is not in `0.1.x` gameplay.
|
||||||
`preset_kwargs()` (bpy-free, unit-tested) plus `export_static()` /
|
|
||||||
`export_character()` wrappers that import `bpy` lazily. Every caller imports it
|
|
||||||
and passes `selection=True/False`; the seven duplicated blocks are gone.
|
|
||||||
- Callers in `Tools/` resolve the module with
|
|
||||||
`sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))`; the Blender-side
|
|
||||||
`Art/blender/site/export_site_fbx.py` computes the repo root
|
|
||||||
(`..`, `..`, `..`) and joins `Tools`. Getting that wrong silently inserts
|
|
||||||
`Tools/Tools` — check the import path when adding a caller.
|
|
||||||
- Verified end to end: scale test, hero, frontage (6/6), site (7/7) and north
|
|
||||||
buildings all re-exported through the preset, re-imported with **0 smoothing
|
|
||||||
warnings**, DemoMap rebuilt and `-qaselftest` all PASS (walk, transect 0 holes,
|
|
||||||
wall probe 0/35, facade probe 0/9).
|
|
||||||
- `mpfb_character_export.py` was not re-run: it needs the MPFB add-on on
|
|
||||||
`BLENDER_USER_SCRIPTS` ("Add-on not loaded: bl_ext.blender_org.mpfb"), which is
|
|
||||||
pre-existing and unrelated; the character is not in `0.1.x` gameplay.
|
|
||||||
|
|
||||||
## 2026-11-09 — v0.1.2 released
|
## 2026-11-09 — v0.1.2 released
|
||||||
|
|
||||||
- Cornice finished: projecting corona over a **52-dentil course** (0.18 × 0.30 ×
|
- Cornice finished: projecting corona over a **52-dentil course** (0.18 × 0.30 × 0.22 m at 0.45 m centres, z 12.12–12.34, 0.25 m clear of the corona). Hero is now 3 044 tris across 148 parts, still 14.50 m. Verified: packaged `-qaselftest` all PASS (transect 0 holes, wall probe 0/35, facade probe 0/9).
|
||||||
0.22 m at 0.45 m centres, z 12.12–12.34, 0.25 m clear of the corona). Hero is now
|
- `CHANGELOG.md` gets the `0.1.2` entry (added/changed/fixed/verified/limitations), the `0.1.2` section is removed from `TODO.md`, `ROADMAP.md` status updated, tag `v0.1.2` cut after the merge.
|
||||||
3 044 tris across 148 parts, still 14.50 m. Verified: packaged `-qaselftest` all
|
- Assumptions recorded rather than hidden: dentil spacing, base and abacus dimensions are not resolvable in our reference photography.
|
||||||
PASS (transect 0 holes, wall probe 0/35, facade probe 0/9).
|
|
||||||
- `CHANGELOG.md` gets the `0.1.2` entry (added/changed/fixed/verified/limitations),
|
|
||||||
the `0.1.2` section is removed from `TODO.md`, `ROADMAP.md` status updated, tag
|
|
||||||
`v0.1.2` cut after the merge.
|
|
||||||
- Assumptions recorded rather than hidden: dentil spacing, base and abacus
|
|
||||||
dimensions are not resolvable in our reference photography.
|
|
||||||
|
|
||||||
## 2026-11-09 — elevation cameras levelled (straight verticals)
|
## 2026-11-09 — elevation cameras levelled (straight verticals)
|
||||||
|
|
||||||
- archviz MUST: an elevation must not lean. `hero-facade-front` looked up ~4° from
|
- archviz MUST: an elevation must not lean. `hero-facade-front` looked up ~4° from 4.2 m at a 7.6 m target; `demo-spawn-view` and `demo-street-level` were pitched too. All three now put the camera at the target's height, so `FindLookAtRotation` yields pitch 0 and roll 0 and verticals are parallel by construction.
|
||||||
4.2 m at a 7.6 m target; `demo-spawn-view` and `demo-street-level` were pitched
|
- Do not try to _measure_ verticality from the rendered PNG with a naive edge scan: banners, windows and the parapet produce different edge sets at different heights and the pairwise match is meaningless (it reported a 418 px "drift"). Verify it geometrically in the camera list instead.
|
||||||
too. All three now put the camera at the target's height, so
|
|
||||||
`FindLookAtRotation` yields pitch 0 and roll 0 and verticals are parallel by
|
|
||||||
construction.
|
|
||||||
- Do not try to *measure* verticality from the rendered PNG with a naive edge
|
|
||||||
scan: banners, windows and the parapet produce different edge sets at different
|
|
||||||
heights and the pairwise match is meaningless (it reported a 418 px "drift").
|
|
||||||
Verify it geometrically in the camera list instead.
|
|
||||||
|
|
||||||
## 2026-11-09 — 0.1.3 packaging/setup fixes
|
## 2026-11-09 — 0.1.3 packaging/setup fixes
|
||||||
|
|
||||||
- **`Tools/ue_common.py`**: `make_writable()` clears the read-only bit an LFS
|
- **`Tools/ue_common.py`**: `make_writable()` clears the read-only bit an LFS `.umap` checks out with (otherwise `save_map()` returns False with no error and the staged actors vanish), and `require_saved()` makes that failure visible. `ue_hero_facade_import.py` and `ue_import_north_buildings.py` never did this — which is why importing the hero appeared to succeed while saving nothing. Verified by forcing `chmod u-w` on DemoMap first: the run logged `map writable: chmod u+w … (was 0o444)`, then `save_map: True`, exit 0.
|
||||||
`.umap` checks out with (otherwise `save_map()` returns False with no error and
|
- Gotcha while adding it: `ue_hero_facade_import.py` had **no** `import os`/`sys` (only `unreal`), so the sys.path line raised `NameError: name 'sys' is not defined` inside the commandlet — an exit code of 255 with no obvious cause.
|
||||||
the staged actors vanish), and `require_saved()` makes that failure visible.
|
- Docs: `build.md` gained the current pipeline table (7 site FBXs, frontage, north, hero, map), the `Builds/GigLineQA` archive, the packaged-log path, `-qaselftest` usage and a **traps** table; `setup.md` marks the fresh-checkout build verified and documents the two optional API keys; architecture 07 + combined doc corrected the build-output path.
|
||||||
`ue_hero_facade_import.py` and `ue_import_north_buildings.py` never did this —
|
|
||||||
which is why importing the hero appeared to succeed while saving nothing.
|
## 2026-11-09 — 0.1.4 materials + architecture-camera pass
|
||||||
Verified by forcing `chmod u-w` on DemoMap first: the run logged
|
|
||||||
`map writable: chmod u+w … (was 0o444)`, then `save_map: True`, exit 0.
|
- **Material graphs**: `ensure_material()` now supports masonry coursing (`courses_m`) and roughness variation. **UE's `WorldPosition` is in CENTIMETRES**, so a course height in metres must be ×100 — the first attempt divided by 0.55 and produced coursing 100× too fine to see.
|
||||||
- Gotcha while adding it: `ue_hero_facade_import.py` had **no** `import os`/`sys`
|
- **`connect_material_expressions` returns False instead of raising**, so an unknown pin name leaves an input unconnected and the material silently compiles with a default (a mask of 0 is invisible). All links now go through a `link()` helper that logs failures. There is also **no `MaterialExpressionLess`**; use `Frac → Divide → OneMinus → Clamp` for a soft threshold band, or `MaterialExpressionStep`/`SmoothStep` if a hard one is wanted.
|
||||||
(only `unreal`), so the sys.path line raised `NameError: name 'sys' is not
|
- **Material assignment was slot-0-only**, which on the hero is the _banner_ — the stone, shadowed stone and glazing kept their flat Blender materials. Assignment is now per slot, keyed on the imported slot material's name (`glass`/`banner`/`stoneshadow`/`stone`/`roof`/`wall`). Hero: 4/4 slots.
|
||||||
defined` inside the commandlet — an exit code of 255 with no obvious cause.
|
- New materials: `M_FacadeStoneShadow` (same coursing, a stop darker), `M_FacadeGlass` (roughness 0.14, specular 0.62 ≈ IOR 1.5 expressed without a refraction pass), `M_FacadeBanner` (the one saturated surface).
|
||||||
- Docs: `build.md` gained the current pipeline table (7 site FBXs, frontage, north,
|
- **Camera FOV**: levelling the elevation cameras left the building small in frame (39 % sky). Added optional per-shot `ShotFieldOfView` to `AGigLineShotActor` and a 4th element on the shot tuples; a long lens (42–55°) fills the frame from across the street while the camera stays level, so verticals stay parallel. `review-hero-facade-front` sky 39 → 20 %, `demo-spawn-view` 23 → 16 %.
|
||||||
hero, map), the `Builds/GigLineQA` archive, the packaged-log path, `-qaselftest`
|
|
||||||
usage and a **traps** table; `setup.md` marks the fresh-checkout build verified and
|
## 2026-11-09 — facade texture maps (0.1.4)
|
||||||
documents the two optional API keys; architecture 07 + combined doc corrected the
|
|
||||||
build-output path.
|
- `Tools/generate_facade_textures.py` writes deterministic 512² base-colour and roughness maps (ashlar stone, stucco, asphalt, concrete) with a pure-zlib PNG encoder and hash-based value noise — no numpy/Pillow, ~14 s for all eight, and byte-identical on a re-run (checked by md5sum).
|
||||||
|
- `ue_build_demo_map.py` imports them to `/Game/Hollywood/Textures` and samples them **world-aligned**: WorldPosition (cm) → ×(1/tile_cm) → ComponentMask(R,G) → TextureSample UVs. `tile_cm` per material: stone 440, stucco 260, asphalt 900, concrete 250.
|
||||||
|
- Gotchas: `MaterialExpressionTextureSample`'s UV input pin is **"UVs"**, and the output pin for colour is **"RGB"** / roughness **"R"**. `ComponentMask` pins are R/G/B/A booleans. A textured material skips the graph coursing on purpose — overlaying both would double the course pattern.
|
||||||
|
- The script needs `ROOT` in `ue_build_demo_map.py` and `sys` imported; forgetting either fails with `NameError` and an exit code of 0 from the commandlet (always read the result file under /tmp/gigline).
|
||||||
|
|
||||||
|
## 2026-11-09 — façade module kit (0.1.4)
|
||||||
|
|
||||||
|
- `Tools/facade_kit_build.py` builds the kit at the measured **4.96 m bay** and **3.10 m storey**: pilaster, sill, lintel, window bay, shopfront bay, door bay, cornice, parapet + coping = 16 meshes, exported per-object with `fbx_export_preset` plus a JSON part report (dims + evidence per piece).
|
||||||
|
- `Tools/ue_import_facade_kit.py` → `/Game/Hollywood/Kit/*`, complex-as-simple collision (a convex hull would fence off the shopfront glazing).
|
||||||
|
- `Tools/ue_apply_facade_kit.py` lays the kit out from each building's own bbox: `bays = round(width / 496 cm)`, storeys from height minus the 310 cm ground floor. **638 pieces** over six frontage buildings + both north buildings.
|
||||||
|
- **Bug worth remembering:** the first window piece was modelled entirely at +Y (into the wall), so every window sat _inside_ the solid blockout wall and was invisible in every render while the placement maths looked right. The kit now projects the surround 3 cm proud with the glazing 6 cm behind its face — a solid-wall blockout cannot carry a real recess, so the piece has to project.
|
||||||
|
- Blender gotcha: clearing the scene _including materials_ invalidates handles already taken ("StructRNA of type Material has been removed"); clear objects and orphan meshes only, and create the material after the clear.
|
||||||
|
- The kit meshes are spawned at the world origin with their position baked into the mesh (like the frontage exports), so placement uses the bbox directly and must not add the actor location.
|
||||||
|
|
||||||
|
## 2026-11-09 — CRITICAL: blockout must be regenerated with --origin
|
||||||
|
|
||||||
|
- Regenerating the blockout in 0.1.2 (for the 3.1 m storey height) was run **without `--origin`**, so Blosm centred on the extract instead of fitting the project frame. The whole frontage shifted about **119 m in X and 2.4 m in Y**, and the hero ended up outside the 6902/6838 gap — the "verified placement" was undone silently, and every render still looked like a plausible block.
|
||||||
|
- Correct invocation (README documents it, and the affine fit prints `frame correction residual: max 0.0 cm`): `blosm_blockout.py --osm … --bounds 34.1006,-118.3422,34.1020,-118.3370 --level-height 3.1 --origin 34.101545,-118.338718 --assets-dir … --out …`
|
||||||
|
- After re-export + re-import, the frontage spans are back to the verified values: 6922 −24276..−17700, 6902 −17749..−14591, **hero −14644..−12028 between them**, theatre −12082..−8210, and the north buildings at −22889..−18442 / −27071..−22799.
|
||||||
|
- **`ue_import_north_buildings.py` also lacked `import sys`**, so its sys.path line raised NameError and the import silently did nothing (exit 255). Same trap as the hero importer: these scripts imported only `unreal`.
|
||||||
|
- Lesson: after any blockout regeneration, probe the staged buildings' world X spans and compare them with `Geo/verification/2026-11-06-el-capitan-block.md` before trusting a render. A shift of this size is invisible in a screenshot.
|
||||||
|
|
||||||
|
## 2026-11-09 — kit naming + topology validation (blender-modeler checklist)
|
||||||
|
|
||||||
|
- Kit assets now follow the Unreal convention: the Blender object keeps its readable name (`Kit_Pilaster`), the exported asset and file are `SM_Kit_Pilaster`. The kit loader strips the `SM_` prefix so the layout code still addresses pieces by short name.
|
||||||
|
- `facade_kit_build.py::validate()` runs the blender-modeler cleanup checklist on every piece before handoff — non-manifold edges, loose vertices, triangle count — and records the result in the part report. Current kit: 16 pieces, **all manifold, no loose geometry, 12 tris each**.
|
||||||
|
- `ue_import_facade_kit.py` now deletes kit assets that the current build no longer produces, so a rename cannot leave two generations of the same piece in the folder (the rename from `Kit_*` to `SM_Kit_*` did exactly that until it did).
|
||||||
|
|
||||||
|
## 2026-11-09 — visual comparison loop; screenshots are demo-only
|
||||||
|
|
||||||
|
- `Tools/visual_loop.py` closes the reference → render → feedback loop. `--verify` gates captures against `Tools/visual_targets.json` (blank, tone-less, wrong-size fail); `--compare` measures luma, RMS contrast, edge density, palette distance and a 3x3 luma grid against the reference and prints a 0–1 score; `--iteration N` writes `Art/reference/iterations/N/{metrics.json,feedback.md}`. The feedback names the skill to consult, so the next modelling pass starts from a measurement rather than an opinion.
|
||||||
|
- **Iterations are gitignored** (`/Art/reference/iterations/`); only confirmed shots are promoted into `Screenshots/`. A test asserts the ignore rule and that every target is a demo-map shot.
|
||||||
|
- `Screenshots/` now holds **demo-map captures only** — the review staging map is no longer rendered for review. `render_screenshots.sh --verify` renders the demo map, collects the shots and runs the verification gate in one step.
|
||||||
|
- **PIL gotchas:** `FIND_EDGES` leaves its own border ring at 128 (236/3600 px on a 60x60 frame = 6.5% "detail" on a perfectly flat image), so crop **after** filtering — cropping before does not help, the new border gets the same treatment. Palette shares must divide by the full-image area, not the cropped one.
|
||||||
|
- First comparison (iteration 2): spawn view 0.70, street level 0.35, crossing 0.41, north buildings 0.34. Edge density is the dominant gap everywhere (0.04–0.11 against 0.20–0.32 in the references) — the render still lacks the relief the photographs carry, which is exactly what the `0.1.4` façade work is for.
|
||||||
|
|
||||||
|
## 2026-11-09 — theatre marquee + blade sign
|
||||||
|
|
||||||
|
- `Tools/theatre_trim_build.py` builds the El Capitan Theatre's signature features in **world coordinates**: marquee canopy 12.0 × 2.60 m at a 4.60 m soffit, illuminated fascia, recessed soffit, vertical blade sign 1.30 × 5.20 m with frame and bracket. `Tools/ue_stage_theatre_trim.py` imports to `/Game/Hollywood/Theatre` and stages one actor per piece (single spawn at the origin — no placement arithmetic).
|
||||||
|
- **Two placement bugs, both invisible in a render:**
|
||||||
|
1. **Axis sign.** Blender +Y is Unreal −Y, so a face at Unreal Y +1001 is Blender Y −10.01. Using +10.01 put the whole marquee on the far side of the boulevard.
|
||||||
|
2. **Absolute Z.** Trim heights were absolute while the pavement at the theatre is 4.56 m above the project datum, so the canopy hung 5 cm above the pavement — the QA wall probe caught it as a knee-height blocker. `GROUND_Z` now offsets every height.
|
||||||
|
- Wall probe: a scan row at Y 1000 sat ~1 cm from the frontage building line and grazed the theatre wall once the kit's proud shopfronts went in. Rows now sample the street envelope only (−1400, −167, 800, −1382, 718).
|
||||||
|
|
||||||
|
## 2026-11-09 — 0.1.4 signed off; 0.1.5 created for the carried geometry
|
||||||
|
|
||||||
|
- Owner reviewed the four demo captures locally and approved the set. Signoff recorded in `Docs/reviews/0.1.4-review.md` (7/7 items) and in the per-shot `Approved` column of `Screenshots/README.md`.
|
||||||
|
- The three geometry items that `0.1.4` did not finish — retail neighbours from their own references, hero fluting/motto band, blockout cleaning — move to a new **`0.1.5` Façade finishing** patch rather than growing this one, plus a much-needed **1440p performance re-measure** (625 kit actors have never been measured; `0.1.0` measured 414 fps).
|
||||||
|
- Decision recorded because it sets a precedent: finish and sign off a version when its _stated_ scope is done, and carry leftover scope into the next patch — do not let a patch accumulate unmeasured geometry.
|
||||||
|
|
||||||
|
## 2026-11-09 — quality priority flipped: realism first, performance last
|
||||||
|
|
||||||
|
- Owner decision: realism leads; performance closes the quality list. Applied in
|
||||||
|
`Docs/Architecture/01_introduction_and_goals.md`, `10_quality_requirements.md`,
|
||||||
|
`04_solution_strategy.md`, the combined `architecture.md`, `Docs/release-checks.md`
|
||||||
|
(performance section now last, with the reason), `ROADMAP.md` (a "Quality priority"
|
||||||
|
section) and `AGENTS.md` (the tie-break rule next to the scope-cutting rule).
|
||||||
|
- Ordering used: 1 visual realism, 2 geographic fidelity, 3 walkability/usability,
|
||||||
|
4 build reproducibility/portability, 5 provenance, 6 performance.
|
||||||
|
- **Both revisions carry the same guard so the order cannot be used as an excuse:**
|
||||||
|
performance is a *floor*, not a preference — 60 FPS at 1440p without frame
|
||||||
|
generation must be met at every release, and if realism cannot fit, the realism
|
||||||
|
scope is cut deliberately and recorded in `CHANGELOG.md`, never the frame rate.
|
||||||
|
Walkability outranks realism in one direction: a block the player falls through is
|
||||||
|
not a demo.
|
||||||
|
|
||||||
|
## 2026-11-09 — 0.1.5 start: retail reference + kit layout maths
|
||||||
|
|
||||||
|
- Commons holds exactly **one** usable reference for the retail run:
|
||||||
|
`Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg` (Gb321, CC BY-SA
|
||||||
|
4.0, 1920×953) — 6800 Lee Drug **and** its neighbour at street level, shopfronts
|
||||||
|
and parapets visible (vision pre-check: yes). Nothing for 6822 or 6806–6820, so
|
||||||
|
those two façades still have no reference and stay blocked.
|
||||||
|
- **Kit layout was wrong for tall/wide buildings**, not just ugly: bays were
|
||||||
|
`round(width / 496)`, so 6922 (65.8 m wide, 37.1 m tall) got **13 bays** — a
|
||||||
|
window every 4.96 m across the whole frontage — and the storey count came out at
|
||||||
|
11 × 310 cm = 34.1 m against a 35.7 m window zone, leaving bare wall under the
|
||||||
|
cornice. Fixed: `MAX_BAYS = 9` (module widens instead of subdividing), and the
|
||||||
|
storey spread is derived from the available height (`usable / storeys`) so the top
|
||||||
|
band always lands inside the building. Result: 6922 9 × 11, theatre 8 × 6,
|
||||||
|
6806 7 × 3, 6800 4 × 3, TCL 9 × 5.
|
||||||
|
|
||||||
|
## 2026-11-09 — hero fluting + motto band; and a silent placement divergence
|
||||||
|
|
||||||
|
- Fluting: 20 grooves per column as thin shadow-tone cylinders set into the shaft
|
||||||
|
(120 pieces) rather than boolean cuts — the eye reads them in silhouette and
|
||||||
|
grazing light, booleans would cost far more for that. Motto band is now a real
|
||||||
|
recess: surround 6 cm proud, panel 12 cm behind (was an 8 cm dark stripe).
|
||||||
|
Hero 148 parts/3 044 tris -> 271/5 492, still 14.50 m.
|
||||||
|
- **The real find:** `ue_hero_facade_import.py` had its own hero position
|
||||||
|
`(-12024.5, -1450, 0)` — the pre-`0.1.0` value, before the frontage placement fix
|
||||||
|
moved the hero to `(-13336, +1307)`. Running the importer on its own therefore
|
||||||
|
teleported the hero **13 m east and onto the north side of the boulevard**, and
|
||||||
|
nothing complained: a displaced building still renders. The only symptom was the
|
||||||
|
reference score dropping 0.69 -> 0.56 with the 3x3 composition correlation
|
||||||
|
flipping +0.54 -> -0.08.
|
||||||
|
- Fix: `Tools/ue_placement.py` is now the single source of truth (hero position,
|
||||||
|
verified frontage spans, `span_error`/`describe_span_failure`). Both
|
||||||
|
`ue_hero_facade_import.py` and `ue_build_demo_map.py` import it, the import asserts
|
||||||
|
the staged span and logs `PLACEMENT ERROR` when it does not match, and tests assert
|
||||||
|
the stale literal cannot come back. Spawn-view score back to **0.72** (best yet).
|
||||||
|
- Lesson to keep: **duplicated placement constants drift silently.** The metric that
|
||||||
|
caught it was the composition correlation, not a visual review.
|
||||||
|
|
||||||
|
## 2026-11-09 — 1440p re-measured with the kit: 363 fps, ~12% cost
|
||||||
|
|
||||||
|
- 2560×1440, Epic preset, TSR, VSync off, no frame generation: **2.76 ms average
|
||||||
|
(363 fps)**, p50 2.73, p95 3.17, p99 3.64, max 4.62 — against `0.1.0`'s 2.42 ms
|
||||||
|
(414 fps). The 625 kit actors + 8 generated textures cost about **12 %** and leave
|
||||||
|
~6× the 60 fps floor.
|
||||||
|
- **Variance trap confirmed a third time:** runs reporting ~105 fps (9.5 ms) were
|
||||||
|
two overlapping game instances — an earlier launch had not exited. `pgrep -af
|
||||||
|
Binaries/Linux/GigLine` should show one wrapper + one binary; 363 fps reproduced
|
||||||
|
twice on a clean run. Always count instances before believing a perf number.
|
||||||
|
- Series committed at `Docs/perf/0.1.5-kit-walk-frametimes.csv`; comparison table in
|
||||||
|
`Docs/release-checks.md`.
|
||||||
|
|
||||||
|
## 2026-11-09 — 604 unmaterialed actors: material rebuild was delete+recreate
|
||||||
|
|
||||||
|
- The "blockout cleaning pass" turned out to need **material and placement hygiene,
|
||||||
|
not more geometry**. `Tools/ue_audit_scene.py` (new) found **604 of 620 mesh actors
|
||||||
|
with no material**: kit pieces and theatre trim all untextured.
|
||||||
|
- **Cause:** `ensure_material` in `ue_build_demo_map.py` deleted and recreated each
|
||||||
|
`/Game/Hollywood/Site/M_*` asset every run ("so a look change cannot be masked by a
|
||||||
|
stale asset"). That orphane every reference: actors staged by other tools pointed at
|
||||||
|
deleted assets and fell back to the default material. The map looked only *slightly*
|
||||||
|
flat — no error, no warning.
|
||||||
|
- **Fix:** update the material **in place** — load it, `MaterialEditingLibrary.
|
||||||
|
delete_all_material_expressions(mat)`, rebuild the graph, recompile, save. Note
|
||||||
|
`Material.expressions` is protected from Python; use the library's deleter.
|
||||||
|
- **Ordering rule now documented and enforced:** the demo builder rebuilds materials,
|
||||||
|
so kit and theatre staging must run *after* it. `render_screenshots.sh` now runs
|
||||||
|
demo map → kit assembly → theatre trim → scene audit → shot cameras → capture, and
|
||||||
|
**fails on a non-zero audit count**.
|
||||||
|
- Second find: 53 duplicated kit labels (both shop piers of a bay shared one name).
|
||||||
|
- Scene audit after the fixes: **0 failures** (all materialed, all spans verified, no
|
||||||
|
duplicate labels). Packaged QA still PASS; spawn-view reference score 0.72.
|
||||||
|
|
||||||
|
## 2026-11-09 — v0.1.5 released
|
||||||
|
|
||||||
|
- All actionable `0.1.5` work is done: hero fluting + recessed motto band, kit layout
|
||||||
|
for wide/tall buildings, placement single-source-of-truth, 1440p re-measure
|
||||||
|
(363 fps), and the material/placement hygiene that the "blockout cleaning" task
|
||||||
|
actually needed (604 unmaterialed actors, 53 duplicate labels) with a new scene
|
||||||
|
audit gate.
|
||||||
|
- Only item carried forward is the **retail frontage (6822 / 6806–6820 / 6800)**,
|
||||||
|
blocked because Commons holds no usable reference for two of the three buildings
|
||||||
|
and substituting the theatre's photographs would misattribute the façades. It now
|
||||||
|
lives in a `0.1.6` Retail frontage patch.
|
||||||
|
- Precedent applied again: tag when the stated scope is done, carry blocked scope
|
||||||
|
into the next patch rather than letting a release wait indefinitely on missing
|
||||||
|
reference photography.
|
||||||
+5
-1
@@ -2,9 +2,13 @@
|
|||||||
|
|
||||||
Development roadmap for this project: milestone goals, scope, decisions, and release gates. Actionable tasks, claims, and progress marks live in [TODO.md](TODO.md).
|
Development roadmap for this project: milestone goals, scope, decisions, and release gates. Actionable tasks, claims, and progress marks live in [TODO.md](TODO.md).
|
||||||
|
|
||||||
|
## Quality priority
|
||||||
|
|
||||||
|
**Realism leads; performance is a floor.** Façade identity, proportion, material and light are judged against the reference photographs and drive design decisions. Performance closes the quality list: 60 FPS at 1440p without frame generation is a budget every release must meet, and if realism cannot fit inside it the realism scope is cut deliberately and recorded — never the frame rate.
|
||||||
|
|
||||||
## Status at a glance — 2026-11-08 15:30
|
## Status at a glance — 2026-11-08 15:30
|
||||||
|
|
||||||
Workstation, geo pipeline, and engine validation are done. The art pipeline has a first blockout, terrain, and street in metres, in one shared coordinate frame. **Project bootstrap is complete**: `Game/GigLine` builds, packages, and both content-acceptance tests (static-mesh scale, MPFB skeletal character) pass. **`v0.1.0` released 2026-11-08**: the walkable demo, hero façade, verified block frontage, first-person controls, ODbL attribution, and every release check pass (traversal, placement, collision, 60 fps at 1440p, fresh-checkout build, packaged build with controls). Next: the `0.1.1`–`0.1.4` patch series (collision detail, scale/façade corrections, packaging fixes, real building façades), then `0.2.0` (player body, third-person camera, animation).
|
Workstation, geo pipeline, and engine validation are done. The art pipeline has a first blockout, terrain, and street in metres, in one shared coordinate frame. **Project bootstrap is complete**: `Game/GigLine` builds, packages, and both content-acceptance tests (static-mesh scale, MPFB skeletal character) pass. **`v0.1.0` released 2026-11-08**: the walkable demo, hero façade, verified block frontage, first-person controls, ODbL attribution, and every release check pass (traversal, placement, collision, 60 fps at 1440p, fresh-checkout build, packaged build with controls). Next: the `0.1.1`–`0.1.4` patch series (collision detail, scale/façade corrections, packaging fixes, real building façades), **Next:** `0.1.6` retail frontage (6822, 6806–6820, 6800 — blocked on reference photography for two of the three), then `0.2.0` (player body, third-person camera, animation).
|
||||||
|
|
||||||
| Area | State |
|
| Area | State |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
|||||||
+112
-45
@@ -1,63 +1,130 @@
|
|||||||
# Screenshots — visual review
|
# Screenshots — demo-map visual review
|
||||||
|
|
||||||
Rendered in-game captures of the current project content, generated by [`Tools/render_screenshots.sh`](../Tools/render_screenshots.sh). The PNGs are committed alongside this README so a reviewer sees exactly what the build rendered.
|
Rendered captures of **the demo map only** — what a player sees when the demo runs. Generated by [`Tools/render_screenshots.sh`](../Tools/render_screenshots.sh); the PNGs are committed so a reviewer sees exactly what the build rendered.
|
||||||
|
|
||||||
|
The review staging map (`/Game/Test/ReviewMap`) is not part of this set: it exists to hold the camera list and the hero detail rig, and it is not what ships.
|
||||||
|
|
||||||
## How to regenerate
|
## How to regenerate
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
Tools/render_screenshots.sh
|
Tools/render_screenshots.sh --verify
|
||||||
```
|
```
|
||||||
|
|
||||||
Rebuilds `Game/GigLine/Content/Test/ReviewMap.umap` from current content, runs the game on it (and on the DemoMap for the walkable views), walks the fixed camera list via `AGigLineShotActor`, captures each shot, copies the PNGs here and exits — about a minute. The camera list lives in `Tools/ue_build_review_map.py`; add new shots there, remove retired ones here.
|
Stages the demo shot cameras, runs the game on `/Game/Hollywood/DemoMap`, copies the PNGs into `Screenshots/demo/` and then runs [`Tools/visual_loop.py`](../Tools/visual_loop.py) `--verify` to check that each shot still shows the content its expectation describes. About a minute.
|
||||||
|
|
||||||
> Naming: shots use their stable camera name (no index prefix), so adding or reordering cameras never renumbers files or orphans the old ones. Click an image for the full-size PNG.
|
## Status at a glance
|
||||||
|
|
||||||
## Shot list
|
Every shot is embedded below, in order, so the whole review happens in this file.
|
||||||
|
|
||||||
**Status: whole set ☑ approved 2026-11-09** (owner review of PR #27). Every shot was re-rendered from current `main` content after the building-height calibration was adopted (`v0.1.2`): Mapbox-derived **3.1 m per level** (`Geo/tools/block_height_report.py`), explicit overrides for three rear outbuildings, and the regenerated blockout and frontage that follow — 6922 is now 39.1 m (was 42.1 m) and the El Capitan Theatre 20.5 m (was 20.0 m).
|
| # | Shot | Verified | Approved |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | [Spawn view](#1-spawn-view) | ☑ 2026-11-09 | ☑ 2026-11-09 |
|
||||||
|
| 2 | [Street level](#2-street-level) | ☑ 2026-11-09 | ☑ 2026-11-09 |
|
||||||
|
| 3 | [Crossing](#3-crossing) | ☑ 2026-11-09 | ☑ 2026-11-09 |
|
||||||
|
| 4 | [North buildings](#4-north-buildings) | ☑ 2026-11-09 | ☑ 2026-11-09 |
|
||||||
|
| 5 | [Third-person body](#5-third-person-body) | ☑ 2026-11-09 | ☐ pending |
|
||||||
|
|
||||||
| Round | What changed |
|
**Verified** is what `--verify` reports: the frame is not blank, is the right size, and has tonal range. **Approved** is the owner's verdict, recorded here. Each shot also has an **expectation** in [`Tools/visual_targets.json`](../Tools/visual_targets.json) describing the content it must show.
|
||||||
| --- | --- |
|
|
||||||
| 2026-11-07 | First approved set; block frontage pass corrected hero and north-building placement |
|
|
||||||
| 2026-11-08 | Re-rendered after `0.1.1` street geometry (sidewalk upstand flush at the building edge, crossing ramps graded ≤ 8 %); `demo-north-buildings` camera re-pointed (was 9.5 km west, 85 % sky) |
|
|
||||||
| 2026-11-09 | Re-rendered after the `0.1.2` building-height calibration (3.1 m per level). 12 of 14 PNGs changed; `review-demo-spawn-view` and `review-demo-street-level` are byte-identical to the `0.1.1` round, so the height change reads mostly on the frontage silhouettes |
|
|
||||||
|
|
||||||
| # | File | Preview | Map | View | What it shows | ☑ |
|
---
|
||||||
| --- | --- | --- | --- | --- | --- | --- |
|
|
||||||
| 1 | `review-overview.png` | [<img src="review-overview.png" alt="overview" width="180">](review-overview.png) | Review | 7 m up, NE | Whole review layout: character + both cubes on the floor | ☑ 2026-11-09 |
|
## 1. Spawn view
|
||||||
| 2 | `review-character.png` | [<img src="review-character.png" alt="character" width="180">](review-character.png) | Review | eye height, south | MPFB mannequin, idle pose + warm skin; feet meet the plane | ☑ 2026-11-09 |
|
|
||||||
| 3 | `review-scale-cubes.png` | [<img src="review-scale-cubes.png" alt="scale cubes" width="180">](review-scale-cubes.png) | Review | south | Floating vs grounded cube: cast vs contact shadow | ☑ 2026-11-09 |
|

|
||||||
| 4 | `review-player-placement.png` | [<img src="review-player-placement.png" alt="player placement" width="180">](review-player-placement.png) | Review | spawn | The `AGigLineCharacter` pawn, capsule height and landing | ☑ 2026-11-09 |
|
|
||||||
| 5 | `review-npc-prefab.png` | [<img src="review-npc-prefab.png" alt="npc prefab" width="180">](review-npc-prefab.png) | Review | mid, west | The `AGigLineNPCPerson` prefab: collision capsule + visible MPFB body, feet on Z 0 | ☑ 2026-11-09 |
|
- **File:** `demo/review-demo-spawn-view.png`
|
||||||
| 6 | `review-ground-contact.png` | [<img src="review-ground-contact.png" alt="ground contact" width="180">](review-ground-contact.png) | Review | knee height, feet | Floating/sinking character is obvious here | ☑ 2026-11-09 |
|
- **View:** north sidewalk, 14 m back, level horizon
|
||||||
| 7 | `review-shadow-compare.png` | [<img src="review-shadow-compare.png" alt="shadow compare" width="180">](review-shadow-compare.png) | Review | low angle | Detached (floating) vs touching (grounded) shadows | ☑ 2026-11-09 |
|
- **Expected content:** the whole south block — the neoclassical hero façade between its neighbouring buildings, both sidewalks and the road in the foreground
|
||||||
| 8 | `review-hero-portals.png` | [<img src="review-hero-portals.png" alt="hero portals" width="180">](review-hero-portals.png) | Review | 8 m, eye height | Three genuine 1.2 m portal recesses with flights and plinth | ☑ 2026-11-09 |
|
- **Verified:** ☑ 2026-11-09 · **Approved:** ☑ 2026-11-09
|
||||||
| 9 | `review-hero-facade-front.png` | [<img src="review-hero-facade-front.png" alt="hero facade front" width="180">](review-hero-facade-front.png) | Review | road side, straight-on | Hero façade elevation and column rhythm | ☑ 2026-11-09 |
|
|
||||||
| 10 | `review-hero-facade-oblique.png` | [<img src="review-hero-facade-oblique.png" alt="hero facade oblique" width="180">](review-hero-facade-oblique.png) | Review | north-west | Body depth and column line standing proud of the mass | ☑ 2026-11-09 |
|
---
|
||||||
| 11 | `demo/review-demo-spawn-view.png` | [<img src="demo/review-demo-spawn-view.png" alt="spawn view" width="180">](demo/review-demo-spawn-view.png) | Demo | north sidewalk | The whole block: hero façade between its neighbours, both sidewalks and the road | ☑ 2026-11-09 |
|
|
||||||
| 12 | `demo/review-demo-street-level.png` | [<img src="demo/review-demo-street-level.png" alt="street level" width="180">](demo/review-demo-street-level.png) | Demo | eye height, north side | The hero colonnade with the adjacent frontage buildings either side | ☑ 2026-11-09 |
|
## 2. Street level
|
||||||
| 13 | `demo/review-demo-crossing.png` | [<img src="demo/review-demo-crossing.png" alt="crossing" width="180">](demo/review-demo-crossing.png) | Demo | along the crosswalk | The designated mid-block crossing at s=130 m (UE X -14956): three zebra bars on the asphalt, with the south frontage and the hero colonnade behind | ☑ 2026-11-09 |
|
|
||||||
| 14 | `demo/review-demo-north-buildings.png` | [<img src="demo/review-demo-north-buildings.png" alt="north buildings" width="180">](demo/review-demo-north-buildings.png) | Demo | across the Blvd | North-side buildings (TCL, Tussaud's) seen from the south side across the road. **2026-11-08:** camera re-pointed to the buildings' mass centre (was 9.5 km west → 85% sky); now frames both north buildings. Flat surfaces = `0.1.4` façade scope. | ☑ 2026-11-09 |
|

|
||||||
|
|
||||||
|
- **File:** `demo/review-demo-street-level.png`
|
||||||
|
- **View:** eye height, north side
|
||||||
|
- **Expected content:** the hero colonnade with the adjacent frontage buildings either side, shopfronts at pavement level
|
||||||
|
- **Verified:** ☑ 2026-11-09 · **Approved:** ☑ 2026-11-09
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Crossing
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **File:** `demo/review-demo-crossing.png`
|
||||||
|
- **View:** from the north sidewalk along the crosswalk
|
||||||
|
- **Expected content:** the designated mid-block crossing (s=130 m, UE X −14956) — zebra bars on the asphalt, south frontage and colonnade behind
|
||||||
|
- **Verified:** ☑ 2026-11-09 · **Approved:** ☑ 2026-11-09
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. North buildings
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **File:** `demo/review-demo-north-buildings.png`
|
||||||
|
- **View:** across the Boulevard from the south side
|
||||||
|
- **Expected content:** the two north-side buildings (TCL Chinese Theatre, Madame Tussaud's), full height in frame
|
||||||
|
- **Verified:** ☑ 2026-11-09 · **Approved:** ☑ 2026-11-09
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Third-person body
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **File:** `demo/review-demo-third-person.png`
|
||||||
|
- **View:** third-person boom, ~6 m behind the pawn, captured with `-thirdperson` so the pawn starts in the third-person rig
|
||||||
|
- **Expected content:** the player's own visible body on the north sidewalk, seen from behind, with the hero façade across the street
|
||||||
|
- **Verified:** ☑ 2026-11-09 · **Approved:** ☐ pending
|
||||||
|
|
||||||
|
What this shot is evidence for, and what it is not:
|
||||||
|
|
||||||
|
- **Implemented and visible:** the pawn carries a visible skeletal body with the warm skin material, and a spring-arm camera with collision testing. The **V** toggle swaps first and third person; the shipped default stays first person and the review passes `-thirdperson`.
|
||||||
|
- **Not working:** the figure is in **bind pose, arms out**. The idle clip is bound and `PlayAnimation` is called — the run logs `GigLine: playing body clip Character_AnimHuman_rig_GigLine_Idle` — so the call succeeds and the pose does not apply. Tracked in `TODO.md` under "Verify animation plays correctly on the imported rig" with that evidence.
|
||||||
|
- **Camera collision is implemented but unproven:** the spawn point has open space behind it, so no shot exercises the arm shortening.
|
||||||
|
- The skin material needed `used_with_skeletal_mesh=True`; without it Unreal silently substituted the default grey, which is what the first capture of this shot showed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The iteration loop (uncommitted)
|
||||||
|
|
||||||
|
`Tools/visual_loop.py` measures each capture against its reference photograph and turns the deltas into modelling actions for the Blender skill packs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 Tools/visual_loop.py --compare --iteration 3 # writes Art/reference/iterations/3/
|
||||||
|
```
|
||||||
|
|
||||||
|
That directory is **gitignored**: iterations stay uncommitted until a shot is confirmed, and only confirmed shots are promoted into `Screenshots/demo/`. The feedback file names the affected skill (`blender-modeler`, `archviz`, `materials`, `lookdev`, `camera-cinematography`) so the next modelling pass has a starting point rather than an opinion.
|
||||||
|
|
||||||
|
Note the comparison is only meaningful when the shot and the reference frame the same subject — a wide block view measured against a tight façade crop will always report low edge similarity.
|
||||||
|
|
||||||
## Scene conventions
|
## Scene conventions
|
||||||
|
|
||||||
- **Review map** (`/Game/Test/ReviewMap`): 160 m floor, top face at Z 0; the character measures Z 0..83 cm. One warm key light plus sky fill.
|
- **Demo map** (`/Game/Hollywood/DemoMap`): fixed daytime — sun pitch −62 / yaw 120 (intensity 4, warm), sky light 2.6, sky atmosphere, height fog, fixed exposure.
|
||||||
- **Cubes**: `Cube_ScaleTest_A` rests on the floor, `Cube_ScaleTest_B` floats at Z 2 m; both are exactly 1 m — the scale reference.
|
- **Cameras**: elevation shots sit at their target height (pitch 0, so verticals stay parallel) and use a 42–68° lens to fill the frame from across the street.
|
||||||
- **Demo map** (`/Game/Hollywood/DemoMap`): fixed daytime — sun at pitch −50 / yaw 120 (intensity 4, warm), sky light 2.6, sky atmosphere, height fog, fixed exposure.
|
- **Materials**: every surface carries a look-developed material from `Tools/ue_build_demo_map.py` — noise-blended base colour, masonry coursing, and a generated texture set (`Tools/generate_facade_textures.py`) sampled world-aligned. Explicit roughness and specular per surface type.
|
||||||
- **Materials**: every exported surface carries a look-developed material built in `Tools/ue_build_demo_map.py` (`ensure_material`) because Blender's flat diffuse colours do not survive the FBX → Unreal import. Each has an explicit roughness and a subtle world-space noise blend between two shades: `M_Asphalt` (0.92), `M_Concrete` (0.88), `M_KerbConcrete` (0.85), `M_Ground` (0.98), `M_RoadPaint` (0.62), `M_FacadeStone` (0.72) and `M_Stucco` (0.86) for the north-side buildings.
|
|
||||||
- **Lighting is dynamic-only** (`r.AllowStaticLighting=False`, all lights Movable), so no lighting build is ever required.
|
|
||||||
|
|
||||||
## What to look for
|
|
||||||
|
|
||||||
- **Ground contact**: feet meet the plane with no gap and no shadow seam.
|
|
||||||
- **Scale**: everything checks against the 1 m cubes.
|
|
||||||
- **Façade rhythm**: six columns at 4.96 m across 25.75 m should read as the photographs in silhouette.
|
|
||||||
- **Frontage heights (new in this round)**: 6922 and the El Capitan Theatre were re-cut to the 3.1 m storey height. Check they still read as the taller west end and the lower cinema respectively against `Art/reference/hollywood/el-capitan-theatre-blade-sign-day-wikimedia-2008-10.jpg`, and that the ordering of the six frontage buildings is unchanged.
|
|
||||||
|
|
||||||
## Known cosmetic issues
|
## Known cosmetic issues
|
||||||
|
|
||||||
- Floor speckle from the `Constant3Vector` wiring in `Tools/ue_build_review_map.py`.
|
Every issue is tracked in `TODO.md` under the version that will fix it; an issue with no ticket is a bug in the plan, not a note.
|
||||||
- Hero façade renders pale — Blender flat colours with no normal/roughness, so it reads as untextured stone (the `0.1.4` façade patch, not a lighting bug).
|
|
||||||
- Banners sit on the column line rather than the wall (acceptable: they hang on the columns in the photographs).
|
| Issue | Where it is tracked | State |
|
||||||
- ~~Banners and window panels float detached from the colonnade wall~~ — **window panes fixed 2026-11-09**: they sat at Y −1.98, 1.44 m in _front_ of the wall and ahead of the column line; they now sit on the wall plane at Y −0.56, behind the columns, and read as dark openings (see `review-hero-facade-front`).
|
| --- | --- | --- |
|
||||||
- Between kerb and building line, the walkable surface is the terrain mesh, not a modelled pavement.
|
| Hero stone renders pale — the material is a procedural graph and a generated texture set, not a measured surface | `0.9.0` → measured material library | Open |
|
||||||
|
| Hero façade has no doors at street level left and right of the central bay, and the central-bay elements are not positioned as the photographs show | `0.9.0` → per-building models, hero façade | Open |
|
||||||
|
| Third-person body renders in bind pose: the clip is bound and `PlayAnimation` is called, but the pose does not apply | `0.2.0` → verify animation plays correctly | Open (blocking `0.2.0`) |
|
||||||
|
| Camera collision is implemented but never exercised against a wall | `0.2.0` → camera collision | Open |
|
||||||
|
| Between kerb and building line the walkable surface is the terrain mesh, not a modelled pavement | `0.6.0` → visual polish | Open |
|
||||||
|
|
||||||
|
Accepted by the owner, not a defect:
|
||||||
|
|
||||||
|
- Banners hang on the column line rather than flush to the wall — that is how they hang in the photographs.
|
||||||
|
|
||||||
|
Resolved and removed:
|
||||||
|
|
||||||
|
- "The El Capitan Theatre has no marquee or blade sign" — `Tools/ue_stage_theatre_trim.py` stages all six trim actors; verified in `/tmp/gigline/theatre-import.txt`.
|
||||||
|
- "Materials are not yet created in Blender or fully integrated" — contradicted by the scene conventions above.
|
||||||
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.
Binary file not shown.
@@ -9,36 +9,31 @@ Actionable task list for this project. This file is the single, central place fo
|
|||||||
- When all steps in a version section are done: write a `CHANGELOG.md` entry, then remove the version section from this file.
|
- When all steps in a version section are done: write a `CHANGELOG.md` entry, then remove the version section from this file.
|
||||||
- Release gate: a version is tagged only on the owner's explicit go-ahead once its release checks pass, not automatically when the checkboxes close.
|
- Release gate: a version is tagged only on the owner's explicit go-ahead once its release checks pass, not automatically when the checkboxes close.
|
||||||
|
|
||||||
## 0.1.4 — Proper building façades (patch)
|
## 0.1.6 — Retail frontage (patch)
|
||||||
|
|
||||||
Suggested blender skills: **procedural-modeling** + **geometry-nodes** (the façade module kit — bay, pilaster, cornice, parapet, window, shopfront, door — as reusable node groups), **archviz** (real-world scale and façade composition), **materials** (procedural brick, concrete, stone, render, glass — the ported generators), **texture-workflow** (AO/curvature/normal bakes and decals for the motto band and medallions), **uv-workflow** (texel density and modular UV layouts for the kit), **scene-assembly** (assembling kit pieces onto the fifteen block buildings), **lookdev** (screenshot-comparison loop until shading reads correctly), **historical-worlds** or **realistic-style** (1920s Hollywood Blvd neoclassical direction), **asset-optimization** + **unreal-export** (pre-export validation, `ue_metric_v1` preset), **qa-review** (final gate before the `Tools/render_screenshots.sh` review).
|
Suggested blender skills: **references** (reference-image-match), **archviz**, **historical-worlds** for the 1920s–1990s retail mix on this run.
|
||||||
|
|
||||||
### Patch tasks
|
### Patch tasks
|
||||||
|
|
||||||
- [ ] **Finish the Blosm blockout cleaning/detailing (moved from the deferred `0.1.0` item).** Import state: `Art/blender/blockout/blockout-2026-11-06.blend` (86 meshes, EPSG:26911, 0.0 cm affine residual) with cleaning tooling in `Geo/tools/` and `Art/blender/blockout/blosm_blockout.py`. To do here: (a) untextured roofs/facades → **Replace placeholder materials** below; (b) Blosm `setupScript` façade styles → **Give OSM buildings a façade style** below; (c) the 3 of 15 buildings lacking height data are the `0.1.2` placeholder-heights item.
|
- [ ] **Model 6822 Budget, 6806–6820 and 6800 Lee Drug from their own references.** `Art/reference/retail-neighbours/` holds 6800 and its neighbour (Gb321, CC BY-SA 4.0); Commons has nothing usable for 6822 or 6806–6820, so those two need photography before they can be modelled honestly. Do not substitute the El Capitan Theatre's 18-frame set — it would misattribute the façades.
|
||||||
- [ ] **Define the façade module.** One set of reusable pieces (bay, pilaster, cornice, parapet, window, shopfront, door) at a fixed module, exported with the `ue_metric_v1` preset so every building assembles from the same kit.
|
- [ ] Review every façade change with `Tools/render_screenshots.sh --verify` before tagging.
|
||||||
- [ ] **Replace placeholder materials.** Every blockout building currently carries Blender flat colours. Port the procedural generators from `Tools/sibling_asset_audit.py`'s sibling project (`brick_facade`, `seamless_concrete`, `asphalt`) and add stone, render, and glass, then wire them through Unreal material instances.
|
- [ ] Tag `v0.1.6` and write `CHANGELOG.md` entry; then remove this section.
|
||||||
- [ ] **Give OSM buildings a façade style.** Blosm exposes `scene.blosm.setupScript` (`setup/neoclassical.py` is the example) so a building can carry a real elevation instead of a plain extrusion. Build one style script for the south frontage and one for the north.
|
|
||||||
- [ ] **Hero façade detail pass.** Ionic capitals and fluting, the parapet motto band and medallions as geometry or decal, window rhythm behind the colonnade, and the three portal flights at their photographed proportions.
|
|
||||||
- [ ] **El Capitan Theatre (6838) and the retail neighbours.** Model from the 18-frame 5472×3648 reference set; the theatre shares the measured 4.96 m bay rhythm with the temple.
|
|
||||||
- [ ] Review every façade change with `Tools/render_screenshots.sh` before tagging.
|
|
||||||
- [ ] Tag `v0.1.4` and write `CHANGELOG.md` entry; then remove this section.
|
|
||||||
|
|
||||||
## 0.2.0 — MPFB player body, third-person camera, basic animation (weeks 7–9)
|
## 0.2.0 — MPFB player body, third-person camera, basic animation (weeks 7–9)
|
||||||
|
|
||||||
### Character setup
|
### Character setup - active - session-5598ce7e-27e3-4c64-bb49-c04a4a31ee58 (branch `anim-body-0.2.0`)
|
||||||
|
|
||||||
- [x] Generate MPFB human base mesh; export baked materials and deform skeleton. Done in bootstrap: idle-pose 19 158-vert human with a 137-bone standard rig, `Tools/mpfb_character_export.py` (now `--pose idle` + warm `make_diffuse`).
|
- [x] Generate MPFB human base mesh; export baked materials and deform skeleton. Done in bootstrap: idle-pose 19 158-vert human with a 137-bone standard rig, `Tools/mpfb_character_export.py` (now `--pose idle` + warm `make_diffuse`).
|
||||||
- [x] Import character into Unreal; validate skeleton, scale, and materials. Done: `/Game/Test/Character` — `SkeletalMesh` (138 bones), `Skeleton`, warm `M_Character_Export` material with baked `T_Character_Diffuse`.
|
- [x] Import character into Unreal; validate skeleton, scale, and materials. Done: `/Game/Test/Character` — `SkeletalMesh` (138 bones), `Skeleton`, warm `M_Character_Export` material with baked `T_Character_Diffuse`.
|
||||||
- [x] Promote the character asset into a reusable NPC prefab (visible body, collision capsule, idle pose) ready for pedestrians (`0.5.0`) once the third-person body lands. Done 2026-11-07: `AGigLineNPCPerson` (see the 0.1.0 Character-development item); `0.5.0` reuses it as the pedestrian base.
|
- [x] Promote the character asset into a reusable NPC prefab (visible body, collision capsule, idle pose) ready for pedestrians (`0.5.0`) once the third-person body lands. Done 2026-11-07: `AGigLineNPCPerson` (see the 0.1.0 Character-development item); `0.5.0` reuses it as the pedestrian base.
|
||||||
- [ ] Create basic idle/walk animation set.
|
- [x] Create basic idle/walk animation set. Done 2026-11-09: `Tools/character_anim_build.py` authors `GigLine_Idle` (60 f) and `GigLine_Walk` (30 f) at 30 fps from parametric cycles, and `Tools/ue_character_anim_import.py` imports them. Both AnimSequences verified in `/Game/Test/Character`: Idle 1.967 s, Walk 0.967 s. Limb axes were **measured** on the rig, not assumed — the probe rotated `upperleg01.L` 0.5 rad per axis and read `foot.L`, which showed X is the fore/aft swing (Y twists, Z abducts).
|
||||||
- [ ] Verify animation plays correctly on the imported rig.
|
- [A] Verify animation plays correctly on the imported rig. **Playback is broken and now measurable**: the pawn's mesh is bound, both clips resolve, and the run logs `GigLine: playing body clip Character_AnimHuman_rig_GigLine_Idle`, but shot 5 still renders the body in bind pose (arms out). So `PlayAnimation` is called and the pose is not applied — next step is to check whether the imported clip carries bone curves at all (the FBX bake may be sampling only keyed bones) or whether the component needs `SetAnimationMode(AnimationSingleNode)` before `SetAnimation`/`Play`.
|
||||||
|
|
||||||
### Camera and controls
|
### Camera and controls
|
||||||
|
|
||||||
- [ ] Add third-person camera following the player body.
|
- [x] Add third-person camera following the player body. Done 2026-11-09: `USpringArmComponent` + camera on the capsule, boom 340 cm, lag 8, and the body is the animated skeletal mesh with the skin material. Review shot 5 (`demo-third-person`, `Screenshots/README.md`) shows the rig; the shipped default view stays first person.
|
||||||
- [ ] Add first/third-person toggle.
|
- [x] Add first/third-person toggle. **V**, via `IA_ToggleCamera`; the toggle swaps the active camera and the body's visibility. `-thirdperson` on the command line starts in third person so the review is reproducible.
|
||||||
- [ ] Add camera collision to prevent clipping through geometry.
|
- [x] Add camera collision to prevent clipping through geometry. The spring arm's collision test (`bDoCollisionTest`, probe 12 cm on `ECC_Camera`) shortens the boom instead of letting the camera pass through a wall. **Not yet exercised against a wall in a shot** — the spawn point has open space behind it, so this is implemented but unproven.
|
||||||
- [ ] Document any input changes.
|
- [ ] Document any input changes.
|
||||||
|
|
||||||
### Release checks
|
### Release checks
|
||||||
|
|||||||
+13
-1
@@ -16,6 +16,14 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b
|
|||||||
| Tool | Purpose |
|
| Tool | Purpose |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `check-lfs.sh` | Fails if the `lfs` filter is missing or an LFS-tracked file was committed as a plain blob |
|
| `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. |
|
| `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. |
|
| `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 |
|
| `sibling_asset_audit.py` | Reads the sibling project's prop library from GLB headers into `Art/props/reuse-manifest.json`; copies nothing |
|
||||||
@@ -34,6 +42,7 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b
|
|||||||
| Tool | Purpose |
|
| Tool | Purpose |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `hero_facade_build.py` | Parametric hero façade; emits a JSON report naming the evidence behind every part |
|
| `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 |
|
| `mpfb_character_export.py` | MPFB human, standard rig, baked skin, FBX export |
|
||||||
|
|
||||||
## Unreal import and level building
|
## Unreal import and level building
|
||||||
@@ -45,6 +54,7 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b
|
|||||||
| `ue_build_demo_map.py` | Builds `/Game/Hollywood/DemoMap`: lighting, façade, site meshes, player start |
|
| `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_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_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 |
|
| `ue_character_import.py` | Skeletal import plus the material/texture chain Interchange skips |
|
||||||
|
|
||||||
## Visual review
|
## Visual review
|
||||||
@@ -52,7 +62,9 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b
|
|||||||
| Tool | Purpose |
|
| Tool | Purpose |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `render_screenshots.py` / `render_screenshots.sh` | Rebuilds the review map, runs the game, captures the shot list into `Screenshots/` |
|
| `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 helper — a human must still approve. |
|
| `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
|
## Gameplay QA
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Author the basic character animation set (0.2.0): idle and walk.
|
||||||
|
|
||||||
|
Three clips on the MPFB standard rig, sampled from parametric cycles rather than
|
||||||
|
hand-keyed poses, so the loop is closed by construction and every key can be
|
||||||
|
re-derived from the numbers below:
|
||||||
|
|
||||||
|
GigLine_Idle 60 frames @ 30 fps — breathing sway, feet planted
|
||||||
|
GigLine_Walk 30 frames @ 30 fps — one full stride, left and right contact
|
||||||
|
|
||||||
|
blender -b Art/blender/character/character-2026-11-07.blend \
|
||||||
|
--python Tools/character_anim_build.py -- \
|
||||||
|
--out /tmp/gigline/character-anim.fbx \
|
||||||
|
--report /tmp/gigline/character-anim-report.json
|
||||||
|
|
||||||
|
Rotation axes are **measured, not assumed**. A probe on the actual rig rotated
|
||||||
|
`upperleg01.L` by 0.5 rad on each axis and read the resulting `foot.L` head:
|
||||||
|
|
||||||
|
X -> foot Y -0.022 -> +0.362 (fore/aft swing: the axis used here)
|
||||||
|
Y -> twist along the leg, foot barely moves
|
||||||
|
Z -> foot swings sideways into the other leg
|
||||||
|
|
||||||
|
The arm probe gives the same answer for `upperarm01`, so limbs swing on local X
|
||||||
|
and positive X sends the limb backwards. Everything else (spine lean, head nod)
|
||||||
|
uses small X deltas for the same reason.
|
||||||
|
|
||||||
|
The rig is `pelvis.L → upperleg01 → upperleg02 → lowerleg01 → lowerleg02 → foot`,
|
||||||
|
all deform bones, no constraints, so a plain pose-bone rotation deforms the mesh.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
import fbx_export_preset as fbx_preset # noqa: E402
|
||||||
|
|
||||||
|
FPS = 30
|
||||||
|
CLIPS = {"Idle": 60, "Walk": 30}
|
||||||
|
|
||||||
|
# Stride amplitude in radians at the hip. A human walk swings the thigh about
|
||||||
|
# 25-30 deg forward and 15-20 back; 0.45 rad (~26 deg) is a normal-pace stride.
|
||||||
|
HIP = 0.45
|
||||||
|
# Knee flexion during swing, measured from the photographs of a normal gait.
|
||||||
|
KNEE = 0.55
|
||||||
|
# Arm swing counter-phases the leg; a relaxed walk swings about 20 deg.
|
||||||
|
ARM = 0.32
|
||||||
|
ELBOW = 0.18
|
||||||
|
|
||||||
|
PARTS: list[dict] = []
|
||||||
|
|
||||||
|
# The .blend stores the raw A-pose bind: `mpfb_character_export.py --pose idle`
|
||||||
|
# bakes the arms down only in the exported FBX, not in the file. A clip that
|
||||||
|
# animates only swing therefore leaves the arms out in an A — which is exactly how
|
||||||
|
# the first third-person render looked. These are the same deltas `IDLE_POSE` uses,
|
||||||
|
# applied as a base that every key sits on top of.
|
||||||
|
BASE = {
|
||||||
|
"shoulder01.L": (0.0, -0.15, -0.03),
|
||||||
|
"shoulder01.R": (0.0, 0.15, 0.03),
|
||||||
|
"upperarm01.L": (0.0, 0.0, -1.55),
|
||||||
|
"upperarm01.R": (0.0, 0.0, 1.55),
|
||||||
|
"lowerarm01.L": (0.0, 0.0, -0.10),
|
||||||
|
"lowerarm01.R": (0.0, 0.0, 0.10),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def log(msg):
|
||||||
|
PARTS.append(msg)
|
||||||
|
print(f"[anim] {msg}")
|
||||||
|
|
||||||
|
|
||||||
|
def clear_animation(arm):
|
||||||
|
"""Drop any pre-existing actions so a rerun does not stack clips."""
|
||||||
|
for action in list(bpy.data.actions):
|
||||||
|
bpy.data.actions.remove(action)
|
||||||
|
if arm.animation_data:
|
||||||
|
arm.animation_data_clear()
|
||||||
|
|
||||||
|
|
||||||
|
def pose_bones(arm):
|
||||||
|
bpy.context.view_layer.objects.active = arm
|
||||||
|
bpy.ops.object.mode_set(mode="POSE")
|
||||||
|
for pb in arm.pose.bones:
|
||||||
|
pb.rotation_mode = "XYZ"
|
||||||
|
return arm.pose.bones
|
||||||
|
|
||||||
|
|
||||||
|
def key_euler(pb, frame, x=0.0, y=0.0, z=0.0):
|
||||||
|
"""Key a pose-bone rotation on top of the arms-down base."""
|
||||||
|
bx, by, bz = BASE.get(pb.name, (0.0, 0.0, 0.0))
|
||||||
|
pb.rotation_euler = (bx + x, by + y, bz + z)
|
||||||
|
pb.keyframe_insert(data_path="rotation_euler", frame=frame)
|
||||||
|
|
||||||
|
|
||||||
|
def clip_idle(arm, frames):
|
||||||
|
"""Breathing sway: spine and head counter-rotate, arms drift, feet planted."""
|
||||||
|
bones = pose_bones(arm)
|
||||||
|
action = bpy.data.actions.new("GigLine_Idle")
|
||||||
|
arm.animation_data_create()
|
||||||
|
arm.animation_data.action = action
|
||||||
|
|
||||||
|
for frame in range(1, frames + 1):
|
||||||
|
t = 2.0 * math.pi * (frame - 1) / frames
|
||||||
|
breathe = math.sin(t)
|
||||||
|
# Chest lifts and settles; the head counter-rotates so the gaze stays level.
|
||||||
|
key_euler(bones["spine03"], frame, x=-0.020 * breathe)
|
||||||
|
key_euler(bones["spine05"], frame, x=-0.012 * breathe)
|
||||||
|
key_euler(bones["neck01"], frame, x=0.010 * breathe)
|
||||||
|
key_euler(bones["head"], frame, x=0.008 * breathe)
|
||||||
|
# Arms hang and drift a little; nothing swings.
|
||||||
|
key_euler(bones["upperarm01.L"], frame, x=0.020 * breathe)
|
||||||
|
key_euler(bones["upperarm01.R"], frame, x=-0.020 * breathe)
|
||||||
|
|
||||||
|
action.use_fake_user = True
|
||||||
|
log(f"Idle: {frames} frames, spine/neck/head/arms, feet planted")
|
||||||
|
return action
|
||||||
|
|
||||||
|
|
||||||
|
def clip_walk(arm, frames):
|
||||||
|
"""One full stride: left contact at frame 1, right contact at the half-cycle."""
|
||||||
|
bones = pose_bones(arm)
|
||||||
|
action = bpy.data.actions.new("GigLine_Walk")
|
||||||
|
arm.animation_data.action = action
|
||||||
|
|
||||||
|
for frame in range(1, frames + 1):
|
||||||
|
t = 2.0 * math.pi * (frame - 1) / frames
|
||||||
|
swing = math.sin(t)
|
||||||
|
# Hip swing, opposite phase per leg; negative X is forward.
|
||||||
|
key_euler(bones["upperleg01.L"], frame, x=-HIP * swing)
|
||||||
|
key_euler(bones["upperleg01.R"], frame, x=HIP * swing)
|
||||||
|
# The knee only flexes while the leg is swinging forward, so it is driven
|
||||||
|
# by the half-rectified swing rather than the raw sine.
|
||||||
|
knee_l = KNEE * max(0.0, math.sin(t + math.pi))
|
||||||
|
knee_r = KNEE * max(0.0, math.sin(t))
|
||||||
|
key_euler(bones["lowerleg01.L"], frame, x=knee_l)
|
||||||
|
key_euler(bones["lowerleg01.R"], frame, x=knee_r)
|
||||||
|
# Ankles keep the foot roughly level: counter the hip plus half the knee.
|
||||||
|
key_euler(bones["foot.L"], frame, x=0.25 * HIP * swing - 0.35 * knee_l)
|
||||||
|
key_euler(bones["foot.R"], frame, x=-0.25 * HIP * swing - 0.35 * knee_r)
|
||||||
|
# Arms counter-phase the legs, elbows carry a constant relaxed bend.
|
||||||
|
key_euler(bones["upperarm01.L"], frame, x=ARM * swing)
|
||||||
|
key_euler(bones["upperarm01.R"], frame, x=-ARM * swing)
|
||||||
|
key_euler(bones["lowerarm01.L"], frame, x=ELBOW + 0.10 * swing)
|
||||||
|
key_euler(bones["lowerarm01.R"], frame, x=ELBOW - 0.10 * swing)
|
||||||
|
# Torso counter-rotation: the shoulders turn against the hips.
|
||||||
|
key_euler(bones["spine04"], frame, z=0.08 * swing)
|
||||||
|
key_euler(bones["neck01"], frame, z=-0.04 * swing)
|
||||||
|
|
||||||
|
action.use_fake_user = True
|
||||||
|
log(f"Walk: {frames} frames, stride {HIP:.2f} rad hip / {KNEE:.2f} knee / {ARM:.2f} arm")
|
||||||
|
return action
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None) -> int:
|
||||||
|
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
ap.add_argument("--out", default="/tmp/gigline/character-anim.fbx")
|
||||||
|
ap.add_argument("--report", default=None)
|
||||||
|
args = ap.parse_args(argv)
|
||||||
|
|
||||||
|
arm = next((o for o in bpy.data.objects if o.type == "ARMATURE"), None)
|
||||||
|
if arm is None:
|
||||||
|
raise SystemExit("no armature in the .blend")
|
||||||
|
mesh = next((o for o in bpy.data.objects if o.type == "MESH"), None)
|
||||||
|
log(f"rig: {arm.name}, {len(arm.pose.bones)} bones; mesh {mesh.name if mesh else 'none'} "
|
||||||
|
f"{len(mesh.data.vertices) if mesh else 0} verts")
|
||||||
|
|
||||||
|
bpy.context.scene.render.fps = FPS
|
||||||
|
clear_animation(arm)
|
||||||
|
idle = clip_idle(arm, CLIPS["Idle"])
|
||||||
|
walk = clip_walk(arm, CLIPS["Walk"])
|
||||||
|
|
||||||
|
bpy.ops.object.mode_set(mode="OBJECT")
|
||||||
|
# Export armature + mesh together: the preset's character flags carry the
|
||||||
|
# bake_anim settings that make Unreal create AnimSequences.
|
||||||
|
bpy.ops.object.select_all(action="DESELECT")
|
||||||
|
arm.select_set(True)
|
||||||
|
if mesh:
|
||||||
|
mesh.select_set(True)
|
||||||
|
bpy.context.view_layer.objects.active = arm
|
||||||
|
|
||||||
|
os.makedirs(os.path.dirname(args.out), exist_ok=True)
|
||||||
|
fbx_preset.export_character(args.out, selection=True)
|
||||||
|
log(f"exported {args.out}")
|
||||||
|
|
||||||
|
if args.report:
|
||||||
|
with open(args.report, "w", encoding="utf-8") as handle:
|
||||||
|
json.dump({
|
||||||
|
"fps": FPS,
|
||||||
|
"rig": arm.name,
|
||||||
|
"bones": len(arm.pose.bones),
|
||||||
|
"clips": [
|
||||||
|
{"action": idle.name, "frames": CLIPS["Idle"]},
|
||||||
|
{"action": walk.name, "frames": CLIPS["Walk"]},
|
||||||
|
],
|
||||||
|
"axis_evidence": "probe: upperleg01.L X 0.5 rad moves foot.L "
|
||||||
|
"Y -0.022 -> +0.362; Y twists, Z abducts",
|
||||||
|
"amplitudes_rad": {"hip": HIP, "knee": KNEE, "arm": ARM, "elbow": ELBOW},
|
||||||
|
"export": os.path.basename(args.out),
|
||||||
|
"log": PARTS,
|
||||||
|
}, handle, indent=2)
|
||||||
|
print(f"[anim] report -> {args.report}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user