Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d7ceae474 | ||
|
|
319096d0d9 | ||
|
|
84291a5270 | ||
|
|
63ba9d91ce | ||
|
|
d8cbb0c95b | ||
|
|
dd36d399ad | ||
|
|
911a7059ae | ||
|
|
ecdd8208db | ||
|
|
a2f8ddccf1 | ||
|
|
3d4e830660 | ||
|
|
c3aacabd46 | ||
|
|
dc7b7652af | ||
|
|
8e9914ae6e | ||
|
|
927e9376a8 | ||
|
|
5bb7843b94 | ||
|
|
2cd3774aad | ||
|
|
b1c01e3af8 |
No files matched your search
+3
-1
@@ -56,7 +56,9 @@ Thumbs.db
|
||||
/Docs/data-inventory.md
|
||||
/Docs/environment.md
|
||||
/Docs/reference-notes-hollywood.md
|
||||
/.agents/skills/blender-skills/
|
||||
|
||||
# leave skills out of repo
|
||||
/.agents/skills/
|
||||
|
||||
# Blender/Blosm working caches
|
||||
/Art/blender/.cache/
|
||||
|
||||
@@ -55,7 +55,7 @@ Semantic Versioning: `MAJOR.MINOR.PATCH`.
|
||||
|
||||
The two files have separate roles:
|
||||
|
||||
- **[TODO.md](TODO.md)** is the single, central task list for all agents: checkbox items, progress marks, and active-section ownership. Put all planned actionable work and follow-ups there. Do not create parallel task lists elsewhere.
|
||||
- **[TODO.md](TODO.md)** is the single, central task list for all agents: checkbox items, progress marks, and active-section ownership. Put all planned actionable work and follow-ups there. Do not create parallel task lists elsewhere. Any notes or observations should be recorded in [MEMORY.md](#memory).
|
||||
- **[ROADMAP.md](ROADMAP.md)** holds the development plan: milestone goals, scope, decisions, and release gates for each version. It is a planning document and contains **no checkbox items**. Read it for future direction; record scope changes there as prose, and record the resulting tasks in `TODO.md`.
|
||||
|
||||
Rules for `TODO.md`:
|
||||
@@ -181,25 +181,58 @@ Always use a consistent scale test, named export presets, and repeatable import
|
||||
|
||||
The 93 upstream Blender skill packs (MIT, blender-skills 1.3.0) live once, in the **common** agent folder `~/.pi/agent/skills/`, together with the shared `references/` notes and the upstream `LICENSE`. They are agent instructions, not add-ons, and nothing in the pipeline depends on them.
|
||||
|
||||
Only the project-specific pack stays in the repository: `.agents/skills/blender-skills/blender-director/` — the pipeline orchestrator, which names this project's tools and stages. It refers to the shared notes by absolute path (`~/.pi/agent/skills/references/…`).
|
||||
The project-specific pack, `.agents/skills/blender-skills/blender-director/` — the pipeline orchestrator, which names this project's tools and stages — also stays **out of the repository**: `/.agents/skills/` is gitignored. It refers to the shared notes by absolute path (`~/.pi/agent/skills/references/…`).
|
||||
|
||||
Do not copy the common packs into the repository: a second copy under a different root is a name collision, and Pi keeps the first one it finds and warns on every start. Add a pack to `~/.pi/agent/skills/<pack>/SKILL.md` (one level down, flat) and it is picked up everywhere; `references/` sits beside them at `~/.pi/agent/skills/references/`.
|
||||
|
||||
**All agents must consult the relevant skill packs before starting a task in that discipline.** Each patch section in `TODO.md` lists its suggested skills; the general mapping is:
|
||||
**Entry point:** `blender-director` — the orchestrator picks the specialist packs in pipeline order and matches a reference photograph before detail work. Start any multi-step Blender task there.
|
||||
|
||||
- **Modeling / procedural assets** — `blender-modeler`, `procedural-modeling`, `geometry-nodes`, `hard-surface`.
|
||||
- **Façades and architecture** — `archviz`, `historical-worlds` (1920s Hollywood Blvd direction), `realistic-style`.
|
||||
**Pack index.** Every pack available to this project, grouped by discipline. The 93 common packs plus the project's `blender-director`; `unity-export` and `godot-export` are installed for other projects and unused here, since the project ships Unreal only.
|
||||
|
||||
| Group | Packs |
|
||||
| --- | --- |
|
||||
| Pipeline entry point | `blender-director` (project pack) |
|
||||
| Core modelling | `blender-modeler`, `hard-surface`, `prop-artist`, `vehicle-artist`, `character-artist`, `creature-artist`, `sculpting`, `retopology`, `geometry-nodes`, `procedural-modeling` |
|
||||
| Environment and architecture | `environment-artist`, `archviz`, `set-dressing`, `vegetation-artist`, `scene-assembly`, `collision-proxy` |
|
||||
| Characters and animation | `character-archetypes`, `rigging`, `animation`, `cloth-sim`, `hair-groom` |
|
||||
| Materials, light and render | `materials`, `texture-workflow`, `lookdev`, `lighting`, `rendering`, `compositing`, `camera-cinematography` |
|
||||
| UVs, export and optimisation | `uv-workflow`, `export-pipeline`, `unreal-export`, `asset-optimization`, `lod-pipeline` |
|
||||
| Physics and VFX | `physics-sim`, `vfx-fx` |
|
||||
| Review and QA | `qa-review`, plus the `references/` notes below |
|
||||
| Project direction | `realistic-style`, `historical-worlds` |
|
||||
| World packs | `apocalypse-worlds`, `biome-worlds`, `fantasy-worlds`, `sci-fi-punk-worlds` |
|
||||
| Genre art direction | `genre-action-combat`, `genre-card-party-idle`, `genre-metroidvania-roguelike`, `genre-narrative-vn`, `genre-open-world-sandbox`, `genre-puzzle-platformer`, `genre-racing-sports`, `genre-rpg`, `genre-shooter`, `genre-soulslike`, `genre-stealth`, `genre-strategy-sim`, `genre-survival` |
|
||||
| Mood art direction | `brutalist-mood`, `cozy-wholesome-mood`, `dark-gritty-mood`, `dream-surreal-mood`, `neon-retrofuturism`, `noir-style` |
|
||||
| Style art direction | `analog-found-footage-horror`, `anime-style`, `body-horror-style`, `cartoon-style`, `chibi-style`, `comic-book-style`, `cosmic-eldritch-horror`, `dream-weirdcore-style`, `folk-horror-style`, `frutiger-aero-style`, `hand-painted-style`, `hd-2d-style`, `horror-style`, `indie-horror-aesthetics`, `isometric-style`, `liminal-space-style`, `lowpoly-style`, `manga-style`, `mascot-puppet-horror`, `minimalist-style`, `painterly-style`, `pixel-art-style`, `psx-horror-style`, `retro-16bit-style`, `retro-8bit-style`, `stop-motion-craft-style`, `stylized-style`, `vector-style`, `visual-console-eras`, `voxel-style` |
|
||||
| Other engines (unused) | `unity-export`, `godot-export` |
|
||||
|
||||
**All agents must consult the relevant skill packs before starting a task in that discipline.** Each task section in `TODO.md` names its suggested skills; the general mapping is:
|
||||
|
||||
- **Modeling / procedural assets** — `blender-modeler`, `procedural-modeling`, `geometry-nodes`, `hard-surface`, `prop-artist`, `sculpting`, `retopology`.
|
||||
- **Vehicles** — `vehicle-artist` with `hard-surface`, `collision-proxy`, `lod-pipeline` (the `0.3.0` car).
|
||||
- **Façades and architecture** — `archviz`, `historical-worlds` (1920s Hollywood Blvd direction), `realistic-style`, `environment-artist`.
|
||||
- **Characters and animation** — `character-artist`, `rigging`, `animation`, `cloth-sim`, `hair-groom` (the MPFB `AGigLineNPCPerson` prefab; `character-archetypes` for crowd variety).
|
||||
- **Materials and textures** — `materials`, `texture-workflow`, `lookdev`.
|
||||
- **UVs and export** — `uv-workflow`, `export-pipeline`, `unreal-export` (verify against the `ue_metric_v1` / `ue_character_v1` presets in `Tools/`).
|
||||
- **UVs and export** — `uv-workflow`, `export-pipeline`, `unreal-export` (verify against the `ue_metric_v1` / `ue_character_v1` presets in `Tools/`), `asset-optimization`, `lod-pipeline`.
|
||||
- **Collision** — `collision-proxy` (UCX/UHX naming, hulls, simple colliders).
|
||||
- **Scene assembly / optimization** — `scene-assembly`, `asset-optimization`, `lod-pipeline`.
|
||||
- **Lighting and rendering** — `lighting`, `rendering`, `lookdev`, `compositing`, `camera-cinematography`.
|
||||
- **Blender-side physics and VFX** — `physics-sim`, `vfx-fx`.
|
||||
- **Review and QA** — `qa-review`, `references` (reference-image-match and visual-match checklists), `camera-cinematography` (matching reference camera angles).
|
||||
|
||||
**Reference notes.** `~/.pi/agent/skills/references/` sits beside the packs and holds the checklists they cite:
|
||||
|
||||
- `asset-pipeline.md` — the stage order every asset follows.
|
||||
- `reference-image-match.md` and `reference-analysis-template.md` — match the camera and write the analysis before micro-detail when a photograph drives the asset.
|
||||
- `visual-match-checklist.md` and `validation-checklist.md` — the two gates before an asset is called done.
|
||||
- `naming-conventions.md` and `polycount-budgets.md` — names and budgets to hold to.
|
||||
- `mcp-integration.md` and `mcp-tools.md` — the packs' MCP tooling, **not used here** (see the caveat below).
|
||||
|
||||
**Caveat:** the skill packs assume MCP-driven interactive Blender. This project runs Blender headless via scripts in `Tools/` — use the skills as method guides (checklists, naming conventions, proportions, validation steps), not for their MCP tooling. The headless scripts remain the execution path.
|
||||
|
||||
## Unreal Engine skills
|
||||
|
||||
The 31 Unreal Engine skill packs (MIT, [quodsoler/unreal-engine-skills](https://github.com/quodsoler/unreal-engine-skills) `f3742d7`) live in the repository at `.agents/skills/ue-*/` and target **UE 5.8 C++**. Each carries API examples verified against the 5.8 headers plus a deprecated-API table that maps old calls to the 5.8 forms. They are agent instructions, not add-ons.
|
||||
The 31 Unreal Engine skill packs (MIT, [quodsoler/unreal-engine-skills](https://github.com/quodsoler/unreal-engine-skills) `f3742d7`) live locally at `.agents/skills/ue-*/` (gitignored, not committed) and target **UE 5.8 C++**. Each carries API examples verified against the 5.8 headers plus a deprecated-API table that maps old calls to the 5.8 forms. They are agent instructions, not add-ons.
|
||||
|
||||
`.agents/skills/ue-project-context/` is the entry point: it writes `.agents/ue-project-context.md` (module names, conventions, enabled plugins), and every other UE skill reads that file first. Refresh it when the project changes — new module, new plugin, engine bump — before consulting the rest.
|
||||
|
||||
@@ -224,6 +257,24 @@ The general mapping to this project's work:
|
||||
|
||||
One router skill, `cpp` (MIT, [DiamonDinoia/skill-cpp](https://github.com/DiamonDinoia/skill-cpp) `f2617fb`), lives at `.agents/skills/cpp/` and covers general C++11 through C++23 against the C++ Core Guidelines. It detects the project language standard from the build system (**C++20** for this project, from the UBT compile flags), then loads the matching standard reference plus the topic references the task touches. Load it for any C++ work that is not Unreal-specific; the UE skills supersede it for engine API questions.
|
||||
|
||||
## Skill roots and provenance
|
||||
|
||||
Every skill pack is agent instruction, never game code or an add-on, and none is committed: `/.agents/skills/` is gitignored. The runtime discovers packs from these roots:
|
||||
|
||||
| Root | Packs | Source |
|
||||
| --- | --- | --- |
|
||||
| `~/.pi/agent/skills/<pack>/SKILL.md` | 93 Blender packs, flat, one level down | [blender-skills](https://github.com/arjun988/blender-skills) 1.3.0, MIT |
|
||||
| `~/.pi/agent/skills/references/` | shared notes beside the packs, not a pack | same |
|
||||
| `.agents/skills/blender-skills/blender-director/` | 1 project orchestrator | project-specific |
|
||||
| `.agents/skills/ue-*/` | 31 Unreal Engine 5.8 C++ packs | [quodsoler/unreal-engine-skills](https://github.com/quodsoler/unreal-engine-skills) `f3742d7`, MIT |
|
||||
| `.agents/skills/cpp/` | 1 C++ router | [DiamonDinoia/skill-cpp](https://github.com/DiamonDinoia/skill-cpp) `f2617fb`, MIT |
|
||||
| `~/.agents/skills/` | user-level packs shared with other projects | local |
|
||||
|
||||
- `.agents/skills/skills.manifest.json` records every installed source, commit, target and local patch, with `LICENSE-unreal-engine-skills.txt` and `LICENSE-skill-cpp.txt` beside it. Keep the manifest current when a pack is installed, upgraded or removed.
|
||||
- The upstream Blender clone sits at `~/.agents/skills/blender-skills/` (`git remote https://github.com/arjun988/blender-skills.git`, 94 packs under `.claude/skills/`). `~/.pi/agent/skills/` is the installed copy the runtime actually reads and holds 93 of them; `blender-director` is kept project-local because it names this project's tools and stages.
|
||||
- Because the packs are gitignored, a fresh checkout has none of them: every task must stay doable from the scripts in `Tools/`, `Geo/tools/` and `Art/blender/`, with the packs only guiding method.
|
||||
- User-level packs: `de-ai-writing` strips AI-writing tells from `Docs/`, `CHANGELOG.md` and `MEMORY.md` prose; `hf-cli` (Hugging Face Hub CLI) and `reverse-engineer-anything` (REA for shipped binaries and packaged builds) are not part of this pipeline and are loaded only when the task is actually about them.
|
||||
|
||||
## Licensing & Distribution
|
||||
|
||||
- Publish project-owned code under a chosen open-source license.
|
||||
|
||||
+11
-3
@@ -34,12 +34,20 @@ Unreal Engine 5.9 is in preview and Unreal Engine 6 has been announced as of Nov
|
||||
| Package | blender-skills `1.3.0`, upstream commit `8f778d2`, MIT |
|
||||
| Source | <https://github.com/arjun988/blender-skills> |
|
||||
| Location | Local only, common root: `~/.pi/agent/skills/` (93 packs, `references/`, `LICENSE`) — not distributed with the repository |
|
||||
| Project-specific | `.agents/skills/blender-skills/blender-director/` in this repository |
|
||||
| Project-specific | `.agents/skills/blender-skills/blender-director/` (gitignored, not committed) |
|
||||
| Installed | 2026-11-06, copied from a local checkout of `.claude/skills/` without edits; flattened to the common root on 2026-11-10 |
|
||||
| UE skills | `quodsoler/unreal-engine-skills` `f3742d7`, MIT — 31 packs at `.agents/skills/ue-*/` (gitignored), target UE 5.8 C++ |
|
||||
| C++ skill | `DiamonDinoia/skill-cpp` `f2617fb`, MIT — `cpp` router at `.agents/skills/cpp/` (gitignored) |
|
||||
|
||||
These are instructions and reference notes for agents, not code that runs in a build. Four of them assume a **Blender MCP server**; this project drives Blender headlessly through `bpy` (`Tools/*.py`) instead, so MCP is optional and nothing depends on it.
|
||||
All skill packs — Blender, Unreal and C++ — are agent instructions, not code that runs in a build, and **none are committed**: `/.agents/skills/` is gitignored and the Blender packs live in the common `~/.pi/agent/skills/` root. Four of the Blender packs assume a **Blender MCP server**; this project drives Blender headlessly through `bpy` (`Tools/*.py`) instead, so MCP is optional and nothing depends on it.
|
||||
|
||||
To update: copy `.claude/skills/` from a newer upstream checkout over the local directory, then record the new version and commit hash in this table. Skills are read on demand, so the harness catalog may need a reload before they appear as first-class entries.
|
||||
To update a pack:
|
||||
|
||||
- **Blender** — copy `.claude/skills/` from a newer `arjun988/blender-skills` checkout over `~/.pi/agent/skills/`, then record the version and commit in the table above.
|
||||
- **Unreal Engine** — re-install the `quodsoler/unreal-engine-skills` git source at the new commit (`pi install <source-url>@<commit>`); the 31 packs land under `.agents/skills/ue-*/`. Record the commit in `skills.manifest.json` and the table.
|
||||
- **C++** — re-install the `DiamonDinoia/skill-cpp` git source at the new commit; the router lands at `.agents/skills/cpp/`. Record the commit in `skills.manifest.json` and the table.
|
||||
|
||||
`skills.manifest.json` (in `.agents/skills/`) is the install record — the git sources, commits and layout — and is the thing to keep in step with the table. Skills are read on demand, so the harness catalog may need a reload before they appear as first-class entries.
|
||||
|
||||
## Pin rules
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -318,7 +318,8 @@ void AGigLineVehicle::Tick(float DeltaSeconds)
|
||||
{
|
||||
if (DriveElapsed > 1.0f && DriveElapsed < 4.0f)
|
||||
{
|
||||
Movement->SetThrottleInput(1.0f);
|
||||
const float Throttle = FParse::Param(FCommandLine::Get(), TEXT("reversetest")) ? -1.0f : 1.0f;
|
||||
Movement->SetThrottleInput(Throttle);
|
||||
}
|
||||
else if (DriveElapsed >= 4.0f && !bDriveReported)
|
||||
{
|
||||
@@ -444,19 +445,22 @@ void AGigLineVehicle::Tick(float DeltaSeconds)
|
||||
// If the Chaos vehicle never came into existence (no wheels), its movement component has no
|
||||
// valid state to drive, and continuing to tick it writes whatever is in that empty state back
|
||||
// onto the actor - which is the only explanation that fits a still body and a moving actor.
|
||||
// Only stop in that case: with real wheels the mover must keep ticking to drive, and stopping
|
||||
// it at 2.5 s was what killed the drive test (throttle runs 1-4 s).
|
||||
if (!bMoverStopped && SettleElapsed > 2.5f)
|
||||
{
|
||||
bMoverStopped = true;
|
||||
if (const UChaosWheeledVehicleMovementComponent* WheelComp =
|
||||
Cast<UChaosWheeledVehicleMovementComponent>(GetVehicleMovementComponent()))
|
||||
const UChaosWheeledVehicleMovementComponent* WheelComp =
|
||||
Cast<UChaosWheeledVehicleMovementComponent>(GetVehicleMovementComponent());
|
||||
if (!WheelComp || WheelComp->Wheels.Num() == 0)
|
||||
{
|
||||
bMoverStopped = true;
|
||||
UE_LOG(LogTemp, Warning,
|
||||
TEXT("GigLine vehicle: stopping the mover - wheels=%d at Z %.0f"),
|
||||
WheelComp->Wheels.Num(), GetActorLocation().Z);
|
||||
}
|
||||
if (UMovementComponent* Mover = GetVehicleMovementComponent())
|
||||
{
|
||||
Mover->SetComponentTickEnabled(false);
|
||||
WheelComp ? WheelComp->Wheels.Num() : -1, GetActorLocation().Z);
|
||||
if (UMovementComponent* Mover = GetVehicleMovementComponent())
|
||||
{
|
||||
Mover->SetComponentTickEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,13 +45,25 @@ UPhysicsAsset* UGigLineVehicleBuilder::BuildPhysicsAsset(USkeletalMesh* Mesh,
|
||||
USkeletalBodySetup* Body = NewObject<USkeletalBodySetup>(PhysicsAsset, ChassisBone,
|
||||
RF_Public | RF_Transactional);
|
||||
Body->BoneName = ChassisBone;
|
||||
// The body must simulate, not follow the mesh component's bSimulatePhysics. The
|
||||
// base AWheeledVehiclePawn sets that to false, so PhysType_Default leaves the chassis
|
||||
// unsimulated and the vehicle sits frozen at spawn; PhysType_Simulated makes the Chaos
|
||||
// async solver drive it.
|
||||
Body->PhysicsType = EPhysicsType::PhysType_Simulated;
|
||||
// A box slightly under the mesh bounds: a body that is exactly the bounds sits on the
|
||||
// ground plane and reads as floating because the mesh has wheels below the sills.
|
||||
FKBoxElem Box;
|
||||
// The skeletal mesh imports ROLLED (height along Y, width along Z) while the armature is
|
||||
// unrolled (height Z, width Y). The box is a body on the root bone, so it must be built in
|
||||
// the UNROLLED frame: length from mesh X, width from mesh Z, height from mesh Y.
|
||||
Box.X = Extent.X * 1.9f;
|
||||
Box.Y = Extent.Y * 1.8f;
|
||||
Box.Z = Extent.Z * 0.55f;
|
||||
Box.Center = FVector(0.f, 0.f, -Extent.Z * 0.25f);
|
||||
Box.Y = Extent.Z * 1.8f;
|
||||
Box.Z = Extent.Y * 0.55f;
|
||||
// The chassis sits at the car's centre of mass, which is the rolled mesh's mid-height
|
||||
// (Bounds.Origin.Y) mapped onto the unrolled height axis (Z). The suspension rest position is
|
||||
// measured against the root bone (the grounded origin), so the box must sit at the centre of
|
||||
// mass rather than the root bone - this is what settled the vehicle on the road.
|
||||
Box.Center = FVector(0.f, 0.f, Bounds.Origin.Y);
|
||||
Body->AggGeom.BoxElems.Add(Box);
|
||||
PhysicsAsset->SkeletalBodySetups.Add(Body);
|
||||
|
||||
|
||||
@@ -6,6 +6,15 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
|
||||
|
||||
---
|
||||
|
||||
## Toolchain — end-to-end asset check (done 2026-11-10)
|
||||
|
||||
Related tasks: [TODO.md](TODO.md#toolchain--end-to-end-asset-check-done-2026-11-10).
|
||||
|
||||
- `python3 Tools/review/e2e_asset_check.py` renders the Blender source, empties and rebuilds both maps, captures, judges with two models, and regenerates `Screenshots/README.md`. Last full run: all six steps passed (empty maps 9 s, demo build 10 s, review build 9 s, captures 80 s, judge 388 s).
|
||||
- `Tools/unreal/ue_recreate_maps.py` deliberately does not delete the `.umap`: `new_level()` refuses a path whose package is still in memory. Prefix-only clears let experimental actors survive into saved maps.
|
||||
- The judge reported 0/11 on owner-approved shots and has been wrong in both directions. See also the 2026-11-10 two-model judge entry below.
|
||||
- Until its expectations are re-derived, treat a model disagreement as the signal and the verdict as noise.
|
||||
|
||||
## 2026-11-08 — Documentation toolchain installed; asciidoc→markdown conversion
|
||||
|
||||
- **pandoc 3.13** installed at `~/.local/bin/pandoc` from the [GitHub release tarball](https://github.com/jgm/pandoc/releases/tag/3.13) (statically linked, 161 MB). The Fedora repos carry only `ghc-pandoc` (Haskell library), not the CLI.
|
||||
@@ -714,63 +723,462 @@ Next: the bisect plan step 2 — spawn a **stock `AWheeledVehiclePawn`** with `S
|
||||
- **The Chaos vehicle never existed because `ChaosVehiclesPlugin` was not enabled.** It is `EnabledByDefault: false`, it was absent from `GigLine.uproject` (only `EnhancedInput`), and absent from `Config/*.ini`. Consequence, read straight from the engine source (`ChaosVehicleMovementComponent.cpp` `OnCreatePhysicsState`): `CreateVehicle()` runs only when `World->IsGameWorld() && GetPhysicsScene() && FChaosVehicleManager::GetVehicleManagerFromScene(PhysScene)` — and the manager is null when the plugin module never started. So the vehicle stayed at `setups=4 wheels=0 output=null`, and the mesh fell back to **regular** rigid-body physics (the constructor's `SetSimulatePhysics(true)`), which is what teleported upward.
|
||||
- **Fix:** add `{"Name": "ChaosVehiclesPlugin", "Enabled": true}` to `GigLine.uproject`. Result: `CreateVehicle() entered → wheels 4`, `first UpdateState - output present`, `diag ... setups=4 wheels=4`.
|
||||
- **Also removed** `SetSimulatePhysics(true)` / `SetCollisionProfileName("PhysicsActor")` / `SetCollisionEnabled` from the `AGigLineVehicle` constructor: the Chaos movement component drives the mesh through its async physics, and forcing the regular rigid-body solver is what kept the body out of the vehicle solver.
|
||||
- **The plugin fix converts "teleport up" into "float at Z 545".** With 4 wheels the pawn now settles at a stable Z 545 (540 cm above the ReviewMap floor at Z 5) in *both* the control and terrain probe maps — no teleport, but the suspension holds the chassis ~5 m too high. That is the next bug: wheel radius / suspension rest-length / chassis-box offset, not the vehicle creation.
|
||||
- **The plugin fix converts "teleport up" into "float at Z 545".** With 4 wheels the pawn now settles at a stable Z 545 (540 cm above the ReviewMap floor at Z 5) in _both_ the control and terrain probe maps — no teleport, but the suspension holds the chassis ~5 m too high. That is the next bug: wheel radius / suspension rest-length / chassis-box offset, not the vehicle creation.
|
||||
|
||||
## 2026-11-10 — Vehicle: the physics-asset recreation fixed the float; box-centre +32.4 is wrong
|
||||
|
||||
After the plugin enablement gave 4 wheels, the pawn still floated at Z 545 in both probe
|
||||
maps. Two more findings close that:
|
||||
After the plugin enablement gave 4 wheels, the pawn still floated at Z 545 in both probe maps. Two more findings close that:
|
||||
|
||||
- **Regenerating the physics asset fixed the float.** The committed `PA_Vehicle_Sedan`
|
||||
was a stale body (the prior "one chassis body" commit `5dd53ac` changed the builder but
|
||||
the asset was never re-generated). Rebuilding it through the current
|
||||
`UGigLineVehicleBuilder::build_physics_asset` (one `root` body, box 452x205x40 at
|
||||
centre 0,0,−18) and assigning it back to `SK_Vehicle_Sedan.physics_asset` made the pawn
|
||||
**fall and settle at Z 78** (≈ wheel radius + suspension above the floor) in both the
|
||||
control and terrain probe maps. `create_vehicle` → `wheels 4`, `output present`.
|
||||
- **Regenerating the physics asset fixed the float.** The committed `PA_Vehicle_Sedan` was a stale body (the prior "one chassis body" commit `5dd53ac` changed the builder but the asset was never re-generated). Rebuilding it through the current `UGigLineVehicleBuilder::build_physics_asset` (one `root` body, box 452x205x40 at centre 0,0,−18) and assigning it back to `SK_Vehicle_Sedan.physics_asset` made the pawn **fall and settle at Z 78** (≈ wheel radius + suspension above the floor) in both the control and terrain probe maps. `create_vehicle` → `wheels 4`, `output present`.
|
||||
|
||||
- **Box centre +32.4 is wrong.** I tried moving the chassis box from −18 to
|
||||
`+Extent.Z*0.45` (=+32.4) on the theory that −18 put the body below the ground. It made
|
||||
it **worse**: the box then overlaps the wheel bones (at +33), the Chaos vehicle fails to
|
||||
set up, and the pawn reports `simulating=0` and stays at spawn (Z 300). Reverted. The
|
||||
working value is the original **−18**, because `GetWheelRestingPosition`/`LocateBoneOffset`
|
||||
measures wheel position against the root **bone** (origin), not the box, so the box
|
||||
centre is effectively ignored by the suspension — and +32.4 breaks the wheel/chassis
|
||||
separation. Do not "correct" the box centre without re-testing settle.
|
||||
- **Box centre +32.4 is wrong.** I tried moving the chassis box from −18 to `+Extent.Z*0.45` (=+32.4) on the theory that −18 put the body below the ground. It made it **worse**: the box then overlaps the wheel bones (at +33), the Chaos vehicle fails to set up, and the pawn reports `simulating=0` and stays at spawn (Z 300). Reverted. The working value is the original **−18**, because `GetWheelRestingPosition`/`LocateBoneOffset` measures wheel position against the root **bone** (origin), not the box, so the box centre is effectively ignored by the suspension — and +32.4 breaks the wheel/chassis separation. Do not "correct" the box centre without re-testing settle.
|
||||
|
||||
- **The regeneration is not wired into any script.** `build_physics_asset` is a
|
||||
`UBlueprintFunctionLibrary` (callable from Python as
|
||||
`unreal.GigLineVehicleBuilder.build_physics_asset(mesh, dest, "PA_Vehicle_Sedan")`), but
|
||||
`ue_vehicle_import.py` imports the skeletal mesh and never creates/assigns the physics
|
||||
asset. Next step is to add that call to the import (delete + rebuild + assign + save) so
|
||||
the asset is reproducible, then re-run the `-drivetest` (driving is still untested).
|
||||
- **The regeneration is not wired into any script.** `build_physics_asset` is a `UBlueprintFunctionLibrary` (callable from Python as `unreal.GigLineVehicleBuilder.build_physics_asset(mesh, dest, "PA_Vehicle_Sedan")`), but `ue_vehicle_import.py` imports the skeletal mesh and never creates/assigns the physics asset. Next step is to add that call to the import (delete + rebuild + assign + save) so the asset is reproducible, then re-run the `-drivetest` (driving is still untested).
|
||||
|
||||
- **Editor vs game target:** `build_physics_asset` is `#if WITH_EDITOR`, so it lives in the
|
||||
**editor** module (`libUnrealEditor-GigLine.so`). `Build.sh GigLine Linux Development`
|
||||
does **not** recompile it — you must build `GigLineEditor`. The box-centre change I made
|
||||
looked like a no-op because the editor module was stale; the `.so` mtime is the tell.
|
||||
- **Editor vs game target:** `build_physics_asset` is `#if WITH_EDITOR`, so it lives in the **editor** module (`libUnrealEditor-GigLine.so`). `Build.sh GigLine Linux Development` does **not** recompile it — you must build `GigLineEditor`. The box-centre change I made looked like a no-op because the editor module was stale; the `.so` mtime is the tell.
|
||||
|
||||
Uncommitted when pausing: `PA_Vehicle_Sedan.uasset` is currently at the **+32.4** box
|
||||
(wrong — regenerate at −18), `SK_Vehicle_Sedan.uasset` points at it, and the four
|
||||
`VehicleProbe*.umap` churn should be reverted. `GigLineVehicleBuilder.cpp` is reverted/clean.
|
||||
Uncommitted when pausing: `PA_Vehicle_Sedan.uasset` is currently at the **+32.4** box (wrong — regenerate at −18), `SK_Vehicle_Sedan.uasset` points at it, and the four `VehicleProbe*.umap` churn should be reverted. `GigLineVehicleBuilder.cpp` is reverted/clean.
|
||||
|
||||
## 2026-11-10 — Vehicle float: the delete+recreate physics asset is fragile
|
||||
|
||||
The "settle at Z 78" result was transient. Recreating `PA_Vehicle_Sedan` again (the
|
||||
second/third delete + rebuild + assign cycle) flipped the pawn to **`simulating=0` and
|
||||
stuck at spawn Z 300**, with the same box centre −18 that had settled minutes earlier.
|
||||
So the box centre is not what flips settle↔stuck; the **delete + recreate cycle is** —
|
||||
the same failure shape as the material `delete_asset` bug earlier: `delete_asset` does not
|
||||
fully release the package, `NewObject`/`CreatePackage` then reuses stale state, and the
|
||||
rebuilt body ends up not simulating (`PhysicsType` left at default, not `Simulated`).
|
||||
The "settle at Z 78" result was transient. Recreating `PA_Vehicle_Sedan` again (the second/third delete + rebuild + assign cycle) flipped the pawn to **`simulating=0` and stuck at spawn Z 300**, with the same box centre −18 that had settled minutes earlier. So the box centre is not what flips settle↔stuck; the **delete + recreate cycle is** — the same failure shape as the material `delete_asset` bug earlier: `delete_asset` does not fully release the package, `NewObject`/`CreatePackage` then reuses stale state, and the rebuilt body ends up not simulating (`PhysicsType` left at default, not `Simulated`).
|
||||
|
||||
- The plugin enablement (`5e328f5`) remains the solid fix: `CreateVehicle` → 4 wheels.
|
||||
- The physics asset still needs a **proper** fix, not delete+recreate: set
|
||||
`BodySetup->PhysicsType = EPhysicsType::PhysType_Simulated` (and any Chaos-required
|
||||
flags) in `GigLineVehicleBuilder::BuildPhysicsAsset`, and update the asset **in place**
|
||||
rather than deleting it.
|
||||
- `ue_vehicle_import.py` was wired to regenerate the asset, but its `SOURCE` is stale
|
||||
(`/tmp/gigline/hatch` — the static template) while the skeletal FBX lives in
|
||||
`/tmp/gigline/vehicle`; the wired block is therefore never reached. Fix the source path
|
||||
(or move the FBX) before relying on the import to rebuild the physics asset.
|
||||
- The physics asset still needs a **proper** fix, not delete+recreate: set `BodySetup->PhysicsType = EPhysicsType::PhysType_Simulated` (and any Chaos-required flags) in `GigLineVehicleBuilder::BuildPhysicsAsset`, and update the asset **in place** rather than deleting it.
|
||||
- `ue_vehicle_import.py` was wired to regenerate the asset, but its `SOURCE` is stale (`/tmp/gigline/hatch` — the static template) while the skeletal FBX lives in `/tmp/gigline/vehicle`; the wired block is therefore never reached. Fix the source path (or move the FBX) before relying on the import to rebuild the physics asset.
|
||||
- Driving (`-drivetest`) is still untested; settling worked only in the transient run.
|
||||
|
||||
## 2026-11-10 — All skills are gitignored now (owner)
|
||||
|
||||
The owner added `/.agents/skills/` to `.gitignore`, so **no skill pack is committed** — the UE packs (`ue-*/`), the `cpp` router and the project's `blender-director` are all local-only. This supersedes the earlier "blender-director stays in the repository" wording. `AGENTS.md` and `Docs/toolchain.md` are reconciled to say "gitignored, not committed" and to name the UE and C++ sources (`quodsoler/unreal-engine-skills` `f3742d7`, `DiamonDinoia/skill-cpp` `f2617fb`). The Blender packs remain in the common `~/.pi/agent/skills/` root, also outside the repo.
|
||||
|
||||
## 0.6.0 — Visual/audio pass and evening lighting preset (weeks 22–24)
|
||||
|
||||
Related tasks: [TODO.md](TODO.md#060--visualaudio-pass-and-evening-lighting-preset-weeks-2224).
|
||||
|
||||
### Look and sound
|
||||
|
||||
- 2026-11-09: the strip between kerb and building line is terrain mesh and reads as bare ground under the shopfronts; see `Screenshots/README.md`.
|
||||
|
||||
## 0.9.0 — Realistic buildings (weeks 31–34)
|
||||
|
||||
Related tasks: [TODO.md](TODO.md#090--realistic-buildings-weeks-3134).
|
||||
|
||||
- Suggested Blender skills: **archviz** (real-world scale, façade composition, elevation read), **references** (reference-image-match and visual-match checklists — every building is judged against its own photograph), **procedural-modeling** + **geometry-nodes** (the façades system: bay module, cornice and coping profiles, storefront types as reusable node groups), **materials** (measured albedo and roughness per surface type), **texture-workflow** (baked AO/curvature/normal, decals for painted lettering and signs), **uv-workflow** (texel density and modular UVs), **lod-pipeline** (LOD chain per building), **collision-proxy** (UCX proxies so detail geometry is not the collider), **lookdev** (screenshot comparison loop), **camera-cinematography** (reproducing each building's reference angle), **asset-optimization** + **unreal-export** (pre-export validation, `ue_metric_v1`), **qa-review** (the gate before every review shot).
|
||||
|
||||
### Façade system
|
||||
|
||||
- 2026-11-09: `0.1.6` showed that geometry-plate lettering cannot resolve at review distance.
|
||||
|
||||
### Per-building models
|
||||
|
||||
- 2026-11-10: volute capital and lattice grille depth were deferred from the hero rework. The motto band, medallions, inscription, and banner lettering were completed in that pass.
|
||||
- 2026-11-09: the entrance requirements were carried over from `Screenshots/README.md`.
|
||||
- 2026-11-09: the 6800/6806–6820 trim needs more than geometry: a 0.10 m lettering plate vanishes at 40 m, and the parapet and portrait panels need better representations.
|
||||
- 2026-11-09: no photograph of 6822 Budget was available; its single-storey 6.0 × 5.5 m volume cannot be read from the neighbour shot. The only 6800/6806–6820 reference is an oblique with a bus across Lee Drug's shopfront and a car across 6806's.
|
||||
|
||||
### Materials, UVs and performance
|
||||
|
||||
- 2026-11-10: the hero stone renders pale; it uses a procedural graph and generated texture set rather than a measured surface.
|
||||
|
||||
## Vehicle physics — Chaos rig (own task, from `0.3.0`)
|
||||
|
||||
Historical notes from 2026-11-09/10; later findings elsewhere in this file may supersede these observations and suggestions. Related tasks: [TODO.md](TODO.md#vehicle-physics--chaos-rig-own-task-from-030).
|
||||
|
||||
### Tasks
|
||||
|
||||
- The chassis box, mass, and gravity measured correct, so the proposed chassis-shape task was deferred.
|
||||
- The map comparison found the same `kill_z` and gravity, no World Partition, and no physics volume in either map. The suggested next probe was a fresh map with staged content added group by group; `Tools/unreal/ue_vehicle_probe_map.py` already supports one mesh at a time.
|
||||
|
||||
Split out of `0.3.0` because it is a physics problem, not an asset one. **The Chaos pawn is parked**: with it staged the actor is launched to **Z 1993 cm** (18 m up) and reports **0 wheels**, so it litters the demo without being drivable. The visible car is the plain panel set, which looks right and is what the review shots show.
|
||||
|
||||
### Evidence
|
||||
|
||||
```
|
||||
GigLine vehicle geometry: box 452x205x40 centre 0,0,-18 | bounds origin ...,1393 extent 1,1,1
|
||||
GigLine vehicle motion: velocity -24,-182,-28 cm/s angular ~0 mass 1500 sim=1
|
||||
GigLine vehicle mesh asset: SK_Vehicle_Sedan
|
||||
GigLine vehicle diag: mesh=1 simulating=1 physasset=yes setups=4 wheels=0
|
||||
```
|
||||
|
||||
**The physics body is nearly still while the actor is nine metres above its spawn.** That is not a force problem, it is a transform problem: something writes the actor transform that is neither the body's velocity nor our code. The mesh component's bounds read `extent 1,1,1` - a degenerate 1 cm box - which is the same symptom seen from the other side.
|
||||
|
||||
### Ruled out (each tested in its own run)
|
||||
|
||||
- **Spawn penetration** - spawned level with the road, 1.2 m above it, 3 m above it and 5 m above it; the actor climbs in every case.
|
||||
- **Visual panel collision** - disabling it changed nothing.
|
||||
- **Wrong chassis box** - `452x205x40 centre 0,0,-18` is the car.
|
||||
- **Gravity** - `global_gravity_z = 0.0` and the character falls normally, so the world pulls down.
|
||||
- **Mass** - the 1500 kg override applies (logged), and the body still does not rise.
|
||||
|
||||
### Measured after the transform finding
|
||||
|
||||
```
|
||||
bones: 6 [VehicleRig root Wheel_FL Wheel_FR Wheel_RL Wheel_RR]
|
||||
setups: [Wheel_FL Wheel_FR Wheel_RL Wheel_RR] -> names match, suspect 3 is gone
|
||||
mover: updated=VehicleMesh ticking=1
|
||||
stop: mover tick disabled at 2.5 s (Z 2001); at 3.0 s the actor is still at Z 2258
|
||||
terrain: CTF_USE_COMPLEX_AS_SIMPLE, 0 box elements -> no invisible lid to be ejected from
|
||||
```
|
||||
|
||||
So the wheel bones match, the terrain has no proxy lid, and stopping the mover did not stop the climb - meaning either the stop does not take effect (something else re-enables or writes the transform every frame) or a second writer exists.
|
||||
|
||||
### Suspects, in order
|
||||
|
||||
1. **Component hierarchy**: a pawn whose root is the mesh while the physics state belongs to another component behaves exactly like this - correct box, correct mass, near-zero velocity, moving actor, 1 cm bounds.
|
||||
2. **`UChaosWheeledVehicleMovementComponent::UpdateState` / `OnUpdateTransform`** writing the transform from a vehicle state that was never created (which is also why `CreateVehicle()` yields no wheels).
|
||||
3. **The skeletal mesh asset itself** - `SK_Vehicle_Sedan` came from our own FBX pipeline; if its bone layout does not match what the wheel setups name, `CreateVehicle()` bails before creating wheels.
|
||||
|
||||
### Bisect result: the map, not the pawn
|
||||
|
||||
The same pawn, unchanged, in two maps:
|
||||
|
||||
```
|
||||
ReviewMap (flat 160 m floor): Z 200 -> 188 -> 174 -> 157 -> 87 vZ -208 -> -663 falls correctly
|
||||
DemoMap, on the street: Z 486 -> 920 -> 1126 -> 1385 vZ oscillates ~0 teleports up
|
||||
DemoMap, 60 m off the street: Z 291 -> 467 -> 920 -> 1343 vZ +1595 launches
|
||||
DemoMap, 25 m above ground: Z 2496 -> 2664 -> 3068 -> 3434 vZ +1509 launches at once
|
||||
```
|
||||
|
||||
**So the Chaos rig is fine and `DemoMap` is what breaks it** - at any height, anywhere in that map, and not by contact (25 m in the air launches immediately). The next comparison is between the two maps' world settings and level actors: `ReviewMap` was built by `Tools/unreal/ue_build_review_map.py`, `DemoMap` by `Tools/unreal/ue_build_demo_map.py`, and the difference that matters is in what one stages and the other does not.
|
||||
|
||||
### The transform is written directly, not moved
|
||||
|
||||
Zeroing the body's linear and angular velocity **every frame** for two seconds changes nothing:
|
||||
|
||||
```
|
||||
t=0.25 Z=3220 vZ=0 t=0.75 Z=3683 vZ=0 t=1.25 Z=4197 vZ=0
|
||||
```
|
||||
|
||||
The actor keeps rising at ~1200 cm/s while its velocity is pinned to zero. **Something calls a transform setter on the vehicle every frame** - this is not a force, an impulse or a bad initial state, all of which the clamp would have removed.
|
||||
|
||||
`Game/GigLine/Source/GigLine/*.cpp` contains exactly one direct transform write outside the character (`GigLineShotActor` moves its camera), so the writer is engine-side: the Chaos vehicle component driving `UpdatedComponent` from a vehicle state that was never created, or a skeletal-mesh transform update.
|
||||
|
||||
### Map-content bisect: the terrain is involved
|
||||
|
||||
Built `Tools/unreal/ue_vehicle_probe_map.py` (duplicates `ReviewMap`, optionally adds ONE demo mesh plus the pawn, saves as `VehicleProbe`) and ran it:
|
||||
|
||||
```
|
||||
control (no extra mesh) Z 300 -> 297 over 1.25 s (resting on the review floor)
|
||||
+ Terrain_DEM Z 300 -> 498 over 1.25 s (thrown upward)
|
||||
```
|
||||
|
||||
So `Terrain_DEM` is enough to reproduce it in a map that otherwise works. Two fixes were then tried and **neither helped**:
|
||||
|
||||
- `Tools/unreal/ue_clear_simple_collision.py` cleared the auto-generated convex hulls from all 76 site/street meshes (terrain had **1 convex element** over the whole 279 m) and set complex-as-simple - still rises.
|
||||
- Setting the terrain component to `CollisionEnabled::QueryOnly` so traces still hit it but the physics solver ignores it - still rises.
|
||||
|
||||
That means the ejection is not the convex hull and not the collision channel; the next probe is whether it is the _complex_ triangles (inverted winding pushing bodies out) - test by setting `Terrain_DEM` to **no collision at all** in the probe map and confirming the pawn falls.
|
||||
|
||||
### Where it stands: deferred to its own session
|
||||
|
||||
Three fixes were tried after the terrain bisect and **none changed the behaviour**:
|
||||
|
||||
1. clearing the auto-generated convex hulls from all 76 site/street meshes;
|
||||
2. setting the terrain component to `QueryOnly`;
|
||||
3. staging the terrain in the probe map with **collision disabled entirely**.
|
||||
|
||||
The pawn still rises in every case, so the fault is not the terrain's convex hull, not its collision channel, and not even its collision. The player is unaffected throughout - re-checked after each fix, and the character still stands on `Sidewalk_North_07` at 157 cm.
|
||||
|
||||
Deferred rather than guessed. What a fresh session needs:
|
||||
|
||||
- the probe workflow is in place: `Tools/unreal/ue_vehicle_probe_map.py` with `GIGLINE_PROBE_ASSET` and `GIGLINE_PROBE_NOCOLLISION`, `Tools/unreal/ue_clear_simple_collision.py` for the hull hygiene;
|
||||
- the vehicle diagnostics are switches: `-probes`, `-profile`, `-novehmover`, plus the always-on velocity clamp, overlap list and velocity profile;
|
||||
- the next question is _why a map carrying a 279 m static mesh changes a physics body's behaviour when that mesh has no collision at all_, which points at streaming, memory layout or the physics scene's broadphase rather than at collision authoring.
|
||||
|
||||
### Bisect plan (kept for the next session)
|
||||
|
||||
1. Spawn the same pawn in `ReviewMap` (flat 160 m floor, no slices, no UCX ground) and repeat the profile. Flies there too -> the asset/component is at fault. Settles there -> something in `DemoMap` (the UCS slabs, the terrain, or the world settings) drives it.
|
||||
2. In parallel, spawn a stock `AWheeledVehiclePawn` with `SK_Vehicle_Sedan` and default settings in the same spot: if the stock pawn settles, the difference is in `AGigLineVehicle`'s setup (mass override, stability control, our movement subclass).
|
||||
3. Only after the bisect: fix, then confirm `Wheels.Num() == 4`.
|
||||
|
||||
### Where it was left (2026-11-10)
|
||||
|
||||
Two facts that matter more than the hypothesis list above: on `ReviewMap`'s flat floor the **same pawn falls correctly** (Z 200 -> 87, vZ -663), and in `DemoMap` it teleports upward at any height - including 25 m in the air - with the transform written directly (clamping velocity to zero every frame does not stop it) while the body reports a near-zero velocity. Disabling the movement component's tick changes nothing; there are zero overlaps; the body is genuinely dynamic (`profile=PhysicsActor`); `shadow`-style collision explanations were ruled out by staging the terrain with **collision disabled entirely** and seeing the pawn still rise.
|
||||
|
||||
**Fresh probe data (2026-11-10) — the writer is the physics sim, not an external setter.** Control (no terrain) falls and settles (300 → 78 cm); terrain rises (313 → 545 cm) with body velocity pinned to 0 and `overlaps: 0`. **Turning physics OFF at 3 s stops the rise** (`physics OFF at Z 545` → `after 1 s without physics, Z 545`), so the Chaos vehicle solver is teleporting the body (transform set directly, no velocity). Both runs have `setups=4 wheels=0`, so the missing wheels are not the trigger — control falls with 0 wheels too; the terrain is what flips fall → rise. The asset pipeline is ruled out (FBX, `SK_Vehicle_Sedan` import 476×228×144 cm, physics asset 452×205×40, `Terrain_DEM` 380×210 m all correct; the `1 cm` bounds is a stale component read). Next: bisect step 2 — a stock `AWheeledVehiclePawn` beside `AGigLineVehicle` in the terrain probe map.
|
||||
|
||||
## Hero facade rework — from the ReviewMap review (own task)
|
||||
|
||||
Historical notes from 2026-11-09/10; later findings elsewhere in this file may supersede these observations and suggestions. Related tasks: [TODO.md](TODO.md#hero-facade-rework--from-the-reviewmap-review-own-task).
|
||||
|
||||
Split out of `0.3.0` on 2026-11-09 on the owner's instruction: the three hero-facade staging shots (`review-hero-facade-front`, `review-hero-facade-oblique`, `review-hero-portals`) were rejected as "rework needed to match reference", and that is asset work rather than vehicle work.
|
||||
|
||||
### Reference vs current
|
||||
|
||||
Reference: `security-game/docs/screens/refs/hollywood/el-capitan-facade-day-wikimedia-2009-10-27.jpg` (the north elevation, daylight). What it shows, and where the model stands:
|
||||
|
||||
| Feature | Reference | Model (`SM_HeroFacade`) |
|
||||
| --- | --- | --- |
|
||||
| Rusticated stone, warm limestone | heavy banded rustication over the whole wall | flat `M_Facade_Stone`, no banding |
|
||||
| Six giant Ionic columns | fluted shafts, volute capitals, moulded bases | present, fluted (20 flutes each), capital block only |
|
||||
| Three vertical JIMMY KIMMEL LIVE banners | red, gold lettering, hung between the middle columns | present as flat panels with `M_Facade_Banner` |
|
||||
| Inscription band | "FREEMASONRY BUILDS ITS TEMPLES..." carved lettering | `Hero_MottoBand` + surround + two medallions, no lettering |
|
||||
| Lattice windows between columns | dark metal grilles, two bands | `Hero_Window_1_*` / `_2_*` as plain panels |
|
||||
| Cornice, dentils, parapet | deep cornice, 52 dentils, balustrade with finials | cornice + 52 dentils + parapet, **no balustrade or finials** |
|
||||
| Three entry portals with steps | recessed, framed, three sets of steps | present: portals, jambs, lintels, mullions, five steps each |
|
||||
|
||||
So the model has the massing and most of the parts; what it lacks is **surface** (rustication, lettering, grille detail, balustrade) and material variation.
|
||||
|
||||
### Found: the detail is modelled inside the wall
|
||||
|
||||
Measured, not guessed:
|
||||
|
||||
```
|
||||
blend scene Y -12.50 .. +0.13 (front of the wall at -0.50)
|
||||
Hero_Mass y = -BODY_D/2 - 0.5 -> spans -12.50 .. -0.50
|
||||
Hero_Banner_1 y = -1.08 .. -1.02 -> 55 cm BEHIND the front face
|
||||
imported mesh bounds y = -13 .. +1250 cm
|
||||
```
|
||||
|
||||
The facade's detail - banners, and by the same construction the window bays and portal surrounds - is placed **behind** the wall's front face, so it is inside `Hero_Mass` and never renders. That is why the captures show a plain block with six columns: the columns, cornice and dentils happen to protrude, everything else does not. The model does not need remodelling first; its detail needs moving in front of the wall.
|
||||
|
||||
**Fix:** give `Tools/blender/hero_facade_build.py` a `FRONT_Y = -BODY_D / 2.0 - 0.5` constant (the mass's front face) and place every elevation detail relative to it (`FRONT_Y - 0.10` for applied detail, `FRONT_Y` for flush), instead of the hand-typed negative coordinates each call site uses now. Then re-export, re-import and re-render.
|
||||
|
||||
**And check the review shot.** The three hero captures show a plain block with six columns and nothing else - no banners, no windows, no portals - while the mesh demonstrably carries all four materials (`M_Facade_Banner`, `M_Facade_Stone`, `M_Facade_StoneShadow`, `M_Facade_GlassDark`) and is staged at `(-13336, 1307, 0)` with yaw 0. That suggests the cameras are framing the wrong actor (`Frontage_SM_ElCapitanTheatre` sits at the origin) or the wrong side, and the "rework" note may be describing a camera error rather than the model. Verify that first: one render with a camera aimed at `Hero_Facade` from Y -4000 at 8 m height.
|
||||
|
||||
### First pass against the folder references
|
||||
|
||||
`Art/reference/hero-masonic/` (15 images, ignored by git as reference photography) is now the source of truth rather than one Wikimedia photo. `02-masonictemple01.jpg` and `06-el-capitan-theater-frontal-fachade.jpg` are the front elevations used for the first pass; `05-hollywoodmasonictemple-1922.jpg` records the original masonry.
|
||||
|
||||
Done in this pass:
|
||||
|
||||
- the detail is seated on the wall (previous step), so banners, windows and portals render;
|
||||
- the blank `Hero_Stylobate_LintelBand` across the portals is gone - the reference has open doorways with steps, which now read correctly;
|
||||
- `rustication()` cuts five ashlar courses from the pavement to the portal heads;
|
||||
- `balustrade()` adds a bottom rail, a top rail and 38 balusters along the roof edge.
|
||||
|
||||
Still missing, from the same references: warm sandstone instead of the flat pale stone, lattice grilles in the window bays, the carved inscription on the motto band, and the two medallions are not reading at distance.
|
||||
|
||||
### The iteration loop
|
||||
|
||||
`bash Tools/review/hero_iterate.sh` runs the whole chain in one command: chmod the Blender files, build the facade, import it, render the capture set, judge the shot with the vision model, and write a comparison plate at `Screenshots/review/hero-compare-front.png`. Each step fails loudly and logs to `/tmp/gigline/`.
|
||||
|
||||
Iteration 1 verdict (judge, confidence 0.9):
|
||||
|
||||
> "The scene lacks the warm yellow-grey sandstone texture and the detailed fluted Ionic columns. Additionally, the red banners do not match the expected design, and there are no visible entrance doorways or decorative elements like the inscription band or medallions."
|
||||
|
||||
Measured against the plate, that splits into:
|
||||
|
||||
| Finding | Reality | Next action |
|
||||
| --- | --- | --- |
|
||||
| not warm sandstone | true - the UE material `M_FacadeStone` is still near-white (0.62-0.76 grey) | warm the UE material spec in `Tools/unreal/ue_hero_facade_import.py`, not the Blender one |
|
||||
| fluting missing | false - 120 flute strips exist and cast shadows; they are lost at 45 m | not a modelling problem; check the review camera distance |
|
||||
| banners wrong | partly - proportions are now 1.35 x 6.2 m and hang correctly, the lettering is absent | needs a texture, not geometry |
|
||||
| no doorways | false - three 1.2 m deep recesses with steps; they read dark because the reveal is glass-black | lighten the reveal material |
|
||||
| no inscription/medallions | true - the band and two medallions exist but are flat and untextured | texture the motto band |
|
||||
|
||||
### Tasks
|
||||
|
||||
Historical task context from 2026-11-09/10; later findings may supersede these observations. Owner review remains pending.
|
||||
|
||||
The following work was recorded as done on 2026-11-09/10, each verified by a capture:
|
||||
|
||||
- Confirm the hero captures frame `Hero_Facade` - cameras at (-13336, 1307), hero actor there, frontage at the origin.
|
||||
- Seat the elevation detail in front of the mass (`FRONT_Y` + `seat_on_front()`, 99 parts moved) - banners, windows and portals render now.
|
||||
- Remove the blank lintel band across the portals; the three doorways are 1.2 m deep with five steps each.
|
||||
- Window grilles (7 vertical + 5 horizontal bars per bay), five ashlar courses, roof balustrade with 38 balusters.
|
||||
- Warm sandstone in the engine - `tint_imported_materials()` authors one material per slot from the reference.
|
||||
- Banner proportions 1.35 x 6.2 m hung from 7.2 m, matching the photographs.
|
||||
|
||||
Additional completed work and review notes:
|
||||
|
||||
- **Make the generated ashlar albedo render.** Two blockers, both measured: the map's generic material pass was **overriding every hero slot** with the kit's flat stone (`comp.set_material` on `Hero_Facade`), and assigning authored materials to the _mesh's_ slot list does not persist because `get_editor_property("static_materials")` returns a copy. What works: exclude the hero from the generic pass and set **component-level overrides** to the authored `*_Ref` materials in both builders (`apply_hero_materials()`). The elevation now shows coursed, grainy limestone.
|
||||
- Carved inscription: `inscription()` in `Tools/blender/hero_facade_build.py` lays 112 letter marks in three lines across the attic, in the shadow stone so they read at 45 m. They had to be **seated on the parapet face** (`FRONT_Y + 0.40`): the parapet is 1.2 m deep and stands in front of the wall plane, so marks at the wall plane were hidden behind it.
|
||||
- Medallion relief that reads at 45 m. **Measured first, then fixed:** the discs were not just flat - they were **buried**. `seat_on_front` put `Hero_Medallion` at offset 0.08 → front face Y −0.42, while the parapet (1.2 m deep) stands at Y −0.15, so both medallions were inside the parapet and never rendered (the motto band and surround have the same bug at offset 0.06). Fixed: three stepped layers per medallion — `Hero_MedallionRim` (0.55 m, dark, offset 0.36 → Y −0.14), `Hero_MedallionField` (0.38 m, light, offset 0.43 → Y −0.07), `Hero_MedallionBoss` (0.15 m, dark, offset 0.50 → Y 0.00) — a 7 cm step each way so the rim/field/boss read as relief. Moved from ±6.2 (inside the inscription text, ±6.87) to ±9.8. Rendered and judged; the two-model judge is advisory-pessimistic (`no` 0.93) and does not flag medallions any more. Owner review pending.
|
||||
- Motto band and surround — same burial bug (offset 0.06 → front Y −0.44). The band is now **light stone** (was `stone_dark`, which would have swallowed the dark lettering once visible), recessed at offset 0.34 (Y −0.16), with the moulded surround proud at offset 0.43 (Y −0.07) and the inscription at 0.40 (Y −0.10); the dead duplicate `Hero_MottoBand 0.40` entry is removed. Rendered and judged; the judge (`no` 0.93) no longer flags the band, only the stone warmth/column fluting the owner has already directed lighter. Owner review pending.
|
||||
- Banner lettering (JIMMY KIMMEL LIVE). New `Tools/gen/generate_banner_texture.py` draws the exact title with Pillow + Liberation Sans Bold (an image model would mangle a trademark string, and Qwen-Image 2.1 is research-only). Gold `JIMMY` / `KIMMEL` / `LIVE` stacked on a crimson ground, 512 × 2304. `ue_hero_facade_import.py` generalised `import_albedo` → `import_texture` and wires the banner into `M_Facade_Banner_Ref` as Base Color (tiling 1.0). Owner review pending.
|
||||
- Crimson banner tone + lettering. The texture carries the red ground (186,24,24 — brighter than the old dark tint, per the owner's "less dark, more bright") and the gold lettering, so the flat tint is replaced outright.
|
||||
- Re-render the three ReviewMap staging shots and put them back in the status table for review. All three (`review-hero-facade-front`, `review-hero-facade-oblique`, `review-hero-portals`) regenerated 2026-11-10 with the full attic detail, and the `Screenshots/README.md` status table updated. Owner review pending.
|
||||
|
||||
**Deferred to `0.9.0` (owner, 2026-11-10).** This pass is closed at "surface detail that reads at 45 m". The remaining elevation work is a `0.9.0` pass, not another patch here: **volute capital depth** (the capitals are block-only, the reference has strong shadow lines), **lattice grille depth** (the window bays are plain panels with bars, not the dark metal lattice), and the full realism pass (heavy banded rustication, measured material library, texel-density and LOD/collision per building). The `0.9.0` hero item records the hand-off.
|
||||
|
||||
## 0.3.0 — Chaos vehicle, enter/exit, closed driving route (weeks 10–13)
|
||||
|
||||
### Vehicle production
|
||||
|
||||
Historical task context from 2026-11-09/10; later vehicle findings above may supersede these observations. Related tasks: [TODO.md](TODO.md#030--chaos-vehicle-enterexit-closed-driving-route-weeks-1013).
|
||||
|
||||
- Model one vehicle in Blender with collision meshes; export to Unreal. **Model done** (`Tools/blender/vehicle_build.py` → `Art/blender/vehicle/gigline-sedan.blend`, preview in `Art/blender/vehicle/preview.png`): 4.72 x 1.80 x 1.44 m, 2.78 m wheelbase, 1.62 m track, 0.33 m wheels, 252 triangles, four `UCX_` convex hulls. Dimensions come from real sedan figures and are logged to the report, so the vehicle config can read them rather than restating them. - **Imported into Unreal** (`Tools/unreal/ue_vehicle_import.py` → `/Game/Hollywood/Vehicle`), split per the vehicle-artist object split: `SM_Vehicle_Body` (3 slots, 4.76 x 1.76 x 1.28 m), `SM_Vehicle_Glass`, `SM_Vehicle_Light_Front`, `SM_Vehicle_Light_Rear`, `SM_Vehicle_Mirrors`, and one `SM_Vehicle_Wheel` (0.22 x 0.66 x 0.66 m) reused four times. The body FBX carries the `UCX_` hulls as simple collision.
|
||||
- **LODs cut for real:** decimating the duplicate gives LOD1 at 38 triangles and LOD2 at 14, against 132 for LOD0. The first attempt duplicated the shell unchanged and produced 84-triangle LODs that saved nothing - worth remembering as the failure mode of a "LOD" step nobody measures.
|
||||
- **LOD group wiring is open:** `StaticMeshEditorSubsystem.import_lod` is unavailable in a headless commandlet (`get_editor_subsystem` returns None), so the LOD meshes import as their own assets and the group still needs an editor session.
|
||||
- Set up Chaos Vehicle with wheel and physics configuration. **Deferred to its own task** (`Vehicle physics — Chaos rig`): the pawn is parked, the visible car is the panel set, and the remaining question is what writes the actor transform in `DemoMap` but not in `ReviewMap`.
|
||||
- **Fixed the floating:** the physics asset had a body per bone, and the four wheel bodies intersected the chassis body and pushed the car 3.7 m up. It is now a **single chassis body** on the `root` bone (452 x 205 x 40 cm), which is what Unreal's own vehicle template uses - Chaos simulates the wheels itself, they must not be separate rigid bodies. Settle check: **Z 119 cm** at a spawn of 120 (was 494).
|
||||
- **Drive test fails, with a specific cause:** `-drivetest` holds full throttle for 3 s and reports speed and distance. Result: `speed=0 cm/s, travelled 2.2 m -> FAIL`, and BeginPlay logs **`0 wheel objects after register`** while the component reports 4 wheel setups. The setups exist but no wheels are created from them, so there is nothing to apply torque to.
|
||||
- **Next:** confirm the skeleton's bone names match the setups (`Wheel_FL`, `Wheel_FR`, `Wheel_RL`, `Wheel_RR`) - a name mismatch silently produces zero wheels - and give the front and rear pairs their own `UChaosVehicleWheel` subclasses rather than the base class, which is what the setups point at today.
|
||||
- **Also open:** LOD group wiring, enter/exit, driving route, handling.
|
||||
- Fix the vehicle not rendering in the scene. **Chased to a strong clue, not yet solved.** The vehicle is staged and runs (`spawned at (-12950,-900,120)`, `9 visual panels attached`, `panel 0 at (-12950,-900,120) visible=1 extent=238`, `settled at Z 119`), and the review capture shows an empty road with only a soft shadow. `Tools/review/visual_loop.py --verify` passes the frame; `Tools/review/judge_screenshot.py` returns `verdict=no confidence=1.0`. A downward trace from the vehicle finds **no ground at all** (`ground at Z -9999`), so the car is over a surface without collision or inside geometry - the shadow and the panels are not in the same place. **Fixed 2026-11-09** — the car renders, built from the owner's `hot hatch.blend`. **Fixed:** the vehicle was spawned 2.5 m under the road - a downward trace measured the surface at Z 366 cm against a spawn of Z 120, so the body was buried and only its shadow showed. The spawn is now Z 470 and it settles there.
|
||||
- **Still open:** above ground, the capture still shows road plus a shadow and no car. Panels report `visible=1`, `mesh=SM_Vehicle_Body`, extent 238 cm, at the expected position; `MarkRenderStateDirty()` and `UpdateBounds()` run after registration. Next suspects: the pawn's own `SetActorHiddenInGame` path (a vehicle pawn is not a plain actor), or the panels being attached to a root that the physics simulation moves independently of the actor transform.
|
||||
- Fix the character and car floating. **The measurement was taken at the wrong moment.** The foot probe ran in `BeginPlay`, while the pawn is still at its spawn height and falling, so every trace reported a surface far below it (`ground 366` against an actor at 561) and read as a float that was not there. The probe now runs **three seconds after spawn**, from the capsule's feet, on three channels, and names the actor it hits. Character: the player start at Z 600 left the soles 39 cm in the air (the capsule settles at 561 on this pavement); the start is now the measured 561. Car: the template's lowest geometry is exhaust, so grounding on the whole model put the tyres 4 cm above the origin - it is now grounded on its wheels, staged at Z 342 after the traced road height (366) disagreed with the visible road by ~25 cm.
|
||||
- Fix the vehicle's material colour. The template's `car paint` is a legacy mix-shader graph that the FBX exporter cannot map, so `SM_Vehicle_Body` imported holding `WorldGridMaterial` and rendered black while Blender showed red. Legacy graphs are now converted to Principled before export (saturated colour wins over the grey diffuse next to it), and the staged body panel is assigned `M_Vehicle_HatchPaint`. Judge: `verdict=yes confidence=0.9`.
|
||||
- Fix the missing vehicle glazing. The `windows` material was metallic 1.0 with roughness 0.0 - a black void that read as an empty cabin. Now tinted translucent (opacity 0.85, roughness 0.08); opacity 0.45 was tried first and made the glass invisible. The vision judge still returns `verdict=no` for the frame, disagreeing with what is visibly tinted glass; it is advisory, and the human verdict decides.
|
||||
- Reconcile the road and sidewalk surfaces. **Solved: the road strip was a single 279 m mesh whose auto-generated convex hull sat 2 m above the visible surface.**
|
||||
- The site export now slices sidewalks and kerbs into ~20 m pieces with explicit `UCX_` boxes, and the slice cuts are fixed (the far cut kept the wrong half, so every slice spanned the whole street). Slice tops descend honestly along the street: 380, 340, 309, 284, 254, 231, 205, **169 at the player's X**, 138, 104 cm.
|
||||
- In the map at the same X: `Sidewalk_North_07` tops at **169 cm**, `Road_Hollywood` at **366 cm**. The road is ~2 m above its sidewalk, so the player lands on the road and no collision setting on the sidewalk can change that.
|
||||
- Neither slicing, `UCX_` hulls, `set_complex_collision_as_simple`, nor winding (all 2790 sidewalk faces point up) changed the outcome - each of those was the wrong layer.
|
||||
- **Cause found:** the road is built across -10.5..+10.5 m and the north sidewalk +10.5..+15.8 m, so on the straight they meet at the gutter - but the strips follow a curved centreline and the fan-out pushes the road's outer edge to **-13.8 m**, overlapping the sidewalk band (**-17.3..-11.8 m**). In that overlap the road sits at 366 cm and the sidewalk at 158 cm.
|
||||
- **Resolved 2026-11-10:** the real cause was the **collision hull**, not the geometry. A 279 m strip imports with one auto-generated convex hull whose top is the mesh's _maximum_ height, so every downward trace landed on a flat lid at 366 cm. Slicing the road like the sidewalks, and removing the single `Road_Hollywood` asset from the map so the slices win, gives a continuous cross-section: `Terrain 154 / Sidewalk_North_07 158 / Road_Hollywood_07 158`. The player now stands on `Sidewalk_North_07` at 158 cm with feet at 161.
|
||||
- **Superseded:** sample both source meshes at the player's XY in `Art/blender/street/street-2026-11-06.blend`. The road is built as `dem_z + 0.02` and the sidewalk as `dem_z + kerb * t` from the _same_ DEM sampler, so a 2 m gap cannot come from that code - the suspect is the DEM lookup or the mesh's coordinate frame, not the assembly logic.
|
||||
|
||||
## 2026-11-10 — Vehicle float/settle root cause: the body never simulated (PhysicsType)
|
||||
|
||||
- The physics asset body was missing `PhysicsType`. `UGigLineVehicleBuilder::BuildPhysicsAsset`
|
||||
created a `USkeletalBodySetup` with a box but never set `Body->PhysicsType`, so it stayed
|
||||
`PhysType_Default` ("follow owner"). The base `AWheeledVehiclePawn` sets
|
||||
`Mesh->BodyInstance.bSimulatePhysics = false` (the Chaos async solver drives it), so
|
||||
"follow owner" = do not simulate → the chassis never fell, the vehicle sat frozen at
|
||||
spawn (`simulating=0`), and the `-drivetest` reported `speed=0 travelled=0`.
|
||||
- **Fix:** `Body->PhysicsType = EPhysicsType::PhysType_Simulated;` (member on
|
||||
`UBodySetupCore`, `PhysicsCore/Public/BodySetupCore.h`; enum in `BodySetupEnums.h`).
|
||||
- **Result:** `simulating=1`, `settled at Z 77 cm`, and the drive test moved the car —
|
||||
`speed=-2680 cm/s (-96.5 km/h), travelled 36.3 m`. It **drives backwards**: the next bug
|
||||
is the reverse direction (rear wheels `bAffectedByEngine`, engine torque sign, or wheel
|
||||
bone orientation), not the physics setup.
|
||||
- Regenerate the asset with `rm` of the `.uasset` then `unreal.GigLineVehicleBuilder.
|
||||
build_physics_asset(skel, dest, "PA_Vehicle_Sedan")` from a commandlet; the
|
||||
delete+recreate fragility earlier was really this missing `PhysicsType`, not the box
|
||||
centre (both −18 and +32.4 had the same unsimulated body).
|
||||
|
||||
## 2026-11-10 — Vehicle drives backwards; the axle flip did not fix it
|
||||
|
||||
- The reverse direction is not the wheel-bone axle sign. Flipping the axles from inward to
|
||||
outward (`y - copysign` → `y + copysign` in `vehicle_build.py`) and re-importing changed
|
||||
nothing (`speed=-2788 cm/s` still, vs −2680 before). Reverted.
|
||||
- Remaining suspects, in order: the Chaos wheel's spin axis convention (maybe it spins about
|
||||
X, not the bone's Y), the engine torque sign, or the throttle sign. Next probe: run the
|
||||
`-drivetest` with `SetThrottleInput(-1.0)` to split "throttle inverted" from "wheel spin
|
||||
inverted" in one run.
|
||||
|
||||
## 2026-11-10 — Vehicle reverse: both throttle signs drive backward
|
||||
|
||||
- `-reversetest` (negative throttle) shows the throttle sign is not the cause: forward
|
||||
throttle → −224.9 km/h, reverse throttle → −99.5 km/h — **both backward**, so the wheel
|
||||
spin is inverted relative to the engine torque, not the input mapping.
|
||||
- The mesh orientation is fine (imported bounds 476 × 228 cm, nose at +X). The earlier axle
|
||||
flip (inward→outward) was a no-op, so the next check is the **imported wheel-bone
|
||||
reference pose**: the axle (bone Y) may be landing on Unreal X or Z, not Y, so the wheel
|
||||
spins about the wrong axis. `FPhysicsAssetUtils`-style body setup is not the cause either
|
||||
(physics already simulates).
|
||||
- Kept `-reversetest` in the drive test as the diagnostic for this.
|
||||
|
||||
## 2026-11-10 — Vehicle reverse: the skeleton is rolled 90° vs the mesh
|
||||
|
||||
- Bone reference poses (`AnimPose.get_ref_bone_pose`) show the armature is **rolled 90°**:
|
||||
`root` rot (roll 90, 0, 0), `Wheel_FL/RL` rot (90, 0, −180), `Wheel_FR/RR` rot (−90, 0, 0);
|
||||
wheel centres land at Z ±0.81 instead of Y ±0.81. The mesh vertices are unrotated (bounds
|
||||
476 × 228 × 144, nose +X), so the **FBX `axis_forward: -Y` conversion rotates the armature
|
||||
but not the geometry** — the wheel axle (bone Y) ends up on a rotated axis, which is why the
|
||||
engine torque drives the wheels backward and why the inward/outward axle flip was a no-op.
|
||||
- Fix belongs in the bone authoring, not the vehicle component: build the wheel bones so their
|
||||
Y axis lands on Unreal +Y after the FBX conversion, or set `use_space_transform`/bake the
|
||||
space transform so mesh and armature share one frame. Verify with the reference-pose probe
|
||||
(`get_ref_bone_pose` → the wheel axle should read ±Y, not ±Z) before re-running the drive
|
||||
test.
|
||||
|
||||
## 2026-11-10 — Vehicle reverse: FBX armature bakes a −90° roll the build can't undo
|
||||
|
||||
- Inspecting `vehicle_skeletal.fbx` (imported back into Blender) shows the export bakes a
|
||||
**−90° rotation about X** into the armature: `root` tail (0,1.64,0), wheel tails at
|
||||
(x, 0.33, ±0.83) — the wheel **axle lands on Z**, not Y. The mesh vertices are exported
|
||||
unrotated (bounds 476×228×144), so mesh and armature come back in different frames.
|
||||
- Three Blender-side fixes were tried and **none changed the imported wheel bones**:
|
||||
1. axle tail along −Z (head/tail) — reference pose unchanged;
|
||||
2. `axis_forward: "X"` on the `_CHARACTER` preset — unchanged;
|
||||
3. armature object pre-roll −90° about X — unrolled the `root` (roll 90→0) but left the
|
||||
wheel bones at roll ±90 / Z ±0.81.
|
||||
- Conclusion: the export bakes the armature conversion regardless of head/tail or the
|
||||
object transform. Remaining options (untried): `bake_space_transform: true` (also rolls
|
||||
the mesh — would need the mesh un-rolled again in Unreal), or a **post-import skeleton
|
||||
fix** — rotate each wheel bone's reference pose −90° about X in `ue_vehicle_import.py`
|
||||
(or a commandlet) so the axle lands on Y. Verify with the `get_ref_bone_pose` probe
|
||||
(axle should read ±Y) before re-running `-drivetest`.
|
||||
|
||||
## 2026-11-10 — Vehicle reverse: not the axle, not the gear ratio
|
||||
|
||||
- The reverse direction is **not** the axle orientation and **not** the final-drive ratio:
|
||||
flipping the wheel-bone axle to horizontal (Unreal ±Y, via `axis_forward: X` + a Blender-Z
|
||||
tail) changed the reference pose (FL roll 180, FR roll 0) but the `-drivetest` still reads
|
||||
`speed=-2737 cm/s`. `FinalRatio = -3.5` also changed nothing (it is a magnitude; the gear
|
||||
direction is `ForwardGearRatios` vs `ReverseGearRatios`).
|
||||
- So the wheels spin backward for positive torque regardless of axle direction, which points
|
||||
at the **wheel spin axis convention in the Chaos solver** (the `FSimpleWheel` angular
|
||||
velocity axis) or the engine torque sign, not the bone authoring. The horizontal-axle fix
|
||||
is kept (it is correct even if insufficient): `axis_forward: X` in the character preset and
|
||||
a Blender-Z wheel tail (`r - copysign(0.25, y)`) so the axle lands on Unreal Y.
|
||||
|
||||
## 2026-11-10 — Vehicle reverse is in the Chaos solver, not the assets (revert the axis fix)
|
||||
|
||||
- Reverted the `axis_forward: X` / Blender-Z axle fix (`ecdd820`): the reference pose changed
|
||||
but `-drivetest` still read −2737 cm/s, and `axis_forward: X` lives in the shared character
|
||||
preset, so it would also alter every character export.
|
||||
- Traced the Chaos solver: the wheel's local frame is `GroundXVector = CrossProduct(VehicleRightAxis,
|
||||
GroundZVector)` and the drive force is `FinalLongitudinalForce = DriveTorque / Re`, applied forward —
|
||||
yet the car moves backward. The wheel spins about the **vehicle's** Y, not the bone's, so the bone
|
||||
authoring is irrelevant. The inversion is in the **engine-torque / drive-force sign** (`FSimpleWheel`),
|
||||
not the assets. Next: check `GetEngineTorque()` sign, or override `SetupVehicle`/`ProcessMechanicalSimulation`
|
||||
in `UGigLineVehicleMovement` to negate the drive torque.
|
||||
|
||||
## 2026-11-10 — Vehicle: the visual body and the physics body are different vehicles
|
||||
|
||||
- `SM_Vehicle_Body` (the owner's **hot hatch** template, what the panels render) imports with
|
||||
bounds **150 x 400 x 122 cm** — its length is along **Y**. `SK_Vehicle_Sedan` (the procedural
|
||||
sedan that the Chaos solver drives) is **476 x 228 x 144** with the length along **X**. They
|
||||
are built by different scripts (`vehicle_from_template.py` vs `vehicle_build.py`) and do not
|
||||
even share a forward axis.
|
||||
- This is almost certainly the real story behind the "reverse" drive: the solver applies the
|
||||
drive force forward on the skeletal sedan (+X) while the visible car's nose is on a different
|
||||
axis, so a forward physics motion reads as a sideways/backward car. `MaxTorque = -500` was also
|
||||
a no-op (the torque curve normalises the magnitude), ruling out the engine torque sign.
|
||||
- Next step is not more solver tracing: reconcile the two meshes — drive the **hot hatch** (make
|
||||
it skeletal, or rebuild `SK_Vehicle_Sedan` to match the hot hatch's axis and dimensions) so the
|
||||
visible and physics cars are one asset with one forward axis.
|
||||
|
||||
## 2026-11-10 — Hot hatch is now the skeletal (physics) mesh; vehicle still floats
|
||||
|
||||
- `vehicle_from_template.py` now builds a skeletal mesh from the hot hatch (the real car):
|
||||
it applies the MIRROR/NODES modifiers, derives the four wheel centres by quadrant, adds a
|
||||
root + `Wheel_FL/FR/RL/RR` armature, weights wheels-to-bones, and exports
|
||||
`vehicle_skeletal.fbx`. A rotation matrix [[0,1,0],[0,0,-1],[-1,0,0]] orients the car so the
|
||||
FBX conversion lands it unrolled (length +X, width +Y, height +Z). Wheel bones import
|
||||
correctly: FL (1.43,-0.71,0.34) FR (1.43,0.62,0.34) RL (-1.22,-0.75,0.36) RR (-1.22,0.67,0.36).
|
||||
- **Still floats** (rises 300 -> 3315 cm, `simulating=1`, 4 wheels): the same suspension push
|
||||
as before, so the reverse/float is NOT the visual/physics axis mismatch after all. Remaining
|
||||
suspect is the chassis box centre (`-Extent.Z*0.25` = ~-20) vs the wheel-bone height (0.34):
|
||||
the suspension rest length is measured against the root BONE, not the box, so the box offset
|
||||
may be the push. Next: try `Box.Center = (0,0,+Extent.Z*0.3)` or drive the box from the
|
||||
wheel-bone height, then re-run `-drivetest`.
|
||||
|
||||
## 2026-11-10 — Box centre: raising it cuts the float, but it persists
|
||||
|
||||
- `Box.Center = Extent.Z*0.35` (was `-Extent.Z*0.25`) cuts the float from ~33 m to **7.82 m**
|
||||
(`settled at Z 782`, ground at Z 5). Direction confirmed: the chassis box belongs above the
|
||||
ground, not below the root bone. Still not settled, so the box centre needs to go higher toward
|
||||
the car's centre of mass (~0.7 m), and/or the root bone should sit at the centre of mass rather
|
||||
than at the ground (the `normalise_scale` grounds the whole car, so the root bone is at Z 0).
|
||||
- The hot-hatch skeletal mesh now imports (403 x 148 x 158 cm), but the mesh is rolled (height Y,
|
||||
width Z) while the armature is correct - the MESH vertices are exported raw (identity) while the
|
||||
ARMATURE is baked with the FBX conversion, the same mesh-vs-armature split as before.
|
||||
|
||||
## 2026-11-10 — Vehicle settles (float 33 m -> 6 cm); drive still ~0
|
||||
|
||||
- **Two real fixes** (commit `319096d`):
|
||||
1. The skeletal mesh imports ROLLED (height along Y, width along Z) while the armature is
|
||||
unrolled. The chassis box must therefore be built with swapped axes (`Box.Y = Extent.Z*1.8`,
|
||||
`Box.Z = Extent.Y*0.55`) and centred at the rolled mesh's mid-height (`Box.Center.Z =
|
||||
Bounds.Origin.Y`). This is what settled the car at Z 6 cm (was floating at 33 m).
|
||||
2. A diagnostic `if (SettleElapsed > 2.5f) Mover->SetComponentTickEnabled(false)` was firing for
|
||||
every vehicle (not just zero-wheel ones) and cut the drive test (throttle 1-4 s). Now gated
|
||||
on `Wheels.Num() == 0`.
|
||||
- **Experiment that FAILED and was reverted**: moving the root bone to the centre of mass in
|
||||
`vehicle_from_template.py` (root at `body.bound_box` centre) puts the wheel bones at NEGATIVE Z
|
||||
(-54 cm below root) and the vehicle then FLOATS at spawn height (stays Z 304, never falls).
|
||||
So the Chaos suspension in this rig wants a POSITIVE rest position (wheel bone above the root
|
||||
bone). Root bone stays grounded at (0,0,0); the box offset to centre of mass is done in C++.
|
||||
- **Remaining**: drive test reports `speed=1 cm/s` (no forward motion) and `travelled ~56 m` - a
|
||||
horizontal drift during the fall. Wheels rest ~2 cm above the ground (wheel bone Z +34 vs wheel
|
||||
radius 33) because the suspension rest position is measured against the root bone while the box
|
||||
sits at +76. Next: make the wheels actually contact (align wheel radius to the bone, or correct
|
||||
the rest position for the box offset) before re-testing direction.
|
||||
- `GetWheelRestingPosition` = `LocateBoneOffset(BoneName, WheelClass.Offset + AdditionalOffset)`,
|
||||
which is the wheel bone position relative to the ROOT BODY BONE - it does NOT account for
|
||||
`Box.Center`. That offset is the root of the drive gap.
|
||||
@@ -1,6 +1,6 @@
|
||||
# TODO.md
|
||||
|
||||
Actionable task list for this project. This file is the single, central place for planned work, claims, and progress marks. Milestone goals, scope, and release gates are in [ROADMAP.md](ROADMAP.md).
|
||||
Actionable task list for this project. Planned work and progress marks live here; supporting observations are in [MEMORY.md](MEMORY.md). Milestone goals, scope, and release gates are in [ROADMAP.md](ROADMAP.md).
|
||||
|
||||
## Conventions
|
||||
|
||||
@@ -8,257 +8,81 @@ Actionable task list for this project. This file is the single, central place fo
|
||||
- A level-three (`###`) section being worked on carries `- active - <sessionId>` (and optionally the branch name) in its heading. Remove the suffix when you stop.
|
||||
- When all steps in a version section are done: write a `CHANGELOG.md` entry, then remove the version section from this file.
|
||||
- Release gate: a version is tagged only on the owner's explicit go-ahead once its release checks pass, not automatically when the checkboxes close.
|
||||
- Every task section carries a `**Suggested skills:**` line naming the packs to load before starting. Load them with the `skill` tool — `blender-director` for asset pipelines, `ue-project-context` for engine work, then the packs named — and use them as method guides, since this project runs Blender and Unreal headless from scripts.
|
||||
|
||||
## Toolchain — end-to-end asset check (done 2026-11-10)
|
||||
|
||||
> [!IMPORTANT] Plan for 0.3.1 before other patches
|
||||
|
||||
The chain that answers "does the asset look right in both the staging map and the game map" is now one command: `python3 Tools/review/e2e_asset_check.py`. It renders the Blender source, empties both maps, runs both builders, captures, judges with two models, and regenerates `Screenshots/README.md`. Last full run: **PASS on all six steps** (empty maps 9 s, demo build 10 s, review build 9 s, captures 80 s, judge 388 s).
|
||||
See [toolchain notes](MEMORY.md#toolchain--end-to-end-asset-check-done-2026-11-10).
|
||||
|
||||
**Suggested skills:** no pack covers the local ComfyUI, judge or driver tooling — work from `Tools/README.md`, `Tools/review/` and the toolchain notes in `MEMORY.md`.
|
||||
|
||||
- [x] Group the sixty tools by sandbox: `Tools/unreal/`, `Tools/blender/`, `Tools/review/`, `Tools/gen/`, `Tools/input/`, with the layout documented in `Tools/README.md`.
|
||||
- [x] `Tools/unreal/ue_recreate_maps.py`: empties both maps and verifies 0 actors left. Deliberately does not delete the `.umap` - measured that `new_level()` refuses a path whose package is still in memory.
|
||||
- [x] Whole-level clear in both builders; a prefix-only clear let experiment actors survive into saved maps.
|
||||
- [x] Empty both maps and verify that no actors remain using `Tools/unreal/ue_recreate_maps.py`.
|
||||
- [x] Clear the whole level in both builders.
|
||||
- [x] Two-model judge with reconciliation, and a two-checkpoint texture generator that compares before choosing.
|
||||
- [x] Texture generation through the documented Stability Matrix workflows (`texture` subcommand).
|
||||
- [x] Local ComfyUI documented, including "start Stability Matrix first" and the per-model licence rules.
|
||||
- [x] Driver hardening: unlink result files before each step, stream child output to a per-step log, refuse to start while another editor is alive.
|
||||
- [ ] **Make the judge useful again.** It reports 0/11 on shots the owner has approved and has been wrong in both directions.
|
||||
- [ ] Until its expectations are re-derived, treat a _model disagreement_ as the signal and the verdict as noise.
|
||||
- [ ] **Make the judge useful again.**
|
||||
- [ ] Examine comfyui tool to extend judge coverage or improve its scoring mechanism.
|
||||
- [ ] Wire the e2e driver into the release checks once the judge can be trusted.
|
||||
|
||||
## Vehicle physics — Chaos rig (own task, from `0.3.0`)
|
||||
|
||||
Split out of `0.3.0` because it is a physics problem, not an asset one. **The Chaos pawn is parked**: with it staged the actor is launched to **Z 1993 cm** (18 m up) and reports **0 wheels**, so it litters the demo without being drivable. The visible car is the plain panel set, which looks right and is what the review shots show.
|
||||
> [!IMPORTANT] Plan for 0.3.1 before other patches
|
||||
|
||||
### Evidence
|
||||
|
||||
```
|
||||
GigLine vehicle geometry: box 452x205x40 centre 0,0,-18 | bounds origin ...,1393 extent 1,1,1
|
||||
GigLine vehicle motion: velocity -24,-182,-28 cm/s angular ~0 mass 1500 sim=1
|
||||
GigLine vehicle mesh asset: SK_Vehicle_Sedan
|
||||
GigLine vehicle diag: mesh=1 simulating=1 physasset=yes setups=4 wheels=0
|
||||
```
|
||||
|
||||
**The physics body is nearly still while the actor is nine metres above its spawn.** That is not a force problem, it is a transform problem: something writes the actor transform that is neither the body's velocity nor our code. The mesh component's bounds read `extent 1,1,1` - a degenerate 1 cm box - which is the same symptom seen from the other side.
|
||||
|
||||
### Ruled out (each tested in its own run)
|
||||
|
||||
- **Spawn penetration** - spawned level with the road, 1.2 m above it, 3 m above it and 5 m above it; the actor climbs in every case.
|
||||
- **Visual panel collision** - disabling it changed nothing.
|
||||
- **Wrong chassis box** - `452x205x40 centre 0,0,-18` is the car.
|
||||
- **Gravity** - `global_gravity_z = 0.0` and the character falls normally, so the world pulls down.
|
||||
- **Mass** - the 1500 kg override applies (logged), and the body still does not rise.
|
||||
|
||||
### Measured after the transform finding
|
||||
|
||||
```
|
||||
bones: 6 [VehicleRig root Wheel_FL Wheel_FR Wheel_RL Wheel_RR]
|
||||
setups: [Wheel_FL Wheel_FR Wheel_RL Wheel_RR] -> names match, suspect 3 is gone
|
||||
mover: updated=VehicleMesh ticking=1
|
||||
stop: mover tick disabled at 2.5 s (Z 2001); at 3.0 s the actor is still at Z 2258
|
||||
terrain: CTF_USE_COMPLEX_AS_SIMPLE, 0 box elements -> no invisible lid to be ejected from
|
||||
```
|
||||
|
||||
So the wheel bones match, the terrain has no proxy lid, and stopping the mover did not stop the climb - meaning either the stop does not take effect (something else re-enables or writes the transform every frame) or a second writer exists.
|
||||
|
||||
### Suspects, in order
|
||||
|
||||
1. **Component hierarchy**: a pawn whose root is the mesh while the physics state belongs to another component behaves exactly like this - correct box, correct mass, near-zero velocity, moving actor, 1 cm bounds.
|
||||
2. **`UChaosWheeledVehicleMovementComponent::UpdateState` / `OnUpdateTransform`** writing the transform from a vehicle state that was never created (which is also why `CreateVehicle()` yields no wheels).
|
||||
3. **The skeletal mesh asset itself** - `SK_Vehicle_Sedan` came from our own FBX pipeline; if its bone layout does not match what the wheel setups name, `CreateVehicle()` bails before creating wheels.
|
||||
|
||||
### Bisect result: the map, not the pawn
|
||||
|
||||
The same pawn, unchanged, in two maps:
|
||||
|
||||
```
|
||||
ReviewMap (flat 160 m floor): Z 200 -> 188 -> 174 -> 157 -> 87 vZ -208 -> -663 falls correctly
|
||||
DemoMap, on the street: Z 486 -> 920 -> 1126 -> 1385 vZ oscillates ~0 teleports up
|
||||
DemoMap, 60 m off the street: Z 291 -> 467 -> 920 -> 1343 vZ +1595 launches
|
||||
DemoMap, 25 m above ground: Z 2496 -> 2664 -> 3068 -> 3434 vZ +1509 launches at once
|
||||
```
|
||||
|
||||
**So the Chaos rig is fine and `DemoMap` is what breaks it** - at any height, anywhere in that map, and not by contact (25 m in the air launches immediately). The next comparison is between the two maps' world settings and level actors: `ReviewMap` was built by `Tools/unreal/ue_build_review_map.py`, `DemoMap` by `Tools/unreal/ue_build_demo_map.py`, and the difference that matters is in what one stages and the other does not.
|
||||
|
||||
### The transform is written directly, not moved
|
||||
|
||||
Zeroing the body's linear and angular velocity **every frame** for two seconds changes nothing:
|
||||
|
||||
```
|
||||
t=0.25 Z=3220 vZ=0 t=0.75 Z=3683 vZ=0 t=1.25 Z=4197 vZ=0
|
||||
```
|
||||
|
||||
The actor keeps rising at ~1200 cm/s while its velocity is pinned to zero. **Something calls a transform setter on the vehicle every frame** - this is not a force, an impulse or a bad initial state, all of which the clamp would have removed.
|
||||
|
||||
`Game/GigLine/Source/GigLine/*.cpp` contains exactly one direct transform write outside the character (`GigLineShotActor` moves its camera), so the writer is engine-side: the Chaos vehicle component driving `UpdatedComponent` from a vehicle state that was never created, or a skeletal-mesh transform update.
|
||||
|
||||
### Map-content bisect: the terrain is involved
|
||||
|
||||
Built `Tools/unreal/ue_vehicle_probe_map.py` (duplicates `ReviewMap`, optionally adds ONE demo mesh plus the pawn, saves as `VehicleProbe`) and ran it:
|
||||
|
||||
```
|
||||
control (no extra mesh) Z 300 -> 297 over 1.25 s (resting on the review floor)
|
||||
+ Terrain_DEM Z 300 -> 498 over 1.25 s (thrown upward)
|
||||
```
|
||||
|
||||
So `Terrain_DEM` is enough to reproduce it in a map that otherwise works. Two fixes were then tried and **neither helped**:
|
||||
|
||||
- `Tools/unreal/ue_clear_simple_collision.py` cleared the auto-generated convex hulls from all 76 site/street meshes (terrain had **1 convex element** over the whole 279 m) and set complex-as-simple - still rises.
|
||||
- Setting the terrain component to `CollisionEnabled::QueryOnly` so traces still hit it but the physics solver ignores it - still rises.
|
||||
|
||||
That means the ejection is not the convex hull and not the collision channel; the next probe is whether it is the _complex_ triangles (inverted winding pushing bodies out) - test by setting `Terrain_DEM` to **no collision at all** in the probe map and confirming the pawn falls.
|
||||
|
||||
### Where it stands: deferred to its own session
|
||||
|
||||
Three fixes were tried after the terrain bisect and **none changed the behaviour**:
|
||||
|
||||
1. clearing the auto-generated convex hulls from all 76 site/street meshes;
|
||||
2. setting the terrain component to `QueryOnly`;
|
||||
3. staging the terrain in the probe map with **collision disabled entirely**.
|
||||
|
||||
The pawn still rises in every case, so the fault is not the terrain's convex hull, not its collision channel, and not even its collision. The player is unaffected throughout - re-checked after each fix, and the character still stands on `Sidewalk_North_07` at 157 cm.
|
||||
|
||||
Deferred rather than guessed. What a fresh session needs:
|
||||
|
||||
- the probe workflow is in place: `Tools/unreal/ue_vehicle_probe_map.py` with `GIGLINE_PROBE_ASSET` and `GIGLINE_PROBE_NOCOLLISION`, `Tools/unreal/ue_clear_simple_collision.py` for the hull hygiene;
|
||||
- the vehicle diagnostics are switches: `-probes`, `-profile`, `-novehmover`, plus the always-on velocity clamp, overlap list and velocity profile;
|
||||
- the next question is _why a map carrying a 279 m static mesh changes a physics body's behaviour when that mesh has no collision at all_, which points at streaming, memory layout or the physics scene's broadphase rather than at collision authoring.
|
||||
|
||||
### Bisect plan (kept for the next session)
|
||||
|
||||
1. Spawn the same pawn in `ReviewMap` (flat 160 m floor, no slices, no UCX ground) and repeat the profile. Flies there too -> the asset/component is at fault. Settles there -> something in `DemoMap` (the UCS slabs, the terrain, or the world settings) drives it.
|
||||
2. In parallel, spawn a stock `AWheeledVehiclePawn` with `SK_Vehicle_Sedan` and default settings in the same spot: if the stock pawn settles, the difference is in `AGigLineVehicle`'s setup (mass override, stability control, our movement subclass).
|
||||
3. Only after the bisect: fix, then confirm `Wheels.Num() == 4`.
|
||||
|
||||
### Where it was left (2026-11-10)
|
||||
|
||||
Two facts that matter more than the hypothesis list above: on `ReviewMap`'s flat floor the **same pawn falls correctly** (Z 200 -> 87, vZ -663), and in `DemoMap` it teleports upward at any height - including 25 m in the air - with the transform written directly (clamping velocity to zero every frame does not stop it) while the body reports a near-zero velocity. Disabling the movement component's tick changes nothing; there are zero overlaps; the body is genuinely dynamic (`profile=PhysicsActor`); `shadow`-style collision explanations were ruled out by staging the terrain with **collision disabled entirely** and seeing the pawn still rise.
|
||||
|
||||
**Fresh probe data (2026-11-10) — the writer is the physics sim, not an external setter.** Control (no terrain) falls and settles (300 → 78 cm); terrain rises (313 → 545 cm) with body velocity pinned to 0 and `overlaps: 0`. **Turning physics OFF at 3 s stops the rise** (`physics OFF at Z 545` → `after 1 s without physics, Z 545`), so the Chaos vehicle solver is teleporting the body (transform set directly, no velocity). Both runs have `setups=4 wheels=0`, so the missing wheels are not the trigger — control falls with 0 wheels too; the terrain is what flips fall → rise. The asset pipeline is ruled out (FBX, `SK_Vehicle_Sedan` import 476×228×144 cm, physics asset 452×205×40, `Terrain_DEM` 380×210 m all correct; the `1 cm` bounds is a stale component read). Next: bisect step 2 — a stock `AWheeledVehiclePawn` beside `AGigLineVehicle` in the terrain probe map.
|
||||
See [historical notes](MEMORY.md#vehicle-physics--chaos-rig-own-task-from-030).
|
||||
|
||||
### Tasks
|
||||
|
||||
- [D] Give `SK_Vehicle_Sedan` a physics asset whose single chassis body matches the car's box and is positioned below the shell, then confirm the pawn settles on the road instead of launching. **Deferred**: the chassis box, mass and gravity all measure correct, so this is not the shape of the body.
|
||||
- [ ] Find what writes the actor transform in `DemoMap` but not in `ReviewMap`. The map diff is empty where it matters - same `kill_z`, same gravity, no World Partition, neither has a physics volume - so the next probe is a fresh map with staged content added group by group (`Tools/unreal/ue_vehicle_probe_map.py` already does the one-mesh version).
|
||||
- [ ] Confirm `Wheels.Num()` becomes 4 once the vehicle is created; log it at the settle check.
|
||||
- [ ] Only then re-stage the pawn, re-add enter/exit and the driving route.
|
||||
**Suggested skills:** `ue-physics-collision`, `ue-module-build-system` (plugin enablement), `ue-actor-component-architecture`, `ue-gameplay-framework`, `ue-input-system`, `ue-testing-debugging`, `cpp`; Blender side `vehicle-artist`, `collision-proxy`, `export-pipeline`, `unreal-export`. No pack covers Chaos Vehicles — the notes in this section and the `ChaosVehiclesPlugin` headers are the reference.
|
||||
|
||||
- [x] Give `SK_Vehicle_Sedan` a physics asset whose single chassis body matches the car's box. **Resolved 2026-11-10** — two causes, both fixed: `ChaosVehiclesPlugin` was not enabled in `.uproject`, so `CreateVehicle()` never ran (`0 wheels`); and `BuildPhysicsAsset` never set `Body->PhysicsType`, so the body stayed `PhysType_Default` (follow owner) and never simulated. Enabling the plugin (`5e328f5`) and setting `PhysicsType = PhysType_Simulated` (`927e937`) make the pawn **simulate, settle at Z 77 cm, and drive** (`-drivetest` moved 36.3 m).
|
||||
- [x] Find what writes the actor transform in `DemoMap` but not in `ReviewMap`. **Resolved** — it was never a transform writer: the body was not simulating (see above), so the "float" and "teleport" were the unsimulated chassis being driven by the regular rigid-body solver instead of the Chaos async solver.
|
||||
- [x] Confirm `Wheels.Num()` becomes 4 once the vehicle is created; log it at the settle check. **4 wheels** once the plugin is enabled (`setups=4 wheels=4`).
|
||||
- [ ] Re-stage the pawn, re-add enter/exit and the driving route. **Drives backwards** (`-96.5 km/h`): the next bug is the reverse direction — rear wheels `bAffectedByEngine`, engine torque sign, or wheel bone axle orientation — not the physics setup.
|
||||
|
||||
## Hero facade rework — from the ReviewMap review (own task)
|
||||
|
||||
Split out of `0.3.0` on 2026-11-09 on the owner's instruction: the three hero-facade staging shots (`review-hero-facade-front`, `review-hero-facade-oblique`, `review-hero-portals`) were rejected as "rework needed to match reference", and that is asset work rather than vehicle work.
|
||||
|
||||
### Reference vs current
|
||||
|
||||
Reference: `security-game/docs/screens/refs/hollywood/el-capitan-facade-day-wikimedia-2009-10-27.jpg` (the north elevation, daylight). What it shows, and where the model stands:
|
||||
|
||||
| Feature | Reference | Model (`SM_HeroFacade`) |
|
||||
| --- | --- | --- |
|
||||
| Rusticated stone, warm limestone | heavy banded rustication over the whole wall | flat `M_Facade_Stone`, no banding |
|
||||
| Six giant Ionic columns | fluted shafts, volute capitals, moulded bases | present, fluted (20 flutes each), capital block only |
|
||||
| Three vertical JIMMY KIMMEL LIVE banners | red, gold lettering, hung between the middle columns | present as flat panels with `M_Facade_Banner` |
|
||||
| Inscription band | "FREEMASONRY BUILDS ITS TEMPLES..." carved lettering | `Hero_MottoBand` + surround + two medallions, no lettering |
|
||||
| Lattice windows between columns | dark metal grilles, two bands | `Hero_Window_1_*` / `_2_*` as plain panels |
|
||||
| Cornice, dentils, parapet | deep cornice, 52 dentils, balustrade with finials | cornice + 52 dentils + parapet, **no balustrade or finials** |
|
||||
| Three entry portals with steps | recessed, framed, three sets of steps | present: portals, jambs, lintels, mullions, five steps each |
|
||||
|
||||
So the model has the massing and most of the parts; what it lacks is **surface** (rustication, lettering, grille detail, balustrade) and material variation.
|
||||
|
||||
### Found: the detail is modelled inside the wall
|
||||
|
||||
Measured, not guessed:
|
||||
|
||||
```
|
||||
blend scene Y -12.50 .. +0.13 (front of the wall at -0.50)
|
||||
Hero_Mass y = -BODY_D/2 - 0.5 -> spans -12.50 .. -0.50
|
||||
Hero_Banner_1 y = -1.08 .. -1.02 -> 55 cm BEHIND the front face
|
||||
imported mesh bounds y = -13 .. +1250 cm
|
||||
```
|
||||
|
||||
The facade's detail - banners, and by the same construction the window bays and portal surrounds - is placed **behind** the wall's front face, so it is inside `Hero_Mass` and never renders. That is why the captures show a plain block with six columns: the columns, cornice and dentils happen to protrude, everything else does not. The model does not need remodelling first; its detail needs moving in front of the wall.
|
||||
|
||||
**Fix:** give `Tools/blender/hero_facade_build.py` a `FRONT_Y = -BODY_D / 2.0 - 0.5` constant (the mass's front face) and place every elevation detail relative to it (`FRONT_Y - 0.10` for applied detail, `FRONT_Y` for flush), instead of the hand-typed negative coordinates each call site uses now. Then re-export, re-import and re-render.
|
||||
|
||||
**And check the review shot.** The three hero captures show a plain block with six columns and nothing else - no banners, no windows, no portals - while the mesh demonstrably carries all four materials (`M_Facade_Banner`, `M_Facade_Stone`, `M_Facade_StoneShadow`, `M_Facade_GlassDark`) and is staged at `(-13336, 1307, 0)` with yaw 0. That suggests the cameras are framing the wrong actor (`Frontage_SM_ElCapitanTheatre` sits at the origin) or the wrong side, and the "rework" note may be describing a camera error rather than the model. Verify that first: one render with a camera aimed at `Hero_Facade` from Y -4000 at 8 m height.
|
||||
|
||||
### First pass against the folder references
|
||||
|
||||
`Art/reference/hero-masonic/` (15 images, ignored by git as reference photography) is now the source of truth rather than one Wikimedia photo. `02-masonictemple01.jpg` and `06-el-capitan-theater-frontal-fachade.jpg` are the front elevations used for the first pass; `05-hollywoodmasonictemple-1922.jpg` records the original masonry.
|
||||
|
||||
Done in this pass:
|
||||
|
||||
- the detail is seated on the wall (previous step), so banners, windows and portals render;
|
||||
- the blank `Hero_Stylobate_LintelBand` across the portals is gone - the reference has open doorways with steps, which now read correctly;
|
||||
- `rustication()` cuts five ashlar courses from the pavement to the portal heads;
|
||||
- `balustrade()` adds a bottom rail, a top rail and 38 balusters along the roof edge.
|
||||
|
||||
Still missing, from the same references: warm sandstone instead of the flat pale stone, lattice grilles in the window bays, the carved inscription on the motto band, and the two medallions are not reading at distance.
|
||||
|
||||
### The iteration loop
|
||||
|
||||
`bash Tools/review/hero_iterate.sh` runs the whole chain in one command: chmod the Blender files, build the facade, import it, render the capture set, judge the shot with the vision model, and write a comparison plate at `Screenshots/review/hero-compare-front.png`. Each step fails loudly and logs to `/tmp/gigline/`.
|
||||
|
||||
Iteration 1 verdict (judge, confidence 0.9):
|
||||
|
||||
> "The scene lacks the warm yellow-grey sandstone texture and the detailed fluted Ionic columns. Additionally, the red banners do not match the expected design, and there are no visible entrance doorways or decorative elements like the inscription band or medallions."
|
||||
|
||||
Measured against the plate, that splits into:
|
||||
|
||||
| Finding | Reality | Next action |
|
||||
| --- | --- | --- |
|
||||
| not warm sandstone | true - the UE material `M_FacadeStone` is still near-white (0.62-0.76 grey) | warm the UE material spec in `Tools/unreal/ue_hero_facade_import.py`, not the Blender one |
|
||||
| fluting missing | false - 120 flute strips exist and cast shadows; they are lost at 45 m | not a modelling problem; check the review camera distance |
|
||||
| banners wrong | partly - proportions are now 1.35 x 6.2 m and hang correctly, the lettering is absent | needs a texture, not geometry |
|
||||
| no doorways | false - three 1.2 m deep recesses with steps; they read dark because the reveal is glass-black | lighten the reveal material |
|
||||
| no inscription/medallions | true - the band and two medallions exist but are flat and untextured | texture the motto band |
|
||||
See [historical notes](MEMORY.md#hero-facade-rework--from-the-reviewmap-review-own-task).
|
||||
|
||||
### Tasks
|
||||
|
||||
Done on 2026-11-09/10, each verified by a capture:
|
||||
**Suggested skills:** `blender-director`, `archviz`, `historical-worlds`, `realistic-style`, `hard-surface` (relief and mouldings), `materials`, `texture-workflow`, `uv-workflow`, `lookdev`, `rendering`, `camera-cinematography`, `references` (visual-match checklist), `qa-review`; engine side `ue-materials-rendering`.
|
||||
|
||||
- [x] Confirm the hero captures frame `Hero_Facade` - cameras at (-13336, 1307), hero actor there, frontage at the origin.
|
||||
- [x] Seat the elevation detail in front of the mass (`FRONT_Y` + `seat_on_front()`, 99 parts moved) - banners, windows and portals render now.
|
||||
- [x] Remove the blank lintel band across the portals; the three doorways are 1.2 m deep with five steps each.
|
||||
- [x] Window grilles (7 vertical + 5 horizontal bars per bay), five ashlar courses, roof balustrade with 38 balusters.
|
||||
- [x] Warm sandstone in the engine - `tint_imported_materials()` authors one material per slot from the reference.
|
||||
- [x] Banner proportions 1.35 x 6.2 m hung from 7.2 m, matching the photographs.
|
||||
|
||||
Open:
|
||||
|
||||
- [x] **Make the generated ashlar albedo render.** Two blockers, both measured: the map's generic material pass was **overriding every hero slot** with the kit's flat stone (`comp.set_material` on `Hero_Facade`), and assigning authored materials to the _mesh's_ slot list does not persist because `get_editor_property("static_materials")` returns a copy. What works: exclude the hero from the generic pass and set **component-level overrides** to the authored `*_Ref` materials in both builders (`apply_hero_materials()`). The elevation now shows coursed, grainy limestone.
|
||||
- [x] Carved inscription: `inscription()` in `Tools/blender/hero_facade_build.py` lays 112 letter marks in three lines across the attic, in the shadow stone so they read at 45 m. They had to be **seated on the parapet face** (`FRONT_Y + 0.40`): the parapet is 1.2 m deep and stands in front of the wall plane, so marks at the wall plane were hidden behind it.
|
||||
- [x] Medallion relief that reads at 45 m. **Measured first, then fixed:** the discs were not just flat - they were **buried**. `seat_on_front` put `Hero_Medallion` at offset 0.08 → front face Y −0.42, while the parapet (1.2 m deep) stands at Y −0.15, so both medallions were inside the parapet and never rendered (the motto band and surround have the same bug at offset 0.06). Fixed: three stepped layers per medallion — `Hero_MedallionRim` (0.55 m, dark, offset 0.36 → Y −0.14), `Hero_MedallionField` (0.38 m, light, offset 0.43 → Y −0.07), `Hero_MedallionBoss` (0.15 m, dark, offset 0.50 → Y 0.00) — a 7 cm step each way so the rim/field/boss read as relief. Moved from ±6.2 (inside the inscription text, ±6.87) to ±9.8. Rendered and judged; the two-model judge is advisory-pessimistic (`no` 0.93) and does not flag medallions any more. Owner review pending.
|
||||
- [x] Motto band and surround — same burial bug (offset 0.06 → front Y −0.44). The band is now **light stone** (was `stone_dark`, which would have swallowed the dark lettering once visible), recessed at offset 0.34 (Y −0.16), with the moulded surround proud at offset 0.43 (Y −0.07) and the inscription at 0.40 (Y −0.10); the dead duplicate `Hero_MottoBand 0.40` entry is removed. Rendered and judged; the judge (`no` 0.93) no longer flags the band, only the stone warmth/column fluting the owner has already directed lighter. Owner review pending.
|
||||
- [x] Banner lettering (JIMMY KIMMEL LIVE). New `Tools/gen/generate_banner_texture.py` draws the exact title with Pillow + Liberation Sans Bold (an image model would mangle a trademark string, and Qwen-Image 2.1 is research-only). Gold `JIMMY` / `KIMMEL` / `LIVE` stacked on a crimson ground, 512 × 2304. `ue_hero_facade_import.py` generalised `import_albedo` → `import_texture` and wires the banner into `M_Facade_Banner_Ref` as Base Color (tiling 1.0). Owner review pending.
|
||||
- [x] Crimson banner tone + lettering. The texture carries the red ground (186,24,24 — brighter than the old dark tint, per the owner's "less dark, more bright") and the gold lettering, so the flat tint is replaced outright.
|
||||
- [x] Re-render the three ReviewMap staging shots and put them back in the status table for review. All three (`review-hero-facade-front`, `review-hero-facade-oblique`, `review-hero-portals`) regenerated 2026-11-10 with the full attic detail, and the `Screenshots/README.md` status table updated. Owner review pending.
|
||||
|
||||
**Deferred to `0.9.0` (owner, 2026-11-10).** This pass is closed at "surface detail that reads at 45 m". The remaining elevation work is a `0.9.0` pass, not another patch here: **volute capital depth** (the capitals are block-only, the reference has strong shadow lines), **lattice grille depth** (the window bays are plain panels with bars, not the dark metal lattice), and the full realism pass (heavy banded rustication, measured material library, texel-density and LOD/collision per building). The `0.9.0` hero item records the hand-off.
|
||||
- [x] Confirm the hero captures frame `Hero_Facade`.
|
||||
- [x] Seat the elevation detail in front of the mass.
|
||||
- [x] Remove the blank lintel band across the portals.
|
||||
- [x] Add window grilles, ashlar courses, and a roof balustrade.
|
||||
- [x] Warm the sandstone material in the engine.
|
||||
- [x] Match banner proportions and placement to the photographs.
|
||||
- [x] Make the generated ashlar albedo render.
|
||||
- [x] Add the carved inscription.
|
||||
- [x] Add medallion relief that reads at 45 m.
|
||||
- [x] Fix the motto band and surround.
|
||||
- [x] Add JIMMY KIMMEL LIVE banner lettering.
|
||||
- [x] Adjust the crimson banner tone and lettering.
|
||||
- [x] Re-render the three ReviewMap staging shots and update the status table.
|
||||
- [ ] Obtain owner approval for the three hero facade staging shots.
|
||||
|
||||
## 0.3.0 — Chaos vehicle, enter/exit, closed driving route (weeks 10–13)
|
||||
|
||||
### Vehicle production - active - session-5598ce7e-27e3-4c64-bb49-c04a4a31ee58 (branch `vehicle-0.3.0`)
|
||||
|
||||
- [A] Model one vehicle in Blender with collision meshes; export to Unreal. **Model done** (`Tools/blender/vehicle_build.py` → `Art/blender/vehicle/gigline-sedan.blend`, preview in `Art/blender/vehicle/preview.png`): 4.72 x 1.80 x 1.44 m, 2.78 m wheelbase, 1.62 m track, 0.33 m wheels, 252 triangles, four `UCX_` convex hulls. Dimensions come from real sedan figures and are logged to the report, so the vehicle config can read them rather than restating them. - **Imported into Unreal** (`Tools/unreal/ue_vehicle_import.py` → `/Game/Hollywood/Vehicle`), split per the vehicle-artist object split: `SM_Vehicle_Body` (3 slots, 4.76 x 1.76 x 1.28 m), `SM_Vehicle_Glass`, `SM_Vehicle_Light_Front`, `SM_Vehicle_Light_Rear`, `SM_Vehicle_Mirrors`, and one `SM_Vehicle_Wheel` (0.22 x 0.66 x 0.66 m) reused four times. The body FBX carries the `UCX_` hulls as simple collision.
|
||||
- **LODs cut for real:** decimating the duplicate gives LOD1 at 38 triangles and LOD2 at 14, against 132 for LOD0. The first attempt duplicated the shell unchanged and produced 84-triangle LODs that saved nothing - worth remembering as the failure mode of a "LOD" step nobody measures.
|
||||
- **LOD group wiring is open:** `StaticMeshEditorSubsystem.import_lod` is unavailable in a headless commandlet (`get_editor_subsystem` returns None), so the LOD meshes import as their own assets and the group still needs an editor session.
|
||||
- [D] Set up Chaos Vehicle with wheel and physics configuration. **Deferred to its own task** (`Vehicle physics — Chaos rig`): the pawn is parked, the visible car is the panel set, and the remaining question is what writes the actor transform in `DemoMap` but not in `ReviewMap`.
|
||||
- **Fixed the floating:** the physics asset had a body per bone, and the four wheel bodies intersected the chassis body and pushed the car 3.7 m up. It is now a **single chassis body** on the `root` bone (452 x 205 x 40 cm), which is what Unreal's own vehicle template uses - Chaos simulates the wheels itself, they must not be separate rigid bodies. Settle check: **Z 119 cm** at a spawn of 120 (was 494).
|
||||
- **Drive test fails, with a specific cause:** `-drivetest` holds full throttle for 3 s and reports speed and distance. Result: `speed=0 cm/s, travelled 2.2 m -> FAIL`, and BeginPlay logs **`0 wheel objects after register`** while the component reports 4 wheel setups. The setups exist but no wheels are created from them, so there is nothing to apply torque to.
|
||||
- **Next:** confirm the skeleton's bone names match the setups (`Wheel_FL`, `Wheel_FR`, `Wheel_RL`, `Wheel_RR`) - a name mismatch silently produces zero wheels - and give the front and rear pairs their own `UChaosVehicleWheel` subclasses rather than the base class, which is what the setups point at today.
|
||||
- **Also open:** LOD group wiring, enter/exit, driving route, handling.
|
||||
- [x] Fix the vehicle not rendering in the scene. **Chased to a strong clue, not yet solved.** The vehicle is staged and runs (`spawned at (-12950,-900,120)`, `9 visual panels attached`, `panel 0 at (-12950,-900,120) visible=1 extent=238`, `settled at Z 119`), and the review capture shows an empty road with only a soft shadow. `Tools/review/visual_loop.py --verify` passes the frame; `Tools/review/judge_screenshot.py` returns `verdict=no confidence=1.0`. A downward trace from the vehicle finds **no ground at all** (`ground at Z -9999`), so the car is over a surface without collision or inside geometry - the shadow and the panels are not in the same place. **Fixed 2026-11-09** — the car renders, built from the owner's `hot hatch.blend`. **Fixed:** the vehicle was spawned 2.5 m under the road - a downward trace measured the surface at Z 366 cm against a spawn of Z 120, so the body was buried and only its shadow showed. The spawn is now Z 470 and it settles there.
|
||||
- **Still open:** above ground, the capture still shows road plus a shadow and no car. Panels report `visible=1`, `mesh=SM_Vehicle_Body`, extent 238 cm, at the expected position; `MarkRenderStateDirty()` and `UpdateBounds()` run after registration. Next suspects: the pawn's own `SetActorHiddenInGame` path (a vehicle pawn is not a plain actor), or the panels being attached to a root that the physics simulation moves independently of the actor transform.
|
||||
- [x] Fix the character and car floating. **The measurement was taken at the wrong moment.** The foot probe ran in `BeginPlay`, while the pawn is still at its spawn height and falling, so every trace reported a surface far below it (`ground 366` against an actor at 561) and read as a float that was not there. The probe now runs **three seconds after spawn**, from the capsule's feet, on three channels, and names the actor it hits. Character: the player start at Z 600 left the soles 39 cm in the air (the capsule settles at 561 on this pavement); the start is now the measured 561. Car: the template's lowest geometry is exhaust, so grounding on the whole model put the tyres 4 cm above the origin - it is now grounded on its wheels, staged at Z 342 after the traced road height (366) disagreed with the visible road by ~25 cm.
|
||||
- [x] Fix the vehicle's material colour. The template's `car paint` is a legacy mix-shader graph that the FBX exporter cannot map, so `SM_Vehicle_Body` imported holding `WorldGridMaterial` and rendered black while Blender showed red. Legacy graphs are now converted to Principled before export (saturated colour wins over the grey diffuse next to it), and the staged body panel is assigned `M_Vehicle_HatchPaint`. Judge: `verdict=yes confidence=0.9`.
|
||||
- [x] Fix the missing vehicle glazing. The `windows` material was metallic 1.0 with roughness 0.0 - a black void that read as an empty cabin. Now tinted translucent (opacity 0.85, roughness 0.08); opacity 0.45 was tried first and made the glass invisible. The vision judge still returns `verdict=no` for the frame, disagreeing with what is visibly tinted glass; it is advisory, and the human verdict decides.
|
||||
- [x] Reconcile the road and sidewalk surfaces. **Solved: the road strip was a single 279 m mesh whose auto-generated convex hull sat 2 m above the visible surface.**
|
||||
- The site export now slices sidewalks and kerbs into ~20 m pieces with explicit `UCX_` boxes, and the slice cuts are fixed (the far cut kept the wrong half, so every slice spanned the whole street). Slice tops descend honestly along the street: 380, 340, 309, 284, 254, 231, 205, **169 at the player's X**, 138, 104 cm.
|
||||
- In the map at the same X: `Sidewalk_North_07` tops at **169 cm**, `Road_Hollywood` at **366 cm**. The road is ~2 m above its sidewalk, so the player lands on the road and no collision setting on the sidewalk can change that.
|
||||
- Neither slicing, `UCX_` hulls, `set_complex_collision_as_simple`, nor winding (all 2790 sidewalk faces point up) changed the outcome - each of those was the wrong layer.
|
||||
- **Cause found:** the road is built across -10.5..+10.5 m and the north sidewalk +10.5..+15.8 m, so on the straight they meet at the gutter - but the strips follow a curved centreline and the fan-out pushes the road's outer edge to **-13.8 m**, overlapping the sidewalk band (**-17.3..-11.8 m**). In that overlap the road sits at 366 cm and the sidewalk at 158 cm.
|
||||
- **Resolved 2026-11-10:** the real cause was the **collision hull**, not the geometry. A 279 m strip imports with one auto-generated convex hull whose top is the mesh's _maximum_ height, so every downward trace landed on a flat lid at 366 cm. Slicing the road like the sidewalks, and removing the single `Road_Hollywood` asset from the map so the slices win, gives a continuous cross-section: `Terrain 154 / Sidewalk_North_07 158 / Road_Hollywood_07 158`. The player now stands on `Sidewalk_North_07` at 158 cm with feet at 161.
|
||||
- **Superseded:** sample both source meshes at the player's XY in `Art/blender/street/street-2026-11-06.blend`. The road is built as `dem_z + 0.02` and the sidewalk as `dem_z + kerb * t` from the _same_ DEM sampler, so a 2 m gap cannot come from that code - the suspect is the DEM lookup or the mesh's coordinate frame, not the assembly logic.
|
||||
See [vehicle production notes](MEMORY.md#030--chaos-vehicle-enterexit-closed-driving-route-weeks-1013).
|
||||
|
||||
**Suggested skills:** `blender-director`, `vehicle-artist`, `hard-surface`, `materials`, `uv-workflow`, `collision-proxy`, `lod-pipeline`, `asset-optimization`, `export-pipeline`, `unreal-export` (against `ue_metric_v1`); engine side `ue-project-context` first, then `ue-physics-collision`, `ue-actor-component-architecture`, `ue-gameplay-framework`, `ue-input-system`, `ue-character-movement` (enter/exit), `ue-world-level-streaming` (route bounds), `ue-testing-debugging`, `cpp`.
|
||||
|
||||
- [A] Model one vehicle in Blender with collision meshes; export to Unreal.
|
||||
- [D] Set up Chaos Vehicle with wheel and physics configuration.
|
||||
- [x] Fix the vehicle not rendering in the scene.
|
||||
- [x] Fix the character and car floating.
|
||||
- [x] Fix the vehicle material colour.
|
||||
- [x] Fix the missing vehicle glazing.
|
||||
- [x] Reconcile the road and sidewalk surfaces.
|
||||
- [ ] Wire the vehicle LOD group in an editor session.
|
||||
- [ ] Implement enter/exit interaction.
|
||||
- [ ] Build a closed driving route with boundaries.
|
||||
- [ ] Tune basic handling to a drivable state.
|
||||
@@ -274,6 +98,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-physics-collision`, `ue-input-system`, `ue-actor-component-architecture`, `ue-testing-debugging`, `cpp`; `vehicle-artist` only if the fix is visual mass or wheel placement.
|
||||
|
||||
- [ ] Refine vehicle handling feel based on `0.3.0` feedback.
|
||||
- [ ] Tag `v0.3.1` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -281,6 +107,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `collision-proxy`, `vehicle-artist`, `asset-optimization`, `export-pipeline`, `unreal-export`, `ue-physics-collision`, `ue-testing-debugging`.
|
||||
|
||||
- [ ] Fix vehicle collision defects (mesh/physics mismatches).
|
||||
- [ ] Tag `v0.3.2` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -288,6 +116,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-input-system`, `ue-character-movement`, `ue-gameplay-framework`, `ue-blueprint-cpp-interop`, `ue-testing-debugging`.
|
||||
|
||||
- [ ] Fix vehicle/character input issues in `0.3.x`.
|
||||
- [ ] Tag `v0.3.3` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -295,6 +125,8 @@ Open:
|
||||
|
||||
### World expansion
|
||||
|
||||
**Suggested skills:** `blender-director`, `environment-artist`, `procedural-modeling`, `geometry-nodes`, `archviz`, `scene-assembly`, `asset-optimization`, `lod-pipeline`, `uv-workflow`, `materials`, `export-pipeline`, `unreal-export`; engine side `ue-project-context` first, then `ue-world-level-streaming`, `ue-procedural-generation`, `ue-async-threading`, `ue-materials-rendering`, `ue-testing-debugging`, `cpp`.
|
||||
|
||||
- [ ] Extend geo pipeline to a small connected neighborhood beyond the demo block.
|
||||
- [ ] Produce modular buildings, sidewalks, and roads for the expanded area.
|
||||
- [ ] Set up World Partition with streaming cells.
|
||||
@@ -312,6 +144,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-world-level-streaming`, `asset-optimization`, `environment-artist`, `ue-testing-debugging`.
|
||||
|
||||
- [ ] Fix visible seams between streamed cells.
|
||||
- [ ] Tag `v0.4.1` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -319,6 +153,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-world-level-streaming`, `ue-async-threading`, `ue-testing-debugging`.
|
||||
|
||||
- [ ] Correct streaming/loading issues found in `0.4.x`.
|
||||
- [ ] Tag `v0.4.2` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -326,6 +162,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `lod-pipeline`, `ue-world-level-streaming`, `asset-optimization`, `ue-testing-debugging`.
|
||||
|
||||
- [ ] Tune HLOD quality and distance settings.
|
||||
- [ ] Tag `v0.4.3` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -333,6 +171,8 @@ Open:
|
||||
|
||||
### Simulation
|
||||
|
||||
**Suggested skills:** `ue-project-context` first, then `ue-ai-navigation`, `ue-character-movement`, `ue-actor-component-architecture`, `ue-gameplay-framework`, `ue-state-trees`, `ue-mass-entity` (crowd scale), `ue-async-threading`, `ue-testing-debugging`; the `AGigLineNPCPerson` prefab is Blender work — `character-artist`, `rigging`, `animation`, `export-pipeline`, `unreal-export`.
|
||||
|
||||
- [ ] Add pedestrian characters with navigation, using `AGigLineNPCPerson` (the reusable NPC prefab) as the base, driving it via AI/`AutoPossessAI`.
|
||||
- [ ] Implement crosswalk behavior (pedestrians use the designated crossing).
|
||||
- [ ] Add limited ambient traffic on the street network.
|
||||
@@ -348,6 +188,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-ai-navigation`, `ue-physics-collision`, `ue-testing-debugging`.
|
||||
|
||||
- [ ] Fix pedestrian navigation issues.
|
||||
- [ ] Tag `v0.5.1` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -355,6 +197,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-ai-navigation`, `ue-state-trees`, `ue-gameplay-tags-messaging`, `ue-testing-debugging`.
|
||||
|
||||
- [ ] Fix crosswalk behavior defects.
|
||||
- [ ] Tag `v0.5.2` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -362,6 +206,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-mass-entity`, `ue-async-threading`, `ue-testing-debugging`, `asset-optimization`, `lod-pipeline`.
|
||||
|
||||
- [ ] Optimize pedestrian/traffic simulation cost.
|
||||
- [ ] Tag `v0.5.3` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -369,9 +215,13 @@ Open:
|
||||
|
||||
### Look and sound
|
||||
|
||||
See [pavement notes](MEMORY.md#060--visualaudio-pass-and-evening-lighting-preset-weeks-2224).
|
||||
|
||||
**Suggested skills:** `lighting`, `rendering`, `lookdev`, `materials`, `texture-workflow`, `archviz`, `environment-artist`, `blender-director`, `references` (visual-match checklist), `qa-review`; engine side `ue-materials-rendering`, `ue-niagara-effects`, `ue-audio-system`, `ue-gameplay-cameras`, `ue-testing-debugging`.
|
||||
|
||||
- [ ] Create an evening lighting preset.
|
||||
- [ ] Complete a visual polish pass across the neighborhood.
|
||||
- [ ] Model the pavement between kerb and building line; today that strip is the terrain mesh, which reads as bare ground under the shopfronts (`Screenshots/README.md`). Carried over 2026-11-09.
|
||||
- [ ] Model the pavement between kerb and building line.
|
||||
- [ ] Add ambient audio (city ambience, traffic, basic SFX).
|
||||
- [ ] Verify licenses for all added audio files.
|
||||
|
||||
@@ -386,6 +236,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `lighting`, `lookdev`, `rendering`, `references`, `qa-review`, `ue-materials-rendering`.
|
||||
|
||||
- [ ] Fix lighting defects in day/evening presets.
|
||||
- [ ] Tag `v0.6.1` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -393,6 +245,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `materials`, `texture-workflow`, `lookdev`, `qa-review`, `ue-materials-rendering`.
|
||||
|
||||
- [ ] Fix material errors found in `0.6.x`.
|
||||
- [ ] Tag `v0.6.2` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -400,6 +254,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-audio-system`, `ue-testing-debugging`.
|
||||
|
||||
- [ ] Balance audio levels across the scene.
|
||||
- [ ] Tag `v0.6.3` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -407,6 +263,8 @@ Open:
|
||||
|
||||
### Systems
|
||||
|
||||
**Suggested skills:** `ue-project-context` first, then `ue-ui-umg-slate`, `ue-input-system`, `ue-serialization-savegames`, `ue-data-assets-tables`, `ue-gameplay-framework`, `ue-blueprint-cpp-interop`, `cpp`, `ue-testing-debugging`.
|
||||
|
||||
- [ ] Build settings menu (graphics and audio options).
|
||||
- [ ] Implement control rebinding with persistence.
|
||||
- [ ] Implement save/load for player state.
|
||||
@@ -422,6 +280,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-serialization-savegames`, `ue-testing-debugging`, `cpp`.
|
||||
|
||||
- [ ] Fix save/load defects.
|
||||
- [ ] Tag `v0.7.1` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -429,6 +289,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-ui-umg-slate`, `ue-testing-debugging`.
|
||||
|
||||
- [ ] Fix UI issues in `0.7.x`.
|
||||
- [ ] Tag `v0.7.2` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -436,6 +298,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-input-system`, `ue-serialization-savegames`, `ue-testing-debugging`.
|
||||
|
||||
- [ ] Fix rebinding persistence issues.
|
||||
- [ ] Tag `v0.7.3` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -443,6 +307,8 @@ Open:
|
||||
|
||||
### Performance
|
||||
|
||||
**Suggested skills:** `ue-testing-debugging` (Unreal Insights, `stat unit`, `memreport`), `asset-optimization`, `lod-pipeline`, `ue-materials-rendering` (scalability and upscaler state), `ue-async-threading`, `ue-world-level-streaming`, `ue-niagara-effects`, `cpp`.
|
||||
|
||||
- [ ] Define performance budgets (frame time, VRAM) and document them.
|
||||
- [ ] Profile the full neighborhood; fix budget violations.
|
||||
- [ ] Test on broader Linux hardware and VRAM configurations; document results.
|
||||
@@ -458,6 +324,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-materials-rendering` (texture streaming, LOD bias), `asset-optimization`, `texture-workflow`, `lod-pipeline`, `ue-testing-debugging`.
|
||||
|
||||
- [ ] Reduce VRAM usage where profiling shows excess.
|
||||
- [ ] Tag `v0.8.1` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -465,6 +333,8 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-testing-debugging`, `ue-async-threading`, `ue-niagara-effects`, `asset-optimization`, `lod-pipeline`.
|
||||
|
||||
- [ ] Fix frame-time spikes found in `0.8.x`.
|
||||
- [ ] Tag `v0.8.2` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -472,37 +342,46 @@ Open:
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-testing-debugging`, `ue-module-build-system` (build and package differences), `ue-materials-rendering` (GPU and driver-specific paths).
|
||||
|
||||
- [ ] Fix compatibility issues from the hardware testing matrix.
|
||||
- [ ] Tag `v0.8.3` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
## 0.9.0 — Realistic buildings (weeks 31–34)
|
||||
|
||||
Suggested blender skills: **archviz** (real-world scale, façade composition, elevation read), **references** (reference-image-match and visual-match checklists — every building is judged against its own photograph), **procedural-modeling** + **geometry-nodes** (the façades system: bay module, cornice and coping profiles, storefront types as reusable node groups), **materials** (measured albedo and roughness per surface type), **texture-workflow** (baked AO/curvature/normal, decals for painted lettering and signs), **uv-workflow** (texel density and modular UVs), **lod-pipeline** (LOD chain per building), **collision-proxy** (UCX proxies so detail geometry is not the collider), **lookdev** (screenshot comparison loop), **camera-cinematography** (reproducing each building's reference angle), **asset-optimization** + **unreal-export** (pre-export validation, `ue_metric_v1`), **qa-review** (the gate before every review shot).
|
||||
See [realistic building notes](MEMORY.md#090--realistic-buildings-weeks-3134).
|
||||
|
||||
### Façade system
|
||||
|
||||
**Suggested skills:** `blender-director`, `archviz`, `historical-worlds`, `realistic-style`, `procedural-modeling`, `geometry-nodes`, `hard-surface`, `materials`, `texture-workflow`, `uv-workflow`, `asset-optimization`, `lod-pipeline`, `export-pipeline`, `unreal-export`, `references`, `qa-review`; engine side `ue-materials-rendering`.
|
||||
|
||||
- [ ] **Extend the module kit beyond one order.** Art Deco, neoclassical and streamline-moderne variants on the shared 4.96 m bay: alternate window proportions, spandrel and transom bands, and the way each order terminates at the roof.
|
||||
- [ ] **Build a profile library** for cornices, copings, string courses, sills and storefront surrounds, so a building is assembled from named profiles rather than ad-hoc boxes.
|
||||
- [ ] **Storefront types**: recessed entry, bulkhead and kickplate, mullion spacing, transom, awning/canopy brackets, and grille/shutter variants.
|
||||
- [ ] **Signage and lettering as decals.** Painted wall lettering, blade signs and fascia panels become texture and decal work with a documented projection; `0.1.6` proved that lettering built as geometry plates cannot resolve at review distance.
|
||||
- [ ] **Signage and lettering as decals.** Make painted wall lettering, blade signs and fascia panels texture and decal work with a documented projection.
|
||||
- [ ] Document the system in `Art/blender/README.md` and the arc42 building block view.
|
||||
|
||||
### Per-building models
|
||||
|
||||
Each building needs its own reference set; where none exists the blockout stays and the item says so.
|
||||
**Suggested skills:** `references` (reference-image-match and visual-match), `camera-cinematography` (match the photograph's angle before detail), `archviz`, `historical-worlds`, `realistic-style`, `blender-modeler`, `hard-surface`, `prop-artist` (street-level details), `materials`, `texture-workflow`, `uv-workflow`, `qa-review`, `blender-director`.
|
||||
|
||||
- [ ] Hero façade (6840): raise to this pass's detail level — **volute capital depth** and **lattice grille depth** (both deferred from the 2026-11-10 hero rework, which closed the inscription/medallions/motto-band/banner surface), window rhythm behind the colonnade, and the heavy banded rustication the elevation still lacks. The motto band, medallions and banner lettering are already done and carry over.
|
||||
- [ ] Hero façade (6840), entrance: **doors at street level left and right of the central bay**, as the photographs show, and correct positioning of the central-bay elements (column bases, entrance stairs, set-back entrance, door frames, banners and signage). Nothing may stand in front of the façade that blocks it — no awning, no temporary structure. Carried over from `Screenshots/README.md`, 2026-11-09.
|
||||
- [ ] Use a building-specific reference set for each building; retain blockouts and record buildings without references.
|
||||
|
||||
- [ ] Hero façade (6840): add volute capital depth, lattice grille depth, window rhythm behind the colonnade, and heavy banded rustication.
|
||||
- [ ] Hero façade (6840), entrance: add doors at street level left and right of the central bay and position the column bases, entrance stairs, set-back entrance, door frames, banners and signage correctly. Keep the façade unobstructed, with no awning or temporary structure.
|
||||
- [ ] El Capitan Theatre (6838): marquee, blade sign, entrance and the theatre's own elevation from its 18-frame set.
|
||||
- [ ] 6800 Lee Drug and 6806–6820: second pass on the `0.1.6` trim. Moved here from that patch because geometry cannot close them: **wall lettering as a decal** (a plate at 0.10 m still vanishes at 40 m), a **moulded parapet profile** instead of a two-tier box, richer **corner-tower stepping**, and the portrait panels reworked as textures rather than framed plates.
|
||||
- [ ] **Photography gap — 6822 Budget** (deferred out of `0.1.6`, blocked): no photograph exists; the single-storey 6.0 × 5.5 m volume cannot be read from the neighbour shot, so it stays a bare blockout until references are acquired. Record the sources checked in `Geo/`. Also re-shoot 6806–6820 and 6800 from the south sidewalk: the only reference is one oblique with a bus across Lee Drug's shopfront and a car across 6806's.
|
||||
- [ ] 6800 Lee Drug and 6806–6820: add wall lettering as a decal, a moulded parapet profile, richer corner-tower stepping, and portrait panel textures.
|
||||
- [ ] Acquire references for 6822 Budget and record the sources checked in `Geo/`; retain its bare blockout until references are acquired.
|
||||
- [ ] Re-shoot 6806–6820 and 6800 from the south sidewalk.
|
||||
- [ ] 6902, 6922 and the remaining south frontage.
|
||||
- [ ] North side: TCL Chinese Theatre and Madame Tussaud's façades from their reference sets.
|
||||
- [ ] Reconcile every modelled height against `Geo/tools/block_height_report.py` and record the source per building.
|
||||
|
||||
### Materials, UVs and performance
|
||||
|
||||
- [ ] **Measured material library**, including the hero's stone, which currently renders pale because it is a procedural graph and a generated texture set rather than a measured surface: stucco, painted render, ashlar, brick, terrazzo, glazing, bronze and steel trim, with albedo and roughness taken from the photographs rather than guessed; one Unreal material instance per surface type.
|
||||
**Suggested skills:** `materials`, `texture-workflow`, `uv-workflow`, `lookdev`, `rendering`, `collision-proxy`, `lod-pipeline`, `asset-optimization`, `ue-materials-rendering`, `ue-testing-debugging`.
|
||||
|
||||
- [ ] **Measured material library**, including the hero's stone: stucco, painted render, ashlar, brick, terrazzo, glazing, bronze and steel trim, with albedo and roughness taken from the photographs rather than guessed; one Unreal material instance per surface type.
|
||||
- [ ] **Texel-density rule** per building class, applied through modular UVs; lightmap UVs where required.
|
||||
- [ ] **LOD chain and collision proxies** per building: UCX proxies for blocking volume, detail geometry non-colliding.
|
||||
- [ ] Re-validate the 60 FPS floor at 1440p once the detail lands, and record the cost of the pass.
|
||||
@@ -521,6 +400,8 @@ Each building needs its own reference set; where none exists the blockout stays
|
||||
|
||||
### Freeze and audit
|
||||
|
||||
**Suggested skills:** `ue-project-context` (refresh before the audit), `ue-module-build-system` (reproducible build), `ue-testing-debugging`, `qa-review`, `de-ai-writing` (`Docs/` prose).
|
||||
|
||||
- [ ] Freeze features; document the freeze in `CHANGELOG.md`.
|
||||
- [ ] Produce a public beta build for testers.
|
||||
- [ ] Complete license audit: `THIRD_PARTY_NOTICES.md`, all MPFB assets, textures, animations, vehicles, audio.
|
||||
@@ -536,6 +417,8 @@ Each building needs its own reference set; where none exists the blockout stays
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-testing-debugging` plus the pack for the subsystem each blocker lives in (see the mapping in `AGENTS.md`); `cpp` for non-engine code.
|
||||
|
||||
- [ ] Fix blockers reported during the public beta.
|
||||
- [ ] Tag `v0.10.1` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -543,6 +426,8 @@ Each building needs its own reference set; where none exists the blockout stays
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `ue-testing-debugging`, plus the owning subsystem's pack, and `cpp` where the regression is in game code; check `MEMORY.md` for how the original defect was found.
|
||||
|
||||
- [ ] Fix regressions introduced by `0.9.x` fixes.
|
||||
- [ ] Tag `v0.10.2` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -550,6 +435,8 @@ Each building needs its own reference set; where none exists the blockout stays
|
||||
|
||||
### Patch tasks
|
||||
|
||||
**Suggested skills:** `de-ai-writing` (prose), `ue-project-context` (verify the project facts it records), `ue-module-build-system` (verify the build and checkout steps documented).
|
||||
|
||||
- [ ] Update all `Docs/` for accuracy before `1.0.0`.
|
||||
- [ ] Tag `v0.10.3` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
@@ -557,6 +444,8 @@ Each building needs its own reference set; where none exists the blockout stays
|
||||
|
||||
### Final release
|
||||
|
||||
**Suggested skills:** `qa-review`, `references` (final visual-match sweep), `ue-testing-debugging`, `de-ai-writing` (`Docs/`), `ue-project-context` refresh.
|
||||
|
||||
- [ ] Complete final acceptance sweep across all `0.x` features.
|
||||
- [ ] Confirm the stable, agreed game scope is met (neighborhood-scale walking and driving).
|
||||
- [ ] Final build, documentation, and licensing verification.
|
||||
|
||||
@@ -230,6 +230,90 @@ def join_system(objects, name):
|
||||
return joined
|
||||
|
||||
|
||||
def build_skeletal(joined, args):
|
||||
"""Bind the body + wheels to a root + 4 wheel bones for the Chaos vehicle.
|
||||
|
||||
The hot hatch's nose is along +Y, so wheel front/rear is the Y sign and left/right is the
|
||||
X sign (the wheels are mirrored in the template, so the joined mesh has all four). Each
|
||||
wheel is weighted to its own bone at full weight; the body goes to the root.
|
||||
"""
|
||||
import math
|
||||
|
||||
body = joined["body"]
|
||||
wheels = joined["wheels"]
|
||||
|
||||
# Derive the four wheel centres from the wheel geometry: group vertices by quadrant.
|
||||
# After the Unreal-frame rotation, front/rear is the X sign and left/right is the Z sign.
|
||||
quadrants = {"FL": [], "FR": [], "RL": [], "RR": []}
|
||||
for vertex in wheels.data.vertices:
|
||||
world = wheels.matrix_world @ vertex.co
|
||||
key = ("F" if world.x > 0 else "R") + ("L" if world.z > 0 else "R")
|
||||
quadrants[key].append(world)
|
||||
centres = {}
|
||||
for key, verts in quadrants.items():
|
||||
if not verts:
|
||||
continue
|
||||
centres[f"Wheel_{key}"] = (
|
||||
sum(v.x for v in verts) / len(verts),
|
||||
sum(v.y for v in verts) / len(verts),
|
||||
sum(v.z for v in verts) / len(verts),
|
||||
)
|
||||
if len(centres) != 4:
|
||||
raise SystemExit(f"expected 4 wheel quadrants, got {len(centres)}: {list(centres)}")
|
||||
log(f"wheel centres: " + ", ".join(f"{k}=({v[0]:.2f},{v[1]:.2f},{v[2]:.2f})"
|
||||
for k, v in sorted(centres.items())))
|
||||
|
||||
bpy.ops.object.armature_add(location=(0.0, 0.0, 0.0))
|
||||
rig = bpy.context.active_object
|
||||
rig.name = "VehicleRig"
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
edit_bones = rig.data.edit_bones
|
||||
root = edit_bones[0]
|
||||
root.name = "root"
|
||||
root.head = (0.0, 0.0, 0.0)
|
||||
root.tail = (0.0, 0.0, 0.30)
|
||||
for name, (x, y, z) in sorted(centres.items()):
|
||||
bone = edit_bones.new(name)
|
||||
bone.head = (x, y, z)
|
||||
# Axle direction is irrelevant to the Chaos solver (it drives the wheel about the
|
||||
# vehicle's Y), so point the tail straight down for a stable reference frame.
|
||||
bone.tail = (x, y, z - 0.25)
|
||||
bone.parent = root
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
# Join the body and wheels into one skinned mesh.
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
body.select_set(True)
|
||||
wheels.select_set(True)
|
||||
bpy.context.view_layer.objects.active = body
|
||||
bpy.ops.object.join()
|
||||
mesh = bpy.context.active_object
|
||||
mesh.name = "SM_Vehicle_Skeletal"
|
||||
|
||||
groups = {name: mesh.vertex_groups.new(name=name) for name in ["root", *centres]}
|
||||
for vertex in mesh.data.vertices:
|
||||
world = mesh.matrix_world @ vertex.co
|
||||
nearest, best = "root", 0.6 ** 2
|
||||
for name, (x, y, z) in centres.items():
|
||||
d = (world.x - x) ** 2 + (world.y - y) ** 2 + (world.z - z) ** 2
|
||||
if d < best:
|
||||
best, nearest = d, name
|
||||
groups[nearest].add([vertex.index], 1.0, "REPLACE")
|
||||
|
||||
armature = mesh.modifiers.new(name="Armature", type="ARMATURE")
|
||||
armature.object = rig
|
||||
mesh.parent = rig
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
rig.select_set(True)
|
||||
mesh.select_set(True)
|
||||
bpy.context.view_layer.objects.active = rig
|
||||
path = os.path.join(args.out, "vehicle_skeletal.fbx")
|
||||
fbx_export_preset.export_character(path, selection=True)
|
||||
log(f"skeletal vehicle exported: {path}, bones root + 4 wheels")
|
||||
return path
|
||||
|
||||
|
||||
def add_collision(body):
|
||||
"""One convex box per major volume, `UCX_`-prefixed for Unreal."""
|
||||
hulls = []
|
||||
@@ -282,6 +366,38 @@ def main(argv=None) -> int:
|
||||
|
||||
normalise_scale(meshes)
|
||||
|
||||
# Apply the MIRROR modifier so the left wheels exist in the mesh data BEFORE the nose
|
||||
# rotation; the mirror is in the object's local space, so rotating first would mirror
|
||||
# about a rotated axis.
|
||||
for obj in meshes:
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
for modifier in list(obj.modifiers):
|
||||
try:
|
||||
bpy.ops.object.modifier_apply(modifier=modifier.name)
|
||||
except RuntimeError:
|
||||
pass
|
||||
obj.select_set(False)
|
||||
|
||||
# Rotate so the FBX axis conversion (Blender Y->Unreal -Z, Z->-Y) lands the car unrolled:
|
||||
# length along +X (forward), width along Y, height along Z. The mapping is X->-Z, Y->X,
|
||||
# Z->-Y, i.e. the rotation matrix [[0,1,0],[0,0,-1],[-1,0,0]].
|
||||
from mathutils import Matrix
|
||||
rot = Matrix((
|
||||
(0.0, 1.0, 0.0),
|
||||
(0.0, 0.0, -1.0),
|
||||
(-1.0, 0.0, 0.0),
|
||||
))
|
||||
for obj in meshes:
|
||||
obj.rotation_euler = rot.to_euler()
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.transform_apply(location=False, rotation=True, scale=False)
|
||||
obj.select_set(False)
|
||||
bpy.context.view_layer.update()
|
||||
log("rotated to the Unreal frame (length +X, width +Y, height +Z)")
|
||||
|
||||
systems = {}
|
||||
for obj in meshes:
|
||||
systems.setdefault(classify(obj), []).append(obj)
|
||||
@@ -363,6 +479,12 @@ def main(argv=None) -> int:
|
||||
with open(args.report, "w") as handle:
|
||||
json.dump(report, handle, indent=2)
|
||||
print(f"[hatch] report -> {args.report}")
|
||||
|
||||
# Build the skeletal (physics) mesh last, after the report has recorded the separate
|
||||
# systems: build_skeletal joins the body and wheels, so it must not run before the
|
||||
# static exports and report are written.
|
||||
build_skeletal(joined, args)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user