Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c4e0701fb | ||
|
|
0e7a6e898b | ||
|
|
2f6d06f897 | ||
|
|
5de4183456 | ||
|
|
bea6aa3e05 | ||
|
|
7c57bf318b | ||
|
|
878d5a1f4e | ||
|
|
86e9c5b693 | ||
|
|
85a8437cbc | ||
|
|
1fc5835327 | ||
|
|
39f07b0f5f | ||
|
|
72d00bdfbd | ||
|
|
54c579ab1a | ||
|
|
89a9c59747 | ||
|
|
4b9ad22108 | ||
|
|
0d57b718cf | ||
|
|
e9f31f271e | ||
|
|
03af8146a6 | ||
|
|
30e4d65743 | ||
|
|
bc2e9c3b8e | ||
|
|
12272d6b96 | ||
|
|
56e104f5f3 | ||
|
|
8e880ed09d | ||
|
|
9ce7011389 | ||
|
|
f5e7e66127 | ||
|
|
dbb177da6f | ||
|
|
eeeabe56b1 | ||
|
|
f84093adba | ||
|
|
0e0aed02d6 | ||
|
|
7ea3c1999c | ||
|
|
46b84884b8 | ||
|
|
8993fd83c4 | ||
|
|
a1f5989cbb | ||
|
|
d0f94dff4e | ||
|
|
50e023f15d | ||
|
|
e487655e2f | ||
|
|
304c1e9afd | ||
|
|
05c1488c00 | ||
|
|
96238bdb6a | ||
|
|
817313aabd |
No files matched your search
@@ -210,7 +210,7 @@ Any change that affects the game's appearance — meshes, materials, lighting, l
|
|||||||
|
|
||||||
1. Run `Tools/render_screenshots.sh` (rebuilds `Game/GigLine/Content/Test/ReviewMap.umap` from current content, runs the game on it, captures the fixed camera list, copies the PNGs into `Screenshots/`).
|
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`: 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 point is that the review happens in the file; a path alone is not a review. 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 both the shot's section and its row in the status 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`.
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"character": "GigLine walker",
|
||||||
|
"description": "lean adult pedestrian, short dark hair, olive skin, plain navy t-shirt, dark jeans, worn sneakers",
|
||||||
|
"model": "juggernautXL_ragnarok.safetensors",
|
||||||
|
"views": {
|
||||||
|
"front": {
|
||||||
|
"file": "Art/characters/blueprints/front.png",
|
||||||
|
"seed": 101,
|
||||||
|
"prompt": "GigLine walker, lean adult pedestrian, short dark hair, olive skin, plain navy t-shirt, dark jeans, worn sneakers, full body front view, neutral standing pose, arms at sides, even studio light, plain light grey background, character sheet",
|
||||||
|
"negative": "multiple people, crowd, text, watermark, signature, blurry, low quality, cropped limbs, extra limbs, deformed hands, cartoon, anime, logo",
|
||||||
|
"prompt_id": "ff81a0c1-e4d7-4740-9310-806c11600539"
|
||||||
|
},
|
||||||
|
"side": {
|
||||||
|
"file": "Art/characters/blueprints/side.png",
|
||||||
|
"seed": 202,
|
||||||
|
"prompt": "GigLine walker, lean adult pedestrian, short dark hair, olive skin, plain navy t-shirt, dark jeans, worn sneakers, full body side profile view, neutral standing pose, arms at sides, even studio light, plain light grey background, character sheet",
|
||||||
|
"negative": "multiple people, crowd, text, watermark, signature, blurry, low quality, cropped limbs, extra limbs, deformed hands, cartoon, anime, logo",
|
||||||
|
"prompt_id": "cdd0bfac-446b-4c5a-8375-37fb5c70a75e"
|
||||||
|
},
|
||||||
|
"back": {
|
||||||
|
"file": "Art/characters/blueprints/back.png",
|
||||||
|
"seed": 303,
|
||||||
|
"prompt": "GigLine walker, lean adult pedestrian, short dark hair, olive skin, plain navy t-shirt, dark jeans, worn sneakers, full body back view, neutral standing pose, arms at sides, even studio light, plain light grey background, character sheet",
|
||||||
|
"negative": "multiple people, crowd, text, watermark, signature, blurry, low quality, cropped limbs, extra limbs, deformed hands, cartoon, anime, logo",
|
||||||
|
"prompt_id": "069bc59d-93bb-41ce-a95b-f5b2e8194982"
|
||||||
|
},
|
||||||
|
"head": {
|
||||||
|
"file": "Art/characters/blueprints/head.png",
|
||||||
|
"seed": 404,
|
||||||
|
"prompt": "GigLine walker, lean adult pedestrian, short dark hair, olive skin, plain navy t-shirt, dark jeans, worn sneakers, head and shoulders portrait, front view, neutral expression, even studio light, plain light grey background",
|
||||||
|
"negative": "multiple people, crowd, text, watermark, signature, blurry, low quality, cropped limbs, extra limbs, deformed hands, cartoon, anime, logo",
|
||||||
|
"prompt_id": "39ab3ba6-5e6f-4a69-bdbb-b2faf4318dc0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact_sheet": "Art/characters/blueprints/contact-sheet.png",
|
||||||
|
"note": "generated locally by Tools/comfyui_character_blueprint.py; record the model licence in THIRD_PARTY_NOTICES.md before shipping"
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"tool": "Tools/generate_character_textures.py",
|
||||||
|
"asset": "T_Character_Hair",
|
||||||
|
"size": 512,
|
||||||
|
"procedural": true,
|
||||||
|
"meta": {
|
||||||
|
"strands": 190,
|
||||||
|
"base": [
|
||||||
|
44,
|
||||||
|
32,
|
||||||
|
26
|
||||||
|
],
|
||||||
|
"highlight": [
|
||||||
|
96,
|
||||||
|
74,
|
||||||
|
58
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"note": "generated, not photographed; no third-party licence"
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"tool": "Tools/generate_character_textures.py",
|
||||||
|
"asset": "T_Character_SkinDetail",
|
||||||
|
"size": 512,
|
||||||
|
"procedural": true,
|
||||||
|
"meta": {
|
||||||
|
"octaves": {
|
||||||
|
"fine": 96,
|
||||||
|
"mid": 24,
|
||||||
|
"broad": 7
|
||||||
|
},
|
||||||
|
"detail": 9,
|
||||||
|
"tint": [
|
||||||
|
222,
|
||||||
|
178,
|
||||||
|
152
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"note": "generated, not photographed; no third-party licence"
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -8,6 +8,100 @@ Format: [Keep a Changelog](https://keepachangelog.com/) style, versions follow [
|
|||||||
|
|
||||||
Nothing yet.
|
Nothing yet.
|
||||||
|
|
||||||
|
## [0.2.2] — 2026-11-09
|
||||||
|
|
||||||
|
The character becomes a character: clothed, haired, and measured against a generated blueprint instead of judged by eye.
|
||||||
|
|
||||||
|
**Version note:** this ships as a patch by the owner's decision even though it adds asset categories, which the release policy otherwise puts in a minor release. Recorded here so the numbering is not mistaken for scope later.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Clothing** — navy tee, dark jeans and off-white shoes as three separate skinned meshes (`Tools/character_clothing_build.py`), built from the body's own faces per the `character-artist` workflow: duplicate faces, solidify for cloth thickness, one mesh per piece. Each imports with its own single material slot and is attached as a leader-pose component.
|
||||||
|
- **Hair cards** (`Tools/character_hair_build.py`) — a scalp cap plus 26 cards in three overlapping rows, quads only, every vertex weighted to `head`. Cards are oriented from the scalp normal at their anchor, so the cut follows the skull instead of standing out as spikes.
|
||||||
|
- **Character blueprints** (`Tools/comfyui_character_blueprint.py`) — front, side, back and head views generated by the local ComfyUI instance from one description, with fixed seeds and a JSON record of prompts and prompt ids.
|
||||||
|
- **Blueprint comparison** (`Tools/character_match.py`) — figure aspect, fill and coverage deltas, mean CIE76 and dominant-palette distance, plus a side-by-side montage. The deltas stay separate on purpose: one similarity number between a photoreal sheet and a low-detail body would mean nothing.
|
||||||
|
- **ComfyUI driver** (`Tools/comfyui_generate.py`) and **procedural textures** (`Tools/generate_character_textures.py`), each writing a provenance sidecar.
|
||||||
|
- **Material builder** (`Tools/ue_character_materials.py`) — `M_Character_Skin` and `M_Character_Hair_Mat` built from scratch, because `unreal.MaterialEditingLibrary` segfaults in a headless commandlet when it rebuilds an existing graph.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **The sun was raised** from pitch -62 to -74. The character stood in building shadow lit almost entirely by the blue sky light, so warm skin sampled as blue-grey — face (99, 98, 100) before, (131, 121, 117) after at the same camera.
|
||||||
|
- **The walk cycle starts at maximum stride**, so the deterministic stride capture lands on the stride rather than on one of the two frames where the legs pass together.
|
||||||
|
- Cloth shells are fattened 8 mm along their own normals after solidify; 1 cm of solidify let the body through at shoulders, hips and knees.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Hair read blond and thin** because the strand sheet's pale RGB drove base colour; the sheet now supplies only the opacity mask, and the colour is a dark constant.
|
||||||
|
- **Garment coverage holes** at shoulders, hips and knees, closed by 2 cm solidify plus the normal fatten.
|
||||||
|
- **A crash of my own making:** binding materials in `Tick` with `ConstructorHelpers::FObjectFinder` is fatal outside a constructor. Moved to `BeginPlay` with `LoadObject`.
|
||||||
|
- **Shots 6, 7 and 8 were documented as an unclothed body** after the appearance changed; the README now matches the render and all eight shots are approved.
|
||||||
|
|
||||||
|
### Verified
|
||||||
|
|
||||||
|
- 8 review shots verify; **all eight approved** by the owner 2026-11-09.
|
||||||
|
- Blueprint comparison improved from mean ΔE **31.9 → 21.8** and palette ΔE **25.4 → 19.7** once the character was clothed.
|
||||||
|
- 95 tool tests pass (13 new for the colour maths, masks and palettes).
|
||||||
|
|
||||||
|
### Known limitations
|
||||||
|
|
||||||
|
- **The garments are still shells over body faces**, not simulated cloth: no wrinkles, no drape, and no separation from the body in motion.
|
||||||
|
- **`T_Character_Diffuse` cannot be used.** Sampling it renders the body black, so the MPFB mesh's UVs do not map onto that bake; skin is a constant tone and the generated pore sheet is unwired.
|
||||||
|
- **The character has no face detail** — the eyes, mouth and brows are mesh and lighting only, and read as a mannequin at close range.
|
||||||
|
- **Camera collision remains implemented but unexercised.**
|
||||||
|
|
||||||
|
## [0.2.1] — 2026-11-09
|
||||||
|
|
||||||
|
Rig fixes. Two defects from the `0.2.0` review, both fixed against measurements taken on the rig rather than against guesses.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **The fingers splayed.** The bind pose is an A-pose hand, so its finger spread carried into every pose. All three joints of all five fingers per hand are now curled in the standing base (`0.28 / 0.34 / 0.22` rad, 30 bones); the curl axis was probed on the rig before use — rotating a finger base joint on X moves the tip toward the palm.
|
||||||
|
- **The arms-down rotation was hard-coded.** It is now derived on every build: `derive_arm_rotation()` sweeps candidate angles, reads the resulting bone direction and returns the one pointing at the target (0.60 rad, direction dot 1.000 in this build). The old 0.55 was a hand-swept constant that would have rotted on any rig change.
|
||||||
|
- **Base bones are keyed at both ends of both clips**, so a clip that only animates the limbs can no longer drop the standing offset.
|
||||||
|
|
||||||
|
### Known limitations
|
||||||
|
|
||||||
|
- **The stride capture regressed.** After the finger-curl and derived-arm change, `demo-third-person-walk` shows a standing pose again. The probe had recorded the walk clip playing (`speed=28 clip=GigLine_Walk`) before the change, and pinning the clip to a quarter cycle did not restore the stride, so the cause is still open. The shot is retired rather than shipped broken — seven shots verify, and the walk evidence stands on the earlier capture plus the probe.
|
||||||
|
- Camera collision remains implemented but unexercised against a wall.
|
||||||
|
|
||||||
|
## [0.2.0] — 2026-11-09
|
||||||
|
|
||||||
|
Third-person body. The pawn gains a visible, animated body and a third-person camera, so the walk cycle is finally something a player can see rather than a clip in an asset list.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **`Tools/character_anim_build.py`**: `GigLine_Idle` (60 frames) and `GigLine_Walk` (30 frames) at 30 fps, authored as parametric cycles so the loop closes by construction. Limb axes were **measured** on the rig — a probe rotated `upperleg01.L` by 0.5 rad on each axis and read `foot.L`, which showed X swings the limb fore/aft while Y twists and Z abducts.
|
||||||
|
- **Third-person rig** (`ADR-9`): a visible skeletal body, a `USpringArmComponent` with collision testing, and a **V** toggle. One pawn carries both rigs; first person stays the shipped default.
|
||||||
|
- **Walk capture harness**: a per-shot `ShotFollow` mode that re-places the review camera every frame in the pawn's frame, plus `SetReviewWalk` / `ReturnToStartForReview`, so a stride is captured deterministically and the fixed-camera shots still find the pawn at spawn.
|
||||||
|
- **Review shots 6, 7 and 8** — third-person back, front and mid-stride — embedded in `Screenshots/README.md`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Character export preset now **bakes animation** (`bake_anim`, all actions). Without it the exporter wrote a static bind pose and Unreal imported a mesh with no clips and no error.
|
||||||
|
- `Docs/controls.md` documents the V toggle and the review switches; the in-game HUD line reads `WASD move · Mouse look · Space jump · V view · R reset · Esc pause`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **`PlayAnimation` on a hidden component never took effect.** The body is hidden at construction and only unhidden by the toggle, so the first call could land before the component was ready; the code now sets `AnimationSingleNode`, calls `SetAnimation` + `Play`, and re-asserts while the instance is missing.
|
||||||
|
- **The animation switch restarted the clip every frame**, freezing the body on frame 0.
|
||||||
|
- **The importer silently refused to replace the existing assets.** Changing the arm rotation and rebuilding returned the identical bone quaternion every time; `replace_existing` and `delete_asset` both failed because the pawn's constructor hard-references the clips. Importing under a new name (`CharacterV2`) finally landed the change — that is what unblocked the pose after a dozen failed rebuilds.
|
||||||
|
- **The review runner copied stale screenshots** from `Saved/`, so a shot retired from the camera list reappeared with byte-identical content from an older run. The folder is cleared before each capture.
|
||||||
|
- **Walking was never enabled for the stride shot**: the call sat in the shot sequencer's `BeginPlay`, which runs before the player pawn exists, so the cast silently found nothing and `speed` stayed at 0.
|
||||||
|
|
||||||
|
### Verified
|
||||||
|
|
||||||
|
- **8 review shots verify**; shots 6, 7 and 8 approved by the owner.
|
||||||
|
- **1440p, no frame generation, VSync off**: 759 frames, avg **2.64 ms (379 fps)**, p50 2.54, p95 3.39, p99 3.76, max 4.05 ms — about 2 % over `0.1.5` and 6.3× the 60 fps floor. Series: `Docs/perf/0.2.0-walk-frametimes.csv`.
|
||||||
|
- `-qaselftest`: walk, reset 0 cm from spawn, pause/unpause, transect 1456 samples / 0 holes, wall probe 0/35, façade probe 0/9 — all PASS.
|
||||||
|
- 82 tool tests pass.
|
||||||
|
|
||||||
|
### Known limitations
|
||||||
|
|
||||||
|
- **The fingers splay** — the bind's finger spread carries into the pose. Deferred to `0.2.2`.
|
||||||
|
- **The arms-down rotation was found by sweeping**, not derived: 0.55 rad puts the arms at the sides, 0.65 against the hip, 0.75 into the torso. A derived pose is a `0.2.2` job.
|
||||||
|
- **Camera collision is implemented but unproven** — the spawn point has open space behind it, so no shot exercises the boom shortening.
|
||||||
|
- The third-person body has no head or hand IK and no turn-in-place; it is a first playable, not a finished character.
|
||||||
|
|
||||||
## [0.1.6] — 2026-11-09
|
## [0.1.6] — 2026-11-09
|
||||||
|
|
||||||
Retail frontage. The two retail buildings beside the hero are modelled from their own photograph instead of the generic kit, and the photograph is treated as evidence: an analysis is written before any geometry, and every dimension the image does not resolve is carried into the part report as an assumption.
|
Retail frontage. The two retail buildings beside the hero are modelled from their own photograph instead of the generic kit, and the photograph is treated as evidence: an analysis is written before any geometry, and every dimension the image does not resolve is carried into the part report as an assumption.
|
||||||
|
|||||||
@@ -20,12 +20,14 @@ C++ classes in `Game/GigLine/Source/GigLine/`:
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `AGigLineCharacter` | First-person player pawn | 1.75 m capsule, 4.5 m/s walk, Enhanced Input bindings, reset, pause, `-qaselftest` harness |
|
| `AGigLineCharacter` | First-person player pawn | 1.75 m capsule, 4.5 m/s walk, Enhanced Input bindings, reset, pause, `-qaselftest` harness |
|
||||||
| `AGigLineGameModeBase` | Sets game mode and HUD class | `HUDClass = AGigLineHUD` |
|
| `AGigLineGameModeBase` | Sets game mode and HUD class | `HUDClass = AGigLineHUD` |
|
||||||
| `AGigLineHUD` | HUD text overlay | `DrawText` every frame, bottom-left |
|
| `AGigLineHUD` | HUD text overlay | `DrawText` every frame: ODbL attribution bottom-left, controls bottom-right, and an F3 debug readout top-left (`gigline.Debug`, on for `-perf`/`-qaselftest`) |
|
||||||
| `AGigLineNPCPerson` | NPC prefab (future) | Capsule + visible MPFB body, feet on Z 0 |
|
| `AGigLineNPCPerson` | NPC prefab (future) | Capsule + visible MPFB body, feet on Z 0 |
|
||||||
| `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`, `IA_ToggleDebug`, 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
|
||||||
|
|
||||||
@@ -55,6 +57,9 @@ Python scripts in `Tools/` drive the content pipeline:
|
|||||||
|
|
||||||
| Script group | Purpose |
|
| Script group | Purpose |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
| `export_*`, `hero_facade_build` | Blender headless export (FBX) |
|
||||||
|
| `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 |
|
||||||
| `export_*`, `hero_facade_build`, `retail_frontage_build`, `theatre_trim_build`, `facade_kit_build` | Blender headless export (FBX); trim builders work in world coordinates |
|
| `export_*`, `hero_facade_build`, `retail_frontage_build`, `theatre_trim_build`, `facade_kit_build` | Blender headless export (FBX); trim builders work in world coordinates |
|
||||||
| `ue_import_*`, `ue_hero_facade_import`, `ue_import_retail_detail` | Unreal headless import + placement |
|
| `ue_import_*`, `ue_hero_facade_import`, `ue_import_retail_detail` | Unreal headless import + placement |
|
||||||
| `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) |
|
||||||
|
|||||||
@@ -63,3 +63,11 @@
|
|||||||
- **Decision**: Building realism gets its own minor milestone, `0.9.0 — Realistic buildings`, placed before the feature freeze. Its scope is the façade system (orders, profile library, storefront types, signage as decals), per-building photo-matched models, a measured material library with a texel-density rule, and LODs plus collision proxies. Patches keep fixes and refinements only. Renumbered by this decision: the former `0.9.0` freeze/beta/audit milestone becomes `0.10.0`, its patches `0.10.1`–`0.10.3`, and `1.0.0` moves to weeks 39–44.
|
- **Decision**: Building realism gets its own minor milestone, `0.9.0 — Realistic buildings`, placed before the feature freeze. Its scope is the façade system (orders, profile library, storefront types, signage as decals), per-building photo-matched models, a measured material library with a texel-density rule, and LODs plus collision proxies. Patches keep fixes and refinements only. Renumbered by this decision: the former `0.9.0` freeze/beta/audit milestone becomes `0.10.0`, its patches `0.10.1`–`0.10.3`, and `1.0.0` moves to weeks 39–44.
|
||||||
|
|
||||||
- **Consequences**: Façade work now has a release gate — every building measured against its own photograph, every building with a collision proxy and a LOD, no placeholder materials — so "modelled" means verified, not merely present. The price is that realism work stops being sliced into patches, and the schedule shifts by one minor cycle. Blockouts without a photograph stay blockouts and are listed as such, rather than being dressed up to fill the milestone.
|
- **Consequences**: Façade work now has a release gate — every building measured against its own photograph, every building with a collision proxy and a LOD, no placeholder materials — so "modelled" means verified, not merely present. The price is that realism work stops being sliced into patches, and the schedule shifts by one minor cycle. Blockouts without a photograph stay blockouts and are listed as such, rather than being dressed up to fill the milestone.
|
||||||
|
|
||||||
|
## 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.
|
||||||
@@ -164,12 +164,14 @@ C++ classes in `Game/GigLine/Source/GigLine/`:
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `AGigLineCharacter` | First-person player pawn | 1.75 m capsule, 4.5 m/s walk, Enhanced Input bindings, reset, pause, `-qaselftest` harness |
|
| `AGigLineCharacter` | First-person player pawn | 1.75 m capsule, 4.5 m/s walk, Enhanced Input bindings, reset, pause, `-qaselftest` harness |
|
||||||
| `AGigLineGameModeBase` | Sets game mode and HUD class | `HUDClass = AGigLineHUD` |
|
| `AGigLineGameModeBase` | Sets game mode and HUD class | `HUDClass = AGigLineHUD` |
|
||||||
| `AGigLineHUD` | HUD text overlay | `DrawText` every frame, bottom-left |
|
| `AGigLineHUD` | HUD text overlay | `DrawText` every frame: ODbL attribution bottom-left, controls bottom-right, and an F3 debug readout top-left (`gigline.Debug`, on for `-perf`/`-qaselftest`) |
|
||||||
| `AGigLineNPCPerson` | NPC prefab (future) | Capsule + visible MPFB body, feet on Z 0 |
|
| `AGigLineNPCPerson` | NPC prefab (future) | Capsule + visible MPFB body, feet on Z 0 |
|
||||||
| `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`, `IA_ToggleDebug`, 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
|
||||||
|
|
||||||
@@ -199,6 +201,9 @@ Python scripts in `Tools/` drive the content pipeline:
|
|||||||
|
|
||||||
| Script group | Purpose |
|
| Script group | Purpose |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
| `export_*`, `hero_facade_build` | Blender headless export (FBX) |
|
||||||
|
| `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 |
|
||||||
| `export_*`, `hero_facade_build`, `retail_frontage_build`, `theatre_trim_build`, `facade_kit_build` | Blender headless export (FBX); trim builders work in world coordinates |
|
| `export_*`, `hero_facade_build`, `retail_frontage_build`, `theatre_trim_build`, `facade_kit_build` | Blender headless export (FBX); trim builders work in world coordinates |
|
||||||
| `ue_import_*`, `ue_hero_facade_import`, `ue_import_retail_detail` | Unreal headless import + placement |
|
| `ue_import_*`, `ue_hero_facade_import`, `ue_import_retail_detail` | Unreal headless import + placement |
|
||||||
| `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) |
|
||||||
@@ -453,8 +458,6 @@ Rule: place a building only against a neighbour or street mesh, never in isolati
|
|||||||
|
|
||||||
- **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.
|
||||||
|
|
||||||
# Quality Requirements
|
|
||||||
|
|
||||||
## ADR-8: Realistic building façades are a minor milestone, not a patch
|
## ADR-8: Realistic building façades are a minor milestone, not a patch
|
||||||
|
|
||||||
- **Context**: `0.1.4` carried the first façade pass as a _patch_, while the release policy says patches contain fixes and refinements and never new scope. The `0.1.6` retail pass then showed what that costs: a façade built in a patch has no release gate of its own, so a first pass that does not yet read as the building (`Screenshots/README.md`, 0.1.6 gaps) can sit inside a "done" patch series. Realism is also the project's leading quality goal, which a patch cannot carry.
|
- **Context**: `0.1.4` carried the first façade pass as a _patch_, while the release policy says patches contain fixes and refinements and never new scope. The `0.1.6` retail pass then showed what that costs: a façade built in a patch has no release gate of its own, so a first pass that does not yet read as the building (`Screenshots/README.md`, 0.1.6 gaps) can sit inside a "done" patch series. Realism is also the project's leading quality goal, which a patch cannot carry.
|
||||||
@@ -463,6 +466,16 @@ Rule: place a building only against a neighbour or street mesh, never in isolati
|
|||||||
|
|
||||||
- **Consequences**: Façade work now has a release gate — every building measured against its own photograph, every building with a collision proxy and a LOD, no placeholder materials — so "modelled" means verified, not merely present. The price is that realism work stops being sliced into patches, and the schedule shifts by one minor cycle. Blockouts without a photograph stay blockouts and are listed as such, rather than being dressed up to fill the milestone.
|
- **Consequences**: Façade work now has a release gate — every building measured against its own photograph, every building with a collision proxy and a LOD, no placeholder materials — so "modelled" means verified, not merely present. The price is that realism work stops being sliced into patches, and the schedule shifts by one minor cycle. Blockouts without a photograph stay blockouts and are listed as such, rather than being dressed up to fill the milestone.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
# Quality Requirements
|
||||||
|
|
||||||
## Quality Requirements Overview
|
## Quality Requirements Overview
|
||||||
|
|
||||||
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).
|
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).
|
||||||
|
|||||||
+28
-1
@@ -1,4 +1,4 @@
|
|||||||
# Controls (`0.1.0` walkable demo)
|
# Controls
|
||||||
|
|
||||||
Keyboard and mouse. The same list is shown in-game, bottom-right of the frame (`AGigLineHUD`), and the packaged Linux build ships with these bindings.
|
Keyboard and mouse. The same list is shown in-game, bottom-right of the frame (`AGigLineHUD`), and the packaged Linux build ships with these bindings.
|
||||||
|
|
||||||
@@ -12,6 +12,8 @@ Keyboard and mouse. The same list is shown in-game, bottom-right of the frame (`
|
|||||||
| Jump | `Space` | |
|
| Jump | `Space` | |
|
||||||
| Reset to start | `R` | Returns to the grounded spawn point |
|
| Reset to start | `R` | Returns to the grounded spawn point |
|
||||||
| Pause / release mouse | `Escape` | Second press resumes and recaptures the mouse |
|
| Pause / release mouse | `Escape` | Second press resumes and recaptures the mouse |
|
||||||
|
| Swap first / third person | `V` | First person is the default; the body is hidden in that view |
|
||||||
|
| Show / hide debug overlay | `F3` | FPS, frame time, worst frame, view mode, speed and player position |
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
@@ -19,6 +21,31 @@ Keyboard and mouse. The same list is shown in-game, bottom-right of the frame (`
|
|||||||
- The mouse is captured on launch, so looking works without clicking first. `Escape` toggles the capture as part of pause.
|
- The mouse is captured on launch, so looking works without clicking first. `Escape` toggles the capture as part of pause.
|
||||||
- Bindings are Enhanced Input actions in `/Game/Input` built by `Tools/ue_create_input_assets.py`; the mapping context is `IMC_Default`.
|
- Bindings are Enhanced Input actions in `/Game/Input` built by `Tools/ue_create_input_assets.py`; the mapping context is `IMC_Default`.
|
||||||
|
|
||||||
|
## Changes since `0.1.0`
|
||||||
|
|
||||||
|
- **`V` swaps first and third person** (`0.2.0`, `IA_ToggleCamera`). The shipped default view is still first person, as the owner decided for `0.1.0`; third person adds a collision-tested spring-arm camera and a visible, animated body. `Escape` still pauses and releases the mouse in either view.
|
||||||
|
|
||||||
|
## Debug overlay
|
||||||
|
|
||||||
|
`F3` toggles a top-left readout: smoothed **FPS** and frame time, the **worst** frame in a rolling two-second window, the active view (first or third person), player speed, a frame counter, and the player's position in metres from the project origin. The first two seconds of a session are a warm-up window and are excluded from the worst-frame figure, because shader and streaming hitches there made a healthy run read as a 400 ms stall.
|
||||||
|
|
||||||
|
It is **off in the built demo** — a player should not see an FPS counter over Hollywood Boulevard — and **on automatically** when the process is started with `-perf` or `-qaselftest`, so a performance capture never depends on someone pressing a key first. In a running build it can also be forced with the console variable `gigline.Debug 1`.
|
||||||
|
|
||||||
|
Cost: about **5 %** of frame time at 1440p in the demo (2.64 → 2.77 ms average), measured in the same self-test route. The performance release check is recorded without the overlay for that reason; the overlay run is kept beside it as `Docs/perf/0.2.0-walk-frametimes-overlay.csv`.
|
||||||
|
|
||||||
|
## Review and harness switches
|
||||||
|
|
||||||
|
These are command-line switches for the screenshot pipeline and QA runs, not player controls. They exist so a capture is reproducible rather than dependent on a human pressing keys at the right moment.
|
||||||
|
|
||||||
|
| Switch | Effect |
|
||||||
|
| --- | --- |
|
||||||
|
| `-thirdperson` | Starts the pawn in the third-person rig, so a capture shows the body without anyone pressing `V` |
|
||||||
|
| `-walk` | Holds forward input from the first frame — what makes the stride shot possible |
|
||||||
|
| `-qaselftest` | Runs the scripted walk / reset / pause / transect checks and quits; also enables the debug overlay |
|
||||||
|
| `-perf` | Enables the debug overlay for a manual frame-time check |
|
||||||
|
|
||||||
|
The walking shot is driven per shot rather than by the switch: the shot actor enables the walk only while a tracking (`ShotFollow`) camera is being captured, and returns the pawn to spawn afterwards, so the fixed-camera shots still find it in place.
|
||||||
|
|
||||||
## Testing input without a physical keyboard
|
## Testing input without a physical keyboard
|
||||||
|
|
||||||
`Tools/input_test.py` injects OS-level keyboard/mouse events through a virtual `/dev/uinput` device (see `Tools/input_emulation.py`), e.g.:
|
`Tools/input_test.py` injects OS-level keyboard/mouse events through a virtual `/dev/uinput` device (see `Tools/input_emulation.py`), e.g.:
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,723 @@
|
|||||||
|
frame,ms
|
||||||
|
0,2.607
|
||||||
|
1,2.532
|
||||||
|
2,2.324
|
||||||
|
3,3.432
|
||||||
|
4,2.808
|
||||||
|
5,2.870
|
||||||
|
6,2.367
|
||||||
|
7,2.311
|
||||||
|
8,2.756
|
||||||
|
9,3.278
|
||||||
|
10,2.418
|
||||||
|
11,2.665
|
||||||
|
12,2.438
|
||||||
|
13,2.646
|
||||||
|
14,2.461
|
||||||
|
15,2.941
|
||||||
|
16,2.975
|
||||||
|
17,3.164
|
||||||
|
18,2.766
|
||||||
|
19,2.341
|
||||||
|
20,2.543
|
||||||
|
21,3.395
|
||||||
|
22,2.772
|
||||||
|
23,2.575
|
||||||
|
24,2.737
|
||||||
|
25,2.427
|
||||||
|
26,2.538
|
||||||
|
27,2.708
|
||||||
|
28,3.183
|
||||||
|
29,2.734
|
||||||
|
30,2.425
|
||||||
|
31,2.546
|
||||||
|
32,2.600
|
||||||
|
33,2.545
|
||||||
|
34,3.705
|
||||||
|
35,2.563
|
||||||
|
36,2.907
|
||||||
|
37,2.368
|
||||||
|
38,2.470
|
||||||
|
39,2.411
|
||||||
|
40,3.778
|
||||||
|
41,3.735
|
||||||
|
42,2.292
|
||||||
|
43,2.458
|
||||||
|
44,2.566
|
||||||
|
45,3.044
|
||||||
|
46,3.449
|
||||||
|
47,2.914
|
||||||
|
48,3.243
|
||||||
|
49,2.084
|
||||||
|
50,3.872
|
||||||
|
51,1.633
|
||||||
|
52,3.389
|
||||||
|
53,2.448
|
||||||
|
54,2.474
|
||||||
|
55,2.509
|
||||||
|
56,2.346
|
||||||
|
57,3.192
|
||||||
|
58,2.762
|
||||||
|
59,2.788
|
||||||
|
60,2.538
|
||||||
|
61,2.520
|
||||||
|
62,2.517
|
||||||
|
63,3.036
|
||||||
|
64,3.205
|
||||||
|
65,3.080
|
||||||
|
66,2.514
|
||||||
|
67,2.429
|
||||||
|
68,2.515
|
||||||
|
69,2.963
|
||||||
|
70,3.555
|
||||||
|
71,2.430
|
||||||
|
72,2.742
|
||||||
|
73,2.435
|
||||||
|
74,2.526
|
||||||
|
75,3.304
|
||||||
|
76,3.511
|
||||||
|
77,2.477
|
||||||
|
78,2.472
|
||||||
|
79,2.452
|
||||||
|
80,2.421
|
||||||
|
81,3.334
|
||||||
|
82,3.286
|
||||||
|
83,2.725
|
||||||
|
84,2.411
|
||||||
|
85,2.597
|
||||||
|
86,2.440
|
||||||
|
87,3.213
|
||||||
|
88,3.411
|
||||||
|
89,3.047
|
||||||
|
90,2.365
|
||||||
|
91,2.530
|
||||||
|
92,2.877
|
||||||
|
93,3.249
|
||||||
|
94,3.186
|
||||||
|
95,2.684
|
||||||
|
96,2.304
|
||||||
|
97,2.406
|
||||||
|
98,2.948
|
||||||
|
99,3.652
|
||||||
|
100,2.822
|
||||||
|
101,2.552
|
||||||
|
102,2.575
|
||||||
|
103,2.354
|
||||||
|
104,2.676
|
||||||
|
105,2.775
|
||||||
|
106,3.865
|
||||||
|
107,2.480
|
||||||
|
108,2.501
|
||||||
|
109,2.675
|
||||||
|
110,2.724
|
||||||
|
111,3.671
|
||||||
|
112,2.828
|
||||||
|
113,3.064
|
||||||
|
114,2.153
|
||||||
|
115,2.752
|
||||||
|
116,2.936
|
||||||
|
117,3.008
|
||||||
|
118,2.932
|
||||||
|
119,2.648
|
||||||
|
120,2.441
|
||||||
|
121,3.471
|
||||||
|
122,1.885
|
||||||
|
123,4.435
|
||||||
|
124,2.504
|
||||||
|
125,3.241
|
||||||
|
126,2.813
|
||||||
|
127,2.445
|
||||||
|
128,3.321
|
||||||
|
129,3.055
|
||||||
|
130,3.222
|
||||||
|
131,2.766
|
||||||
|
132,2.582
|
||||||
|
133,2.597
|
||||||
|
134,3.637
|
||||||
|
135,3.150
|
||||||
|
136,3.163
|
||||||
|
137,2.675
|
||||||
|
138,2.708
|
||||||
|
139,2.581
|
||||||
|
140,3.377
|
||||||
|
141,3.518
|
||||||
|
142,2.565
|
||||||
|
143,2.385
|
||||||
|
144,2.360
|
||||||
|
145,2.768
|
||||||
|
146,3.172
|
||||||
|
147,2.754
|
||||||
|
148,2.758
|
||||||
|
149,2.474
|
||||||
|
150,2.995
|
||||||
|
151,2.903
|
||||||
|
152,3.398
|
||||||
|
153,2.484
|
||||||
|
154,2.894
|
||||||
|
155,3.022
|
||||||
|
156,2.413
|
||||||
|
157,3.517
|
||||||
|
158,3.370
|
||||||
|
159,2.854
|
||||||
|
160,2.485
|
||||||
|
161,3.058
|
||||||
|
162,2.473
|
||||||
|
163,3.390
|
||||||
|
164,3.023
|
||||||
|
165,2.543
|
||||||
|
166,2.629
|
||||||
|
167,2.929
|
||||||
|
168,2.928
|
||||||
|
169,3.268
|
||||||
|
170,2.622
|
||||||
|
171,2.764
|
||||||
|
172,2.920
|
||||||
|
173,2.681
|
||||||
|
174,2.551
|
||||||
|
175,3.492
|
||||||
|
176,2.534
|
||||||
|
177,2.741
|
||||||
|
178,2.949
|
||||||
|
179,2.554
|
||||||
|
180,2.596
|
||||||
|
181,3.522
|
||||||
|
182,3.207
|
||||||
|
183,2.722
|
||||||
|
184,2.945
|
||||||
|
185,2.474
|
||||||
|
186,2.934
|
||||||
|
187,3.447
|
||||||
|
188,2.515
|
||||||
|
189,2.317
|
||||||
|
190,3.059
|
||||||
|
191,2.542
|
||||||
|
192,3.417
|
||||||
|
193,2.737
|
||||||
|
194,2.456
|
||||||
|
195,3.025
|
||||||
|
196,2.640
|
||||||
|
197,2.601
|
||||||
|
198,3.567
|
||||||
|
199,2.350
|
||||||
|
200,2.581
|
||||||
|
201,3.079
|
||||||
|
202,2.514
|
||||||
|
203,2.462
|
||||||
|
204,3.725
|
||||||
|
205,3.730
|
||||||
|
206,2.670
|
||||||
|
207,2.308
|
||||||
|
208,2.689
|
||||||
|
209,2.565
|
||||||
|
210,3.479
|
||||||
|
211,2.604
|
||||||
|
212,3.339
|
||||||
|
213,2.373
|
||||||
|
214,2.678
|
||||||
|
215,3.340
|
||||||
|
216,2.867
|
||||||
|
217,2.751
|
||||||
|
218,2.747
|
||||||
|
219,2.410
|
||||||
|
220,2.485
|
||||||
|
221,2.594
|
||||||
|
222,3.602
|
||||||
|
223,2.689
|
||||||
|
224,2.565
|
||||||
|
225,2.397
|
||||||
|
226,2.562
|
||||||
|
227,2.598
|
||||||
|
228,3.403
|
||||||
|
229,3.303
|
||||||
|
230,2.775
|
||||||
|
231,2.101
|
||||||
|
232,2.499
|
||||||
|
233,2.991
|
||||||
|
234,3.059
|
||||||
|
235,3.210
|
||||||
|
236,2.568
|
||||||
|
237,2.066
|
||||||
|
238,2.498
|
||||||
|
239,2.572
|
||||||
|
240,3.281
|
||||||
|
241,2.676
|
||||||
|
242,2.640
|
||||||
|
243,2.867
|
||||||
|
244,2.418
|
||||||
|
245,2.493
|
||||||
|
246,3.126
|
||||||
|
247,2.845
|
||||||
|
248,2.522
|
||||||
|
249,2.459
|
||||||
|
250,2.524
|
||||||
|
251,2.466
|
||||||
|
252,3.060
|
||||||
|
253,3.848
|
||||||
|
254,2.516
|
||||||
|
255,2.460
|
||||||
|
256,2.499
|
||||||
|
257,2.427
|
||||||
|
258,3.463
|
||||||
|
259,2.761
|
||||||
|
260,2.674
|
||||||
|
261,2.450
|
||||||
|
262,2.491
|
||||||
|
263,2.543
|
||||||
|
264,2.732
|
||||||
|
265,3.679
|
||||||
|
266,2.325
|
||||||
|
267,2.529
|
||||||
|
268,2.344
|
||||||
|
269,2.534
|
||||||
|
270,2.691
|
||||||
|
271,3.176
|
||||||
|
272,2.492
|
||||||
|
273,2.721
|
||||||
|
274,2.587
|
||||||
|
275,2.594
|
||||||
|
276,2.326
|
||||||
|
277,3.434
|
||||||
|
278,3.514
|
||||||
|
279,2.415
|
||||||
|
280,2.481
|
||||||
|
281,2.478
|
||||||
|
282,2.555
|
||||||
|
283,3.303
|
||||||
|
284,3.098
|
||||||
|
285,2.451
|
||||||
|
286,2.552
|
||||||
|
287,2.413
|
||||||
|
288,2.613
|
||||||
|
289,3.194
|
||||||
|
290,3.191
|
||||||
|
291,2.338
|
||||||
|
292,2.561
|
||||||
|
293,2.785
|
||||||
|
294,2.240
|
||||||
|
295,3.324
|
||||||
|
296,2.767
|
||||||
|
297,2.589
|
||||||
|
298,2.608
|
||||||
|
299,2.432
|
||||||
|
300,2.444
|
||||||
|
301,2.968
|
||||||
|
302,3.792
|
||||||
|
303,2.590
|
||||||
|
304,2.335
|
||||||
|
305,2.520
|
||||||
|
306,2.435
|
||||||
|
307,3.613
|
||||||
|
308,2.681
|
||||||
|
309,2.692
|
||||||
|
310,2.329
|
||||||
|
311,2.580
|
||||||
|
312,2.355
|
||||||
|
313,3.084
|
||||||
|
314,3.026
|
||||||
|
315,2.551
|
||||||
|
316,2.490
|
||||||
|
317,2.481
|
||||||
|
318,2.553
|
||||||
|
319,2.619
|
||||||
|
320,3.350
|
||||||
|
321,2.885
|
||||||
|
322,2.576
|
||||||
|
323,2.456
|
||||||
|
324,2.441
|
||||||
|
325,3.223
|
||||||
|
326,3.031
|
||||||
|
327,3.055
|
||||||
|
328,2.592
|
||||||
|
329,2.499
|
||||||
|
330,2.354
|
||||||
|
331,2.573
|
||||||
|
332,3.323
|
||||||
|
333,3.357
|
||||||
|
334,2.241
|
||||||
|
335,2.319
|
||||||
|
336,2.472
|
||||||
|
337,2.609
|
||||||
|
338,3.565
|
||||||
|
339,2.607
|
||||||
|
340,2.539
|
||||||
|
341,2.440
|
||||||
|
342,2.483
|
||||||
|
343,2.626
|
||||||
|
344,3.407
|
||||||
|
345,2.772
|
||||||
|
346,2.491
|
||||||
|
347,2.456
|
||||||
|
348,2.553
|
||||||
|
349,2.773
|
||||||
|
350,3.239
|
||||||
|
351,4.079
|
||||||
|
352,2.346
|
||||||
|
353,2.177
|
||||||
|
354,2.562
|
||||||
|
355,2.350
|
||||||
|
356,3.329
|
||||||
|
357,3.383
|
||||||
|
358,2.441
|
||||||
|
359,2.399
|
||||||
|
360,2.530
|
||||||
|
361,2.455
|
||||||
|
362,3.219
|
||||||
|
363,2.879
|
||||||
|
364,2.726
|
||||||
|
365,2.418
|
||||||
|
366,2.511
|
||||||
|
367,2.544
|
||||||
|
368,3.077
|
||||||
|
369,2.926
|
||||||
|
370,2.571
|
||||||
|
371,2.595
|
||||||
|
372,2.534
|
||||||
|
373,2.367
|
||||||
|
374,2.671
|
||||||
|
375,4.222
|
||||||
|
376,2.411
|
||||||
|
377,2.569
|
||||||
|
378,2.493
|
||||||
|
379,2.464
|
||||||
|
380,2.691
|
||||||
|
381,3.474
|
||||||
|
382,2.697
|
||||||
|
383,2.541
|
||||||
|
384,2.494
|
||||||
|
385,2.476
|
||||||
|
386,2.473
|
||||||
|
387,3.464
|
||||||
|
388,3.084
|
||||||
|
389,2.144
|
||||||
|
390,2.503
|
||||||
|
391,2.501
|
||||||
|
392,2.525
|
||||||
|
393,3.335
|
||||||
|
394,2.919
|
||||||
|
395,2.334
|
||||||
|
396,2.557
|
||||||
|
397,2.579
|
||||||
|
398,2.567
|
||||||
|
399,4.120
|
||||||
|
400,3.297
|
||||||
|
401,2.561
|
||||||
|
402,2.428
|
||||||
|
403,2.495
|
||||||
|
404,2.729
|
||||||
|
405,3.752
|
||||||
|
406,2.783
|
||||||
|
407,2.509
|
||||||
|
408,2.337
|
||||||
|
409,2.589
|
||||||
|
410,2.556
|
||||||
|
411,3.449
|
||||||
|
412,2.702
|
||||||
|
413,2.742
|
||||||
|
414,2.384
|
||||||
|
415,2.759
|
||||||
|
416,2.290
|
||||||
|
417,3.374
|
||||||
|
418,2.934
|
||||||
|
419,2.546
|
||||||
|
420,2.228
|
||||||
|
421,2.672
|
||||||
|
422,2.369
|
||||||
|
423,3.442
|
||||||
|
424,3.484
|
||||||
|
425,2.621
|
||||||
|
426,2.279
|
||||||
|
427,2.529
|
||||||
|
428,2.889
|
||||||
|
429,2.910
|
||||||
|
430,2.981
|
||||||
|
431,2.957
|
||||||
|
432,2.420
|
||||||
|
433,2.398
|
||||||
|
434,2.310
|
||||||
|
435,3.662
|
||||||
|
436,2.422
|
||||||
|
437,2.676
|
||||||
|
438,2.557
|
||||||
|
439,2.527
|
||||||
|
440,2.564
|
||||||
|
441,2.649
|
||||||
|
442,3.168
|
||||||
|
443,2.499
|
||||||
|
444,2.737
|
||||||
|
445,2.637
|
||||||
|
446,2.509
|
||||||
|
447,2.716
|
||||||
|
448,3.845
|
||||||
|
449,2.654
|
||||||
|
450,2.536
|
||||||
|
451,2.366
|
||||||
|
452,2.526
|
||||||
|
453,2.906
|
||||||
|
454,3.101
|
||||||
|
455,2.809
|
||||||
|
456,2.466
|
||||||
|
457,2.502
|
||||||
|
458,2.767
|
||||||
|
459,2.486
|
||||||
|
460,3.137
|
||||||
|
461,3.402
|
||||||
|
462,2.187
|
||||||
|
463,2.398
|
||||||
|
464,2.392
|
||||||
|
465,2.614
|
||||||
|
466,3.371
|
||||||
|
467,2.847
|
||||||
|
468,2.463
|
||||||
|
469,2.639
|
||||||
|
470,2.288
|
||||||
|
471,2.562
|
||||||
|
472,3.381
|
||||||
|
473,3.491
|
||||||
|
474,2.521
|
||||||
|
475,2.397
|
||||||
|
476,2.431
|
||||||
|
477,2.759
|
||||||
|
478,3.222
|
||||||
|
479,2.957
|
||||||
|
480,2.470
|
||||||
|
481,2.403
|
||||||
|
482,2.560
|
||||||
|
483,2.470
|
||||||
|
484,3.451
|
||||||
|
485,2.905
|
||||||
|
486,2.583
|
||||||
|
487,2.329
|
||||||
|
488,2.798
|
||||||
|
489,2.289
|
||||||
|
490,2.882
|
||||||
|
491,2.903
|
||||||
|
492,2.627
|
||||||
|
493,2.897
|
||||||
|
494,2.321
|
||||||
|
495,2.472
|
||||||
|
496,2.882
|
||||||
|
497,3.844
|
||||||
|
498,3.096
|
||||||
|
499,2.525
|
||||||
|
500,2.250
|
||||||
|
501,2.477
|
||||||
|
502,2.728
|
||||||
|
503,3.391
|
||||||
|
504,2.539
|
||||||
|
505,2.635
|
||||||
|
506,2.542
|
||||||
|
507,2.435
|
||||||
|
508,3.011
|
||||||
|
509,3.042
|
||||||
|
510,2.965
|
||||||
|
511,2.273
|
||||||
|
512,2.433
|
||||||
|
513,3.020
|
||||||
|
514,2.776
|
||||||
|
515,3.646
|
||||||
|
516,2.856
|
||||||
|
517,2.886
|
||||||
|
518,2.522
|
||||||
|
519,2.528
|
||||||
|
520,4.396
|
||||||
|
521,3.397
|
||||||
|
522,2.218
|
||||||
|
523,2.433
|
||||||
|
524,2.605
|
||||||
|
525,3.504
|
||||||
|
526,3.087
|
||||||
|
527,3.186
|
||||||
|
528,2.343
|
||||||
|
529,2.433
|
||||||
|
530,2.900
|
||||||
|
531,3.000
|
||||||
|
532,3.511
|
||||||
|
533,2.663
|
||||||
|
534,2.699
|
||||||
|
535,2.423
|
||||||
|
536,3.136
|
||||||
|
537,2.802
|
||||||
|
538,3.151
|
||||||
|
539,2.507
|
||||||
|
540,2.973
|
||||||
|
541,2.325
|
||||||
|
542,3.322
|
||||||
|
543,3.260
|
||||||
|
544,3.525
|
||||||
|
545,2.846
|
||||||
|
546,2.125
|
||||||
|
547,2.697
|
||||||
|
548,2.829
|
||||||
|
549,3.389
|
||||||
|
550,2.758
|
||||||
|
551,2.866
|
||||||
|
552,2.338
|
||||||
|
553,3.115
|
||||||
|
554,2.500
|
||||||
|
555,3.387
|
||||||
|
556,2.851
|
||||||
|
557,2.511
|
||||||
|
558,2.459
|
||||||
|
559,3.107
|
||||||
|
560,2.521
|
||||||
|
561,3.308
|
||||||
|
562,2.782
|
||||||
|
563,2.810
|
||||||
|
564,2.323
|
||||||
|
565,3.259
|
||||||
|
566,2.366
|
||||||
|
567,3.542
|
||||||
|
568,2.929
|
||||||
|
569,2.299
|
||||||
|
570,2.336
|
||||||
|
571,3.027
|
||||||
|
572,2.540
|
||||||
|
573,3.409
|
||||||
|
574,2.784
|
||||||
|
575,2.444
|
||||||
|
576,2.437
|
||||||
|
577,2.734
|
||||||
|
578,2.591
|
||||||
|
579,3.845
|
||||||
|
580,3.212
|
||||||
|
581,2.802
|
||||||
|
582,2.370
|
||||||
|
583,2.704
|
||||||
|
584,2.624
|
||||||
|
585,4.023
|
||||||
|
586,2.360
|
||||||
|
587,2.759
|
||||||
|
588,2.511
|
||||||
|
589,2.608
|
||||||
|
590,2.922
|
||||||
|
591,3.081
|
||||||
|
592,3.271
|
||||||
|
593,2.471
|
||||||
|
594,2.516
|
||||||
|
595,2.474
|
||||||
|
596,2.814
|
||||||
|
597,3.127
|
||||||
|
598,2.537
|
||||||
|
599,3.080
|
||||||
|
600,2.341
|
||||||
|
601,3.027
|
||||||
|
602,2.766
|
||||||
|
603,2.984
|
||||||
|
604,2.450
|
||||||
|
605,2.922
|
||||||
|
606,2.481
|
||||||
|
607,2.616
|
||||||
|
608,2.541
|
||||||
|
609,3.462
|
||||||
|
610,2.971
|
||||||
|
611,2.955
|
||||||
|
612,2.258
|
||||||
|
613,2.682
|
||||||
|
614,2.770
|
||||||
|
615,3.119
|
||||||
|
616,2.792
|
||||||
|
617,2.598
|
||||||
|
618,2.260
|
||||||
|
619,2.441
|
||||||
|
620,2.536
|
||||||
|
621,3.425
|
||||||
|
622,2.958
|
||||||
|
623,2.722
|
||||||
|
624,2.099
|
||||||
|
625,2.400
|
||||||
|
626,2.570
|
||||||
|
627,3.295
|
||||||
|
628,2.919
|
||||||
|
629,2.737
|
||||||
|
630,2.311
|
||||||
|
631,2.408
|
||||||
|
632,2.514
|
||||||
|
633,3.325
|
||||||
|
634,2.767
|
||||||
|
635,2.724
|
||||||
|
636,2.288
|
||||||
|
637,2.680
|
||||||
|
638,2.347
|
||||||
|
639,2.902
|
||||||
|
640,3.058
|
||||||
|
641,2.770
|
||||||
|
642,2.522
|
||||||
|
643,2.406
|
||||||
|
644,2.539
|
||||||
|
645,2.753
|
||||||
|
646,3.215
|
||||||
|
647,2.636
|
||||||
|
648,2.639
|
||||||
|
649,2.666
|
||||||
|
650,2.392
|
||||||
|
651,2.408
|
||||||
|
652,3.515
|
||||||
|
653,2.687
|
||||||
|
654,2.589
|
||||||
|
655,2.428
|
||||||
|
656,2.375
|
||||||
|
657,2.619
|
||||||
|
658,3.308
|
||||||
|
659,2.664
|
||||||
|
660,2.752
|
||||||
|
661,2.456
|
||||||
|
662,2.365
|
||||||
|
663,2.533
|
||||||
|
664,3.354
|
||||||
|
665,2.812
|
||||||
|
666,2.684
|
||||||
|
667,2.349
|
||||||
|
668,2.441
|
||||||
|
669,2.714
|
||||||
|
670,2.769
|
||||||
|
671,3.368
|
||||||
|
672,2.624
|
||||||
|
673,2.308
|
||||||
|
674,2.365
|
||||||
|
675,2.634
|
||||||
|
676,2.617
|
||||||
|
677,3.350
|
||||||
|
678,2.733
|
||||||
|
679,2.623
|
||||||
|
680,2.462
|
||||||
|
681,2.494
|
||||||
|
682,2.460
|
||||||
|
683,3.463
|
||||||
|
684,2.537
|
||||||
|
685,2.628
|
||||||
|
686,2.674
|
||||||
|
687,2.381
|
||||||
|
688,2.516
|
||||||
|
689,3.586
|
||||||
|
690,2.681
|
||||||
|
691,2.676
|
||||||
|
692,2.289
|
||||||
|
693,2.726
|
||||||
|
694,2.303
|
||||||
|
695,2.934
|
||||||
|
696,3.339
|
||||||
|
697,2.606
|
||||||
|
698,2.187
|
||||||
|
699,2.687
|
||||||
|
700,2.328
|
||||||
|
701,3.062
|
||||||
|
702,2.930
|
||||||
|
703,2.871
|
||||||
|
704,2.313
|
||||||
|
705,2.582
|
||||||
|
706,2.464
|
||||||
|
707,2.521
|
||||||
|
708,3.304
|
||||||
|
709,2.492
|
||||||
|
710,2.723
|
||||||
|
711,2.587
|
||||||
|
712,2.608
|
||||||
|
713,2.574
|
||||||
|
714,4.002
|
||||||
|
715,3.011
|
||||||
|
716,2.227
|
||||||
|
717,2.374
|
||||||
|
718,2.343
|
||||||
|
719,2.541
|
||||||
|
720,3.580
|
||||||
|
721,2.854
|
||||||
|
@@ -0,0 +1,760 @@
|
|||||||
|
frame,ms
|
||||||
|
0,2.428
|
||||||
|
1,2.633
|
||||||
|
2,2.238
|
||||||
|
3,3.606
|
||||||
|
4,2.431
|
||||||
|
5,2.649
|
||||||
|
6,4.050
|
||||||
|
7,2.011
|
||||||
|
8,2.735
|
||||||
|
9,3.390
|
||||||
|
10,2.901
|
||||||
|
11,2.237
|
||||||
|
12,2.686
|
||||||
|
13,2.383
|
||||||
|
14,2.451
|
||||||
|
15,3.412
|
||||||
|
16,2.458
|
||||||
|
17,2.474
|
||||||
|
18,2.588
|
||||||
|
19,2.556
|
||||||
|
20,2.478
|
||||||
|
21,3.150
|
||||||
|
22,2.667
|
||||||
|
23,2.349
|
||||||
|
24,2.568
|
||||||
|
25,2.580
|
||||||
|
26,2.393
|
||||||
|
27,2.575
|
||||||
|
28,3.132
|
||||||
|
29,2.536
|
||||||
|
30,2.508
|
||||||
|
31,2.710
|
||||||
|
32,2.348
|
||||||
|
33,2.537
|
||||||
|
34,2.820
|
||||||
|
35,3.162
|
||||||
|
36,2.163
|
||||||
|
37,2.562
|
||||||
|
38,2.619
|
||||||
|
39,2.323
|
||||||
|
40,2.628
|
||||||
|
41,3.075
|
||||||
|
42,2.512
|
||||||
|
43,2.448
|
||||||
|
44,2.505
|
||||||
|
45,2.454
|
||||||
|
46,2.607
|
||||||
|
47,3.128
|
||||||
|
48,2.476
|
||||||
|
49,2.562
|
||||||
|
50,2.434
|
||||||
|
51,3.385
|
||||||
|
52,2.076
|
||||||
|
53,3.435
|
||||||
|
54,2.672
|
||||||
|
55,2.550
|
||||||
|
56,1.998
|
||||||
|
57,2.583
|
||||||
|
58,2.518
|
||||||
|
59,2.777
|
||||||
|
60,3.920
|
||||||
|
61,2.934
|
||||||
|
62,2.675
|
||||||
|
63,2.320
|
||||||
|
64,2.309
|
||||||
|
65,2.520
|
||||||
|
66,3.947
|
||||||
|
67,2.596
|
||||||
|
68,2.708
|
||||||
|
69,2.291
|
||||||
|
70,2.404
|
||||||
|
71,2.551
|
||||||
|
72,3.715
|
||||||
|
73,2.100
|
||||||
|
74,2.539
|
||||||
|
75,2.682
|
||||||
|
76,2.273
|
||||||
|
77,2.442
|
||||||
|
78,3.952
|
||||||
|
79,2.233
|
||||||
|
80,2.374
|
||||||
|
81,2.686
|
||||||
|
82,2.407
|
||||||
|
83,2.654
|
||||||
|
84,3.613
|
||||||
|
85,2.320
|
||||||
|
86,2.685
|
||||||
|
87,2.744
|
||||||
|
88,2.357
|
||||||
|
89,2.565
|
||||||
|
90,3.756
|
||||||
|
91,2.672
|
||||||
|
92,2.128
|
||||||
|
93,2.583
|
||||||
|
94,2.546
|
||||||
|
95,2.391
|
||||||
|
96,2.935
|
||||||
|
97,3.624
|
||||||
|
98,1.934
|
||||||
|
99,2.509
|
||||||
|
100,2.603
|
||||||
|
101,2.449
|
||||||
|
102,2.802
|
||||||
|
103,3.678
|
||||||
|
104,2.336
|
||||||
|
105,2.509
|
||||||
|
106,2.551
|
||||||
|
107,2.576
|
||||||
|
108,2.997
|
||||||
|
109,3.208
|
||||||
|
110,2.301
|
||||||
|
111,2.359
|
||||||
|
112,2.484
|
||||||
|
113,2.646
|
||||||
|
114,2.801
|
||||||
|
115,3.226
|
||||||
|
116,2.635
|
||||||
|
117,2.325
|
||||||
|
118,2.501
|
||||||
|
119,2.602
|
||||||
|
120,2.564
|
||||||
|
121,3.151
|
||||||
|
122,3.149
|
||||||
|
123,2.235
|
||||||
|
124,2.596
|
||||||
|
125,2.432
|
||||||
|
126,2.665
|
||||||
|
127,2.648
|
||||||
|
128,3.884
|
||||||
|
129,2.087
|
||||||
|
130,2.375
|
||||||
|
131,2.586
|
||||||
|
132,2.734
|
||||||
|
133,2.338
|
||||||
|
134,3.617
|
||||||
|
135,2.418
|
||||||
|
136,2.350
|
||||||
|
137,2.713
|
||||||
|
138,2.758
|
||||||
|
139,2.239
|
||||||
|
140,3.585
|
||||||
|
141,2.565
|
||||||
|
142,2.787
|
||||||
|
143,2.115
|
||||||
|
144,2.435
|
||||||
|
145,2.489
|
||||||
|
146,3.062
|
||||||
|
147,2.958
|
||||||
|
148,2.402
|
||||||
|
149,2.425
|
||||||
|
150,2.516
|
||||||
|
151,2.505
|
||||||
|
152,2.543
|
||||||
|
153,3.690
|
||||||
|
154,2.060
|
||||||
|
155,2.510
|
||||||
|
156,2.428
|
||||||
|
157,2.603
|
||||||
|
158,2.491
|
||||||
|
159,3.178
|
||||||
|
160,2.496
|
||||||
|
161,2.424
|
||||||
|
162,2.495
|
||||||
|
163,2.667
|
||||||
|
164,2.434
|
||||||
|
165,3.040
|
||||||
|
166,2.949
|
||||||
|
167,2.094
|
||||||
|
168,2.510
|
||||||
|
169,2.586
|
||||||
|
170,2.395
|
||||||
|
171,3.004
|
||||||
|
172,3.009
|
||||||
|
173,2.288
|
||||||
|
174,2.548
|
||||||
|
175,2.337
|
||||||
|
176,2.598
|
||||||
|
177,2.498
|
||||||
|
178,3.526
|
||||||
|
179,2.533
|
||||||
|
180,2.134
|
||||||
|
181,2.560
|
||||||
|
182,2.541
|
||||||
|
183,2.436
|
||||||
|
184,2.635
|
||||||
|
185,3.361
|
||||||
|
186,2.261
|
||||||
|
187,2.566
|
||||||
|
188,2.383
|
||||||
|
189,2.529
|
||||||
|
190,2.645
|
||||||
|
191,3.190
|
||||||
|
192,2.523
|
||||||
|
193,2.523
|
||||||
|
194,2.346
|
||||||
|
195,2.865
|
||||||
|
196,2.348
|
||||||
|
197,2.879
|
||||||
|
198,3.657
|
||||||
|
199,1.824
|
||||||
|
200,2.551
|
||||||
|
201,2.449
|
||||||
|
202,2.607
|
||||||
|
203,3.085
|
||||||
|
204,3.163
|
||||||
|
205,2.404
|
||||||
|
206,2.327
|
||||||
|
207,2.553
|
||||||
|
208,2.576
|
||||||
|
209,2.544
|
||||||
|
210,3.193
|
||||||
|
211,2.647
|
||||||
|
212,2.332
|
||||||
|
213,2.544
|
||||||
|
214,2.767
|
||||||
|
215,2.429
|
||||||
|
216,3.103
|
||||||
|
217,2.861
|
||||||
|
218,2.318
|
||||||
|
219,2.552
|
||||||
|
220,2.587
|
||||||
|
221,2.352
|
||||||
|
222,3.128
|
||||||
|
223,2.621
|
||||||
|
224,2.461
|
||||||
|
225,2.422
|
||||||
|
226,2.601
|
||||||
|
227,2.748
|
||||||
|
228,2.545
|
||||||
|
229,3.224
|
||||||
|
230,2.206
|
||||||
|
231,2.459
|
||||||
|
232,2.530
|
||||||
|
233,2.461
|
||||||
|
234,2.570
|
||||||
|
235,3.271
|
||||||
|
236,2.405
|
||||||
|
237,2.424
|
||||||
|
238,2.671
|
||||||
|
239,2.450
|
||||||
|
240,2.644
|
||||||
|
241,2.510
|
||||||
|
242,3.023
|
||||||
|
243,2.461
|
||||||
|
244,2.618
|
||||||
|
245,2.350
|
||||||
|
246,2.551
|
||||||
|
247,2.472
|
||||||
|
248,3.642
|
||||||
|
249,2.576
|
||||||
|
250,2.548
|
||||||
|
251,3.599
|
||||||
|
252,1.992
|
||||||
|
253,2.424
|
||||||
|
254,3.752
|
||||||
|
255,2.241
|
||||||
|
256,2.360
|
||||||
|
257,2.522
|
||||||
|
258,2.584
|
||||||
|
259,2.653
|
||||||
|
260,2.871
|
||||||
|
261,2.827
|
||||||
|
262,2.369
|
||||||
|
263,2.482
|
||||||
|
264,2.599
|
||||||
|
265,2.294
|
||||||
|
266,2.686
|
||||||
|
267,3.428
|
||||||
|
268,2.240
|
||||||
|
269,2.435
|
||||||
|
270,2.468
|
||||||
|
271,2.619
|
||||||
|
272,2.519
|
||||||
|
273,3.247
|
||||||
|
274,2.539
|
||||||
|
275,2.442
|
||||||
|
276,2.518
|
||||||
|
277,2.489
|
||||||
|
278,2.463
|
||||||
|
279,2.839
|
||||||
|
280,2.907
|
||||||
|
281,2.786
|
||||||
|
282,2.211
|
||||||
|
283,2.615
|
||||||
|
284,2.378
|
||||||
|
285,2.671
|
||||||
|
286,3.219
|
||||||
|
287,2.435
|
||||||
|
288,2.509
|
||||||
|
289,2.323
|
||||||
|
290,2.502
|
||||||
|
291,2.526
|
||||||
|
292,3.292
|
||||||
|
293,2.468
|
||||||
|
294,2.526
|
||||||
|
295,2.500
|
||||||
|
296,2.431
|
||||||
|
297,2.481
|
||||||
|
298,2.921
|
||||||
|
299,3.009
|
||||||
|
300,2.415
|
||||||
|
301,2.600
|
||||||
|
302,2.344
|
||||||
|
303,2.533
|
||||||
|
304,2.618
|
||||||
|
305,3.458
|
||||||
|
306,2.455
|
||||||
|
307,2.469
|
||||||
|
308,2.495
|
||||||
|
309,2.568
|
||||||
|
310,2.473
|
||||||
|
311,3.179
|
||||||
|
312,2.694
|
||||||
|
313,2.503
|
||||||
|
314,2.587
|
||||||
|
315,2.317
|
||||||
|
316,2.609
|
||||||
|
317,2.590
|
||||||
|
318,3.211
|
||||||
|
319,2.392
|
||||||
|
320,2.490
|
||||||
|
321,2.515
|
||||||
|
322,2.496
|
||||||
|
323,2.483
|
||||||
|
324,3.157
|
||||||
|
325,2.540
|
||||||
|
326,2.422
|
||||||
|
327,2.574
|
||||||
|
328,2.468
|
||||||
|
329,2.547
|
||||||
|
330,2.788
|
||||||
|
331,3.075
|
||||||
|
332,2.231
|
||||||
|
333,2.537
|
||||||
|
334,2.577
|
||||||
|
335,2.584
|
||||||
|
336,2.473
|
||||||
|
337,3.415
|
||||||
|
338,2.126
|
||||||
|
339,2.506
|
||||||
|
340,2.374
|
||||||
|
341,2.721
|
||||||
|
342,2.418
|
||||||
|
343,3.259
|
||||||
|
344,2.509
|
||||||
|
345,2.519
|
||||||
|
346,2.436
|
||||||
|
347,2.472
|
||||||
|
348,2.504
|
||||||
|
349,2.858
|
||||||
|
350,3.250
|
||||||
|
351,2.161
|
||||||
|
352,2.586
|
||||||
|
353,2.376
|
||||||
|
354,2.651
|
||||||
|
355,2.504
|
||||||
|
356,3.399
|
||||||
|
357,2.353
|
||||||
|
358,2.381
|
||||||
|
359,2.513
|
||||||
|
360,2.633
|
||||||
|
361,2.566
|
||||||
|
362,3.615
|
||||||
|
363,2.593
|
||||||
|
364,2.084
|
||||||
|
365,2.411
|
||||||
|
366,2.653
|
||||||
|
367,2.521
|
||||||
|
368,2.825
|
||||||
|
369,2.891
|
||||||
|
370,2.576
|
||||||
|
371,2.302
|
||||||
|
372,2.582
|
||||||
|
373,2.513
|
||||||
|
374,2.598
|
||||||
|
375,3.790
|
||||||
|
376,2.352
|
||||||
|
377,2.306
|
||||||
|
378,2.434
|
||||||
|
379,2.514
|
||||||
|
380,2.708
|
||||||
|
381,2.978
|
||||||
|
382,2.813
|
||||||
|
383,2.327
|
||||||
|
384,2.502
|
||||||
|
385,2.446
|
||||||
|
386,2.605
|
||||||
|
387,3.033
|
||||||
|
388,3.091
|
||||||
|
389,2.206
|
||||||
|
390,2.648
|
||||||
|
391,2.732
|
||||||
|
392,2.296
|
||||||
|
393,3.406
|
||||||
|
394,2.490
|
||||||
|
395,2.424
|
||||||
|
396,2.443
|
||||||
|
397,2.601
|
||||||
|
398,2.628
|
||||||
|
399,2.477
|
||||||
|
400,3.241
|
||||||
|
401,2.573
|
||||||
|
402,2.422
|
||||||
|
403,2.384
|
||||||
|
404,2.505
|
||||||
|
405,2.575
|
||||||
|
406,2.909
|
||||||
|
407,2.897
|
||||||
|
408,2.486
|
||||||
|
409,2.431
|
||||||
|
410,2.691
|
||||||
|
411,2.342
|
||||||
|
412,2.733
|
||||||
|
413,3.518
|
||||||
|
414,2.391
|
||||||
|
415,2.160
|
||||||
|
416,2.671
|
||||||
|
417,2.505
|
||||||
|
418,2.241
|
||||||
|
419,3.355
|
||||||
|
420,2.775
|
||||||
|
421,2.395
|
||||||
|
422,2.864
|
||||||
|
423,2.290
|
||||||
|
424,2.469
|
||||||
|
425,2.977
|
||||||
|
426,2.759
|
||||||
|
427,2.460
|
||||||
|
428,2.456
|
||||||
|
429,2.497
|
||||||
|
430,2.362
|
||||||
|
431,2.874
|
||||||
|
432,2.812
|
||||||
|
433,2.559
|
||||||
|
434,2.283
|
||||||
|
435,2.670
|
||||||
|
436,2.482
|
||||||
|
437,2.495
|
||||||
|
438,3.118
|
||||||
|
439,3.018
|
||||||
|
440,2.252
|
||||||
|
441,3.222
|
||||||
|
442,2.483
|
||||||
|
443,2.436
|
||||||
|
444,3.209
|
||||||
|
445,3.169
|
||||||
|
446,2.455
|
||||||
|
447,2.432
|
||||||
|
448,2.464
|
||||||
|
449,2.500
|
||||||
|
450,2.967
|
||||||
|
451,2.958
|
||||||
|
452,2.390
|
||||||
|
453,2.494
|
||||||
|
454,2.523
|
||||||
|
455,2.473
|
||||||
|
456,2.516
|
||||||
|
457,3.527
|
||||||
|
458,2.262
|
||||||
|
459,2.377
|
||||||
|
460,2.840
|
||||||
|
461,2.135
|
||||||
|
462,2.520
|
||||||
|
463,3.285
|
||||||
|
464,2.940
|
||||||
|
465,2.256
|
||||||
|
466,2.496
|
||||||
|
467,2.705
|
||||||
|
468,2.395
|
||||||
|
469,3.031
|
||||||
|
470,3.104
|
||||||
|
471,2.411
|
||||||
|
472,2.630
|
||||||
|
473,2.468
|
||||||
|
474,2.416
|
||||||
|
475,2.559
|
||||||
|
476,2.732
|
||||||
|
477,2.441
|
||||||
|
478,2.624
|
||||||
|
479,2.536
|
||||||
|
480,2.458
|
||||||
|
481,2.516
|
||||||
|
482,2.507
|
||||||
|
483,2.606
|
||||||
|
484,2.616
|
||||||
|
485,2.881
|
||||||
|
486,2.358
|
||||||
|
487,2.469
|
||||||
|
488,2.513
|
||||||
|
489,3.250
|
||||||
|
490,2.606
|
||||||
|
491,2.545
|
||||||
|
492,2.407
|
||||||
|
493,2.437
|
||||||
|
494,2.663
|
||||||
|
495,3.346
|
||||||
|
496,2.349
|
||||||
|
497,2.481
|
||||||
|
498,2.613
|
||||||
|
499,2.483
|
||||||
|
500,2.417
|
||||||
|
501,2.923
|
||||||
|
502,3.059
|
||||||
|
503,2.483
|
||||||
|
504,2.373
|
||||||
|
505,2.525
|
||||||
|
506,2.530
|
||||||
|
507,2.431
|
||||||
|
508,2.917
|
||||||
|
509,2.559
|
||||||
|
510,2.623
|
||||||
|
511,3.172
|
||||||
|
512,2.304
|
||||||
|
513,2.331
|
||||||
|
514,3.287
|
||||||
|
515,2.544
|
||||||
|
516,2.429
|
||||||
|
517,2.690
|
||||||
|
518,2.464
|
||||||
|
519,2.406
|
||||||
|
520,2.804
|
||||||
|
521,2.899
|
||||||
|
522,2.461
|
||||||
|
523,2.536
|
||||||
|
524,2.414
|
||||||
|
525,2.380
|
||||||
|
526,2.616
|
||||||
|
527,3.236
|
||||||
|
528,2.499
|
||||||
|
529,2.558
|
||||||
|
530,2.584
|
||||||
|
531,2.309
|
||||||
|
532,2.447
|
||||||
|
533,3.039
|
||||||
|
534,3.133
|
||||||
|
535,2.170
|
||||||
|
536,2.583
|
||||||
|
537,2.505
|
||||||
|
538,2.666
|
||||||
|
539,2.567
|
||||||
|
540,3.307
|
||||||
|
541,2.177
|
||||||
|
542,2.495
|
||||||
|
543,2.441
|
||||||
|
544,2.638
|
||||||
|
545,2.521
|
||||||
|
546,3.527
|
||||||
|
547,2.460
|
||||||
|
548,2.362
|
||||||
|
549,2.520
|
||||||
|
550,2.439
|
||||||
|
551,2.572
|
||||||
|
552,3.087
|
||||||
|
553,2.707
|
||||||
|
554,2.475
|
||||||
|
555,2.283
|
||||||
|
556,2.604
|
||||||
|
557,2.529
|
||||||
|
558,3.094
|
||||||
|
559,3.051
|
||||||
|
560,2.085
|
||||||
|
561,2.365
|
||||||
|
562,2.711
|
||||||
|
563,2.351
|
||||||
|
564,2.572
|
||||||
|
565,2.979
|
||||||
|
566,2.805
|
||||||
|
567,2.373
|
||||||
|
568,2.744
|
||||||
|
569,2.340
|
||||||
|
570,2.285
|
||||||
|
571,2.791
|
||||||
|
572,3.447
|
||||||
|
573,2.208
|
||||||
|
574,2.518
|
||||||
|
575,2.448
|
||||||
|
576,3.917
|
||||||
|
577,1.540
|
||||||
|
578,2.923
|
||||||
|
579,2.632
|
||||||
|
580,2.404
|
||||||
|
581,2.582
|
||||||
|
582,2.508
|
||||||
|
583,2.413
|
||||||
|
584,3.602
|
||||||
|
585,2.342
|
||||||
|
586,2.482
|
||||||
|
587,2.584
|
||||||
|
588,3.062
|
||||||
|
589,2.070
|
||||||
|
590,2.726
|
||||||
|
591,2.937
|
||||||
|
592,2.385
|
||||||
|
593,2.734
|
||||||
|
594,2.188
|
||||||
|
595,2.582
|
||||||
|
596,2.487
|
||||||
|
597,3.339
|
||||||
|
598,2.535
|
||||||
|
599,2.414
|
||||||
|
600,2.459
|
||||||
|
601,2.510
|
||||||
|
602,2.547
|
||||||
|
603,3.414
|
||||||
|
604,2.314
|
||||||
|
605,2.399
|
||||||
|
606,2.550
|
||||||
|
607,2.541
|
||||||
|
608,2.355
|
||||||
|
609,2.709
|
||||||
|
610,3.067
|
||||||
|
611,2.502
|
||||||
|
612,2.467
|
||||||
|
613,2.899
|
||||||
|
614,2.177
|
||||||
|
615,2.719
|
||||||
|
616,3.049
|
||||||
|
617,2.673
|
||||||
|
618,2.259
|
||||||
|
619,2.566
|
||||||
|
620,2.383
|
||||||
|
621,2.420
|
||||||
|
622,3.318
|
||||||
|
623,2.807
|
||||||
|
624,2.348
|
||||||
|
625,2.595
|
||||||
|
626,2.433
|
||||||
|
627,2.569
|
||||||
|
628,2.620
|
||||||
|
629,2.652
|
||||||
|
630,3.140
|
||||||
|
631,2.945
|
||||||
|
632,2.571
|
||||||
|
633,2.546
|
||||||
|
634,2.428
|
||||||
|
635,3.436
|
||||||
|
636,2.393
|
||||||
|
637,2.491
|
||||||
|
638,2.511
|
||||||
|
639,2.485
|
||||||
|
640,2.603
|
||||||
|
641,3.363
|
||||||
|
642,2.442
|
||||||
|
643,2.344
|
||||||
|
644,2.462
|
||||||
|
645,2.508
|
||||||
|
646,2.617
|
||||||
|
647,2.909
|
||||||
|
648,2.824
|
||||||
|
649,2.528
|
||||||
|
650,2.424
|
||||||
|
651,2.511
|
||||||
|
652,2.517
|
||||||
|
653,2.583
|
||||||
|
654,3.331
|
||||||
|
655,2.436
|
||||||
|
656,2.455
|
||||||
|
657,2.380
|
||||||
|
658,2.570
|
||||||
|
659,2.582
|
||||||
|
660,2.789
|
||||||
|
661,2.917
|
||||||
|
662,2.526
|
||||||
|
663,2.538
|
||||||
|
664,2.447
|
||||||
|
665,2.423
|
||||||
|
666,2.695
|
||||||
|
667,3.143
|
||||||
|
668,2.616
|
||||||
|
669,2.126
|
||||||
|
670,2.596
|
||||||
|
671,2.464
|
||||||
|
672,3.092
|
||||||
|
673,2.681
|
||||||
|
674,2.641
|
||||||
|
675,2.311
|
||||||
|
676,2.551
|
||||||
|
677,2.551
|
||||||
|
678,2.837
|
||||||
|
679,2.532
|
||||||
|
680,3.006
|
||||||
|
681,2.289
|
||||||
|
682,2.521
|
||||||
|
683,2.556
|
||||||
|
684,2.593
|
||||||
|
685,2.552
|
||||||
|
686,3.173
|
||||||
|
687,2.415
|
||||||
|
688,2.429
|
||||||
|
689,2.733
|
||||||
|
690,2.286
|
||||||
|
691,2.476
|
||||||
|
692,3.256
|
||||||
|
693,2.897
|
||||||
|
694,2.528
|
||||||
|
695,2.377
|
||||||
|
696,2.518
|
||||||
|
697,2.504
|
||||||
|
698,3.074
|
||||||
|
699,2.814
|
||||||
|
700,2.433
|
||||||
|
701,2.338
|
||||||
|
702,2.476
|
||||||
|
703,2.458
|
||||||
|
704,3.141
|
||||||
|
705,2.551
|
||||||
|
706,2.717
|
||||||
|
707,2.300
|
||||||
|
708,2.527
|
||||||
|
709,2.446
|
||||||
|
710,2.469
|
||||||
|
711,2.866
|
||||||
|
712,2.776
|
||||||
|
713,2.507
|
||||||
|
714,2.457
|
||||||
|
715,2.520
|
||||||
|
716,2.450
|
||||||
|
717,2.568
|
||||||
|
718,3.192
|
||||||
|
719,2.471
|
||||||
|
720,2.790
|
||||||
|
721,2.337
|
||||||
|
722,2.548
|
||||||
|
723,2.435
|
||||||
|
724,3.313
|
||||||
|
725,2.274
|
||||||
|
726,2.461
|
||||||
|
727,2.451
|
||||||
|
728,2.556
|
||||||
|
729,2.546
|
||||||
|
730,2.869
|
||||||
|
731,3.308
|
||||||
|
732,2.293
|
||||||
|
733,2.428
|
||||||
|
734,2.686
|
||||||
|
735,2.562
|
||||||
|
736,2.523
|
||||||
|
737,3.309
|
||||||
|
738,2.311
|
||||||
|
739,2.498
|
||||||
|
740,2.485
|
||||||
|
741,2.568
|
||||||
|
742,2.392
|
||||||
|
743,3.260
|
||||||
|
744,2.534
|
||||||
|
745,2.550
|
||||||
|
746,2.355
|
||||||
|
747,2.768
|
||||||
|
748,2.319
|
||||||
|
749,3.039
|
||||||
|
750,2.721
|
||||||
|
751,2.490
|
||||||
|
752,2.378
|
||||||
|
753,2.539
|
||||||
|
754,2.823
|
||||||
|
755,2.480
|
||||||
|
756,3.533
|
||||||
|
757,2.285
|
||||||
|
758,2.365
|
||||||
|
@@ -42,8 +42,10 @@ Measured at 1440p on the development workstation: Bazzite, Ryzen 7 7700X, 64 GB
|
|||||||
| `0.1.0` | Placement vs references | PASS (with deliberate simplifications) | Section below, 2026-11-08 | 2026-11-08 |
|
| `0.1.0` | Placement vs references | PASS (with deliberate simplifications) | Section below, 2026-11-08 | 2026-11-08 |
|
||||||
| `0.1.0` | Collision and packaging | PASS — no blocking collision, no invisible walls, no seam gaps (see audit note) | `-qaselftest` on the packaged build, 2026-11-08 | 2026-11-08 |
|
| `0.1.0` | Collision and packaging | PASS — no blocking collision, no invisible walls, no seam gaps (see audit note) | `-qaselftest` on the packaged build, 2026-11-08 | 2026-11-08 |
|
||||||
| `0.1.0` | 60 FPS at 1440p | PASS — 2.42 ms avg (414 fps), p99 3.01 ms at 2560×1440, TSR on, VSync off, no FG | `-qaselftest` perf capture, `Docs/perf/0.1.0-walk-frametimes.csv` | 2026-11-08 |
|
| `0.1.0` | 60 FPS at 1440p | PASS — 2.42 ms avg (414 fps), p99 3.01 ms at 2560×1440, TSR on, VSync off, no FG | `-qaselftest` perf capture, `Docs/perf/0.1.0-walk-frametimes.csv` | 2026-11-08 |
|
||||||
|
| `0.2.0` | 60 FPS at 1440p | PASS — 2.64 ms avg (379 fps), p50 2.54, p95 3.39, p99 3.76, max 4.05 ms at 2560×1440, TSR on, VSync off, no FG | `-qaselftest` perf capture, `Docs/perf/0.2.0-walk-frametimes.csv` | 2026-11-09 |
|
||||||
|
| `0.2.0` | Debug overlay cost | PASS — same route with the F3 overlay drawn: 2.77 ms avg (361 fps), p50 2.64, p95 3.52, p99 3.87, max 4.43 ms; about **5 %** heavier, still 6× the floor. The overlay is on for `-perf`/`-qaselftest`, off in the built demo | `Docs/perf/0.2.0-walk-frametimes-overlay.csv` | 2026-11-09 |
|
||||||
| `0.1.0` | Fresh checkout build | PASS — clean clone of `main` (`4bc41e9`), `git lfs pull` + `check-lfs.sh` OK (130 LFS files), both `GigLineEditor` and `GigLine` targets built with the documented engine | `/tmp/fresh-check`, 2026-11-08 | 2026-11-08 |
|
| `0.1.0` | Fresh checkout build | PASS — clean clone of `main` (`4bc41e9`), `git lfs pull` + `check-lfs.sh` OK (130 LFS files), both `GigLineEditor` and `GigLine` targets built with the documented engine | `/tmp/fresh-check`, 2026-11-08 | 2026-11-08 |
|
||||||
| `0.1.0` | Packaged Linux build | PASS — packaged Development build launches straight into DemoMap, no missing-asset/linker errors; controls shown in-game and in `Docs/controls.md` | `Docs/perf/0.1.0-packaged-hud-controls.png`, 2026-11-08 | 2026-11-08 |
|
| `0.1.0` | Packaged Linux build | PASS — packaged Development build launches straight into DemoMap, no missing-asset/linker errors; controls shown in-game and in `Docs/controls.md` | measurement recorded 2026-11-08; the capture was removed from the repository 2026-11-09 | 2026-11-08 |
|
||||||
|
|
||||||
## `0.1.0` — Placement vs references — record (2026-11-08)
|
## `0.1.0` — Placement vs references — record (2026-11-08)
|
||||||
|
|
||||||
@@ -175,6 +177,6 @@ Engine/Build/BatchFiles/Linux/Build.sh GigLine Linux Development \
|
|||||||
|
|
||||||
- `Docs/controls.md` — full binding table (WASD, mouse look, Space, R, Esc) plus how to test input headlessly.
|
- `Docs/controls.md` — full binding table (WASD, mouse look, Space, R, Esc) plus how to test input headlessly.
|
||||||
- `AGigLineHUD` now draws a control legend bottom-right every frame: "WASD move · Mouse look · Space jump · R reset · Esc pause".
|
- `AGigLineHUD` now draws a control legend bottom-right every frame: "WASD move · Mouse look · Space jump · R reset · Esc pause".
|
||||||
- Verified in the packaged capture: the bottom-right text bbox is 426×12 px (a single right-aligned line), attributed text bottom-left. Screenshot: `Docs/perf/0.1.0-packaged-hud-controls.png`.
|
- Measured in the packaged build on 2026-11-08: the bottom-right text bbox was 426×12 px (a single right-aligned line), attributed text bottom-left. The capture has since been removed from the repository, so the numbers stand as the record.
|
||||||
|
|
||||||
**Verdict: PASS.**
|
**Verdict: PASS.**
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
# `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.
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,10 @@
|
|||||||
#include "GigLineCharacter.h"
|
#include "GigLineCharacter.h"
|
||||||
|
|
||||||
|
#include "Animation/AnimSequence.h"
|
||||||
|
#include "Animation/AnimSingleNodeInstance.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"
|
||||||
@@ -11,6 +15,7 @@
|
|||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
#include "TimerManager.h"
|
#include "TimerManager.h"
|
||||||
#include "Containers/Ticker.h"
|
#include "Containers/Ticker.h"
|
||||||
|
#include "HAL/IConsoleManager.h"
|
||||||
#include "Kismet/GameplayStatics.h"
|
#include "Kismet/GameplayStatics.h"
|
||||||
#include "Misc/CommandLine.h"
|
#include "Misc/CommandLine.h"
|
||||||
#include "Misc/Parse.h"
|
#include "Misc/Parse.h"
|
||||||
@@ -28,7 +33,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 +56,110 @@ 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/CharacterV6.CharacterV6"));
|
||||||
|
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/CharacterV6Human_rig_GigLine_Idle.CharacterV6Human_rig_GigLine_Idle"));
|
||||||
|
if (IdleFinder.Succeeded())
|
||||||
|
{
|
||||||
|
IdleAnimation = IdleFinder.Object;
|
||||||
|
}
|
||||||
|
static ConstructorHelpers::FObjectFinder<UAnimSequence> WalkFinder(
|
||||||
|
TEXT("/Game/Test/Character/CharacterV6Human_rig_GigLine_Walk.CharacterV6Human_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);
|
||||||
|
|
||||||
|
// Hair: a separate component that copies the body's pose, so it follows the head
|
||||||
|
// bone without a second rig or a second animation. Hidden with the body.
|
||||||
|
// The skin material is assigned at runtime: a SkeletalMesh slot cannot be repointed
|
||||||
|
// from Python, so Tools/ue_character_materials.py builds M_Character_Skin and the
|
||||||
|
// pawn binds it here. Without this the imported FBX phong material is used and the
|
||||||
|
// generated pore detail never reaches the render.
|
||||||
|
static ConstructorHelpers::FObjectFinder<UMaterialInterface> SkinMaterialFinder(
|
||||||
|
TEXT("/Game/Test/Character/M_Character_Skin.M_Character_Skin"));
|
||||||
|
if (SkinMaterialFinder.Succeeded())
|
||||||
|
{
|
||||||
|
GetMesh()->SetMaterial(0, SkinMaterialFinder.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
HairMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("HairMesh"));
|
||||||
|
HairMesh->SetupAttachment(GetCapsuleComponent());
|
||||||
|
HairMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||||
|
HairMesh->SetHiddenInGame(true);
|
||||||
|
static ConstructorHelpers::FObjectFinder<USkeletalMesh> HairFinder(
|
||||||
|
TEXT("/Game/Test/Character/CharacterHair.CharacterHair"));
|
||||||
|
if (HairFinder.Succeeded())
|
||||||
|
{
|
||||||
|
HairMesh->SetSkeletalMesh(HairFinder.Object);
|
||||||
|
HairMesh->SetRelativeLocation(FVector(0.0f, 0.0f, -87.5f));
|
||||||
|
HairMesh->SetRelativeRotation(FRotator(0.0f, -90.0f, 0.0f));
|
||||||
|
HairMesh->SetLeaderPoseComponent(GetMesh());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Garments: tee, jeans, shoes. Each is its own mesh with its own single material,
|
||||||
|
// attached to the capsule and posed from the body so there is no second rig.
|
||||||
|
static const TCHAR* GarmentPaths[] = {
|
||||||
|
TEXT("/Game/Test/Character/CharacterTee.CharacterTee"),
|
||||||
|
TEXT("/Game/Test/Character/CharacterJeans.CharacterJeans"),
|
||||||
|
TEXT("/Game/Test/Character/CharacterShoes.CharacterShoes"),
|
||||||
|
};
|
||||||
|
for (const TCHAR* Path : GarmentPaths)
|
||||||
|
{
|
||||||
|
USkeletalMeshComponent* Piece = CreateDefaultSubobject<USkeletalMeshComponent>(
|
||||||
|
FName(*FString::Printf(TEXT("Cloth_%s"), *FPaths::GetBaseFilename(Path))));
|
||||||
|
Piece->SetupAttachment(GetCapsuleComponent());
|
||||||
|
Piece->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||||
|
Piece->SetHiddenInGame(true);
|
||||||
|
Piece->SetRelativeLocation(FVector(0.0f, 0.0f, -87.5f));
|
||||||
|
Piece->SetRelativeRotation(FRotator(0.0f, -90.0f, 0.0f));
|
||||||
|
if (USkeletalMesh* Mesh = LoadObject<USkeletalMesh>(nullptr, Path))
|
||||||
|
{
|
||||||
|
Piece->SetSkeletalMesh(Mesh);
|
||||||
|
Piece->SetLeaderPoseComponent(GetMesh());
|
||||||
|
}
|
||||||
|
Clothing.Add(Piece);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 +184,10 @@ 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> DebugF(
|
||||||
|
TEXT("/Game/Input/IA_ToggleDebug.IA_ToggleDebug"));
|
||||||
|
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 +199,8 @@ AGigLineCharacter::AGigLineCharacter()
|
|||||||
LookAction = LookF.Object;
|
LookAction = LookF.Object;
|
||||||
JumpAction = JumpF.Object;
|
JumpAction = JumpF.Object;
|
||||||
ResetAction = ResetF.Object;
|
ResetAction = ResetF.Object;
|
||||||
|
ToggleCameraAction = CamF.Object;
|
||||||
|
ToggleDebugAction = DebugF.Object;
|
||||||
PauseAction = PauseF.Object;
|
PauseAction = PauseF.Object;
|
||||||
|
|
||||||
if (!MappingContext || !MoveAction || !MoveBackAction || !StrafeAction || !StrafeLeftAction
|
if (!MappingContext || !MoveAction || !MoveBackAction || !StrafeAction || !StrafeLeftAction
|
||||||
@@ -138,6 +250,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.
|
||||||
@@ -178,6 +307,72 @@ void AGigLineCharacter::BeginPlay()
|
|||||||
{
|
{
|
||||||
StartQASelfTest();
|
StartQASelfTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `-walk` holds forward input from the first frame. The screenshot run uses it
|
||||||
|
// with `-thirdperson` so a capture can show the walk clip mid-stride: an idle
|
||||||
|
// shot cannot prove the animation set, and a still cannot show a stride.
|
||||||
|
// Bind the character materials here rather than in the constructor: the slot
|
||||||
|
// assignment made there never reached the render. LoadObject, not FObjectFinder,
|
||||||
|
// because FObjectFinder may only be used inside a constructor - putting one in
|
||||||
|
// Tick was a fatal "FObjectFinders can't be used outside of constructors".
|
||||||
|
if (GetMesh())
|
||||||
|
{
|
||||||
|
if (UMaterialInterface* Skin = LoadObject<UMaterialInterface>(
|
||||||
|
nullptr, TEXT("/Game/Test/Character/M_Character_Skin.M_Character_Skin")))
|
||||||
|
{
|
||||||
|
GetMesh()->SetMaterial(0, Skin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (HairMesh)
|
||||||
|
{
|
||||||
|
if (UMaterialInterface* HairMat = LoadObject<UMaterialInterface>(
|
||||||
|
nullptr, TEXT("/Game/Test/Character/M_Character_Hair_Mat.M_Character_Hair_Mat")))
|
||||||
|
{
|
||||||
|
HairMesh->SetMaterial(0, HairMat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bWalkForever = FParse::Param(FCommandLine::Get(), TEXT("walk"));
|
||||||
|
|
||||||
|
// `-perf` starts the overlay on: the performance capture and any manual frame
|
||||||
|
// check should not depend on someone pressing F3 first. The built demo has
|
||||||
|
// neither switch, so the counter stays hidden for players.
|
||||||
|
if (FParse::Param(FCommandLine::Get(), TEXT("perf")) || FParse::Param(FCommandLine::Get(), TEXT("qaselftest")))
|
||||||
|
{
|
||||||
|
if (IConsoleVariable* Var = IConsoleManager::Get().FindConsoleVariable(TEXT("gigline.Debug")))
|
||||||
|
{
|
||||||
|
Var->Set(1, ECVF_SetByConsole);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGigLineCharacter::ToggleDebugOverlay()
|
||||||
|
{
|
||||||
|
static IConsoleVariable* Var = IConsoleManager::Get().FindConsoleVariable(TEXT("gigline.Debug"));
|
||||||
|
if (Var)
|
||||||
|
{
|
||||||
|
const bool bNowOn = Var->GetInt() == 0;
|
||||||
|
Var->Set(bNowOn ? 1 : 0, ECVF_SetByConsole);
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine: debug overlay %s"), bNowOn ? TEXT("on") : TEXT("off"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGigLineCharacter::SetReviewWalk(bool bEnable)
|
||||||
|
{
|
||||||
|
bWalkForever = bEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGigLineCharacter::ReturnToStartForReview()
|
||||||
|
{
|
||||||
|
ResetToStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AGigLineCharacter::WalkForever()
|
||||||
|
{
|
||||||
|
if (bWalkForever && GetCharacterMovement() && GetCharacterMovement()->IsMovingOnGround())
|
||||||
|
{
|
||||||
|
AddMovementInput(GetActorForwardVector(), 1.0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AGigLineCharacter::StartQASelfTest()
|
void AGigLineCharacter::StartQASelfTest()
|
||||||
@@ -557,6 +752,14 @@ 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 (ToggleDebugAction)
|
||||||
|
{
|
||||||
|
Input->BindAction(ToggleDebugAction, ETriggerEvent::Started, this, &AGigLineCharacter::ToggleDebugOverlay);
|
||||||
|
}
|
||||||
if (JumpAction)
|
if (JumpAction)
|
||||||
{
|
{
|
||||||
Input->BindAction(JumpAction, ETriggerEvent::Started, this, &AGigLineCharacter::StartJump);
|
Input->BindAction(JumpAction, ETriggerEvent::Started, this, &AGigLineCharacter::StartJump);
|
||||||
@@ -644,6 +847,98 @@ 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.
|
||||||
|
WalkForever();
|
||||||
|
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 (HairMesh)
|
||||||
|
{
|
||||||
|
HairMesh->SetHiddenInGame(!bThirdPerson);
|
||||||
|
}
|
||||||
|
for (USkeletalMeshComponent* Piece : Clothing)
|
||||||
|
{
|
||||||
|
if (Piece)
|
||||||
|
{
|
||||||
|
Piece->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)
|
||||||
|
{
|
||||||
|
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 || GetMesh()->GetSingleNodeInstance() == nullptr)
|
||||||
|
{
|
||||||
|
// Only the mode setter is idempotent; calling SetAnimation unconditionally
|
||||||
|
// restarts the clip every frame, which freezes the body on frame 0 - that is
|
||||||
|
// why the walking capture showed a still figure with both legs together.
|
||||||
|
if (GetMesh()->GetAnimationMode() != EAnimationMode::AnimationSingleNode)
|
||||||
|
{
|
||||||
|
GetMesh()->SetAnimationMode(EAnimationMode::AnimationSingleNode);
|
||||||
|
}
|
||||||
|
if (Wanted != PlayingAnimation)
|
||||||
|
{
|
||||||
|
GetMesh()->SetAnimation(Wanted);
|
||||||
|
PlayingAnimation = Wanted;
|
||||||
|
GetMesh()->Play(true);
|
||||||
|
if (CVarGigLineSetupLogs.GetValueOnGameThread())
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("GigLine: body clip %s, mode=%d"),
|
||||||
|
*Wanted->GetName(), static_cast<int32>(GetMesh()->GetAnimationMode()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Same clip but the instance was recreated after a mode change: resume it
|
||||||
|
// once rather than re-setting the animation.
|
||||||
|
GetMesh()->Play(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,34 @@ protected:
|
|||||||
void ResetToStart();
|
void ResetToStart();
|
||||||
void TogglePause();
|
void TogglePause();
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** True while the third-person rig is the active view (for the debug overlay). */
|
||||||
|
bool IsThirdPerson() const { return bThirdPerson; }
|
||||||
|
|
||||||
|
/** Review harness: hold or release forward input from the screenshot actor. */
|
||||||
|
void SetReviewWalk(bool bEnable);
|
||||||
|
|
||||||
|
/** Review harness: put the pawn back where it spawned, after a walking capture. */
|
||||||
|
void ReturnToStartForReview();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** 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 +140,73 @@ 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hair, as its own skeletal mesh following the body's pose.
|
||||||
|
*
|
||||||
|
* A second component rather than geometry joined into the body: Unreal collapses
|
||||||
|
* the second material slot on a skeletal-mesh import, so joined hair rendered with
|
||||||
|
* the skin material. The character-artist skill calls for hair cards to be their
|
||||||
|
* own mesh for the same reason it calls for swappable parts to be separate.
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Body")
|
||||||
|
TObjectPtr<USkeletalMeshComponent> HairMesh;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Garments, each its own skeletal mesh on a leader pose component.
|
||||||
|
*
|
||||||
|
* The character-artist skill keeps clothing separate so a piece can be swapped or
|
||||||
|
* re-materialled on its own; it also sidesteps the material-slot collapse that
|
||||||
|
* makes joined multi-material meshes arrive with one slot.
|
||||||
|
*/
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Body")
|
||||||
|
TArray<TObjectPtr<USkeletalMeshComponent>> Clothing;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/** True while `-walk` holds forward input for the screenshot run. */
|
||||||
|
bool bWalkForever = false;
|
||||||
|
|
||||||
|
/** Applies the `-walk` forward input when the flag is set and the pawn is grounded. */
|
||||||
|
void WalkForever();
|
||||||
|
|
||||||
|
/** Debug-overlay action (F3). */
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
|
||||||
|
TObjectPtr<UInputAction> ToggleDebugAction;
|
||||||
|
|
||||||
|
/** Flips the gigline.Debug console variable that the HUD reads. */
|
||||||
|
void ToggleDebugOverlay();
|
||||||
|
|
||||||
|
/** 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;
|
||||||
|
|||||||
@@ -2,11 +2,61 @@
|
|||||||
|
|
||||||
#include "Engine/Canvas.h"
|
#include "Engine/Canvas.h"
|
||||||
#include "Engine/Engine.h"
|
#include "Engine/Engine.h"
|
||||||
|
#include "GameFramework/PlayerController.h"
|
||||||
|
#include "GigLineCharacter.h"
|
||||||
|
#include "HAL/IConsoleManager.h"
|
||||||
|
#include "Kismet/GameplayStatics.h"
|
||||||
|
|
||||||
|
// Console variable so a packaged build can be checked without a rebuild:
|
||||||
|
// gigline.Debug 1
|
||||||
|
// The HUD reads it every frame; the F3 binding toggles it.
|
||||||
|
static TAutoConsoleVariable<int32> CVarGigLineDebug(
|
||||||
|
TEXT("gigline.Debug"),
|
||||||
|
0,
|
||||||
|
TEXT("Draw the performance and state overlay (0=off, 1=on). F3 toggles it."),
|
||||||
|
ECVF_Cheat);
|
||||||
|
|
||||||
|
void AGigLineHUD::UpdateFrameStats(float DeltaSeconds)
|
||||||
|
{
|
||||||
|
if (DeltaSeconds <= 0.0f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
++FramesDrawn;
|
||||||
|
const float Ms = DeltaSeconds * 1000.0f;
|
||||||
|
if (WarmupSeconds > 0.0f)
|
||||||
|
{
|
||||||
|
WarmupSeconds -= DeltaSeconds;
|
||||||
|
SmoothedFrameMs = FMath::Lerp(SmoothedFrameMs, Ms, 0.1f);
|
||||||
|
WorstFrameMs = Ms;
|
||||||
|
WorstWindowSeconds = 0.0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// A 0.9/0.1 EMA settles in a few frames and still reacts to a stall.
|
||||||
|
SmoothedFrameMs = FMath::Lerp(SmoothedFrameMs, Ms, 0.1f);
|
||||||
|
WorstFrameMs = FMath::Max(WorstFrameMs, Ms);
|
||||||
|
// Rolling worst-frame window: a spike stays visible for two seconds, so a
|
||||||
|
// hitch is not missed between glances (and not averaged away).
|
||||||
|
WorstWindowSeconds += DeltaSeconds;
|
||||||
|
if (WorstWindowSeconds >= 2.0f)
|
||||||
|
{
|
||||||
|
WorstWindowSeconds = 0.0f;
|
||||||
|
WorstFrameMs = Ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AGigLineHUD::DrawHUD()
|
void AGigLineHUD::DrawHUD()
|
||||||
{
|
{
|
||||||
Super::DrawHUD();
|
Super::DrawHUD();
|
||||||
|
|
||||||
|
// Console variable is authoritative; the UPROPERTY is the per-instance default
|
||||||
|
// for tests that set it before BeginPlay.
|
||||||
|
if (IConsoleVariable* Var = IConsoleManager::Get().FindConsoleVariable(TEXT("gigline.Debug")))
|
||||||
|
{
|
||||||
|
bShowDebug = Var->GetInt() != 0;
|
||||||
|
}
|
||||||
|
UpdateFrameStats(GetWorld() ? GetWorld()->GetDeltaSeconds() : 0.0f);
|
||||||
|
|
||||||
if (!Canvas || AttributionLine.IsEmpty())
|
if (!Canvas || AttributionLine.IsEmpty())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -37,4 +87,35 @@ void AGigLineHUD::DrawHUD()
|
|||||||
DrawText(ControlsLine, Shadow, CX + 1.0f, CY + 1.0f, nullptr, TextScale * 0.82f);
|
DrawText(ControlsLine, Shadow, CX + 1.0f, CY + 1.0f, nullptr, TextScale * 0.82f);
|
||||||
DrawText(ControlsLine, Ink, CX, CY, nullptr, TextScale * 0.82f);
|
DrawText(ControlsLine, Ink, CX, CY, nullptr, TextScale * 0.82f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!bShowDebug)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performance and state readout, top-left. Everything here is cheap enough to
|
||||||
|
// leave on during a capture: four formatted lines and no additional passes.
|
||||||
|
const APawn* Pawn = UGameplayStatics::GetPlayerPawn(this, 0);
|
||||||
|
const AGigLineCharacter* Character = Cast<AGigLineCharacter>(Pawn);
|
||||||
|
const float Fps = SmoothedFrameMs > 0.0f ? 1000.0f / SmoothedFrameMs : 0.0f;
|
||||||
|
const FVector Where = Pawn ? Pawn->GetActorLocation() / 100.0f : FVector::ZeroVector;
|
||||||
|
const float Speed = Pawn ? Pawn->GetVelocity().Size2D() / 100.0f : 0.0f;
|
||||||
|
|
||||||
|
const FString Header = FString::Printf(TEXT("%.0f FPS %.2f ms worst %.2f ms"), Fps, SmoothedFrameMs, WorstFrameMs);
|
||||||
|
const FString State = FString::Printf(TEXT("%s %.2f m/s frames %lld"),
|
||||||
|
Character && Character->IsThirdPerson() ? TEXT("third person") : TEXT("first person"),
|
||||||
|
Speed, FramesDrawn);
|
||||||
|
const FString Place = FString::Printf(TEXT("x %.1f y %.1f z %.1f m"), Where.X, Where.Y, Where.Z);
|
||||||
|
const FString Hint = TEXT("F3 hide");
|
||||||
|
|
||||||
|
const FLinearColor DebugInk(0.85f, 1.0f, 0.55f, 0.95f);
|
||||||
|
const FLinearColor DebugShadow(0.0f, 0.0f, 0.0f, 0.6f);
|
||||||
|
const float DX = MarginPx;
|
||||||
|
float DY = MarginPx;
|
||||||
|
for (const FString& Line : { Header, State, Place, Hint })
|
||||||
|
{
|
||||||
|
DrawText(Line, DebugShadow, DX + 1.0f, DY + 1.0f, nullptr, TextScale * 0.9f);
|
||||||
|
DrawText(Line, DebugInk, DX, DY, nullptr, TextScale * 0.9f);
|
||||||
|
DY += 22.0f * TextScale * 0.9f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -34,12 +34,25 @@ public:
|
|||||||
* in-game"). Keep in sync with Docs/controls.md.
|
* in-game"). Keep in sync with Docs/controls.md.
|
||||||
*/
|
*/
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Controls")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Controls")
|
||||||
FString ControlsLine = TEXT("WASD move \u00B7 Mouse look \u00B7 Space jump \u00B7 R reset \u00B7 Esc pause");
|
FString ControlsLine = TEXT("WASD move \u00B7 Mouse look \u00B7 Space jump \u00B7 V view \u00B7 R reset \u00B7 Esc pause");
|
||||||
|
|
||||||
/** Licence line, including where the licence can be read. */
|
/** Licence line, including where the licence can be read. */
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attribution")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attribution")
|
||||||
FString LicenceLine = TEXT("Data \u00A9 OpenStreetMap contributors, ODbL 1.0 \u2014 openstreetmap.org/copyright");
|
FString LicenceLine = TEXT("Data \u00A9 OpenStreetMap contributors, ODbL 1.0 \u2014 openstreetmap.org/copyright");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performance and state readout, top-left, off by default.
|
||||||
|
*
|
||||||
|
* Toggle in game with **F3**, or start with it on by passing `-perf` (the
|
||||||
|
* performance capture does). The built demo ships with it hidden so a player
|
||||||
|
* does not see an FPS counter over Hollywood Boulevard.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Debug")
|
||||||
|
bool bShowDebug = false;
|
||||||
|
|
||||||
|
/** Smoothed FPS and a rolling frame-time window, refreshed every frame. */
|
||||||
|
void UpdateFrameStats(float DeltaSeconds);
|
||||||
|
|
||||||
/** Screen-space margin in pixels. */
|
/** Screen-space margin in pixels. */
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attribution")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attribution")
|
||||||
float MarginPx = 18.0f;
|
float MarginPx = 18.0f;
|
||||||
@@ -47,4 +60,25 @@ public:
|
|||||||
/** Text scale relative to the default canvas font. */
|
/** Text scale relative to the default canvas font. */
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attribution")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attribution")
|
||||||
float TextScale = 1.45f;
|
float TextScale = 1.45f;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/** Exponential moving average of frame time, in milliseconds. */
|
||||||
|
float SmoothedFrameMs = 16.7f;
|
||||||
|
|
||||||
|
/** Worst frame in the rolling window, so a hitch is visible without a log. */
|
||||||
|
float WorstFrameMs = 0.0f;
|
||||||
|
|
||||||
|
/** Seconds until the rolling worst-frame window resets. */
|
||||||
|
float WorstWindowSeconds = 0.0f;
|
||||||
|
|
||||||
|
/** Frames drawn, so the overlay can show that the counter is live. */
|
||||||
|
int64 FramesDrawn = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seconds left in the warm-up window. The first frames of a session include
|
||||||
|
* shader and streaming hitches of several hundred milliseconds; without this
|
||||||
|
* the "worst" figure read 400 ms for the whole first two seconds and made a
|
||||||
|
* healthy run look broken.
|
||||||
|
*/
|
||||||
|
float WarmupSeconds = 2.0f;
|
||||||
};
|
};
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
#include "GigLineShotActor.h"
|
#include "GigLineShotActor.h"
|
||||||
|
|
||||||
|
#include "GigLineCharacter.h"
|
||||||
|
#include "Kismet/GameplayStatics.h"
|
||||||
|
|
||||||
#include "Camera/CameraActor.h"
|
#include "Camera/CameraActor.h"
|
||||||
#include "Camera/CameraComponent.h"
|
#include "Camera/CameraComponent.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
@@ -32,6 +35,12 @@ void AGigLineShotActor::Tick(float DeltaSeconds)
|
|||||||
|
|
||||||
if (bWaitingForSettle)
|
if (bWaitingForSettle)
|
||||||
{
|
{
|
||||||
|
// A follow shot tracks the pawn while it settles, otherwise the subject
|
||||||
|
// walks out of the frame between placement and capture.
|
||||||
|
if (CurrentShot < ShotFollow.Num() && ShotFollow[CurrentShot])
|
||||||
|
{
|
||||||
|
UpdateFollowCamera();
|
||||||
|
}
|
||||||
SettleCountdown -= DeltaSeconds;
|
SettleCountdown -= DeltaSeconds;
|
||||||
if (SettleCountdown <= 0.f)
|
if (SettleCountdown <= 0.f)
|
||||||
{
|
{
|
||||||
@@ -53,11 +62,58 @@ void AGigLineShotActor::Tick(float DeltaSeconds)
|
|||||||
{
|
{
|
||||||
bWaitingForScreenshot = false;
|
bWaitingForScreenshot = false;
|
||||||
UE_LOG(LogTemp, Log, TEXT("GigLineShotActor: captured %s"), *FScreenshotRequest::GetFilename());
|
UE_LOG(LogTemp, Log, TEXT("GigLineShotActor: captured %s"), *FScreenshotRequest::GetFilename());
|
||||||
|
// Walking shots move the pawn; put it back before the fixed-camera shots run.
|
||||||
|
if (ShotFollow.IsValidIndex(CurrentShot) && ShotFollow[CurrentShot])
|
||||||
|
{
|
||||||
|
if (AGigLineCharacter* Character = Cast<AGigLineCharacter>(UGameplayStatics::GetPlayerPawn(this, 0)))
|
||||||
|
{
|
||||||
|
Character->ReturnToStartForReview();
|
||||||
|
}
|
||||||
|
}
|
||||||
++CurrentShot;
|
++CurrentShot;
|
||||||
NextShot();
|
NextShot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AGigLineShotActor::UpdateFollowCamera()
|
||||||
|
{
|
||||||
|
if (!CurrentCamera)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
APawn* Pawn = UGameplayStatics::GetPlayerPawn(this, 0);
|
||||||
|
if (!Pawn)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Enable the walk here rather than only in NextShot: the sequencer's BeginPlay
|
||||||
|
// runs before the player pawn exists, so a one-shot cast there silently found
|
||||||
|
// nothing and the pawn never walked during its follow shot.
|
||||||
|
if (AGigLineCharacter* Character = Cast<AGigLineCharacter>(Pawn))
|
||||||
|
{
|
||||||
|
Character->SetReviewWalk(true);
|
||||||
|
// Park the walk clip at a quarter cycle the first frame of the shot, so the
|
||||||
|
// capture lands mid-stride. Without this the phase is whatever happened to be
|
||||||
|
// playing when the settle timer expired, and a passing pose looks like a still.
|
||||||
|
if (!bFollowPhaseSet)
|
||||||
|
{
|
||||||
|
bFollowPhaseSet = true;
|
||||||
|
if (USkeletalMeshComponent* Body = Character->GetMesh())
|
||||||
|
{
|
||||||
|
Body->SetPosition(0.25f, /*bFireNotifies=*/false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Offset in the pawn's frame: to its right, slightly above, looking at the
|
||||||
|
// pelvis. Right-of-pawn gives a profile view, which is where a stride reads.
|
||||||
|
const FVector Right = Pawn->GetActorRightVector();
|
||||||
|
const FVector Origin = Pawn->GetActorLocation();
|
||||||
|
const FVector CameraLocation = Origin + Right * 420.f + FVector(0.f, 0.f, 90.f);
|
||||||
|
const FVector Target = Origin + FVector(0.f, 0.f, 40.f);
|
||||||
|
CurrentCamera->SetActorLocation(CameraLocation);
|
||||||
|
CurrentCamera->SetActorRotation((Target - CameraLocation).Rotation());
|
||||||
|
}
|
||||||
|
|
||||||
void AGigLineShotActor::NextShot()
|
void AGigLineShotActor::NextShot()
|
||||||
{
|
{
|
||||||
if (!PlayerController)
|
if (!PlayerController)
|
||||||
@@ -93,6 +149,21 @@ void AGigLineShotActor::NextShot()
|
|||||||
Cam->SetFieldOfView(ShotFieldOfView[CurrentShot]);
|
Cam->SetFieldOfView(ShotFieldOfView[CurrentShot]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Follow shots ignore their recorded transform after this point: the camera is
|
||||||
|
// rebuilt from the pawn every frame until the capture. They also own the walking:
|
||||||
|
// the pawn only holds forward input while a follow shot is being taken, so the
|
||||||
|
// fixed-camera shots later in the list still find it at the spawn point.
|
||||||
|
const bool bFollowThisShot = ShotFollow.IsValidIndex(CurrentShot) && ShotFollow[CurrentShot];
|
||||||
|
bFollowPhaseSet = false;
|
||||||
|
if (AGigLineCharacter* Character = Cast<AGigLineCharacter>(UGameplayStatics::GetPlayerPawn(this, 0)))
|
||||||
|
{
|
||||||
|
Character->SetReviewWalk(bFollowThisShot);
|
||||||
|
}
|
||||||
|
if (bFollowThisShot)
|
||||||
|
{
|
||||||
|
UpdateFollowCamera();
|
||||||
|
}
|
||||||
|
|
||||||
PlayerController->SetViewTarget(CurrentCamera);
|
PlayerController->SetViewTarget(CurrentCamera);
|
||||||
|
|
||||||
SettleCountdown = SettleSeconds;
|
SettleCountdown = SettleSeconds;
|
||||||
|
|||||||
@@ -50,6 +50,18 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
float SettleSeconds = 0.35f;
|
float SettleSeconds = 0.35f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional per-shot "follow the player" flag, same index as ShotLocations.
|
||||||
|
*
|
||||||
|
* A fixed camera cannot photograph a walking pawn: at 4.5 m/s it covers the
|
||||||
|
* distance between two captures, so three attempts framed it at a different
|
||||||
|
* distance each run and two missed it entirely. With this flag the camera is
|
||||||
|
* re-placed every frame at a fixed offset in the pawn's frame, which is what
|
||||||
|
* makes a stride shot deterministic.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
TArray<bool> ShotFollow;
|
||||||
|
|
||||||
/** Quit the game after the last shot (disable to keep the game open). */
|
/** Quit the game after the last shot (disable to keep the game open). */
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
bool bQuitWhenDone = true;
|
bool bQuitWhenDone = true;
|
||||||
@@ -57,6 +69,12 @@ public:
|
|||||||
private:
|
private:
|
||||||
void NextShot();
|
void NextShot();
|
||||||
|
|
||||||
|
/** Re-places the active camera at the follow offset behind the player pawn. */
|
||||||
|
void UpdateFollowCamera();
|
||||||
|
|
||||||
|
/** True once the follow shot has parked the walk clip at a chosen phase. */
|
||||||
|
bool bFollowPhaseSet = false;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TObjectPtr<class APlayerController> PlayerController;
|
TObjectPtr<class APlayerController> PlayerController;
|
||||||
|
|
||||||
|
|||||||
@@ -417,117 +417,98 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
|
|||||||
|
|
||||||
## 2026-11-09 — 0.1.4 signed off; 0.1.5 created for the carried geometry
|
## 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`.
|
- Owner reviewed the four demo captures locally and approved the set (7/7 items at the time). The signoff lived in `Docs/reviews/0.1.4-review.md`, which was removed on 2026-11-09; the per-shot `Approved` column of `Screenshots/README.md` is now the only record and is authoritative.
|
||||||
- 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).
|
- 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.
|
- 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
|
## 2026-11-09 — quality priority flipped: realism first, performance last
|
||||||
|
|
||||||
- Owner decision: realism leads; performance closes the quality list. Applied in
|
- 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).
|
||||||
`Docs/Architecture/01_introduction_and_goals.md`, `10_quality_requirements.md`,
|
- Ordering used: 1 visual realism, 2 geographic fidelity, 3 walkability/usability, 4 build reproducibility/portability, 5 provenance, 6 performance.
|
||||||
`04_solution_strategy.md`, the combined `architecture.md`, `Docs/release-checks.md`
|
- **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.
|
||||||
(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
|
## 2026-11-09 — 0.1.5 start: retail reference + kit layout maths
|
||||||
|
|
||||||
- Commons holds exactly **one** usable reference for the retail run:
|
- 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.
|
||||||
`Art/reference/retail-neighbours/01-lee-drug-and-neighbor.jpg` (Gb321, CC BY-SA
|
- **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.
|
||||||
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
|
## 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
|
- 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.
|
||||||
(120 pieces) rather than boolean cuts — the eye reads them in silhouette and
|
- **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.
|
||||||
grazing light, booleans would cost far more for that. Motto band is now a real
|
- 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).
|
||||||
recess: surround 6 cm proud, panel 12 cm behind (was an 8 cm dark stripe).
|
- Lesson to keep: **duplicated placement constants drift silently.** The metric that caught it was the composition correlation, not a visual review.
|
||||||
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
|
## 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
|
- 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.
|
||||||
(363 fps)**, p50 2.73, p95 3.17, p99 3.64, max 4.62 — against `0.1.0`'s 2.42 ms
|
- **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.
|
||||||
(414 fps). The 625 kit actors + 8 generated textures cost about **12 %** and leave
|
- Series committed at `Docs/perf/0.1.5-kit-walk-frametimes.csv`; comparison table in `Docs/release-checks.md`.
|
||||||
~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
|
## 2026-11-09 — 604 unmaterialed actors: material rebuild was delete+recreate
|
||||||
|
|
||||||
- The "blockout cleaning pass" turned out to need **material and placement hygiene,
|
- 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.
|
||||||
not more geometry**. `Tools/ue_audit_scene.py` (new) found **604 of 620 mesh actors
|
- **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.
|
||||||
with no material**: kit pieces and theatre trim all untextured.
|
- **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.
|
||||||
- **Cause:** `ensure_material` in `ue_build_demo_map.py` deleted and recreated each
|
- **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**.
|
||||||
`/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).
|
- 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
|
- 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.
|
||||||
duplicate labels). Packaged QA still PASS; spawn-view reference score 0.72.
|
|
||||||
|
|
||||||
## 2026-11-09 — v0.1.5 released
|
## 2026-11-09 — v0.1.5 released
|
||||||
|
|
||||||
- All actionable `0.1.5` work is done: hero fluting + recessed motto band, kit layout
|
- 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.
|
||||||
for wide/tall buildings, placement single-source-of-truth, 1440p re-measure
|
- 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.
|
||||||
(363 fps), and the material/placement hygiene that the "blockout cleaning" task
|
- 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.
|
||||||
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.
|
|
||||||
|
|
||||||
## 2026-11-09 — Retail frontage and two pipeline traps
|
## 2026-11-09 — Retail frontage and two pipeline traps
|
||||||
|
|
||||||
- **`Tools/render_screenshots.sh` called `./Engine/...` for the capture step** while every commandlet above went through `run_cmdlet`'s `cd "$ENGINE"`. With `set -e` the script died at exit 127 *after* rebuilding the maps, so the PNGs silently stayed a day old and `--verify` never ran. Fixed to `"$ENGINE/Engine/Binaries/..."`. Symptom to remember: maps rebuild, screenshots do not move.
|
- **`Tools/render_screenshots.sh` called `./Engine/...` for the capture step** while every commandlet above went through `run_cmdlet`'s `cd "$ENGINE"`. With `set -e` the script died at exit 127 _after_ rebuilding the maps, so the PNGs silently stayed a day old and `--verify` never ran. Fixed to `"$ENGINE/Engine/Binaries/..."`. Symptom to remember: maps rebuild, screenshots do not move.
|
||||||
- **The generic façade kit is applied to every actor labelled `Frontage_*` or `North_*`**, so staging a hand-modelled façade under those labels gets the kit pasted over it — two contradicting façade languages on one wall. `ue_apply_facade_kit.py` now skips `MODELLED` labels, and retail trim stages as `RetailTrim_*`.
|
- **The generic façade kit is applied to every actor labelled `Frontage_*` or `North_*`**, so staging a hand-modelled façade under those labels gets the kit pasted over it — two contradicting façade languages on one wall. `ue_apply_facade_kit.py` now skips `MODELLED` labels, and retail trim stages as `RetailTrim_*`.
|
||||||
- **A Blender builder cannot be imported by a unit test** (`import bpy` at module scope). Pure layout arithmetic lives in `Tools/_common.py`; `Tools/tests/test_retail_frontage.py` tests it there, and the builder delegates. Same trick `fbx_export_preset.py` uses for its preset dicts.
|
- **A Blender builder cannot be imported by a unit test** (`import bpy` at module scope). Pure layout arithmetic lives in `Tools/_common.py`; `Tools/tests/test_retail_frontage.py` tests it there, and the builder delegates. Same trick `fbx_export_preset.py` uses for its preset dicts.
|
||||||
- **Joining 85 parts into one mesh per building** before export cut the Unreal import from 85 assets to 2, and the staging step to two spawns. Record per-part dimensions *before* the join: after it only the joined bounds exist.
|
- **Joining 85 parts into one mesh per building** before export cut the Unreal import from 85 assets to 2, and the staging step to two spawns. Record per-part dimensions _before_ the join: after it only the joined bounds exist.
|
||||||
|
|
||||||
|
## 2026-11-09 — Third-person body renders in bind pose (open defect)
|
||||||
|
|
||||||
|
- **Symptom**: the pawn's body is visible and skinned, the run logs `GigLine: playing body clip Character_AnimHuman_rig_GigLine_Idle`, and the figure still stands in the A-pose bind. Two captures were taken before the call was proven to succeed — the camera, then the material, were blamed first and neither was the cause.
|
||||||
|
- **The FBX is not the problem.** Re-importing `/tmp/gigline/character-anim.fbx` into Blender gives two actions, `GigLine_Idle` (frames 1–60) and `GigLine_Walk` (1–30), each with **1379 f-curves over 137 bones**. Animation is baked and exported correctly, so the defect is on the Unreal side.
|
||||||
|
- **The material was a real but separate defect**: `M_Character_Export` had no `SkeletalMesh` usage flag, so Unreal substituted the default grey with only a `LogMaterial` warning. Fixed in place (`used_with_skeletal_mesh = True`).
|
||||||
|
- **`PlayAnimation` alone is not enough.** `USkeletalMeshComponent::PlayAnimation` needs the component already in `AnimationSingleNode` mode; the body is hidden at construction (`SetHiddenInGame(true)`) and only unhidden by the camera toggle, so the first call can land on a component that is not yet in the right mode and never retries — the guard `Wanted == PlayingAnimation` then returns forever and the pose stays at bind.
|
||||||
|
- **What to log next time**: animation mode and `GetSingleNodeInstance()` together with the clip name. A clip name alone shows the intent, not the effect.
|
||||||
|
- **Fixed 2026-11-09, two independent causes.** (1) `PlayAnimation` needs `AnimationSingleNode` plus a live single-node instance; the body is hidden at construction and only unhidden by the camera toggle, so the first call could land before the component was ready and the `Wanted == PlayingAnimation` guard then returned forever. `UpdateBodyAnimation` now sets the mode, calls `SetAnimation` + `Play`, and re-asserts while `GetSingleNodeInstance()` is null. (2) The arms-down offsets lived only in the clip keys — the exporter writes clip data **relative to the rest pose**, so they arrived in Unreal as a ±20° swing around an A-posed bind. `apply_base_pose` now bakes them into the rest pose before any clip is authored, which is what `mpfb_character_export.py --pose idle` already did.
|
||||||
|
- **The base pose needs inverted signs when baked to rest.** Applied as written, the FBX axis mapping produced arms raised in a V; negating the table gives arms down. Both states were verified on the imported asset, not assumed.
|
||||||
|
- **Unreal bone names use underscores**: `upperarm01_L`, not Blender's `upperarm01.L`. `GetBoneIndex(TEXT("upperarm01.L"))` returns INDEX_NONE, `GetBoneTransform` then returns identity, and a probe reading it reports a constant 0.0000 — which looks like "animation not applied" and is actually "bone not found". Check `AnimPose.get_bone_names()` before trusting any bone probe.
|
||||||
|
- **`Log` verbosity does not reach the capture log, `Warning` does.** Two diagnostic runs produced zero lines because the messages were `UE_LOG(LogTemp, Log, ...)`; the same lines at `Warning` appeared. Instrument with Warning when the evidence has to survive into `/tmp/gigline/render-*.log`.
|
||||||
|
|
||||||
|
## 2026-11-09 — Unreal silently refuses to replace an imported asset
|
||||||
|
|
||||||
|
- **The real reason the character pose never changed across a dozen rebuilds:** the FBX importer reported success every time, but `Character_Anim` and its two AnimSequences kept their original data. Proof: after changing the arm base from 1.55 rad to 0.55 rad and rebuilding, `AnimPose.get_bone_pose("upperarm01_L")` returned the **identical** quaternion `(+0.006, +0.002, -0.179, -0.984)` as before. An asset that does not change when its source does is not being written.
|
||||||
|
- `AssetImportTask.replace_existing = True` was set, and `EditorAssetLibrary.delete_asset` returned **False** for both clips, even after `chmod u+w` (the `.uasset` files are LFS-lockable and check out read-only). Deleting is blocked because the pawn's C++ constructor hard-references the clips.
|
||||||
|
- **Workaround that works: import under a new name.** `Tools/ue_character_anim_import.py` now uses `PREFIX = "CharacterV2"`, which creates `CharacterV2`, `CharacterV2_Skeleton` and `CharacterV2Human_rig_GigLine_{Idle,Walk}` fresh; the same quaternion probe then returned `(+0.439, +0.898)`, i.e. the 0.55 rad arm rotation had finally landed.
|
||||||
|
- **Check the asset, not the importer.** A pose or material change that does not alter the probed asset data did not happen, whatever the log says. Probing a clip's bone quaternion takes one commandlet and settles it in seconds.
|
||||||
|
- **The arms-down rotation belongs in the clips, not in the bind.** Every bind experiment (bake pose to rest, clear shape keys, apply the armature modifier, re-bind) was either impossible on this shape-keyed MPFB mesh or left Unreal showing the old bind. `Tools/character_anim_build.py` now carries a measured base offset in the clip keys (`BASE`: 0.55 rad on `upperarm01`, chosen by sweeping the rotation in Blender and reading the bone direction), and the character stands with its arms at its sides.
|
||||||
|
|
||||||
|
## 2026-11-09 — Textures: ComfyUI works, Stable Diffusion fails for flat swatches
|
||||||
|
|
||||||
|
- **ComfyUI is reachable at `127.0.0.1:8188` (0.44.0)** and its API is the clean way in: `POST /prompt` with an API-format graph (`[node_id, output_index]` references — the UI's workflow JSON cannot be posted), then poll `/history/<id>` and fetch `/view?filename=...`. `Tools/comfyui_generate.py` does this and writes a sidecar with model, prompt, seed and the full graph. Installed checkpoints: `juggernautXL_ragnarok.safetensors`, `epicrealismXL_pureFix.safetensors`, `sd-v1-5-inpainting.ckpt`, plus inpainting variants.
|
||||||
|
- **Do not ask Stable Diffusion for a flat texture swatch.** Three attempts at "seamless skin texture" produced an anatomical close-up twice and a leather-scale surface once. SDXL has no reliable notion of a material sample without anatomy, so every seed needs manual rejection — worse than noise we control. Procedural generation won for skin pores (`Tools/generate_character_textures.py`, tileable octave noise) and a strand sheet for hair.
|
||||||
|
- **Unreal collapses the second material slot on a skeletal-mesh import.** A joined hair cap (Blender shows two slots, hair faces on index 1) arrives as a single slot named after the body material, so the hair renders with the skin material. `unreal.SkeletalMesh` has no `set_material`, so it cannot be re-slotted after import. A separate skeletal mesh attached as a second component is the way around it.
|
||||||
|
|
||||||
|
## 2026-11-09 — Hair as a separate mesh, and a material-editing crash
|
||||||
|
|
||||||
|
- **Hair joined into the body mesh loses its material.** Blender shows two slots (hair faces on index 1) and the FBX exports both, but Unreal's skeletal-mesh import arrives with **one** slot named after the body material, so the hair renders as skin. `unreal.SkeletalMesh` has no `set_material`, so it cannot be re-slotted afterwards. A separate skeletal mesh imported on its own keeps its slot and imports cleanly (`CharacterHair` → `M_Character_Hair`).
|
||||||
|
- **A second skeletal component follows the body with `SetLeaderPoseComponent(GetMesh())`** — no second rig, no second animation, and it hides with the body in first person.
|
||||||
|
- **`unreal.MaterialEditingLibrary` segfaults in `-run=pythonscript`** when it clears and rebuilds a material graph. The tool ran its texture imports, then crashed the commandlet with no Python traceback. Graph editing needs an editor session, or a material created from scratch rather than edited in place.
|
||||||
|
|
||||||
|
## 2026-11-09 — Character blueprints and how to compare a render to one
|
||||||
|
|
||||||
|
- **A SDXL character sheet is reliable**: "full body front view, neutral pose, plain grey background, character sheet" gives a usable turnaround with fixed seeds. Four views (front/side/back/head) from one description is enough to evaluate against; the sheet and its JSON live in `Art/characters/blueprints/`.
|
||||||
|
- **Segmenting the character out of a review shot does not work by colour.** The sky band, the pale wall and the dark pavement are each larger and more saturated than the body, so every size-based rule picked a backdrop blob and the first report measured the whole frame. `Tools/character_match.py` now takes an explicit `--render-box`; the proper fix is a background plate rendered without the character.
|
||||||
|
- **The report has to separate the failures it measures.** Aspect, fill, coverage and two colour distances are reported apart on purpose: an absolute similarity number between a photoreal sheet and a low-detail body would mean nothing. The first real comparison (aspect Δ1.24, mean ΔE 31.9) was dominated by the model being **unclothed** while the blueprint wears a tee and jeans.
|
||||||
|
- **`SetMaterial(0, ...)` in the pawn constructor does not visibly land** on the body mesh: `M_Character_Skin` is built and assigned, yet the render stays neutral grey. Either the slot does not exist yet at that point or the assignment is dropped; `BeginPlay` is the next thing to try.
|
||||||
|
- **MPFB vertex groups are polluted by helper geometry.** The body mesh weights its helper cubes to groups named `Left`, `Right`, `HelperGeometry` and `JointCubes` at 1.0, so a "strongest group" lookup returns a helper for most vertices - the first clothing build selected **zero** faces. Filter helper names out before asking which bone a vertex belongs to.
|
||||||
|
- **Blender refuses to apply a modifier to a mesh with shape keys.** The body carries ten; a garment copy must clear them (`shape_key_remove(all=True)`) before solidify can be applied.
|
||||||
|
- **Edit-mode selection reverts to vertex selection.** Setting `polygon.select` in object mode and then entering edit mode re-derives selection from the vertices, which were all still selected - deleting "the unselected faces" wiped the entire garment. Filter with `bmesh.ops.delete(context="FACES")` instead.
|
||||||
|
- **`ConstructorHelpers::FObjectFinder` outside a constructor is a fatal error**, not a warning: the game dies in `Tick` with "FObjectFinders can't be used outside of constructors". Use `LoadObject<T>(nullptr, Path)` for runtime loads and keep `FObjectFinder` to the constructor.
|
||||||
|
- **A 1 cm solidify shell lets the body poke through.** Garments built by duplicating body faces needed 2 cm of solidify plus an 8 mm fatten along their own normals before the cloth read as continuous at shoulders, hips and knees.
|
||||||
|
- **Warm skin sampling as blue-grey was lighting, not material.** The body stood in building shadow and was lit almost entirely by the blue `Demo_SkyLight` (intensity 2.6): face (99, 98, 100), arms (67, 74, 81). Raising the sun to pitch -74 moved the face to (131, 121, 117). A deliberate red test colour reaching the render first proved the material and the slot assignment were fine, which is what stopped a long hunt in the wrong place.
|
||||||
|
- **The Unreal commandlet drops unknown command-line switches.** A `--unlit-test` flag passed after `-script` never reached Python; an environment variable does get through (and even that proved unnecessary once the red test settled the question).
|
||||||
Loaded 100 of 131 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user