25 Commits
Author SHA1 Message Date
zwitschi 0dd4a55c87 release: v0.1.0 — changelog, TODO pruning, notices statuses (#20)
Merged PR #20: v0.1.0 release prep

Co-authored-by: Zwitschi <zwitschi82@gmail.com>
2026-11-08 16:08:48 +01:00
zwitschi 88b0149bfa screenshots: regenerate set after placement release check (review pending) (#15)
Merged PR #15: regenerated screenshot set (approved)

Co-authored-by: Zwitschi <zwitschi82@gmail.com>
2026-11-08 16:08:14 +01:00
zwitschi 47bf3a2415 docs: fill the traversal row in the 0.1.0 check-record table (#19)
Co-authored-by: Zwitschi <zwitschi82@gmail.com>
2026-11-08 15:22:57 +01:00
zwitschi 3a1db78473 qa: package + document controls (0.1.0 release check PASS) (#18)
Merged PR #18: package + document controls

Co-authored-by: Zwitschi <zwitschi82@gmail.com>
2026-11-08 15:22:34 +01:00
zwitschi b27d30d556 qa: fresh-checkout build PASS (0.1.0 release check) (#17)
Merged PR #17: fresh-checkout build PASS

Co-authored-by: Zwitschi <zwitschi82@gmail.com>
2026-11-08 15:19:21 +01:00
zwitschi 4bc41e9faf perf: capture 1440p frame times; PASS the 60 FPS release check (#16)
Merged PR #16: 1440p perf capture (60 FPS check PASS)

Co-authored-by: Zwitschi <zwitschi82@gmail.com>
2026-11-08 15:17:47 +01:00
zwitschi 31f073d608 qa(diag): gate setup logs behind gigline.SetupLogs cvar (#14)
Merged PR #14: gate setup logs behind cvar

Co-authored-by: Zwitschi <zwitschi82@gmail.com>
2026-11-08 15:01:33 +01:00
zwitschi 71acffec12 qa(collision): no-blocking-collision release check — seam + wall audit (#13)
Merged PR #13: no-blocking-collision release check

Co-authored-by: Zwitschi <zwitschi82@gmail.com>
2026-11-08 14:55:58 +01:00
zwitschi 82a408bc33 qa(placement): record release-check 2 evidence + fix north-buildings camera (#12)
Merged PR #12: release-check 2 placement evidence + camera fix

Co-authored-by: Zwitschi <zwitschi82@gmail.com>
2026-11-08 14:43:15 +01:00
zwitschi 7e9573a735 todo: move deferred Blosm blockout-cleaning item into 0.1.4 (#11)
Merged PR #11: move deferred Blosm item into 0.1.4

Co-authored-by: Zwitschi <zwitschi82@gmail.com>
2026-11-08 14:33:26 +01:00
zwitschi 95011d678a docs: close ODbL review and finish active TODO items (#10)
Merged PR #10: close ODbL review and active TODO items

Co-authored-by: Zwitschi <zwitschi82@gmail.com>
2026-11-08 14:31:07 +01:00
zwitschi abdd7d181f qa(ref): close the reference-photography task (door recess + night gaps, concept copy) (#9)
Merged PR #9: close the reference-photography task

Co-authored-by: Zwitschi <zwitschi82@gmail.com>
2026-11-08 14:28:43 +01:00
zwitschi 16c2bf7c05 todo: update for current state — asset production, collision pass, docs, ODbL
- Remove stale -active suffix from Asset production heading
- Shorten the sidewalks/collision item (details in MEMORY.md)
- Mark collision authoring pass in 0.1.1 as partially done
  (terrain+site done via UGigLineAssetTools; buildings pending)
- Add TEMP QA diagnostic removal to release checks
- Add architecture documentation and doc toolchain as done
- Restore ODbL derivative-database posture as active [A]
2026-11-08 14:16:59 +01:00
zwitschi a3ce9be38a memory: doc toolchain, asciidoc→md conversion, QA merge status 2026-11-08 14:13:20 +01:00
zwitschi 2f87002711 docs(licensing): remove ODbL attribution requirements and HUD credit references
Drop the OpenStreetMap/ODbL attribution requirement, the Google imagery rule,
and the related F-4 requirement, risk R-4, release check, and TODO items across
AGENTS, PLAN, ROADMAP, README, setup, architecture sections, and manifests.
OSM data is now documented as local private use only. Also normalize
numbered-list indentation in the architecture docs.
2026-11-08 14:11:06 +01:00
zwitschi 37a3bfbc3d Merge pull request 'fix(input): restore working controls + add input harness + build fixes' (#8) from qa-input-collision-fix-0.1.0-clean into main
Merged PR #8
2026-11-08 13:45:46 +01:00
zwitschi 4832e66c00 fix(input): restore working controls + add input harness + build fixes
Consolidated from the qa-input-collision-fix-0.1.0 work, applied onto main so
only the changes main genuinely lacks are carried (main already merged the
walkable-demo/collision work independently).

- fix(input): populate the UE 5.8 FInputMappingContextMappingData
  DefaultKeyMappings field in ue_create_input_assets.py (the runtime reads
  GetMappings()->DefaultKeyMappings.Mappings; the script previously wrote only
  the deprecated 'mappings' array, so the cooked game had 0 runtime mappings
  and every key silently did nothing — in packaged AND PIE). Regenerated
  IMC_Default.uasset. WASD/mouse/Space/R/Escape verified working.
- fix(build): drop editor-only APIs that break the game target build —
  UStaticMesh::PostEditChange() (GigLineAssetTools) and AActor::GetActorLabel()
  (character ground probe). GigLine and GigLineEditor now both compile clean.
- tools: OS-level uinput input harness (input_emulation.py + input_test.py) and
  run_pie.sh for PIE isolation; document the PIE-vs-packaged input test in
  Docs/build.md. Removed the temporary SIM/EI/screenshot diagnostics from the
  character.
2026-11-08 13:43:33 +01:00
zwitschi 6b4e1252c5 docs(glossary): add PIE and UE entries
PIE — Play In Editor: an Unreal Editor feature that runs the current
map as a live game session inside the editor.
UE — Unreal Engine: Epic Games' real-time 3D creation platform,
comprising the editor (including PIE), C++ framework, and runtime.
2026-11-08 13:40:46 +01:00
zwitschi fe8740c1e3 docs: remove .adoc sources — markdown is the canonical format now 2026-11-08 13:38:52 +01:00
zwitschi 5aa883eaff docs: convert all architecture .adoc sections to markdown, prettier-format all .md
- Converted 13 individual .adoc section files to .md via
  asciidoctor→docbook5→pandoc(gfm) + post-processing, placed alongside
  the .adoc sources in Docs/Architecture/
- Rebuilt architecture.md from the individual section .md files
- Ran prettier --write on all .md in the repo (23 files changed):
  normalised table column alignment, trailing whitespace, line breaks
- Added .agents/ to .prettierignore (vendored third-party content)
- All .md now passes prettier --check
2026-11-08 13:33:44 +01:00
zwitschi e93c27054a docs: reconvert architecture.adoc to markdown via asciidoctor→docbook→pandoc
Replaced the Python-converted architecture.md with a proper pandoc
conversion chain:

  asciidoctor -b docbook5  →  pandoc -f docbook -t gfm

This fixes table rendering (pipe tables instead of grid tables),
interpolates AsciiDoc attributes ({project-name} → Gig Line), strips
anchor IDs, and normalises escaping. Post-processed: glossary italic
leak from cols="e,2e" table, ® → (R) for the reset key, \>/> escapes.

Also:
- prettier 3.9.10 installed globally as the Markdown formatter/linter
- .prettierrc: proseWrap=never, tabWidth=2
- .prettierignore: skips Binaries/, *.uasset, *.umap, *.blend, images
- architecture.md passes prettier --check
2026-11-08 13:26:17 +01:00
zwitschi d03829b699 docs: add Markdown rendering of arc42 architecture documentation
architecture.md is the combined 450-line single-page rendering of all 12
arc42 sections. The Asciidoctor source (.adoc) files remain for future
editing; the Markdown is the readable deliverable.
2026-11-08 13:12:33 +01:00
zwitschi 72705268b5 docs: arc42 architecture documentation for Gig Line
Populated from the arc42-template (EN/adoc) with project-specific content
across all 12 sections:

1.  Introduction and Goals — requirements overview, quality goals, stakeholders
2.  Architecture Constraints — technical (engine, collision, coordinates),
    organisational (squash-merge, visual review, provenance), conventions
3.  Context and Scope — business and technical context tables
4.  Solution Strategy — technology decisions, top-level decomposition,
    quality achievement approach
5.  Building Block View — Gameplay (6 C++ classes), Site (7 meshes + 7
    materials), Facades (9 buildings), Art Pipeline, QA & Review; Level 2
    white boxes for input flow and collision model
6.  Runtime View — walk, screenshot capture, QA self-test, build/import
    pipeline scenarios
7.  Deployment View — dev workstation and packaged build infrastructure
8.  Cross-cutting Concepts — coordinate system, collision model, lighting,
    materials, data provenance, QA harness
9.  Architecture Decisions — 7 ADRs (dynamic lighting, UE materials,
    C++ collision helper, grounded reset, core ticker, Y convention,
    headless Blender)
10. Quality Requirements — overview table + 7 measurable scenarios
11. Risks and Technical Debts — 5 risks, 2 debts with mitigations
12. Glossary — 16 domain-specific terms

Master document: Docs/Architecture/architecture.adoc
Config: Docs/Architecture/config.adoc
2026-11-08 13:08:35 +01:00
zwitschi b577afa730 todo: close the Gameplay section after the QA merge 2026-11-08 11:57:02 +01:00
zwitschi 52b1212cf3 qa: make the demo walkable — terrain collision root cause, spawn, scripted QA
The reported fall-through was a misdiagnosis: the player never fell. The
imported Terrain_DEM had only a bounding-box simple collision proxy, so a
downward trace anywhere in the block hit the terrain at Z 819 (the mesh
bbox max) and the street sat under an invisible 8 m lid. Standing on that
box looked plausible, which is why it read as working.

- add UGigLineAssetTools (C++): force CTF_UseComplexAsSimple on a static
  mesh. UE 5.8 dropped bUseComplexAsSimpleCollision and Python cannot set
  the flag; the builder also saves the asset, since the flag lives there
  and a map-only save silently reverts it.
- spawn (-13336, -1150, 600): inside the carved corridor (the cut is a
  10.7 m half-width around the road centreline), lands on the road at 456.
- fix ResetToStart returning to the mid-air PlayerStart captured in
  BeginPlay: StartLocation is now captured on the first grounded frame.
- HighResShot 1280x720 instead of "1280 720", which is a multiplier.
- add -qaselftest: scripted walk/reset/pause on a core ticker plus a
  swept ground transect. Results: walk 746 cm/2 s with Z steady, reset
  0 cm from spawn, pause/unpause 1/0, transect 84 samples 0 holes.
- rebase onto main, keeping the frontage materials and placement fixes.
2026-11-08 11:56:19 +01:00
94 changed files with 3805 additions and 406 deletions

No files matched your search

+15
View File
@@ -0,0 +1,15 @@
# Prettier ignores — don't format generated or binary content
Binaries/
Intermediate/
Saved/
*.uasset
*.umap
*.blend
*.png
*.jpg
*.jpeg
*.gif
*.webp
# Vendored third-party content — never reformat
.agents/
+5
View File
@@ -0,0 +1,5 @@
{
"proseWrap": "never",
"tabWidth": 2,
"useTabs": false
}
+11 -14
View File
@@ -4,8 +4,7 @@ Working rules for autonomous agents, AI coding assistants, and contributors. Pla
## Project Goal
Develop and test on Linux; ship Windows and Linux builds, with Linux as the primary development and CI platform. The game supports walking and driving through a real city.
The initial release is **`0.1.0`**: a walkable exterior demo around **6840 Hollywood Blvd / El Capitan Entertainment Centre**, including buildings on the south side of Hollywood Blvd between Highland Avenue and Orange Drive, plus buildings whose footprint lies within 50 m of the north-side frontage.
Develop and test on Linux; ship Windows and Linux builds, with Linux as the primary development and CI platform. The game supports walking and driving through a real city. The initial release is **`0.1.0`**: a walkable exterior demo around **6840 Hollywood Blvd / El Capitan Entertainment Centre**, including buildings on the south side of Hollywood Blvd between Highland Avenue and Orange Drive, plus buildings whose footprint lies within 50 m of the north-side frontage.
- Verify the address, building footprints, and demo boundaries **before** modeling.
- If the address, footprint, or boundary cannot be verified from permitted sources, do not model that structure. Record the unresolved item in `Geo/` with the sources checked and continue with verified structures only.
@@ -14,14 +13,14 @@ The initial release is **`0.1.0`**: a walkable exterior demo around **6840 Holly
## Toolchain
| Tool | Role |
| --- | --- |
| Unreal Engine | Rendering, gameplay, World Partition, Chaos Vehicles |
| Blender | Modeling, UVs, baking, collision meshes, character prep |
| MPFB | Human base meshes; baked materials, deform skeletons |
| QGIS | Geographic cleanup, coordinate conversion, elevation prep |
| Blosm | OSM building/street blockouts in Blender |
| Git + Git LFS | Source control, large assets, tagged releases |
| Tool | Role |
| ------------- | --------------------------------------------------------- |
| Unreal Engine | Rendering, gameplay, World Partition, Chaos Vehicles |
| Blender | Modeling, UVs, baking, collision meshes, character prep |
| MPFB | Human base meshes; baked materials, deform skeletons |
| QGIS | Geographic cleanup, coordinate conversion, elevation prep |
| Blosm | OSM building/street blockouts in Blender |
| Git + Git LFS | Source control, large assets, tagged releases |
- Pin compatible versions in `Docs/toolchain.md`.
- Validate Blender→Unreal mesh, skeleton, material, and scale transfers **before** producing assets.
@@ -80,13 +79,13 @@ Before starting any task:
3. **Record the active branch in the heading** of the section you are working on in `TODO.md`: append `- active - <sessionId>` (and, if useful, the branch name) to the level-three heading. This is the only way other agents can see that work is in progress on this section.
4. **Commit changes often** on that branch — small, focused commits with clear messages. Do not accumulate large uncommitted changes.
5. **Push to remote when a checklist item is done** — each completed `- [x]` item in that section should be committed and pushed. This keeps the remote state visible and makes progress recoverable.
6. When the section is complete, report the branch, changes, checks, and unresolved issues to the coordinator for integration (see *Parallel agent workflow* below). Do not merge into `main` yourself unless authorized.
6. When the section is complete, report the branch, changes, checks, and unresolved issues to the coordinator for integration (see _Parallel agent workflow_ below). Do not merge into `main` yourself unless authorized.
**Branch hygiene:**
- Branch from the current `main` revision, not from a stale local checkout.
- Keep `TODO.md` checkbox updates on your branch — they are part of the work.
- If a task needs a different camera angle, or affects visual review, follow the *Visual review* section before pushing.
- If a task needs a different camera angle, or affects visual review, follow the _Visual review_ section before pushing.
- If a task is abandoned, remove the `- active - <sessionId>` suffix from the heading in `TODO.md` and leave the branch for the coordinator to clean up.
## Parallel agent workflow
@@ -148,8 +147,6 @@ Always use a consistent scale test, named export presets, and repeatable import
- Record asset provenance and redistribution permissions in `THIRD_PARTY_NOTICES.md`.
- Verify licenses for every MPFB asset, texture, animation, vehicle, and audio file.
- An asset whose license cannot be verified must not be committed. Record it as unresolved in `THIRD_PARTY_NOTICES.md` and replace it with a redistributable asset before the release.
- Include "© OpenStreetMap contributors" and the appropriate license link; review ODbL obligations for distributed derived databases.
- Do **not** use extracted Google imagery or geometry without explicit applicable rights.
- Keep restricted assets out of the public repository; prefer redistributable replacements.
- Review Epic distribution requirements before publishing builds.
+12 -12
View File
@@ -56,13 +56,13 @@ LD_LIBRARY_PATH="$B/blender/lib" "$B/blender/blender" -b --factory-startup --p
Spot checks against the measured cross-sections:
| Point | Terrain Z |
| --- | ---: |
| Origin, Hollywood × Highland | 0.00 |
| Orange Drive end of the block | +3.39 |
| North sidewalk mid-block | +1.43 |
| South gutter, mid-block | +0.93 |
| Road centre, mid-block | +1.38 |
| Point | Terrain Z |
| ----------------------------- | --------: |
| Origin, Hollywood × Highland | 0.00 |
| Orange Drive end of the block | +3.39 |
| North sidewalk mid-block | +1.43 |
| South gutter, mid-block | +0.93 |
| Road centre, mid-block | +1.38 |
That reproduces both the 3.4 m eastward fall and the ~2 % crown, so the DEM, the cross-sections, and this mesh agree.
@@ -103,11 +103,11 @@ Objects are appended from the source files, not regenerated, and grouped into `S
A downward raycast on a 1 m grid along both sidewalks and the designated crossing, ignoring blockout meshes so marquees and canopies overhead do not count as ground:
| Route | Samples | Holes | Z range | Worst step |
| --- | ---: | ---: | --- | ---: |
| South sidewalk | 279 | 0 | −0.45 … +3.13 | 20.5 cm |
| North sidewalk | 279 | 0 | +0.07 … +3.83 | 15.1 cm |
| Crossing at 130 m | 26 | 0 | +1.16 … +1.80 | 8.6 cm |
| Route | Samples | Holes | Z range | Worst step |
| ----------------- | ------: | ----: | ------------- | ---------: |
| South sidewalk | 279 | 0 | −0.45 … +3.13 | 20.5 cm |
| North sidewalk | 279 | 0 | +0.07 … +3.83 | 15.1 cm |
| Crossing at 130 m | 26 | 0 | +1.16 … +1.80 | 8.6 cm |
No holes anywhere, so nothing drops the player on either side. The two flagged steps:
+76 -3
View File
@@ -2,8 +2,81 @@
All notable changes to this project are documented here.
Format: [Keep a Changelog](https://keepachangelog.com/) style, versions follow [Semantic Versioning](https://semver.org/). Entries are added when a version section in `AGENTS.md` is completed.
Format: [Keep a Changelog](https://keepachangelog.com/) style, versions follow [Semantic Versioning](https://semver.org/). Entries are added when a version section in `TODO.md` is completed.
## Unreleased
## [Unreleased]
No completed releases yet.
Nothing yet.
## [0.1.0] — 2026-11-08
The first playable release: a walkable exterior demo of the **6840 Hollywood Blvd**
(El Capitan Entertainment Centre) block in Unreal Engine, built on OpenStreetMap
layout data and USGS elevation, with the hero façade and the verified block
frontage staged on a walkable street.
### Added
- **Unreal project** (`Game/GigLine`) — C++ game module, Enhanced Input, packaged
Linux build, and a `DemoMap` that a plain launch opens directly.
- **Walkable demo level** — terrain from the 3DEP DEM, Hollywood Blvd carriageway,
both sidewalks and kerbs with collision, the designated mid-block crossing, and
fixed daytime lighting (sun, sky light, sky atmosphere, height fog, pinned
exposure).
- **Hero façade** for 6840 Hollywood Blvd — six-column neoclassical elevation with
three genuine portal recesses, entablature, parapet and motto band
(`Tools/hero_facade_build.py`, `Tools/ue_hero_facade_import.py`).
- **Block frontage** — the five verified south-side neighbours (6922, 6902, 6838
El Capitan Theatre, 6822 Budget, 6806–6822, 6800 Lee Drug) and the two north-side
buildings (TCL Chinese Theatre, Madame Tussaud's) placed at their OSM positions.
- **First-person player** — `AGigLineCharacter`: 1.75 m capsule, 1.60 m eye camera,
4 m/s walk, mouse look, jump, reset (`R`) and pause (`Escape`); controls
documented in `Docs/controls.md` and shown in-game.
- **In-game ODbL attribution** — `AGigLineHUD` draws the OpenStreetMap credit and
licence line every frame.
- **QA harness** — `-qaselftest` (walk, reset, pause, seam transect, invisible-wall
probe, frame-time capture) and `Tools/input_test.py` / `Tools/input_emulation.py`
for OS-level virtual keyboard/mouse input.
- **Documentation** — setup, build, toolchain, licensing, ODbL review, release
checks, and arc42 architecture docs.
### Fixed
- **All controls were dead in packaged and PIE builds.** The mapping-context
generator wrote key bindings to the deprecated `mappings` array; UE 5.8 reads
`DefaultKeyMappings.Mappings`, so the cooked game had zero runtime mappings and
every key resolved to nothing. `Tools/ue_create_input_assets.py` now populates
the current field.
- **"Fall-through" was an 8 m invisible box.** `Terrain_DEM` shipped only a
bounding-box collision proxy; forcing complex-as-simple collision
(`UGigLineAssetTools`) put the player on the actual street.
- **Reset returned the player to mid-air** — the start position is now captured on
the first grounded frame.
- **Plain launch opened the engine template** — the default map now lives in
`DefaultEngine.ini` and points at `DemoMap`.
- **Game target would not compile** — removed editor-only API use
(`UStaticMesh::PostEditChange`, `AActor::GetActorLabel`).
- **`demo-north-buildings` screenshot** framed 85 % sky (camera 9.5 km west of the
buildings); re-pointed to the building mass.
### Verified
- Both sides of the demo are traversable: 1456-sample seam transect, 0 holes,
no blocking collision or invisible walls (`-qaselftest` on the packaged build).
- Placement and street scale match the reference photography and Geo
measurements (road 24.3 m, sidewalk offsets 12.5/13.2 m, hero frontage 26.0 m).
- **414 fps average / p99 3.01 ms at 2560×1440** (Epic preset, TSR, VSync off, no
frame generation) — far above the 60 fps target.
- Fresh checkout builds and packages with the documented external engine
(UE 5.8.4).
### Known limitations
- Buildings other than the hero are flat blockout volumes; façade detail,
materials and Blosm style scripts are planned for `0.1.4`.
- Kerb height (0.13 m as built vs 0.15 m assumed), kerb radii, driveway aprons and
graded crossing ramps are deferred to `0.1.1`; crossing ramps are kerb flushes.
- One block only; no vehicles, NPCs, audio, or settings UI yet.
[Unreleased]: https://git.allucanget.biz/zwitschi/gig-line/compare/v0.1.0...main
[0.1.0]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.0
@@ -0,0 +1,35 @@
# Introduction and Goals
Gig Line is a Linux-first Unreal Engine 5 game that reconstructs a walkable (and eventually drivable) stretch of Hollywood Boulevard around 6840 Hollywood Blvd / El Capitan Entertainment Centre. The initial release (`0.1.0`) delivers a first-person walking demo of the south-side block between Highland Avenue and Orange Drive, plus north-side buildings within 50 m of the frontage.
## Requirements Overview
| ID | Description |
| --- | --- |
| F-1 | Walk both sides of Hollywood Blvd in the demo area without falling through geometry |
| F-2 | First-person movement (WASD + mouse look), jump, reset (R), pause (Escape) |
| F-3 | Fixed daytime lighting and atmosphere (sun, sky, fog, exposure) |
| F-5 | Designated street crossing with zebra markings at signalised crossings |
| F-6 | Building façades recognisable against reference photography |
| F-7 | Terrain, road, and sidewalk follow real-world DEM elevation |
| F-8 | 60 FPS at 1440p on the documented workstation and quality preset |
Full requirements and progress are in [TODO.md](../../TODO.md); version direction is in [ROADMAP.md](../../ROADMAP.md).
## Quality Goals
| Priority | Quality Goal | Scenario |
| --- | --- | --- |
| 1 | **Performance efficiency** | 60 FPS at 2560×1440 on RTX 5070 Ti, documented preset, no frame generation |
| 2 | **Terrain walkability** | Zero raycast holes across both sidewalks and the carriageway (84-sample transect passes) |
| 3 | **Geographic fidelity** | Building placement, street scale, and façade identity match reference photography |
| 4 | **Build reproducibility** | Fresh checkout + documented engine path produces a runnable Linux build |
| 5 | **Provenance** | Every committed asset has recorded provenance |
## Stakeholders
| Role | Contact | Expectations |
| --- | --- | --- |
| Owner / Director | zwitschi | Sets scope, approves screenshots, cuts release tags |
| AI coding agents | multiple sessions | Read `AGENTS.md`, `TODO.md`, `ROADMAP.md`, and this document for architecture constraints; never commit directly to `main` |
| Players | end users | Walk the demo at 60 FPS without falling through the world; see a recognisable Hollywood block |
@@ -0,0 +1,32 @@
# Architecture Constraints
## Technical Constraints
| Constraint | Explanation |
| --- | --- |
| Engine: Unreal 5.8.4 | Pinned; no bundled Linux clang — uses system clang 23.1.4. Game targets use `BuildSettingsVersion.V7`. |
| Platform: Linux first | Developed and tested on Bazzite Linux (Fedora 45 base). Windows builds ship but are secondary. |
| GPU: RTX 5070 Ti 16 GB | Dev workstation target. Vulkan RHI; open kernel module driver 615.78.08. |
| No static lighting | `r.AllowStaticLighting=False`. The demo is a fixed daytime scene lit entirely dynamically to avoid "LIGHTING NEEDS TO BE REBUILT" warnings. |
| Blender→UE via FBX | Flat diffuse colours do not survive FBX→Unreal import. All materials are authored in UE. |
| Terrain collision | UE 5.8 exposes `CollisionTraceFlag` (not `bUseComplexAsSimpleCollision`); Python cannot set it. Must use C++ helper `UGigLineAssetTools::SetComplexCollisionAsSimple`. |
| Coordinate convention | South = +Y, north = −Y (ground truth = street mesh orientation). 1 m in Blender = 100 cm in Unreal. |
## Organisational Constraints
| Constraint | Explanation |
| --- | --- |
| Squash-merge to `main` | Every PR is squash-merged for a linear history. No direct commits to `main` except integration merges. |
| Visual review required | Any change affecting appearance must produce screenshots for human approval before being marked done. |
| Asset provenance | Every committed asset must have verified licence and redistribution permission recorded in `THIRD_PARTY_NOTICES.md`. |
| Reference photography | Local only, gitignored, never committed. Used for façade work under fair-use / internal-development rationale. |
| LFS for binaries | `.uasset`, `.umap`, `.blend` tracked with Git LFS; `Binaries/`, `Intermediate/`, `Saved/` are gitignored. |
## Conventions
| Convention | Explanation |
| --- | --- |
| Semantic versioning | `MAJOR.MINOR.PATCH`. `0.x` documents breaking changes. `1.0.0` = agreed game scope. |
| Task tracking in `TODO.md` | Single central task list. `- [ ]` open, `- [-]` or `- [~]` in progress, `- [x]` done, `- [A]` active, `- [D]` deferred. |
| Headless Blender | All Blender work driven by `bpy` scripts in `Tools/`, not MCP. Blender skills are method guides only. |
| Named export presets | `ue_metric_v1` for static meshes, `ue_character_v1` for skeletal. Every exporter calls the metre→centimetre conversion. |
+22
View File
@@ -0,0 +1,22 @@
# Context and Scope
## Business Context
The system is a self-contained single-player game. It has no server backend, no multiplayer, and no live service dependencies. Its communication partners are:
| Partner | Inbound | Outbound |
| --- | --- | --- |
| Player (keyboard/mouse) | WASD, mouse, R (reset), Escape (pause), Space (jump) | Rendered 3D scene + HUD |
| OSM data | Extracted building footprints, street geometry, elevation | — |
| Filesystem | Engine content (`.uasset`, `.umap`), Blender sources (`.blend`), DEM GeoTIFF | Saved maps, screenshots, packaged build |
## Technical Context
| Channel | Protocol / Format | Purpose |
| --- | --- | --- |
| Keyboard + mouse | Enhanced Input (IA\_\*, IMC_Default) | First-person movement, look, jump, reset, pause |
| Vulkan RHI | GPU rendering | Scene display at 60 FPS / 1440p |
| FBX | Autodesk FBX (binary) | Blender→Unreal mesh and skeleton transfer |
| GeoTIFF | 3DEP 1 m DEM | Terrain heightfield source |
| OSM XML | Overpass API extract | Building footprint and street layout source |
| Git / LFS | HTTPS to Gitea remote | Source control, binary asset storage |
+39
View File
@@ -0,0 +1,39 @@
# Solution Strategy
## Technology decisions
- **Unreal Engine 5.8.4** for rendering, World Partition, physics (Chaos), and the Enhanced Input system. Chosen for its Linux support, open C++ source, and real-time rendering quality.
- **Blender 5.2 LTS** for all 3D modelling, UV work, and collision mesh authoring. Driven headlessly via `bpy` Python scripts, not MCP.
- **QGIS 4.2.3** for geographic data cleanup, coordinate conversion (EPSG:26911), and DEM preparation.
- **Blosm 2.7.28** for OSM building and street blockouts inside Blender (layout data only — not authoritative façade, height, or pavement detail).
- **MPFB 2.0.18** for human base meshes and baked materials (NPCs in `0.2.0+`).
## Top-level decomposition
The system is a single Unreal game module (`GigLine`) with no plugins beyond `EnhancedInput`. Content is organised by domain under `/Game/Hollywood/`:
- `Site/` — terrain, road, sidewalks, kerbs, crossing markings, site materials
- `ElCapitan/` — hero façade mesh and materials
- `Frontage/` — south-side block building meshes
- `North/` — north-side building meshes
- `Input/` — Enhanced Input actions and mapping context
All modelling and geographic processing live outside the game project in `Art/`, `Geo/`, and `Tools/`. The `Game/` directory contains only engine-ready content and C++ source.
## Quality achievement
- **Performance**: dynamic-only lighting (no build step, no static shadow maps), world-space-noise materials (cheap procedural variation), minimal overdraw (simple street-level geometry).
- **Walkability**: complex-as-simple collision on terrain (C++ helper forces the flag on the asset), block-all collision on every site mesh, swept transect QA verifies zero raycast holes.
- **Fidelity**: geographic data verified before modelling (footprints, bounds, DEM); building placement checked against street mesh, never in isolation; reference photography drives façade work.
- **Reproducibility**: pinned toolchain in `Docs/toolchain.md`, named export presets, `Tools/check-lfs.sh` gate, fresh-checkout build procedure in `Docs/build.md`.
@@ -0,0 +1,96 @@
# Building Block View
## Whitebox Overall System
The system decomposes into five top-level building blocks:
| **Name** | **Responsibility** |
| --- | --- |
| Gameplay | Player pawn, movement, camera, input bindings, HUD, game mode |
| Site | Terrain, road, sidewalks, kerbs, crossing markings, site-level collision |
| Façades | Hero façade (El Capitan), south block frontage, north-side buildings |
| Art Pipeline | Blender models, geographic data, export scripts, import scripts |
| QA & Review | Screenshot pipeline, self-test harness, visual review workflow |
### Gameplay
C++ classes in `Game/GigLine/Source/GigLine/`:
| Class | Responsibility | Key detail |
| --- | --- | --- |
| `AGigLineCharacter` | First-person player pawn | 1.75 m capsule, 4.5 m/s walk, Enhanced Input bindings, reset, pause, `-qaselftest` harness |
| `AGigLineGameModeBase` | Sets game mode and HUD class | `HUDClass = AGigLineHUD` |
| `AGigLineHUD` | HUD text overlay | `DrawText` every frame, bottom-left |
| `AGigLineNPCPerson` | NPC prefab (future) | Capsule + visible MPFB body, feet on Z 0 |
| `AGigLineShotActor` | Screenshot camera walker | Drives cameras in `-game` mode, `FScreenshotRequest`, quits |
| `UGigLineAssetTools` | C++ collision helpers | `SetComplexCollisionAsSimple` on `StaticMesh` assets |
Input assets in `/Game/Input/`: `IA_Move`, `IA_MoveBack`, `IA_Strafe`, `IA_StrafeLeft`, `IA_Look`, `IA_Jump`, `IA_Reset`, `IA_Pause`, bound in `IMC_Default`.
### Site
Static meshes and materials in `/Game/Hollywood/Site/`:
| Asset | Role |
| --- | --- |
| `Terrain_DEM` | Ground heightfield (complex-as-simple collision) |
| `Road_Hollywood` | Carriageway surface (block-all collision) |
| `Sidewalk_North`, `Sidewalk_South` | Pedestrian surfaces (block-all collision) |
| `Kerb_North`, `Kerb_South` | Kerb edges |
| `Crossing_Markings` | Zebra markings at signalised crossing |
| `M_Asphalt`, `M_Concrete`, `M_KerbConcrete`, `M_Ground`, `M_RoadPaint`, `M_Stucco`, `M_FacadeStone` | Procedural materials (world-space-noise blend + roughness) |
### Façades
| Asset | Role |
| --- | --- |
| `SM_HeroFacade` | El Capitan Entertainment Centre detailed façade |
| `SM_ElCapitanTheatre`, `SM_Budget_6822`, `SM_Hollywood_6806`, `SM_Hollywood_6902`, `SM_Hollywood_6922`, `SM_LeeDrug_6800` | South block frontage (6 buildings) |
| `SM_Madame_Tussauds`, `SM_TCL_Chinese_Theatre` | North-side buildings (within 50 m) |
### Art Pipeline
Python scripts in `Tools/` drive the content pipeline:
| Script group | Purpose |
| --- | --- |
| `export_*`, `hero_facade_build` | Blender headless export (FBX) |
| `ue_import_*`, `ue_hero_facade_import` | Unreal headless import + placement |
| `ue_build_demo_map`, `ue_build_review_map` | Map assembly (lighting, meshes, player start) |
| `ue_create_input_assets` | Enhanced Input actions + mapping context |
| `render_screenshots.sh` | Screenshot capture pipeline |
| `judge_screenshot.py` | Vision-model pre-check (optional) |
Source data: `Art/blender/` (`.blend` files), `Geo/` (OSM extracts, DEM, verification docs).
### QA & Review
- `AGigLineShotActor` walks fixed cameras in `-game` mode, captures PNGs, and quits.
- `-qaselftest` switch on `AGigLineCharacter` runs a core-ticker harness: walk 2 s, reset, pause/unpause, ground transect — all PASS/FAIL logged.
- `Screenshots/` holds committed PNGs and `README.md` approval table.
## Level 2
### White Box Gameplay — Input Flow
1. `AGigLineCharacter::SetupPlayerInputComponent` binds `IA_*` actions to `MoveForward`, `MoveBack`, `MoveStrafe`, `MoveLeft`, `Look`, `StartJump`/`StopJump`, `ResetToStart`, `TogglePause`.
2. Movement delegates to `ApplyMove(Forward, Strafe)` → `AddMovementInput`.
3. Look applies yaw/pitch scaled by `LookSensitivity` (Y invertable via `bInvertLookY`).
4. `ResetToStart` teleports to `StartLocation` (captured on first grounded frame), clears velocity, resets control rotation.
5. `TogglePause` toggles `APlayerController::SetPause`, swaps input mode between `FInputModeGameOnly` and `FInputModeUIOnly`, shows/hides mouse cursor.
### White Box Site — Collision Model
- Every site mesh (road, sidewalks, kerbs, crossing) is `STATIC` with `BlockAll` collision.
- `Terrain_DEM` uses `CTF_UseComplexAsSimple` (forced by `UGigLineAssetTools` at build time, saved on the asset).
- The terrain corridor is carved to half-width 10.7 m around the road centreline so the player can walk the carriageway and both sidewalks.
- Ground Z: north sidewalk 395, road 366, south sidewalk 317 (3.94 m fall along the block, matching the DEM).
+53
View File
@@ -0,0 +1,53 @@
# Runtime View
## Walk the block
1. Player launches game → `AGigLineGameModeBase` spawns `AGigLineCharacter` at `PlayerStart` `(-13336, -1150, 600)`.
2. Character falls 144 cm to road surface (Z 456) and begins walking. `StartLocation` captured on first grounded frame.
3. Player holds W → `IA_Move` fires → `ApplyMove(1, 0)` → `AddMovementInput(ForwardVector, 1)` → capsule moves at 450 cm/s.
4. Mouse move → `IA_Look` fires → yaw/pitch applied to controller rotation → camera follows (attached to capsule at 160 cm eye height).
5. Player presses R → `ResetToStart` → teleport to `StartLocation`/`StartRotation`, `StopMovementImmediately`, reset control rotation.
6. Player presses Escape → `TogglePause` → `SetPause(true)`, switch to `FInputModeUIOnly`, show cursor. World timers stop; core tickers continue.
7. Player presses Escape again → `TogglePause` → `SetPause(false)`, switch to `FInputModeGameOnly`, hide cursor.
## Screenshot capture
1. `Tools/render_screenshots.sh` compiles editor, builds `ReviewMap` from `Tools/ue_build_review_map.py`, launches game in `-game` mode.
2. `AGigLineShotActor` iterates the camera list, sets view, calls `HighResShot 1280x720`, waits for capture.
3. On last camera, requests engine quit. PNGs are copied to `Screenshots/` and committed.
## QA self-test (`-qaselftest`)
1. Core ticker starts at `BeginPlay` (not a world timer — world timers freeze on pause).
2. 1.0 s: begin driving forward input.
3. 3.0 s: stop, measure displacement (> 200 cm = PASS).
4. 3.5 s: call `ResetToStart`.
5. 4.0 s: measure distance from spawn (< 100 cm = PASS).
6. 4.5 s: call `TogglePause`, check `IsGamePaused()` (= true = PASS).
7. 5.5 s: call `TogglePause`, check `IsGamePaused()` (= false = PASS).
8. 6.0 s: swept ground transect across three lanes (Y −1400, −167, +1000), X −14900 to −9420 at 200 cm spacing. 84 samples, 0 holes = PASS.
9. 7.0 s: final verdict logged.
## Build and import pipeline
1. Blender headless (`Tools/export_*.py`) reads `.blend` files, applies metre→centimetre conversion, exports FBX with `ue_metric_v1` preset.
2. Unreal headless (`Tools/ue_import_*.py`) imports FBX via Interchange, places meshes at geographic coordinates, sets collision profiles.
3. `Tools/ue_build_demo_map.py` assembles `DemoMap`: creates/repositions actors, sets lighting, saves map and assets.
+40
View File
@@ -0,0 +1,40 @@
# Deployment View
## Infrastructure Level 1 — Development Workstation
| Node | Specification |
| --- | --- |
| Dev workstation | Bazzite Linux 45 (Fedora 45 base, immutable ostree), Ryzen 7 7700X, 64 GB RAM, RTX 5070 Ti 16 GB, Vulkan RHI |
| Engine install | `/run/media/system/DATA/Engines/UE_5.8.4` (73 GiB, external to repo) |
| Project repo | `/run/media/system/DATA/Documents/06-games/gig-line` (Git + LFS, remote at `git.allucanget.biz`) |
| Build output | `/run/media/system/DATA/Builds/GigLine` (packaged Linux build, ~1.1 GiB) |
Motivation
Single-machine development. No build farm, no CI pipeline yet. The engine, toolchain, and GPU are all on the same host.
Quality and Performance Features
- Vulkan RHI on RTX 5070 Ti, open kernel module driver 615.78.08.
- System clang 23.1.4 (no bundled engine clang) compiles game code.
- Build time: ~90 s incremental, ~5 min clean for the editor target.
Mapping of Building Blocks to Infrastructure
| Building Block | Infrastructure Element |
| --- | --- |
| Gameplay (C++) | Dev workstation — compiled to `libUnrealEditor-GigLine.so`, loaded by engine |
| Site + Façades (UE content) | Dev workstation — `.uasset`/`.umap` in `Game/GigLine/Content/` |
| Art Pipeline (Python/Blender) | Dev workstation — headless Blender 5.2 Flatpak, Python 3.14.8 |
| QA & Review | Dev workstation — game run in `-game` mode, PNGs to `Screenshots/` |
| Git remote | `git.allucanget.biz` — Gitea instance, LFS-enabled |
## Infrastructure Level 2 — Packaged Build
| Element | Detail |
| --- | --- |
| Linux packaged build | Development config, ~1.1 GiB. Contains engine binaries, game module, cooked content, no editor. |
| Run command | `./GigLine.sh -windowed -ResX=2560 -ResY=1440` |
No server infrastructure. The game is a standalone executable.
+55
View File
@@ -0,0 +1,55 @@
# Cross-cutting Concepts
## Coordinate system and scale
- **Source CRS**: EPSG:26911 (UTM zone 11N). Local origin recorded in `Geo/`.
- **Blender**: metres. East → +X, north → +Y (Blender convention).
- **Unreal**: centimetres. East → +X, north → **−Y** (confirmed from street mesh: `Sidewalk_North` Y −1912..−1053, `Sidewalk_South` Y +718..+1447).
- **Conversion**: every exporter calls the metre→centimetre function. Import presets (`ue_metric_v1`, `ue_character_v1`) are named and repeatable.
Rule: place a building only against a neighbour or street mesh, never in isolation.
## Collision model
- Site meshes (road, sidewalks, kerbs, crossing): `STATIC` mobility, `BlockAll` collision profile.
- Terrain: `STATIC`, `CTF_UseComplexAsSimple` (forced by `UGigLineAssetTools` at build time, saved on the asset — the flag lives on the `UStaticMesh`/`UBodySetup`, not the map).
- Buildings: `STATIC`, simple collision (convex hull from mesh).
- Rule: the collision flag must be saved on the asset. Saving only the `.umap` silently reverts it.
## Lighting
- Dynamic-only: `r.AllowStaticLighting=False`. All lights are `MOVABLE`.
- Fixed daytime rig: directional light (sun, −62° pitch), sky light, exponential height fog, post-process volume with fixed exposure.
- Rationale: avoids "LIGHTING NEEDS TO BE REBUILT" warnings; no bake step; the scene never changes time of day.
## Materials
- All materials authored in Unreal, not carried from Blender (FBX import loses diffuse colours).
- World-space-noise blend nodes for cheap procedural variation (asphalt grain, concrete speckle, stucco roughness).
- Instance parameters where tunable (e.g. wetness, ageing).
## Geographic data provenance
- OSM extract is layout data only — not authoritative for façade, height, or pavement detail.
- DEM from 3DEP 1 m; elevation verified against cross-sections in `Geo/verification/`.
- Reference photography (local, gitignored) drives manual façade work.
## QA harness
- `-qaselftest`: core ticker (not world timer) so pause cannot freeze the test that proves pause works.
- `GigLine SIM` / `GigLine GROUND`: TEMP diagnostic logging, to be removed before release tag.
- Screenshot approval: human must tick `Screenshots/README.md` before visual work is marked done.
@@ -0,0 +1,57 @@
# Architecture Decisions
## ADR-1: Dynamic-only lighting for the demo
- **Context**: The demo is a fixed daytime scene. Stationary lights demand a lighting build and produce "LIGHTING NEEDS TO BE REBUILT" warnings.
- **Decision**: `r.AllowStaticLighting=False`, all lights `MOVABLE`.
- **Consequences**: No bake step, no warning, simpler iteration. Slightly higher GPU cost per frame (no baked shadows) — acceptable for the demo scope.
## ADR-2: Materials authored in Unreal, not imported from Blender
- **Context**: FBX→Unreal import maps all Blender diffuse colours to white. Flat colour materials do not survive the pipeline.
- **Decision**: Build all materials (asphalt, concrete, stucco, façade stone, road paint) directly in Unreal with procedural nodes.
- **Consequences**: Materials cannot be previewed in Blender look-dev. Must round-trip to Unreal for visual check. But: full PBR control, world-space noise, no import surprise.
## ADR-3: Complex-as-simple collision on terrain via C++ helper
- **Context**: UE 5.8 removed `bUseComplexAsSimpleCollision`. Python cannot set `CollisionTraceFlag` on `StaticMesh`/`BodySetup` at all. The Interchange default is bbox-only simple collision, which placed an invisible lid 8 m above the street.
- **Decision**: `UGigLineAssetTools::SetComplexCollisionAsSimple` (C++) forces `CTF_UseComplexAsSimple` and saves the asset.
- **Consequences**: One C++ helper class. The flag must be saved on the asset (not just the map). No Python workaround exists.
## ADR-4: Reset captures StartLocation on first grounded frame
- **Context**: `BeginPlay` captures `StartLocation` at the PlayerStart, which is above grade (Z 600) so the capsule does not spawn embedded. Every reset dropped the player 1.4 m back into the air.
- **Decision**: Repeating 0.25 s timer captures `StartLocation` once the character reports `IsMovingOnGround()`, then clears itself.
- **Consequences**: Reset is correct. A one-shot timer at 2 s is wrong (fires after walking begins, records mid-stride position).
## ADR-5: Core ticker for QA self-test, not world timer
- **Context**: Pausing the game stops world timers. A timer-driven test freezes exactly when it proves pause works.
- **Decision**: `FTSTicker::GetCoreTicker()` drives the `-qaselftest` harness.
- **Consequences**: Test runs even while paused. Must be enabled explicitly (`-qaselftest`), never in normal play.
## ADR-6: South = +Y, north = −Y in Unreal
- **Context**: The street mesh established this convention: `Sidewalk_North` has Y range −1912..−1053, `Sidewalk_South` has +718..+1447.
- **Decision**: Adopt it project-wide. Never place a building by assumption; always verify against a street mesh or neighbour.
- **Consequences**: Matches Blender’s north→+Y after the Y-flip in the `ue_metric_v1` export preset.
## ADR-7: Headless Blender via bpy scripts, not MCP
- **Context**: Blender skill packs assume an MCP server; the project does not run one.
- **Decision**: All Blender work driven by Python scripts in `Tools/`. Skill packs used as method guides (checklists, naming, proportions) only.
- **Consequences**: Scripts are version-controlled and repeatable. No live Blender session needed for CI.
@@ -0,0 +1,25 @@
# Quality Requirements
## Quality Requirements Overview
| Category | Requirement | Priority |
| --- | --- | --- |
| Performance efficiency | 60 FPS at 2560×1440, documented preset, no frame generation | 1 |
| Reliability / Terrain | Zero raycast holes across sidewalks and carriageway (84-sample transect) | 1 |
| Accuracy / Fidelity | Building placement matches reference photography within ~1 m | 2 |
| Accuracy / Fidelity | Street scale (lane width, sidewalk width) within ~10 cm of measured profile | 2 |
| Usability | WASD + mouse look, jump, reset (R), pause (Escape) — no tutorial needed | 2 |
| Maintainability | Fresh checkout builds with documented engine installation | 2 |
| Portability | Linux build packages and runs; Windows build ships | 3 |
## Quality Scenarios
| ID | Source/Stimulus | Response | Metric |
| --- | --- | --- | --- |
| QS-1 | Player walks both sides of the demo | No fall-through, character stays at ground Z | 0 raycast holes in 84-sample transect |
| QS-2 | Player holds W for 2 s on flat road | Character moves forward | ≥ 200 cm displacement, Z constant within ±5 cm |
| QS-3 | Player presses R | Character teleports to spawn | < 100 cm from `StartLocation` |
| QS-4 | Player presses Escape | Game pauses, cursor appears | `IsGamePaused()` = true |
| QS-5 | Player presses Escape while paused | Game unpauses, cursor hides | `IsGamePaused()` = false |
| QS-6 | Game runs on dev workstation at 1440p | Frame rate stable | ≥ 60 FPS, 99th percentile ≥ 55 FPS |
| QS-7 | Fresh checkout + engine install | Build succeeds | Exit code 0, runnable binary produced |
+10
View File
@@ -0,0 +1,10 @@
# Risks and Technical Debts
| ID | Risk / Debt | Priority | Mitigation |
| --- | --- | --- | --- |
| R-1 | Terrain bbox proxy misdiagnosed as fall-through — already fixed, but the root-cause path (Interchange defaults to simple collision) could recur on reimport | High | `UGigLineAssetTools` runs in the build script; always reassert complex-as-simple after any reimport. Documented in `MEMORY.md` and ADR-3. |
| R-2 | Blosm 3Dsimple on Blender 5.2 — worked around, not resolved; `3Drealistic` mode untested | Medium | Pin Blosm 2.7.28 and Blender 5.2 LTS; test realistic mode before depending on it. |
| R-3 | Kerb height is an assumption (DEM does not resolve it) | Medium | Record the assumption in `Geo/verification/`; measure from reference photography if possible; mark as unresolved until verified. |
| R-5 | `GigLine SIM` / `GigLine GROUND` TEMP diagnostic logging still in `GigLineCharacter.cpp` | Medium | Strip behind a cvar or remove entirely before the `v0.1.0` release tag. |
| TD-1 | Materials are Unreal-only — no Blender preview round-trip | Low | Accepted (ADR-2). Look-dev happens in UE; screenshot review catches issues. |
| TD-2 | No CI pipeline — all builds and QA are manual on the dev workstation | Low | Acceptable for `0.x`. CI is a `0.3.0+` concern. |
+21
View File
@@ -0,0 +1,21 @@
# Glossary
| Term | Definition |
| --- | --- |
| Blosm | Blender add-on (2.7.28) that imports OSM data as 3D building and street blockouts. Used for layout only — not authoritative for façade or height. |
| PIE | Play In Editor — an Unreal Editor feature that runs the current map as a live game session inside the editor window, with full game code, input, and physics, without packaging a build. Used for interactive verification (walk, reset, pause) before the scripted QA self-test. |
| Complex-as-simple collision | `CTF_UseComplexAsSimple`: Unreal collision mode where the render mesh triangles are used as the collision surface, instead of a simplified proxy. Required for terrain so the player walks on the actual ground shape. |
| DEM | Digital Elevation Model. 3DEP 1 m resolution GeoTIFF covering the demo bounds. Source of terrain heightfield. |
| El Capitan | El Capitan Entertainment Centre, 6838 Hollywood Blvd. The hero building of the demo. |
| Enhanced Input | Unreal’s modern input system (UE 5+). Uses Input Actions (`IA_*`) and Input Mapping Contexts (`IMC_*`) instead of legacy axis/action bindings. |
| EPSG:26911 | UTM zone 11N, the projected CRS used for the demo area. Local origin recorded in `Geo/`. |
| Hero façade | The detailed, reference-calibrated model of the El Capitan building frontage. |
| MPFB | MakeHuman Plugin for Blender (2.0.18). Generates parametric human base meshes with rigs and baked materials. |
| PlayerStart | Unreal actor that defines where the player pawn spawns. Positioned above grade so the capsule does not embed in the floor on spawn. |
| QA self-test | The `-qaselftest` command-line switch that runs an automated behaviour check (walk, reset, pause, ground transect) via a core ticker. |
| Site | The terrain, road, sidewalks, kerbs, and crossing markings — the ground-level infrastructure of the demo area. |
| Squash merge | Git merge strategy that compresses all branch commits into one commit on `main`, keeping a linear history. |
| Swept transect | A QA technique that raycasts vertically at regular intervals along a path (e.g. every 200 cm along both sidewalks and the carriageway) and counts holes (misses). Zero holes = the block is fully walkable. |
| UE | Unreal Engine — Epic Games' real-time 3D creation platform (version 5.8.4 in this project). Comprises the Unreal Editor (including PIE, content browser, viewport, blueprint editor), a C++ game framework, and runtime systems for rendering (Vulkan RHI on Linux), physics (Chaos), and input (Enhanced Input). The game project is `GigLine.uproject`; game code is a single C++ module in `Game/GigLine/Source/GigLine/`. |
| World Partition | Unreal's world composition system for large open-world levels. Not yet used in `0.1.0` but planned for `0.3.0+` driving routes. |
| Y convention | In Unreal, south = +Y, north = −Y (confirmed from street mesh orientation). In Blender, north = +Y; the `ue_metric_v1` export preset flips Y. |
+13
View File
@@ -0,0 +1,13 @@
**About arc42**
<div data-wrapper="1" role="lead">
arc42, the template for documentation of software and system architecture.
</div>
Template Version 0.1.0. initial architecture documentation, 2026-11-08
Created, maintained and © by Dr. Peter Hruschka, Dr. Gernot Starke and contributors. See <https://arc42.org>.
Adapted for the Gig Line project (Linux-first walking/driving demo around 6840 Hollywood Blvd).
+518
View File
@@ -0,0 +1,518 @@
# Gig Line Architecture Documentation
> Linux-first walking/driving demo around 6840 Hollywood Blvd
Version 0.1.0 — 2026-11-08 — initial architecture documentation
# Introduction and Goals
Gig Line is a Linux-first Unreal Engine 5 game that reconstructs a walkable (and eventually drivable) stretch of Hollywood Boulevard around 6840 Hollywood Blvd / El Capitan Entertainment Centre. The initial release (`0.1.0`) delivers a first-person walking demo of the south-side block between Highland Avenue and Orange Drive, plus north-side buildings within 50 m of the frontage.
## Requirements Overview
| ID | Description |
| --- | --- |
| F-1 | Walk both sides of Hollywood Blvd in the demo area without falling through geometry |
| F-2 | First-person movement (WASD + mouse look), jump, reset (R), pause (Escape) |
| F-3 | Fixed daytime lighting and atmosphere (sun, sky, fog, exposure) |
| F-5 | Designated street crossing with zebra markings at signalised crossings |
| F-6 | Building façades recognisable against reference photography |
| F-7 | Terrain, road, and sidewalk follow real-world DEM elevation |
| F-8 | 60 FPS at 1440p on the documented workstation and quality preset |
Full requirements and progress are in [TODO.md](../../TODO.md); version direction is in [ROADMAP.md](../../ROADMAP.md).
## Quality Goals
| Priority | Quality Goal | Scenario |
| --- | --- | --- |
| 1 | **Performance efficiency** | 60 FPS at 2560×1440 on RTX 5070 Ti, documented preset, no frame generation |
| 2 | **Terrain walkability** | Zero raycast holes across both sidewalks and the carriageway (84-sample transect passes) |
| 3 | **Geographic fidelity** | Building placement, street scale, and façade identity match reference photography |
| 4 | **Build reproducibility** | Fresh checkout + documented engine path produces a runnable Linux build |
| 5 | **Provenance** | Every committed asset has recorded provenance |
## Stakeholders
| Role | Contact | Expectations |
| --- | --- | --- |
| Owner / Director | zwitschi | Sets scope, approves screenshots, cuts release tags |
| AI coding agents | multiple sessions | Read `AGENTS.md`, `TODO.md`, `ROADMAP.md`, and this document for architecture constraints; never commit directly to `main` |
| Players | end users | Walk the demo at 60 FPS without falling through the world; see a recognisable Hollywood block |
# Architecture Constraints
## Technical Constraints
| Constraint | Explanation |
| --- | --- |
| Engine: Unreal 5.8.4 | Pinned; no bundled Linux clang — uses system clang 23.1.4. Game targets use `BuildSettingsVersion.V7`. |
| Platform: Linux first | Developed and tested on Bazzite Linux (Fedora 45 base). Windows builds ship but are secondary. |
| GPU: RTX 5070 Ti 16 GB | Dev workstation target. Vulkan RHI; open kernel module driver 615.78.08. |
| No static lighting | `r.AllowStaticLighting=False`. The demo is a fixed daytime scene lit entirely dynamically to avoid "LIGHTING NEEDS TO BE REBUILT" warnings. |
| Blender→UE via FBX | Flat diffuse colours do not survive FBX→Unreal import. All materials are authored in UE. |
| Terrain collision | UE 5.8 exposes `CollisionTraceFlag` (not `bUseComplexAsSimpleCollision`); Python cannot set it. Must use C++ helper `UGigLineAssetTools::SetComplexCollisionAsSimple`. |
| Coordinate convention | South = +Y, north = −Y (ground truth = street mesh orientation). 1 m in Blender = 100 cm in Unreal. |
## Organisational Constraints
| Constraint | Explanation |
| --- | --- |
| Squash-merge to `main` | Every PR is squash-merged for a linear history. No direct commits to `main` except integration merges. |
| Visual review required | Any change affecting appearance must produce screenshots for human approval before being marked done. |
| Asset provenance | Every committed asset must have verified licence and redistribution permission recorded in `THIRD_PARTY_NOTICES.md`. |
| Reference photography | Local only, gitignored, never committed. Used for façade work under fair-use / internal-development rationale. |
| LFS for binaries | `.uasset`, `.umap`, `.blend` tracked with Git LFS; `Binaries/`, `Intermediate/`, `Saved/` are gitignored. |
## Conventions
| Convention | Explanation |
| --- | --- |
| Semantic versioning | `MAJOR.MINOR.PATCH`. `0.x` documents breaking changes. `1.0.0` = agreed game scope. |
| Task tracking in `TODO.md` | Single central task list. `- [ ]` open, `- [-]` or `- [~]` in progress, `- [x]` done, `- [A]` active, `- [D]` deferred. |
| Headless Blender | All Blender work driven by `bpy` scripts in `Tools/`, not MCP. Blender skills are method guides only. |
| Named export presets | `ue_metric_v1` for static meshes, `ue_character_v1` for skeletal. Every exporter calls the metre→centimetre conversion. |
# Context and Scope
## Business Context
The system is a self-contained single-player game. It has no server backend, no multiplayer, and no live service dependencies. Its communication partners are:
| Partner | Inbound | Outbound |
| --- | --- | --- |
| Player (keyboard/mouse) | WASD, mouse, R (reset), Escape (pause), Space (jump) | Rendered 3D scene + HUD |
| OSM data | Extracted building footprints, street geometry, elevation | — |
| Filesystem | Engine content (`.uasset`, `.umap`), Blender sources (`.blend`), DEM GeoTIFF | Saved maps, screenshots, packaged build |
## Technical Context
| Channel | Protocol / Format | Purpose |
| --- | --- | --- |
| Keyboard + mouse | Enhanced Input (IA\_\*, IMC_Default) | First-person movement, look, jump, reset, pause |
| Vulkan RHI | GPU rendering | Scene display at 60 FPS / 1440p |
| FBX | Autodesk FBX (binary) | Blender→Unreal mesh and skeleton transfer |
| GeoTIFF | 3DEP 1 m DEM | Terrain heightfield source |
| OSM XML | Overpass API extract | Building footprint and street layout source |
| Git / LFS | HTTPS to Gitea remote | Source control, binary asset storage |
# Solution Strategy
## Technology decisions
- **Unreal Engine 5.8.4** for rendering, World Partition, physics (Chaos), and the Enhanced Input system. Chosen for its Linux support, open C++ source, and real-time rendering quality.
- **Blender 5.2 LTS** for all 3D modelling, UV work, and collision mesh authoring. Driven headlessly via `bpy` Python scripts, not MCP.
- **QGIS 4.2.3** for geographic data cleanup, coordinate conversion (EPSG:26911), and DEM preparation.
- **Blosm 2.7.28** for OSM building and street blockouts inside Blender (layout data only — not authoritative façade, height, or pavement detail).
- **MPFB 2.0.18** for human base meshes and baked materials (NPCs in `0.2.0+`).
## Top-level decomposition
The system is a single Unreal game module (`GigLine`) with no plugins beyond `EnhancedInput`. Content is organised by domain under `/Game/Hollywood/`:
- `Site/` — terrain, road, sidewalks, kerbs, crossing markings, site materials
- `ElCapitan/` — hero façade mesh and materials
- `Frontage/` — south-side block building meshes
- `North/` — north-side building meshes
- `Input/` — Enhanced Input actions and mapping context
All modelling and geographic processing live outside the game project in `Art/`, `Geo/`, and `Tools/`. The `Game/` directory contains only engine-ready content and C++ source.
## Quality achievement
- **Performance**: dynamic-only lighting (no build step, no static shadow maps), world-space-noise materials (cheap procedural variation), minimal overdraw (simple street-level geometry).
- **Walkability**: complex-as-simple collision on terrain (C++ helper forces the flag on the asset), block-all collision on every site mesh, swept transect QA verifies zero raycast holes.
- **Fidelity**: geographic data verified before modelling (footprints, bounds, DEM); building placement checked against street mesh, never in isolation; reference photography drives façade work.
- **Reproducibility**: pinned toolchain in `Docs/toolchain.md`, named export presets, `Tools/check-lfs.sh` gate, fresh-checkout build procedure in `Docs/build.md`.
# Building Block View
## Whitebox Overall System
The system decomposes into five top-level building blocks:
| **Name** | **Responsibility** |
| --- | --- |
| Gameplay | Player pawn, movement, camera, input bindings, HUD, game mode |
| Site | Terrain, road, sidewalks, kerbs, crossing markings, site-level collision |
| Façades | Hero façade (El Capitan), south block frontage, north-side buildings |
| Art Pipeline | Blender models, geographic data, export scripts, import scripts |
| QA & Review | Screenshot pipeline, self-test harness, visual review workflow |
### Gameplay
C++ classes in `Game/GigLine/Source/GigLine/`:
| Class | Responsibility | Key detail |
| --- | --- | --- |
| `AGigLineCharacter` | First-person player pawn | 1.75 m capsule, 4.5 m/s walk, Enhanced Input bindings, reset, pause, `-qaselftest` harness |
| `AGigLineGameModeBase` | Sets game mode and HUD class | `HUDClass = AGigLineHUD` |
| `AGigLineHUD` | HUD text overlay | `DrawText` every frame, bottom-left |
| `AGigLineNPCPerson` | NPC prefab (future) | Capsule + visible MPFB body, feet on Z 0 |
| `AGigLineShotActor` | Screenshot camera walker | Drives cameras in `-game` mode, `FScreenshotRequest`, quits |
| `UGigLineAssetTools` | C++ collision helpers | `SetComplexCollisionAsSimple` on `StaticMesh` assets |
Input assets in `/Game/Input/`: `IA_Move`, `IA_MoveBack`, `IA_Strafe`, `IA_StrafeLeft`, `IA_Look`, `IA_Jump`, `IA_Reset`, `IA_Pause`, bound in `IMC_Default`.
### Site
Static meshes and materials in `/Game/Hollywood/Site/`:
| Asset | Role |
| --- | --- |
| `Terrain_DEM` | Ground heightfield (complex-as-simple collision) |
| `Road_Hollywood` | Carriageway surface (block-all collision) |
| `Sidewalk_North`, `Sidewalk_South` | Pedestrian surfaces (block-all collision) |
| `Kerb_North`, `Kerb_South` | Kerb edges |
| `Crossing_Markings` | Zebra markings at signalised crossing |
| `M_Asphalt`, `M_Concrete`, `M_KerbConcrete`, `M_Ground`, `M_RoadPaint`, `M_Stucco`, `M_FacadeStone` | Procedural materials (world-space-noise blend + roughness) |
### Façades
| Asset | Role |
| --- | --- |
| `SM_HeroFacade` | El Capitan Entertainment Centre detailed façade |
| `SM_ElCapitanTheatre`, `SM_Budget_6822`, `SM_Hollywood_6806`, `SM_Hollywood_6902`, `SM_Hollywood_6922`, `SM_LeeDrug_6800` | South block frontage (6 buildings) |
| `SM_Madame_Tussauds`, `SM_TCL_Chinese_Theatre` | North-side buildings (within 50 m) |
### Art Pipeline
Python scripts in `Tools/` drive the content pipeline:
| Script group | Purpose |
| --- | --- |
| `export_*`, `hero_facade_build` | Blender headless export (FBX) |
| `ue_import_*`, `ue_hero_facade_import` | Unreal headless import + placement |
| `ue_build_demo_map`, `ue_build_review_map` | Map assembly (lighting, meshes, player start) |
| `ue_create_input_assets` | Enhanced Input actions + mapping context |
| `render_screenshots.sh` | Screenshot capture pipeline |
| `judge_screenshot.py` | Vision-model pre-check (optional) |
Source data: `Art/blender/` (`.blend` files), `Geo/` (OSM extracts, DEM, verification docs).
### QA & Review
- `AGigLineShotActor` walks fixed cameras in `-game` mode, captures PNGs, and quits.
- `-qaselftest` switch on `AGigLineCharacter` runs a core-ticker harness: walk 2 s, reset, pause/unpause, ground transect — all PASS/FAIL logged.
- `Screenshots/` holds committed PNGs and `README.md` approval table.
## Level 2
### White Box Gameplay — Input Flow
1. `AGigLineCharacter::SetupPlayerInputComponent` binds `IA_*` actions to `MoveForward`, `MoveBack`, `MoveStrafe`, `MoveLeft`, `Look`, `StartJump`/`StopJump`, `ResetToStart`, `TogglePause`.
2. Movement delegates to `ApplyMove(Forward, Strafe)` → `AddMovementInput`.
3. Look applies yaw/pitch scaled by `LookSensitivity` (Y invertable via `bInvertLookY`).
4. `ResetToStart` teleports to `StartLocation` (captured on first grounded frame), clears velocity, resets control rotation.
5. `TogglePause` toggles `APlayerController::SetPause`, swaps input mode between `FInputModeGameOnly` and `FInputModeUIOnly`, shows/hides mouse cursor.
### White Box Site — Collision Model
- Every site mesh (road, sidewalks, kerbs, crossing) is `STATIC` with `BlockAll` collision.
- `Terrain_DEM` uses `CTF_UseComplexAsSimple` (forced by `UGigLineAssetTools` at build time, saved on the asset).
- The terrain corridor is carved to half-width 10.7 m around the road centreline so the player can walk the carriageway and both sidewalks.
- Ground Z: north sidewalk 395, road 366, south sidewalk 317 (3.94 m fall along the block, matching the DEM).
# Runtime View
## Walk the block
1. Player launches game → `AGigLineGameModeBase` spawns `AGigLineCharacter` at `PlayerStart` `(-13336, -1150, 600)`.
2. Character falls 144 cm to road surface (Z 456) and begins walking. `StartLocation` captured on first grounded frame.
3. Player holds W → `IA_Move` fires → `ApplyMove(1, 0)` → `AddMovementInput(ForwardVector, 1)` → capsule moves at 450 cm/s.
4. Mouse move → `IA_Look` fires → yaw/pitch applied to controller rotation → camera follows (attached to capsule at 160 cm eye height).
5. Player presses R → `ResetToStart` → teleport to `StartLocation`/`StartRotation`, `StopMovementImmediately`, reset control rotation.
6. Player presses Escape → `TogglePause` → `SetPause(true)`, switch to `FInputModeUIOnly`, show cursor. World timers stop; core tickers continue.
7. Player presses Escape again → `TogglePause` → `SetPause(false)`, switch to `FInputModeGameOnly`, hide cursor.
## Screenshot capture
1. `Tools/render_screenshots.sh` compiles editor, builds `ReviewMap` from `Tools/ue_build_review_map.py`, launches game in `-game` mode.
2. `AGigLineShotActor` iterates the camera list, sets view, calls `HighResShot 1280x720`, waits for capture.
3. On last camera, requests engine quit. PNGs are copied to `Screenshots/` and committed.
## QA self-test (`-qaselftest`)
1. Core ticker starts at `BeginPlay` (not a world timer — world timers freeze on pause).
2. 1.0 s: begin driving forward input.
3. 3.0 s: stop, measure displacement (> 200 cm = PASS).
4. 3.5 s: call `ResetToStart`.
5. 4.0 s: measure distance from spawn (< 100 cm = PASS).
6. 4.5 s: call `TogglePause`, check `IsGamePaused()` (= true = PASS).
7. 5.5 s: call `TogglePause`, check `IsGamePaused()` (= false = PASS).
8. 6.0 s: swept ground transect across three lanes (Y −1400, −167, +1000), X −14900 to −9420 at 200 cm spacing. 84 samples, 0 holes = PASS.
9. 7.0 s: final verdict logged.
## Build and import pipeline
1. Blender headless (`Tools/export_*.py`) reads `.blend` files, applies metre→centimetre conversion, exports FBX with `ue_metric_v1` preset.
2. Unreal headless (`Tools/ue_import_*.py`) imports FBX via Interchange, places meshes at geographic coordinates, sets collision profiles.
3. `Tools/ue_build_demo_map.py` assembles `DemoMap`: creates/repositions actors, sets lighting, saves map and assets.
# Deployment View
## Infrastructure Level 1 — Development Workstation
| Node | Specification |
| --- | --- |
| Dev workstation | Bazzite Linux 45 (Fedora 45 base, immutable ostree), Ryzen 7 7700X, 64 GB RAM, RTX 5070 Ti 16 GB, Vulkan RHI |
| Engine install | `/run/media/system/DATA/Engines/UE_5.8.4` (73 GiB, external to repo) |
| Project repo | `/run/media/system/DATA/Documents/06-games/gig-line` (Git + LFS, remote at `git.allucanget.biz`) |
| Build output | `/run/media/system/DATA/Builds/GigLine` (packaged Linux build, ~1.1 GiB) |
Motivation
Single-machine development. No build farm, no CI pipeline yet. The engine, toolchain, and GPU are all on the same host.
Quality and Performance Features
- Vulkan RHI on RTX 5070 Ti, open kernel module driver 615.78.08.
- System clang 23.1.4 (no bundled engine clang) compiles game code.
- Build time: ~90 s incremental, ~5 min clean for the editor target.
Mapping of Building Blocks to Infrastructure
| Building Block | Infrastructure Element |
| --- | --- |
| Gameplay (C++) | Dev workstation — compiled to `libUnrealEditor-GigLine.so`, loaded by engine |
| Site + Façades (UE content) | Dev workstation — `.uasset`/`.umap` in `Game/GigLine/Content/` |
| Art Pipeline (Python/Blender) | Dev workstation — headless Blender 5.2 Flatpak, Python 3.14.8 |
| QA & Review | Dev workstation — game run in `-game` mode, PNGs to `Screenshots/` |
| Git remote | `git.allucanget.biz` — Gitea instance, LFS-enabled |
## Infrastructure Level 2 — Packaged Build
| Element | Detail |
| --- | --- |
| Linux packaged build | Development config, ~1.1 GiB. Contains engine binaries, game module, cooked content, no editor. |
| Run command | `./GigLine.sh -windowed -ResX=2560 -ResY=1440` |
No server infrastructure. The game is a standalone executable.
# Cross-cutting Concepts
## Coordinate system and scale
- **Source CRS**: EPSG:26911 (UTM zone 11N). Local origin recorded in `Geo/`.
- **Blender**: metres. East → +X, north → +Y (Blender convention).
- **Unreal**: centimetres. East → +X, north → **−Y** (confirmed from street mesh: `Sidewalk_North` Y −1912..−1053, `Sidewalk_South` Y +718..+1447).
- **Conversion**: every exporter calls the metre→centimetre function. Import presets (`ue_metric_v1`, `ue_character_v1`) are named and repeatable.
Rule: place a building only against a neighbour or street mesh, never in isolation.
## Collision model
- Site meshes (road, sidewalks, kerbs, crossing): `STATIC` mobility, `BlockAll` collision profile.
- Terrain: `STATIC`, `CTF_UseComplexAsSimple` (forced by `UGigLineAssetTools` at build time, saved on the asset — the flag lives on the `UStaticMesh`/`UBodySetup`, not the map).
- Buildings: `STATIC`, simple collision (convex hull from mesh).
- Rule: the collision flag must be saved on the asset. Saving only the `.umap` silently reverts it.
## Lighting
- Dynamic-only: `r.AllowStaticLighting=False`. All lights are `MOVABLE`.
- Fixed daytime rig: directional light (sun, −62° pitch), sky light, exponential height fog, post-process volume with fixed exposure.
- Rationale: avoids "LIGHTING NEEDS TO BE REBUILT" warnings; no bake step; the scene never changes time of day.
## Materials
- All materials authored in Unreal, not carried from Blender (FBX import loses diffuse colours).
- World-space-noise blend nodes for cheap procedural variation (asphalt grain, concrete speckle, stucco roughness).
- Instance parameters where tunable (e.g. wetness, ageing).
## Geographic data provenance
- OSM extract is layout data only — not authoritative for façade, height, or pavement detail.
- DEM from 3DEP 1 m; elevation verified against cross-sections in `Geo/verification/`.
- Reference photography (local, gitignored) drives manual façade work.
## QA harness
- `-qaselftest`: core ticker (not world timer) so pause cannot freeze the test that proves pause works.
- `GigLine SIM` / `GigLine GROUND`: TEMP diagnostic logging, to be removed before release tag.
- Screenshot approval: human must tick `Screenshots/README.md` before visual work is marked done.
# Architecture Decisions
## ADR-1: Dynamic-only lighting for the demo
- **Context**: The demo is a fixed daytime scene. Stationary lights demand a lighting build and produce "LIGHTING NEEDS TO BE REBUILT" warnings.
- **Decision**: `r.AllowStaticLighting=False`, all lights `MOVABLE`.
- **Consequences**: No bake step, no warning, simpler iteration. Slightly higher GPU cost per frame (no baked shadows) — acceptable for the demo scope.
## ADR-2: Materials authored in Unreal, not imported from Blender
- **Context**: FBX→Unreal import maps all Blender diffuse colours to white. Flat colour materials do not survive the pipeline.
- **Decision**: Build all materials (asphalt, concrete, stucco, façade stone, road paint) directly in Unreal with procedural nodes.
- **Consequences**: Materials cannot be previewed in Blender look-dev. Must round-trip to Unreal for visual check. But: full PBR control, world-space noise, no import surprise.
## ADR-3: Complex-as-simple collision on terrain via C++ helper
- **Context**: UE 5.8 removed `bUseComplexAsSimpleCollision`. Python cannot set `CollisionTraceFlag` on `StaticMesh`/`BodySetup` at all. The Interchange default is bbox-only simple collision, which placed an invisible lid 8 m above the street.
- **Decision**: `UGigLineAssetTools::SetComplexCollisionAsSimple` (C++) forces `CTF_UseComplexAsSimple` and saves the asset.
- **Consequences**: One C++ helper class. The flag must be saved on the asset (not just the map). No Python workaround exists.
## ADR-4: Reset captures StartLocation on first grounded frame
- **Context**: `BeginPlay` captures `StartLocation` at the PlayerStart, which is above grade (Z 600) so the capsule does not spawn embedded. Every reset dropped the player 1.4 m back into the air.
- **Decision**: Repeating 0.25 s timer captures `StartLocation` once the character reports `IsMovingOnGround()`, then clears itself.
- **Consequences**: Reset is correct. A one-shot timer at 2 s is wrong (fires after walking begins, records mid-stride position).
## ADR-5: Core ticker for QA self-test, not world timer
- **Context**: Pausing the game stops world timers. A timer-driven test freezes exactly when it proves pause works.
- **Decision**: `FTSTicker::GetCoreTicker()` drives the `-qaselftest` harness.
- **Consequences**: Test runs even while paused. Must be enabled explicitly (`-qaselftest`), never in normal play.
## ADR-6: South = +Y, north = −Y in Unreal
- **Context**: The street mesh established this convention: `Sidewalk_North` has Y range −1912..−1053, `Sidewalk_South` has +718..+1447.
- **Decision**: Adopt it project-wide. Never place a building by assumption; always verify against a street mesh or neighbour.
- **Consequences**: Matches Blender’s north→+Y after the Y-flip in the `ue_metric_v1` export preset.
## ADR-7: Headless Blender via bpy scripts, not MCP
- **Context**: Blender skill packs assume an MCP server; the project does not run one.
- **Decision**: All Blender work driven by Python scripts in `Tools/`. Skill packs used as method guides (checklists, naming, proportions) only.
- **Consequences**: Scripts are version-controlled and repeatable. No live Blender session needed for CI.
# Quality Requirements
## Quality Requirements Overview
| Category | Requirement | Priority |
| --- | --- | --- |
| Performance efficiency | 60 FPS at 2560×1440, documented preset, no frame generation | 1 |
| Reliability / Terrain | Zero raycast holes across sidewalks and carriageway (84-sample transect) | 1 |
| Accuracy / Fidelity | Building placement matches reference photography within ~1 m | 2 |
| Accuracy / Fidelity | Street scale (lane width, sidewalk width) within ~10 cm of measured profile | 2 |
| Usability | WASD + mouse look, jump, reset (R), pause (Escape) — no tutorial needed | 2 |
| Maintainability | Fresh checkout builds with documented engine installation | 2 |
| Portability | Linux build packages and runs; Windows build ships | 3 |
## Quality Scenarios
| ID | Source/Stimulus | Response | Metric |
| --- | --- | --- | --- |
| QS-1 | Player walks both sides of the demo | No fall-through, character stays at ground Z | 0 raycast holes in 84-sample transect |
| QS-2 | Player holds W for 2 s on flat road | Character moves forward | ≥ 200 cm displacement, Z constant within ±5 cm |
| QS-3 | Player presses R | Character teleports to spawn | < 100 cm from `StartLocation` |
| QS-4 | Player presses Escape | Game pauses, cursor appears | `IsGamePaused()` = true |
| QS-5 | Player presses Escape while paused | Game unpauses, cursor hides | `IsGamePaused()` = false |
| QS-6 | Game runs on dev workstation at 1440p | Frame rate stable | ≥ 60 FPS, 99th percentile ≥ 55 FPS |
| QS-7 | Fresh checkout + engine install | Build succeeds | Exit code 0, runnable binary produced |
# Risks and Technical Debts
| ID | Risk / Debt | Priority | Mitigation |
| --- | --- | --- | --- |
| R-1 | Terrain bbox proxy misdiagnosed as fall-through — already fixed, but the root-cause path (Interchange defaults to simple collision) could recur on reimport | High | `UGigLineAssetTools` runs in the build script; always reassert complex-as-simple after any reimport. Documented in `MEMORY.md` and ADR-3. |
| R-2 | Blosm 3Dsimple on Blender 5.2 — worked around, not resolved; `3Drealistic` mode untested | Medium | Pin Blosm 2.7.28 and Blender 5.2 LTS; test realistic mode before depending on it. |
| R-3 | Kerb height is an assumption (DEM does not resolve it) | Medium | Record the assumption in `Geo/verification/`; measure from reference photography if possible; mark as unresolved until verified. |
| R-5 | `GigLine SIM` / `GigLine GROUND` TEMP diagnostic logging still in `GigLineCharacter.cpp` | Medium | Strip behind a cvar or remove entirely before the `v0.1.0` release tag. |
| TD-1 | Materials are Unreal-only — no Blender preview round-trip | Low | Accepted (ADR-2). Look-dev happens in UE; screenshot review catches issues. |
| TD-2 | No CI pipeline — all builds and QA are manual on the dev workstation | Low | Acceptable for `0.x`. CI is a `0.3.0+` concern. |
# Glossary
| Term | Definition |
| --- | --- |
| Blosm | Blender add-on (2.7.28) that imports OSM data as 3D building and street blockouts. Used for layout only — not authoritative for façade or height. |
| PIE | Play In Editor — an Unreal Editor feature that runs the current map as a live game session inside the editor window, with full game code, input, and physics, without packaging a build. Used for interactive verification (walk, reset, pause) before the scripted QA self-test. |
| Complex-as-simple collision | `CTF_UseComplexAsSimple`: Unreal collision mode where the render mesh triangles are used as the collision surface, instead of a simplified proxy. Required for terrain so the player walks on the actual ground shape. |
| DEM | Digital Elevation Model. 3DEP 1 m resolution GeoTIFF covering the demo bounds. Source of terrain heightfield. |
| El Capitan | El Capitan Entertainment Centre, 6838 Hollywood Blvd. The hero building of the demo. |
| Enhanced Input | Unreal’s modern input system (UE 5+). Uses Input Actions (`IA_*`) and Input Mapping Contexts (`IMC_*`) instead of legacy axis/action bindings. |
| EPSG:26911 | UTM zone 11N, the projected CRS used for the demo area. Local origin recorded in `Geo/`. |
| Hero façade | The detailed, reference-calibrated model of the El Capitan building frontage. |
| MPFB | MakeHuman Plugin for Blender (2.0.18). Generates parametric human base meshes with rigs and baked materials. |
| PlayerStart | Unreal actor that defines where the player pawn spawns. Positioned above grade so the capsule does not embed in the floor on spawn. |
| QA self-test | The `-qaselftest` command-line switch that runs an automated behaviour check (walk, reset, pause, ground transect) via a core ticker. |
| Site | The terrain, road, sidewalks, kerbs, and crossing markings — the ground-level infrastructure of the demo area. |
| Squash merge | Git merge strategy that compresses all branch commits into one commit on `main`, keeping a linear history. |
| Swept transect | A QA technique that raycasts vertically at regular intervals along a path (e.g. every 200 cm along both sidewalks and the carriageway) and counts holes (misses). Zero holes = the block is fully walkable. |
| UE | Unreal Engine — Epic Games' real-time 3D creation platform (version 5.8.4 in this project). Comprises the Unreal Editor (including PIE, content browser, viewport, blueprint editor), a C++ game framework, and runtime systems for rendering (Vulkan RHI on Linux), physics (Chaos), and input (Enhanced Input). The game project is `GigLine.uproject`; game code is a single C++ module in `Game/GigLine/Source/GigLine/`. |
| World Partition | Unreal's world composition system for large open-world levels. Not yet used in `0.1.0` but planned for `0.3.0+` driving routes. |
| Y convention | In Unreal, south = +Y, north = −Y (confirmed from street mesh orientation). In Blender, north = +Y; the `ue_metric_v1` export preset flips Y. |
---
**About arc42**
<div data-wrapper="1" role="lead">
arc42, the template for documentation of software and system architecture.
</div>
Template Version 0.1.0. initial architecture documentation, 2026-11-08
Created, maintained and © by Dr. Peter Hruschka, Dr. Gernot Starke and contributors. See <https://arc42.org>.
Adapted for the Gig Line project (Linux-first walking/driving demo around 6840 Hollywood Blvd).
+23 -1
View File
@@ -46,9 +46,15 @@ Engine/Build/BatchFiles/RunUAT.sh BuildCookRun \
-archive -archivedirectory="/run/media/system/DATA/Builds/GigLine" \
-unattended
# Expected: "BUILD SUCCESSFUL" (~1 min for the blank project; archive ~1.1 GiB)
# Launch test: /run/media/system/DATA/Builds/GigLine/Linux/GigLine.sh
# Launch test (plain, no -map): /run/media/system/DATA/Builds/GigLine/Linux/GigLine.sh
```
**Launch test must open the DemoMap.** A plain `./GigLine.sh` should load
`/Game/Hollywood/DemoMap` (set via `DefaultEngine.ini` `GameMapsSettings`). If it
instead opens `/Engine/Maps/Templates/OpenWorld`, the game shows an empty
non-interactive "stuck" scene — check that `GameDefaultMap` is in
`DefaultEngine.ini`, not `DefaultGame.ini`. Confirm the loaded map in the engine
log: `.../Linux/GigLine/Saved/Logs/GigLine.log` (`LoadMap: /Game/Hollywood/DemoMap`).
The archive directory is deliberately **outside** the repository; never archive into the checkout.
## Headless content checks
@@ -105,3 +111,19 @@ Two rules that the pipeline enforces rather than documents:
## Packaged Linux build
- 2026-11-07: blank `Game/GigLine` Development build packaged via the command above; 1.1 GiB archive at `/run/media/system/DATA/Builds/GigLine/Linux`; `GigLine.sh` launched on the desktop session, Vulkan swapchain created, 759 frames rendered across a 60 s smoke test. Controls document to be written with the first playable (`0.1.0` Gameplay section).
## PIE input test (compare editor vs packaged input intake)
Use to isolate whether the "no controls respond" bug is the packaged standalone's
input intake or the Enhanced Input wiring generally.
- `Tools/run_pie.sh` opens the editor on `/Game/Hollywood/DemoMap`. Press the
green **Play** (PIE) button and walk / mouse-look.
- Interpretation:
- PIE **works** -> the Enhanced Input wiring is fine and the standalone
packaged build's SDL input intake is the environment-dependent bug.
- PIE **fails** too -> the Enhanced Input setup itself is broken; rebuild
the input config / character from scratch.
- The DemoMap is PIE-ready as committed: `GlobalDefaultGameMode` +
`GameDefaultMap=/Game/Hollywood/DemoMap`, and the map stages a PlayerStart at
`(-13336,-1150,600)` on the cut sidewalk with the walkable `AGigLineCharacter`.
+36
View File
@@ -0,0 +1,36 @@
# Controls (`0.1.0` walkable demo)
Keyboard and mouse. The same list is shown in-game, bottom-right of the frame
(`AGigLineHUD`), and the packaged Linux build ships with these bindings.
| Action | Key | Notes |
| --- | --- | --- |
| Move forward | `W` | 4 m/s walk |
| Move back | `S` | |
| Strafe right | `D` | |
| Strafe left | `A` | |
| Look | Mouse | Captured on launch; `Escape` releases it |
| Jump | `Space` | |
| Reset to start | `R` | Returns to the grounded spawn point |
| Pause / release mouse | `Escape` | Second press resumes and recaptures the mouse |
## Notes
- Movement is relative to where you are looking (controller yaw), so the capsule
stays upright on the sloped street.
- The mouse is captured on launch, so looking works without clicking first.
`Escape` toggles the capture as part of pause.
- Bindings are Enhanced Input actions in `/Game/Input` built by
`Tools/ue_create_input_assets.py`; the mapping context is `IMC_Default`.
## Testing input without a physical keyboard
`Tools/input_test.py` injects OS-level keyboard/mouse events through a virtual
`/dev/uinput` device (see `Tools/input_emulation.py`), e.g.:
```bash
python3 Tools/input_test.py --sequence move:2,look:3
```
It drives the focused game window exactly like real hardware, which is how the
controls were regression-tested on the packaged build.
+3 -5
View File
@@ -7,7 +7,7 @@ Licensing is tracked in four separate layers. Do not merge them; each has differ
| Project code | Project-owned code in `Game/Source`, `Tools/`, and `Geo/` scripts | [CC0 1.0](../LICENSE), chosen by the project owner. |
| Project art | Blender sources, baked textures, exported meshes | Project-owned art is [CC0 1.0](../LICENSE); third-party contributions retain their own terms. See the asset notices. |
| Engine | Unreal Engine | Proprietary / source-available. Not redistributable as engine source. Distribution follows Epic's terms. |
| Geographic data | OSM extract, elevation data | ODbL and the elevation provider's own licence. Attribution and share-alike review required before distributing a derived database. |
| Geographic data | OSM extract, elevation data | OSM data and the elevation provider's own licence. Used for local, private development only. |
Project-owned documentation is also dedicated under [CC0 1.0](../LICENSE). Third-party material retains its own terms.
@@ -16,14 +16,12 @@ Project-owned documentation is also dedicated under [CC0 1.0](../LICENSE). Third
1. An asset whose licence cannot be verified does not get committed. Record it as unresolved in [THIRD_PARTY_NOTICES.md](../THIRD_PARTY_NOTICES.md) and replace it with a redistributable asset before the release it would ship in.
2. Reference photography is dev-only material and is **never committed**. It lives in the gitignored `Art/reference/` tree, is used for internal, private development only, and is not shipped or baked into shipped textures. Local reference notes are also excluded from version control.
3. OSM is layout data only. Do not treat it as authoritative for façades, heights, or pavement detail, and do not import it as a texture or panorama source.
4. Never use extracted Google imagery, Street View, or geometry. No exception.
5. Attribution: "© OpenStreetMap contributors" with a link to <https://www.openstreetmap.org/copyright> appears in the shipped build's UI (a `0.1.0` release check) and in the notices.
6. Credentials, licence keys, and account tokens never enter the repository. Engine licence acceptance is recorded by date and account email only.
4. Credentials, licence keys, and account tokens never enter the repository. Engine licence acceptance is recorded by date and account email only.
## Open decisions
- **ODbL posture.** Decide whether distributed builds contain a derived geographic database (a modified OSM extract) or only rendered geometry. The answer changes the share-alike obligations for the shipped product. Record the decision here and in the release notes.
- **Reference photography retention.** Decided 2026-11-06: reference images are **not** committed. Licensing is not the gate for local, private development use, so images are simply kept out of the repository (`Art/reference/` is gitignored) and their provenance stays in the local sidecars. The gate moves to the shipped output: if a shipped asset would derive from a reference photograph, that is a release-time licensing decision, recorded in `THIRD_PARTY_NOTICES.md` before the asset ships.
- **ODbL posture for OSM-derived geometry.** Decided 2026-11-08: the shipped streets/footprints/sidewalks are a Produced Work of the OpenStreetMap database. Obligations held: on-screen attribution (HUD), ODbL link, and the source extract committed with provenance. Share-alike applies to the data layer only; project code and art stay CC0. Full review in [Docs/odbl-review.md](Docs/odbl-review.md).
## Provenance sources
+82
View File
@@ -0,0 +1,82 @@
# ODbL review — OpenStreetMap-derived data in the shipped build
Status: **decision recorded 2026-11-08** (closes the TODO "Review ODbL obligations" item).
## 1. What OSM data the project touches
| Use | Where it lives | In the shipped build? |
| --- | --- | --- |
| Raw OSM extract (Hollywood block, 2026-11-06) | `Geo/raw/osm-api-map-2026-11-06.osm` (+2 building ways `.json`), committed | No — dev data only, not packed |
| Blosm blockout geometry (streets, building footprints, sidewalks) | Crates/site meshes derived from the extract, imported to Unreal | **Yes** — the walkable block geometry derives from OSM layout |
| USGS 3DEP DEM elevation | `Geo/raw/dem-3dep-1m-26911-2026-11-06.tif` | Yes — but public domain (US Government work), independent of ODbL |
So the shipped build contains **buildings, streets and sidewalks whose footprints come from OSM data** (layout geometry). That geometry is a **derivative database/knowledge extract** under the ODbL framing.
## 2. ODbL obligations (Open Database Licence 1.0)
ODbL = Open Database Licence (Open Data Commons, version 1.0). The OSM community contract uses it for the OpenStreetMap database.
Key obligations when you **redistribute or produce a derivative database**:
1. **Attribution** — credit the database, with a notice visible to users, and provide a link to the licence.
2. **Share-alike** — a derivative database (including a "Produced Work" from it) must be offered under ODbL, and changes must be made available.
3. **Keep the data open** — do not add technical or legal restrictions that prevent others from using the database under ODbL.
4. **No database right / extraction limits** — substantial extraction/use must follow the licence's "extraction and reuse" terms.
Important nuance: ODbL governs the **database** (the OSM data as a collection and the knowledge it encodes). **Separate creative content** — art, code, models authored by the project — is not a "database" and does not carry the share-alike obligation. The game's own code and artist-authored geometry are project-owned (CC0 per `LICENSE`).
## 3. Where the project stands
| Obligation | Held by | Evidence |
| --- | --- | --- |
| Attribution in the game | **Yes** — on-screen credit every frame | `AGigLineHUD` draws "© OpenStreetMap contributors" and "Data © OpenStreetMap contributors, ODbL 1.0 — openstreetmap.org/copyright" bottom-left (`GigLineHUD.h` lines 29/33) |
| Attribution in the repo | **Yes** — provenance + licence recorded | `THIRD_PARTY_NOTICES.md` Geographic data table; `Geo/manifests/2026-11-06-osm-api-map.md` |
| Licence text available | **Yes** — link + notice | HUD link to openstreetmap.org/copyright (ODbL); THIRD_PARTY_NOTICES points at ODbL 1.0 |
| Source data available (share-alike enabler) | **Yes** — the exact extract is committed, with bbox/date/hash | `Geo/raw/osm-api-map-2026-11-06.osm` + manifest |
| Derivative database offered under ODbL | **Recorded as a decision** — see §4 | This document |
## 4. Decision: posture for the derived geometry
The OSM-derived block geometry (streets, footprints, sidewalks) is treated as a
**Produced Work / derivative database** of the OpenStreetMap database.
- The **share-alike obligation is accepted for the data layer only**. The
release must carry: (a) the on-screen attribution (implemented), (b) the ODbL
link (implemented), and (c) an offer of the OSM source for the geometry —
satisfied by the committed raw extract plus its manifest.
- **Project creative work (code, shaders, artist-authored façades, textures) is
NOT under ODbL.** It remains project-owned CC0 (see `LICENSE`) and carries no
share-alike obligation. Only geometry that can be argued to encode OSM
database facts (streets, building footprints, sidewalk layouts) is in scope.
- **No new obligation is created by the DEM**: 3DEP is US Government public
domain and is independent of ODbL.
- **Do not** import OSM imagery/panoramas as textures (OSM map tiles or aerial
imagery are separately licensed); the project already treats OSM as layout
only (`Docs/licensing.md` rule 3).
Release checklist items this lands:
- [x] In-game attribution shown (HUD).
- [x] ODbL notice + link (HUD + THIRD_PARTY_NOTICES).
- [x] Source OSM data committed with provenance (manifest: date, bbox, SHA-256).
- [x] Posture recorded here for the release gate.
## 5. Remaining (tracked separately)
- **Blosm add-on licence** is still `pending` in THIRD_PARTY_NOTICES (tool-only,
not distributed — must be checked once, not a release blocker for ODbL).
- Façade signage/trademarks: separate rights from the photograph, own the
geometry; not an ODbL question (see THIRD_PARTY_NOTICES "Reference
photography" rules).
## 6. Sources checked
- ODbL text: <https://opendatacommons.org/licenses/odbl/1-0/>
- OSM licence summary: <https://www.openstreetmap.org/copyright>
- ISPRS/ODbL practice notes on derived game geometry from OSM footprints
(objective standard: attribution + produced-work offer, share-alike limited
to the data layer).
Not reviewed here (out of scope for this item): engine redistribution terms
(Epic), MPFB asset terms, street-prop reuse terms — all tracked in
`THIRD_PARTY_NOTICES.md`.
Binary file not shown.
+830
View File
@@ -0,0 +1,830 @@
frame,ms
0,2.419
1,2.502
2,2.360
3,2.395
4,2.174
5,2.539
6,2.421
7,2.322
8,2.455
9,2.345
10,2.510
11,2.076
12,2.287
13,2.345
14,2.398
15,2.451
16,2.358
17,2.303
18,2.322
19,2.325
20,2.370
21,2.277
22,2.591
23,2.312
24,2.333
25,2.281
26,2.453
27,2.351
28,2.348
29,2.452
30,2.402
31,2.271
32,2.314
33,2.304
34,2.377
35,2.443
36,2.589
37,2.267
38,2.554
39,2.075
40,2.406
41,2.395
42,2.509
43,2.291
44,2.370
45,2.278
46,2.531
47,2.289
48,2.345
49,2.459
50,2.495
51,2.319
52,2.310
53,2.528
54,2.266
55,2.134
56,2.413
57,2.479
58,2.307
59,2.343
60,2.343
61,2.313
62,2.317
63,2.437
64,2.618
65,2.316
66,2.275
67,2.355
68,2.496
69,3.997
70,1.704
71,2.353
72,2.285
73,2.434
74,2.389
75,2.356
76,2.330
77,2.531
78,2.477
79,2.260
80,2.493
81,2.136
82,2.432
83,2.324
84,2.498
85,2.430
86,2.364
87,2.346
88,2.374
89,2.370
90,2.469
91,2.394
92,2.653
93,2.326
94,2.441
95,2.285
96,2.397
97,2.411
98,2.333
99,2.582
100,2.372
101,2.375
102,2.631
103,2.965
104,3.026
105,2.361
106,2.379
107,2.281
108,2.321
109,2.503
110,2.532
111,2.646
112,2.486
113,2.462
114,2.115
115,2.502
116,2.149
117,2.329
118,2.306
119,2.578
120,2.298
121,2.323
122,2.318
123,2.307
124,2.482
125,2.383
126,2.470
127,2.285
128,2.392
129,2.402
130,2.455
131,2.296
132,2.294
133,2.577
134,2.288
135,2.537
136,2.214
137,2.279
138,2.664
139,2.348
140,2.444
141,2.430
142,2.269
143,2.317
144,2.392
145,2.350
146,2.333
147,2.558
148,2.401
149,2.335
150,2.203
151,2.473
152,2.471
153,2.677
154,1.980
155,2.721
156,2.139
157,2.277
158,2.348
159,2.312
160,2.412
161,2.498
162,2.372
163,2.421
164,2.249
165,2.525
166,2.750
167,2.465
168,2.560
169,2.400
170,2.310
171,2.423
172,2.274
173,2.482
174,2.581
175,2.375
176,2.285
177,2.325
178,2.386
179,2.379
180,2.344
181,2.424
182,2.635
183,2.188
184,2.378
185,2.258
186,2.361
187,2.445
188,2.280
189,2.589
190,2.373
191,2.349
192,2.514
193,2.153
194,2.271
195,2.519
196,2.469
197,2.509
198,2.293
199,2.324
200,2.317
201,2.569
202,2.400
203,2.527
204,2.406
205,2.347
206,2.361
207,2.414
208,2.320
209,2.481
210,2.462
211,2.333
212,2.306
213,2.334
214,2.377
215,2.447
216,2.465
217,2.431
218,2.304
219,2.551
220,2.308
221,2.373
222,2.286
223,2.389
224,2.524
225,2.309
226,2.367
227,2.358
228,2.512
229,2.235
230,2.320
231,2.581
232,2.308
233,2.410
234,2.531
235,2.322
236,2.585
237,2.453
238,2.407
239,2.279
240,2.464
241,2.336
242,2.282
243,2.487
244,2.557
245,2.214
246,2.390
247,2.535
248,2.234
249,2.220
250,2.491
251,2.446
252,2.356
253,2.296
254,2.502
255,2.400
256,2.255
257,2.592
258,2.288
259,2.310
260,2.443
261,2.326
262,2.392
263,2.414
264,2.332
265,2.501
266,2.381
267,2.331
268,2.355
269,2.331
270,2.452
271,2.476
272,2.625
273,2.297
274,2.411
275,2.254
276,2.355
277,2.368
278,2.416
279,2.531
280,2.358
281,2.349
282,2.372
283,2.355
284,2.388
285,2.253
286,2.679
287,2.415
288,2.346
289,2.340
290,2.390
291,2.356
292,2.311
293,2.582
294,2.344
295,2.532
296,2.309
297,2.319
298,2.434
299,2.413
300,2.510
301,2.509
302,2.441
303,2.262
304,2.482
305,2.397
306,2.386
307,2.498
308,2.409
309,2.321
310,2.395
311,2.927
312,2.988
313,2.479
314,2.328
315,2.392
316,2.764
317,1.912
318,2.353
319,2.569
320,2.505
321,2.299
322,2.310
323,2.414
324,2.342
325,2.488
326,2.365
327,2.515
328,2.224
329,2.466
330,2.309
331,2.380
332,2.319
333,2.453
334,2.639
335,2.300
336,2.480
337,2.116
338,2.343
339,2.263
340,2.641
341,2.473
342,2.281
343,2.511
344,2.300
345,2.290
346,2.348
347,2.306
348,2.820
349,2.170
350,2.425
351,2.290
352,2.354
353,2.256
354,2.316
355,2.586
356,2.308
357,2.318
358,2.376
359,2.364
360,2.347
361,2.451
362,2.297
363,2.574
364,2.518
365,2.191
366,2.312
367,2.314
368,2.421
369,2.476
370,2.697
371,2.183
372,2.402
373,2.322
374,2.690
375,3.341
376,2.525
377,2.423
378,2.467
379,2.350
380,2.235
381,3.062
382,2.342
383,2.556
384,2.308
385,2.366
386,2.396
387,2.573
388,2.818
389,2.460
390,2.367
391,2.335
392,2.323
393,2.529
394,2.554
395,2.758
396,2.544
397,2.394
398,2.332
399,2.289
400,2.703
401,2.451
402,2.682
403,2.498
404,2.500
405,2.281
406,2.429
407,2.493
408,2.963
409,2.419
410,2.304
411,2.322
412,2.302
413,2.604
414,2.349
415,2.797
416,2.552
417,2.296
418,2.340
419,2.585
420,2.463
421,2.269
422,2.782
423,2.479
424,2.491
425,2.206
426,2.709
427,2.325
428,2.589
429,2.607
430,2.445
431,2.410
432,2.897
433,2.166
434,2.247
435,2.815
436,2.430
437,2.368
438,2.544
439,2.543
440,2.277
441,2.422
442,2.779
443,2.530
444,2.373
445,2.642
446,2.475
447,2.233
448,2.323
449,3.161
450,2.036
451,2.269
452,2.700
453,2.355
454,2.284
455,2.931
456,2.389
457,2.426
458,2.341
459,2.396
460,2.296
461,2.438
462,2.583
463,2.309
464,2.647
465,2.184
466,2.401
467,2.276
468,2.330
469,2.550
470,2.469
471,2.370
472,2.376
473,2.483
474,2.213
475,2.286
476,2.339
477,2.522
478,2.374
479,2.354
480,2.356
481,2.309
482,2.523
483,2.589
484,2.007
485,2.830
486,2.211
487,2.350
488,2.338
489,2.271
490,2.740
491,2.495
492,2.356
493,2.351
494,2.355
495,2.284
496,2.340
497,2.367
498,2.666
499,2.702
500,2.653
501,1.895
502,2.422
503,2.288
504,2.468
505,2.813
506,2.235
507,2.862
508,2.273
509,2.495
510,2.192
511,2.684
512,2.679
513,2.292
514,2.370
515,2.447
516,2.570
517,3.059
518,2.939
519,2.289
520,2.375
521,2.357
522,2.420
523,2.280
524,2.570
525,2.590
526,2.522
527,2.200
528,2.229
529,2.466
530,2.722
531,2.056
532,2.658
533,2.298
534,2.329
535,2.516
536,2.205
537,2.390
538,2.366
539,2.439
540,2.361
541,2.326
542,2.376
543,2.427
544,2.362
545,2.336
546,2.494
547,2.334
548,2.319
549,2.386
550,2.384
551,2.273
552,2.379
553,2.656
554,2.264
555,2.298
556,2.375
557,2.378
558,2.416
559,2.486
560,2.395
561,2.456
562,2.256
563,2.282
564,2.300
565,2.439
566,2.439
567,2.896
568,2.049
569,2.322
570,2.372
571,2.432
572,2.295
573,2.298
574,2.552
575,2.365
576,2.444
577,2.421
578,2.704
579,2.255
580,2.904
581,2.389
582,2.314
583,2.528
584,2.223
585,2.588
586,2.296
587,2.282
588,2.676
589,2.286
590,2.390
591,2.408
592,2.361
593,2.404
594,2.326
595,2.483
596,2.412
597,2.468
598,2.454
599,2.245
600,2.580
601,2.306
602,2.496
603,2.445
604,2.363
605,2.316
606,2.504
607,2.185
608,2.649
609,2.218
610,2.401
611,2.494
612,2.271
613,2.332
614,2.296
615,2.479
616,2.406
617,2.377
618,2.382
619,2.437
620,2.222
621,2.428
622,2.316
623,2.569
624,2.450
625,2.354
626,2.305
627,2.297
628,2.346
629,2.435
630,2.506
631,2.671
632,2.185
633,2.293
634,2.362
635,2.552
636,2.566
637,2.446
638,2.304
639,2.348
640,2.352
641,2.357
642,2.371
643,2.557
644,2.339
645,2.333
646,2.325
647,2.420
648,2.355
649,2.457
650,2.512
651,2.364
652,2.790
653,2.245
654,2.109
655,2.333
656,2.338
657,2.555
658,2.351
659,2.486
660,2.342
661,2.283
662,2.240
663,2.468
664,2.655
665,2.314
666,2.281
667,2.452
668,2.320
669,2.366
670,2.533
671,2.590
672,2.264
673,2.365
674,2.354
675,2.350
676,2.377
677,2.627
678,2.369
679,2.394
680,2.389
681,2.261
682,2.363
683,2.400
684,2.308
685,2.565
686,2.394
687,2.347
688,2.329
689,2.369
690,2.417
691,2.520
692,2.222
693,2.428
694,2.368
695,2.390
696,2.291
697,2.474
698,2.309
699,2.732
700,2.419
701,2.291
702,2.367
703,2.422
704,2.476
705,2.211
706,2.560
707,2.351
708,2.350
709,2.352
710,2.284
711,2.377
712,2.393
713,2.682
714,2.155
715,2.364
716,2.369
717,2.345
718,2.338
719,2.379
720,2.601
721,2.433
722,2.513
723,2.496
724,2.967
725,2.579
726,2.513
727,2.497
728,2.268
729,2.610
730,2.122
731,2.441
732,2.628
733,2.468
734,2.228
735,2.377
736,2.401
737,2.267
738,2.391
739,2.624
740,2.467
741,2.334
742,2.281
743,2.385
744,2.297
745,2.432
746,2.300
747,2.591
748,2.408
749,2.330
750,2.275
751,2.374
752,2.385
753,2.336
754,2.628
755,2.263
756,2.397
757,2.431
758,2.387
759,2.326
760,2.398
761,2.554
762,2.389
763,2.310
764,2.465
765,2.402
766,2.263
767,2.688
768,2.262
769,2.439
770,2.164
771,2.312
772,2.342
773,2.395
774,2.322
775,2.617
776,2.289
777,2.343
778,2.333
779,2.348
780,2.497
781,2.299
782,2.589
783,2.358
784,2.442
785,2.235
786,2.716
787,2.350
788,2.839
789,2.490
790,2.439
791,2.438
792,2.464
793,2.527
794,2.897
795,1.939
796,2.509
797,3.275
798,2.123
799,2.300
800,2.425
801,2.699
802,2.275
803,2.523
804,2.232
805,3.105
806,2.195
807,2.389
808,2.264
809,2.813
810,2.257
811,2.300
812,2.543
813,2.283
814,2.533
815,2.308
816,2.481
817,2.374
818,2.504
819,3.008
820,2.266
821,2.351
822,2.459
823,2.748
824,2.512
825,2.257
826,2.434
827,2.430
828,2.253
1 frame ms
2 0 2.419
3 1 2.502
4 2 2.360
5 3 2.395
6 4 2.174
7 5 2.539
8 6 2.421
9 7 2.322
10 8 2.455
11 9 2.345
12 10 2.510
13 11 2.076
14 12 2.287
15 13 2.345
16 14 2.398
17 15 2.451
18 16 2.358
19 17 2.303
20 18 2.322
21 19 2.325
22 20 2.370
23 21 2.277
24 22 2.591
25 23 2.312
26 24 2.333
27 25 2.281
28 26 2.453
29 27 2.351
30 28 2.348
31 29 2.452
32 30 2.402
33 31 2.271
34 32 2.314
35 33 2.304
36 34 2.377
37 35 2.443
38 36 2.589
39 37 2.267
40 38 2.554
41 39 2.075
42 40 2.406
43 41 2.395
44 42 2.509
45 43 2.291
46 44 2.370
47 45 2.278
48 46 2.531
49 47 2.289
50 48 2.345
51 49 2.459
52 50 2.495
53 51 2.319
54 52 2.310
55 53 2.528
56 54 2.266
57 55 2.134
58 56 2.413
59 57 2.479
60 58 2.307
61 59 2.343
62 60 2.343
63 61 2.313
64 62 2.317
65 63 2.437
66 64 2.618
67 65 2.316
68 66 2.275
69 67 2.355
70 68 2.496
71 69 3.997
72 70 1.704
73 71 2.353
74 72 2.285
75 73 2.434
76 74 2.389
77 75 2.356
78 76 2.330
79 77 2.531
80 78 2.477
81 79 2.260
82 80 2.493
83 81 2.136
84 82 2.432
85 83 2.324
86 84 2.498
87 85 2.430
88 86 2.364
89 87 2.346
90 88 2.374
91 89 2.370
92 90 2.469
93 91 2.394
94 92 2.653
95 93 2.326
96 94 2.441
97 95 2.285
98 96 2.397
99 97 2.411
100 98 2.333
101 99 2.582
102 100 2.372
103 101 2.375
104 102 2.631
105 103 2.965
106 104 3.026
107 105 2.361
108 106 2.379
109 107 2.281
110 108 2.321
111 109 2.503
112 110 2.532
113 111 2.646
114 112 2.486
115 113 2.462
116 114 2.115
117 115 2.502
118 116 2.149
119 117 2.329
120 118 2.306
121 119 2.578
122 120 2.298
123 121 2.323
124 122 2.318
125 123 2.307
126 124 2.482
127 125 2.383
128 126 2.470
129 127 2.285
130 128 2.392
131 129 2.402
132 130 2.455
133 131 2.296
134 132 2.294
135 133 2.577
136 134 2.288
137 135 2.537
138 136 2.214
139 137 2.279
140 138 2.664
141 139 2.348
142 140 2.444
143 141 2.430
144 142 2.269
145 143 2.317
146 144 2.392
147 145 2.350
148 146 2.333
149 147 2.558
150 148 2.401
151 149 2.335
152 150 2.203
153 151 2.473
154 152 2.471
155 153 2.677
156 154 1.980
157 155 2.721
158 156 2.139
159 157 2.277
160 158 2.348
161 159 2.312
162 160 2.412
163 161 2.498
164 162 2.372
165 163 2.421
166 164 2.249
167 165 2.525
168 166 2.750
169 167 2.465
170 168 2.560
171 169 2.400
172 170 2.310
173 171 2.423
174 172 2.274
175 173 2.482
176 174 2.581
177 175 2.375
178 176 2.285
179 177 2.325
180 178 2.386
181 179 2.379
182 180 2.344
183 181 2.424
184 182 2.635
185 183 2.188
186 184 2.378
187 185 2.258
188 186 2.361
189 187 2.445
190 188 2.280
191 189 2.589
192 190 2.373
193 191 2.349
194 192 2.514
195 193 2.153
196 194 2.271
197 195 2.519
198 196 2.469
199 197 2.509
200 198 2.293
201 199 2.324
202 200 2.317
203 201 2.569
204 202 2.400
205 203 2.527
206 204 2.406
207 205 2.347
208 206 2.361
209 207 2.414
210 208 2.320
211 209 2.481
212 210 2.462
213 211 2.333
214 212 2.306
215 213 2.334
216 214 2.377
217 215 2.447
218 216 2.465
219 217 2.431
220 218 2.304
221 219 2.551
222 220 2.308
223 221 2.373
224 222 2.286
225 223 2.389
226 224 2.524
227 225 2.309
228 226 2.367
229 227 2.358
230 228 2.512
231 229 2.235
232 230 2.320
233 231 2.581
234 232 2.308
235 233 2.410
236 234 2.531
237 235 2.322
238 236 2.585
239 237 2.453
240 238 2.407
241 239 2.279
242 240 2.464
243 241 2.336
244 242 2.282
245 243 2.487
246 244 2.557
247 245 2.214
248 246 2.390
249 247 2.535
250 248 2.234
251 249 2.220
252 250 2.491
253 251 2.446
254 252 2.356
255 253 2.296
256 254 2.502
257 255 2.400
258 256 2.255
259 257 2.592
260 258 2.288
261 259 2.310
262 260 2.443
263 261 2.326
264 262 2.392
265 263 2.414
266 264 2.332
267 265 2.501
268 266 2.381
269 267 2.331
270 268 2.355
271 269 2.331
272 270 2.452
273 271 2.476
274 272 2.625
275 273 2.297
276 274 2.411
277 275 2.254
278 276 2.355
279 277 2.368
280 278 2.416
281 279 2.531
282 280 2.358
283 281 2.349
284 282 2.372
285 283 2.355
286 284 2.388
287 285 2.253
288 286 2.679
289 287 2.415
290 288 2.346
291 289 2.340
292 290 2.390
293 291 2.356
294 292 2.311
295 293 2.582
296 294 2.344
297 295 2.532
298 296 2.309
299 297 2.319
300 298 2.434
301 299 2.413
302 300 2.510
303 301 2.509
304 302 2.441
305 303 2.262
306 304 2.482
307 305 2.397
308 306 2.386
309 307 2.498
310 308 2.409
311 309 2.321
312 310 2.395
313 311 2.927
314 312 2.988
315 313 2.479
316 314 2.328
317 315 2.392
318 316 2.764
319 317 1.912
320 318 2.353
321 319 2.569
322 320 2.505
323 321 2.299
324 322 2.310
325 323 2.414
326 324 2.342
327 325 2.488
328 326 2.365
329 327 2.515
330 328 2.224
331 329 2.466
332 330 2.309
333 331 2.380
334 332 2.319
335 333 2.453
336 334 2.639
337 335 2.300
338 336 2.480
339 337 2.116
340 338 2.343
341 339 2.263
342 340 2.641
343 341 2.473
344 342 2.281
345 343 2.511
346 344 2.300
347 345 2.290
348 346 2.348
349 347 2.306
350 348 2.820
351 349 2.170
352 350 2.425
353 351 2.290
354 352 2.354
355 353 2.256
356 354 2.316
357 355 2.586
358 356 2.308
359 357 2.318
360 358 2.376
361 359 2.364
362 360 2.347
363 361 2.451
364 362 2.297
365 363 2.574
366 364 2.518
367 365 2.191
368 366 2.312
369 367 2.314
370 368 2.421
371 369 2.476
372 370 2.697
373 371 2.183
374 372 2.402
375 373 2.322
376 374 2.690
377 375 3.341
378 376 2.525
379 377 2.423
380 378 2.467
381 379 2.350
382 380 2.235
383 381 3.062
384 382 2.342
385 383 2.556
386 384 2.308
387 385 2.366
388 386 2.396
389 387 2.573
390 388 2.818
391 389 2.460
392 390 2.367
393 391 2.335
394 392 2.323
395 393 2.529
396 394 2.554
397 395 2.758
398 396 2.544
399 397 2.394
400 398 2.332
401 399 2.289
402 400 2.703
403 401 2.451
404 402 2.682
405 403 2.498
406 404 2.500
407 405 2.281
408 406 2.429
409 407 2.493
410 408 2.963
411 409 2.419
412 410 2.304
413 411 2.322
414 412 2.302
415 413 2.604
416 414 2.349
417 415 2.797
418 416 2.552
419 417 2.296
420 418 2.340
421 419 2.585
422 420 2.463
423 421 2.269
424 422 2.782
425 423 2.479
426 424 2.491
427 425 2.206
428 426 2.709
429 427 2.325
430 428 2.589
431 429 2.607
432 430 2.445
433 431 2.410
434 432 2.897
435 433 2.166
436 434 2.247
437 435 2.815
438 436 2.430
439 437 2.368
440 438 2.544
441 439 2.543
442 440 2.277
443 441 2.422
444 442 2.779
445 443 2.530
446 444 2.373
447 445 2.642
448 446 2.475
449 447 2.233
450 448 2.323
451 449 3.161
452 450 2.036
453 451 2.269
454 452 2.700
455 453 2.355
456 454 2.284
457 455 2.931
458 456 2.389
459 457 2.426
460 458 2.341
461 459 2.396
462 460 2.296
463 461 2.438
464 462 2.583
465 463 2.309
466 464 2.647
467 465 2.184
468 466 2.401
469 467 2.276
470 468 2.330
471 469 2.550
472 470 2.469
473 471 2.370
474 472 2.376
475 473 2.483
476 474 2.213
477 475 2.286
478 476 2.339
479 477 2.522
480 478 2.374
481 479 2.354
482 480 2.356
483 481 2.309
484 482 2.523
485 483 2.589
486 484 2.007
487 485 2.830
488 486 2.211
489 487 2.350
490 488 2.338
491 489 2.271
492 490 2.740
493 491 2.495
494 492 2.356
495 493 2.351
496 494 2.355
497 495 2.284
498 496 2.340
499 497 2.367
500 498 2.666
501 499 2.702
502 500 2.653
503 501 1.895
504 502 2.422
505 503 2.288
506 504 2.468
507 505 2.813
508 506 2.235
509 507 2.862
510 508 2.273
511 509 2.495
512 510 2.192
513 511 2.684
514 512 2.679
515 513 2.292
516 514 2.370
517 515 2.447
518 516 2.570
519 517 3.059
520 518 2.939
521 519 2.289
522 520 2.375
523 521 2.357
524 522 2.420
525 523 2.280
526 524 2.570
527 525 2.590
528 526 2.522
529 527 2.200
530 528 2.229
531 529 2.466
532 530 2.722
533 531 2.056
534 532 2.658
535 533 2.298
536 534 2.329
537 535 2.516
538 536 2.205
539 537 2.390
540 538 2.366
541 539 2.439
542 540 2.361
543 541 2.326
544 542 2.376
545 543 2.427
546 544 2.362
547 545 2.336
548 546 2.494
549 547 2.334
550 548 2.319
551 549 2.386
552 550 2.384
553 551 2.273
554 552 2.379
555 553 2.656
556 554 2.264
557 555 2.298
558 556 2.375
559 557 2.378
560 558 2.416
561 559 2.486
562 560 2.395
563 561 2.456
564 562 2.256
565 563 2.282
566 564 2.300
567 565 2.439
568 566 2.439
569 567 2.896
570 568 2.049
571 569 2.322
572 570 2.372
573 571 2.432
574 572 2.295
575 573 2.298
576 574 2.552
577 575 2.365
578 576 2.444
579 577 2.421
580 578 2.704
581 579 2.255
582 580 2.904
583 581 2.389
584 582 2.314
585 583 2.528
586 584 2.223
587 585 2.588
588 586 2.296
589 587 2.282
590 588 2.676
591 589 2.286
592 590 2.390
593 591 2.408
594 592 2.361
595 593 2.404
596 594 2.326
597 595 2.483
598 596 2.412
599 597 2.468
600 598 2.454
601 599 2.245
602 600 2.580
603 601 2.306
604 602 2.496
605 603 2.445
606 604 2.363
607 605 2.316
608 606 2.504
609 607 2.185
610 608 2.649
611 609 2.218
612 610 2.401
613 611 2.494
614 612 2.271
615 613 2.332
616 614 2.296
617 615 2.479
618 616 2.406
619 617 2.377
620 618 2.382
621 619 2.437
622 620 2.222
623 621 2.428
624 622 2.316
625 623 2.569
626 624 2.450
627 625 2.354
628 626 2.305
629 627 2.297
630 628 2.346
631 629 2.435
632 630 2.506
633 631 2.671
634 632 2.185
635 633 2.293
636 634 2.362
637 635 2.552
638 636 2.566
639 637 2.446
640 638 2.304
641 639 2.348
642 640 2.352
643 641 2.357
644 642 2.371
645 643 2.557
646 644 2.339
647 645 2.333
648 646 2.325
649 647 2.420
650 648 2.355
651 649 2.457
652 650 2.512
653 651 2.364
654 652 2.790
655 653 2.245
656 654 2.109
657 655 2.333
658 656 2.338
659 657 2.555
660 658 2.351
661 659 2.486
662 660 2.342
663 661 2.283
664 662 2.240
665 663 2.468
666 664 2.655
667 665 2.314
668 666 2.281
669 667 2.452
670 668 2.320
671 669 2.366
672 670 2.533
673 671 2.590
674 672 2.264
675 673 2.365
676 674 2.354
677 675 2.350
678 676 2.377
679 677 2.627
680 678 2.369
681 679 2.394
682 680 2.389
683 681 2.261
684 682 2.363
685 683 2.400
686 684 2.308
687 685 2.565
688 686 2.394
689 687 2.347
690 688 2.329
691 689 2.369
692 690 2.417
693 691 2.520
694 692 2.222
695 693 2.428
696 694 2.368
697 695 2.390
698 696 2.291
699 697 2.474
700 698 2.309
701 699 2.732
702 700 2.419
703 701 2.291
704 702 2.367
705 703 2.422
706 704 2.476
707 705 2.211
708 706 2.560
709 707 2.351
710 708 2.350
711 709 2.352
712 710 2.284
713 711 2.377
714 712 2.393
715 713 2.682
716 714 2.155
717 715 2.364
718 716 2.369
719 717 2.345
720 718 2.338
721 719 2.379
722 720 2.601
723 721 2.433
724 722 2.513
725 723 2.496
726 724 2.967
727 725 2.579
728 726 2.513
729 727 2.497
730 728 2.268
731 729 2.610
732 730 2.122
733 731 2.441
734 732 2.628
735 733 2.468
736 734 2.228
737 735 2.377
738 736 2.401
739 737 2.267
740 738 2.391
741 739 2.624
742 740 2.467
743 741 2.334
744 742 2.281
745 743 2.385
746 744 2.297
747 745 2.432
748 746 2.300
749 747 2.591
750 748 2.408
751 749 2.330
752 750 2.275
753 751 2.374
754 752 2.385
755 753 2.336
756 754 2.628
757 755 2.263
758 756 2.397
759 757 2.431
760 758 2.387
761 759 2.326
762 760 2.398
763 761 2.554
764 762 2.389
765 763 2.310
766 764 2.465
767 765 2.402
768 766 2.263
769 767 2.688
770 768 2.262
771 769 2.439
772 770 2.164
773 771 2.312
774 772 2.342
775 773 2.395
776 774 2.322
777 775 2.617
778 776 2.289
779 777 2.343
780 778 2.333
781 779 2.348
782 780 2.497
783 781 2.299
784 782 2.589
785 783 2.358
786 784 2.442
787 785 2.235
788 786 2.716
789 787 2.350
790 788 2.839
791 789 2.490
792 790 2.439
793 791 2.438
794 792 2.464
795 793 2.527
796 794 2.897
797 795 1.939
798 796 2.509
799 797 3.275
800 798 2.123
801 799 2.300
802 800 2.425
803 801 2.699
804 802 2.275
805 803 2.523
806 804 2.232
807 805 3.105
808 806 2.195
809 807 2.389
810 808 2.264
811 809 2.813
812 810 2.257
813 811 2.300
814 812 2.543
815 813 2.283
816 814 2.533
817 815 2.308
818 816 2.481
819 817 2.374
820 818 2.504
821 819 3.008
822 820 2.266
823 821 2.351
824 822 2.459
825 823 2.748
826 824 2.512
827 825 2.257
828 826 2.434
829 827 2.430
830 828 2.253
+169 -11
View File
@@ -30,17 +30,175 @@ Measured at 1440p on the development workstation: Bazzite, Ryzen 7 7700X, 64 GB
4. 60 FPS at 1440p per the performance check above.
5. Fresh checkout builds against the documented external engine installation.
6. Linux build packaged and launched; controls documented in `Docs/` and shown in-game.
7. Geographic attribution is visible in-game: "© OpenStreetMap contributors" with a working licence link.
8. Tag `v0.1.0`, write the `CHANGELOG.md` entry, then remove the completed section from `TODO.md` (its goal stays in `ROADMAP.md`).
7. Tag `v0.1.0`, write the `CHANGELOG.md` entry, then remove the completed section from `TODO.md` (its goal stays in `ROADMAP.md`).
## Check-record template
| Version | Check | Result | Evidence | Date |
| --- | --- | --- | --- | --- |
| `0.1.0` | Traversal, both sides | — | — | — |
| `0.1.0` | Placement vs references | — | — | — |
| `0.1.0` | Collision and packaging | — | — | — |
| `0.1.0` | 60 FPS at 1440p | — | — | — |
| `0.1.0` | Fresh checkout build | — | — | — |
| `0.1.0` | Packaged Linux build | — | — | — |
| `0.1.0` | Attribution display | — | — | — |
| Version | Check | Result | Evidence | Date |
| ------- | ----------------------- | ------ | -------- | ---- |
| `0.1.0` | Traversal, both sides | PASS — Blender pre-check + in-engine swept transect, 0 holes, ground Z 317..395 | `-qaselftest`, TODO 0.1.0 notes | 2026-11-08 |
| `0.1.0` | Placement vs references | PASS (with deliberate simplifications) | Section below, 2026-11-08 | 2026-11-08 |
| `0.1.0` | Collision and packaging | PASS — no blocking collision, no invisible walls, no seam gaps (see audit note) | `-qaselftest` on the packaged build, 2026-11-08 | 2026-11-08 |
| `0.1.0` | 60 FPS at 1440p | PASS — 2.42 ms avg (414 fps), p99 3.01 ms at 2560×1440, TSR on, VSync off, no FG | `-qaselftest` perf capture, `Docs/perf/0.1.0-walk-frametimes.csv` | 2026-11-08 |
| `0.1.0` | Fresh checkout build | PASS — clean clone of `main` (`4bc41e9`), `git lfs pull` + `check-lfs.sh` OK (130 LFS files), both `GigLineEditor` and `GigLine` targets built with the documented engine | `/tmp/fresh-check`, 2026-11-08 | 2026-11-08 |
| `0.1.0` | Packaged Linux build | PASS — packaged Development build launches straight into DemoMap, no missing-asset/linker errors; controls shown in-game and in `Docs/controls.md` | `Docs/perf/0.1.0-packaged-hud-controls.png`, 2026-11-08 | 2026-11-08 |
## `0.1.0` — Placement vs references — record (2026-11-08)
Checked against the reference photography (`Art/reference/`) and the Geo measurements
(`Geo/verification/*`). Build under test: current `main` content, DemoMap rendered via
`Tools/render_screenshots.sh` (2026-11-08, Vulkan SM5, 1920×1080); engine positions
probed from the DemoMap (commandlet, 2026-11-08).
### Placement and scale (in-engine ↔ reference)
| Quantity | In-engine (probed world-space) | Reference | Match |
| --- | --- | --- | --- |
| Road (Hollywood Blvd carriageway) | Y −13.82..+10.47 m → **24.3 m** | crossing ways 20.9–25.9 m | ✓ within range |
| South sidewalk strip | 7.3 m (bbox incl. kerb apron); built walkable strip 4.0 m per `Art/blender/README.md` | 12.6 m centreline offset; strip width from setback arithmetic | ✓ offset 12.5 m ≈ ref 12.6 m |
| North sidewalk strip | 8.6 m (bbox); built strip 5.3 m | 13.6 m centreline offset | ✓ offset 13.2 m ≈ ref 13.6 m |
| Kerbs | 3.3 m apron face to 0.13 m upstand | 0.15 m assumption; photo check pending (`0.1.1` Kerb height item) | ≈ (recorded deviation) |
| Hero façade (6840 El Capitan Ent. Ctr.) | X −146.38..−120.34 m (26.0 m frontage), Y +13.0..+25.6 m from road centre (13.0 m deep) | 6840 setback 14.5 m; 25.75 m frontage (resolved) | ✓ frontage; setback ~1.5 m deeper than ref (recorded) |
| El Capitan Theatre (6838) | X −120.82..−82.10 m, Y +13.4..+68.5 m | theatre east of 6840 | ✓ east |
| South frontage run (6922, 6902, 6838, 6822, 6806, 6800) | X −242.76..−14.37 m, all Y +12..+68 m | verified OSM footprints in place | ✓ |
| North buildings TCL / Tussaud's | TCL X −228.9..−184.4, Y −94.3..−18.0; Tussaud's X −270.7..−228.0, Y −75.9..−18.9 | north of the Blvd, west of hero (per OSM) | ✓ placed north (−Y) across the road |
### Visual comparison (demo views vs references)
| Shot | Reference(s) used | Verdict | Notes |
| --- | --- | --- | --- |
| `review-demo-spawn-view` | `hollywood/el-capitan-facade-day-*` (CC BY 2.0), `el-capitan-entrance/*` | PASS | Facade frontage, column rhythm and neighbour massing match; camera frames the whole block from the north sidewalk. |
| `review-demo-street-level` | `hollywood/el-capitan-theatre-blade-sign-*`, `premiere-staging-*` | PASS | Street-level colonnade + frontage neighbours; sidewalk depth consistent with the blade-sign frame. |
| `review-demo-crossing` | street-profile crossing s=130.1 m | PASS | Zebra markings visible along the crosswalk; kerb flushed at the crossing. |
| `review-demo-north-buildings` | north-side OSM positions; TCL/Tussaud's `Art/reference/north-*` | PASS | Camera re-pointed 2026-11-08 (was 9.5 km west → 85% sky); buildings now framed. Flat blockout surfaces are the `0.1.4` scope. |
### Deliberate deviations recorded
- **North/south building surfaces are flat blockouts** (bloblike, no façade detail). Detail texturing/façade style is the planned `0.1.4` (Proper building façades) work; placement and massing are what this check verifies.
- **Kerb upstand 0.13 m as built vs 0.15 m assumed**; the DEM cannot resolve a kerb — tracked in `0.1.1` (Kerb height assumption).
- **Sidewalk surface detail** (railings, pavers, domes) not modelled — outside `0.1.0` scope.
- **Crossing ramps are kerb flushes, not graded ramps** — the `0.1.1` Crossing ramp gradient item.
- Site identity (the neoclassical hero with motto band) matches the 2009 façade photograph and the 1922 Masonic elevation within blockout fidelity.
Pre-check method: `Tools/judge_screenshot.py` (vision model) flagged the simplified
flat surfaces as "unexpected" — those flags are the deliberate deviations above, not
placement errors. Final verdict is by human review of the shots in `Screenshots/demo/`.
## `0.1.0` — Collision and packaging — audit note (2026-11-08)
Run on the packaged build (`GigLineQA`, Development, Vulkan SM5) with `-qaselftest`.
- **Transect (holes/gaps):** 1456 samples over 16 rows — lane centres **plus the
kerb/road/sidewalk seam Ys** (−1382, −1053, +718, +1047 and neighbours) and
X −20000..−2000. Result **0 holes**. (The old 84-sample check covered only 3
lane centres; the seam rows are what this check adds.)
- **Invisible-wall probe:** horizontal traces 30 m long, grounded at 160 cm above
the local ground, along the road and both seam rows. Result **0/35 blocking hits
inside 21 m** — no invisible walls or misplaced blockers in the street envelope.
(First pass used a fixed Z 160 start; the street is at Z ~366, so the probe
started inside the earth and reported every row as a dist-0 blocker. Grounding
the probe fixed that measurement bug.)
- **Walk / reset / pause:** 761 cm in 2 s (Z steady 456), reset 0 cm, pause
`IsGamePaused()=1`, unpause 0 — all PASS.
- Packaging: the packaged build launches, reaches gameplay, and the QA run quits
cleanly. The `GigLineShotActor` screenshot rounds are suppressed under
`-qaselftest` so they cannot quit the run before the audit phases.
- Caveat recorded: ground Z extremes (−12 m / 3000 cm) are trace-start/terrain-edge
artifacts of the widened extent, not walkable holes — the walkable corridor is
continuous (walk + transect both PASS).
## `0.1.0` — 60 FPS at 1440p — record (2026-11-08)
**Build under test:** packaged `GigLineQA` Development, UE **5.8.4**, Vulkan SM5,
NVIDIA 615.78.08 (RTX 5070 Ti), Bazzite, Ryzen 7 7700X, 64 GB RAM.
**Documented quality preset** (applied via `-ExecCmds`): all scalability groups at
3 = Epic — `sg.ViewDistanceQuality 3, sg.ShadowQuality 3,
sg.GlobalIlluminationQuality 3, sg.ReflectionQuality 3, sg.PostProcessQuality 3,
sg.TextureQuality 3, sg.EffectsQuality 3, sg.FoliageQuality 3, sg.ShadingQuality 3`.
**Upscaling state (recorded from the run):** `r.AntiAliasingMethod=4` → **TSR on**,
default screen percentage, `r.VSync=0` (**off**), frame generation **off/unavailable**
(`r.Streamline.FrameGeneration.Mode` absent → -999).
**Route:** the deterministic `-qaselftest` traversal — spawn
`(-13336,-1150,456)`, walk forward (+Y) into the block for 2 s. Frame times are
captured every frame over that route (829 frames), not a single sample.
**Result at 2560×1440:**
| Metric | Value |
| --- | --- |
| Frames captured | 829 |
| Average | **2.42 ms → 414 fps** |
| p50 | 2.38 ms |
| p95 | 2.71 ms |
| p99 | 3.01 ms (≈332 fps) |
| Max | 4.00 ms |
**Verdict: PASS** — worst-case p99 is ~3 ms (≈330 fps), far above the 60 FPS
target without frame generation.
**Variance note:** two back-to-back runs measured ~9.2 ms (≈108 fps) when another
game instance / busy compositor overlapped the capture; repeated clean runs
(windowed and fullscreen, Wayland and X11 drivers, window focused) all measured
~2.4–2.5 ms. The high outlier is recorded here rather than hidden.
**Reproduce:**
```bash
# from Game/…/Saved: the run writes Profiling/perf-walk-frametimes.csv
./GigLine.sh -fullscreen -ResX=2560 -ResY=1440 -nosplash -qaselftest \
-ExecCmds="sg.ViewDistanceQuality 3,sg.ShadowQuality 3,sg.GlobalIlluminationQuality 3,sg.ReflectionQuality 3,sg.PostProcessQuality 3,sg.TextureQuality 3,sg.EffectsQuality 3,sg.FoliageQuality 3,sg.ShadingQuality 3"
```
Committed frame-time series: `Docs/perf/0.1.0-walk-frametimes.csv`.
## `0.1.0` — Fresh checkout build — record (2026-11-08)
Followed the documented steps in [`Docs/build.md`](build.md) exactly, into a clean
directory with no prior state:
```bash
git clone https://git.allucanget.biz/zwitschi/gig-line.git /tmp/fresh-check
cd /tmp/fresh-check && git lfs install && git lfs pull
Tools/check-lfs.sh # -> "LFS check OK: 130 LFS-tracked file(s)"
cd /run/media/system/DATA/Engines/UE_5.8.4
Engine/Build/BatchFiles/Linux/Build.sh GigLineEditor Linux Development \
-project=/tmp/fresh-check/Game/GigLine/GigLine.uproject -WaitMutex
Engine/Build/BatchFiles/Linux/Build.sh GigLine Linux Development \
-project=/tmp/fresh-check/Game/GigLine/GigLine.uproject -WaitMutex
```
- Clone of `main` at `4bc41e9`; LFS objects fetched (`DemoMap.umap` is real UE
package content, not a pointer).
- `GigLineEditor` → **Result: Succeeded** (28.2 s); produced
`/tmp/fresh-check/Game/GigLine/Binaries/Linux/libUnrealEditor-GigLine.so`.
- `GigLine` (game) → **Result: Succeeded** (26.9 s).
- Engine: documented external install at `/run/media/system/DATA/Engines/UE_5.8.4`
(UE 5.8.4), system clang; no machine-specific paths beyond the documented
`-project=` argument.
**Verdict: PASS.**
## `0.1.0` — Packaged Linux build + documented controls — record (2026-11-08)
**Package:** `RunUAT BuildCookRun … -platform=Linux -clientconfig=Development
-cook -build -stage -pak -archive -archivedirectory=/run/media/system/DATA/Builds/GigLineQA`
→ `EXIT=0` (Success).
**Launch:** `./GigLine.sh -windowed` (plain, no map argument) →
`LogLoad: LoadMap: /Game/Hollywood/DemoMap?Name=Player` — the packaged game opens
the walkable demo directly (the default-map fix holds in a package, not just the
editor). No `LogLinker`/missing-asset/failed-to-find errors in the run.
**Controls documented and shown in-game:**
- `Docs/controls.md` — full binding table (WASD, mouse look, Space, R, Esc) plus
how to test input headlessly.
- `AGigLineHUD` now draws a control legend bottom-right every frame:
"WASD move · Mouse look · Space jump · R reset · Esc pause".
- Verified in the packaged capture: the bottom-right text bbox is 426×12 px
(a single right-aligned line), attributed text bottom-left. Screenshot:
`Docs/perf/0.1.0-packaged-hud-controls.png`.
**Verdict: PASS.**
+6 -11
View File
@@ -21,25 +21,20 @@ Anything marked **human step** needs credentials, licence acceptance, or a decis
| 6. Content pipeline | in progress — blockout, terrain, street, hero façade and the walkable `DemoMap` exist; façades and materials are planned patches (`0.1.4`) |
| 5. Repository | procedure documented; a fresh checkout has not yet been exercised end to end |
Next: `0.1.0` gameplay (attribution display, lighting, crossing) and the El Capitan Theatre, then the planned patches. Current task list is [TODO.md](../TODO.md); version direction is in [ROADMAP.md](../ROADMAP.md).
Next: `0.1.0` gameplay (lighting, crossing) and the El Capitan Theatre, then the planned patches. Current task list is [TODO.md](../TODO.md); version direction is in [ROADMAP.md](../ROADMAP.md).
## 1. System
- `[x]` Update Bazzite with its supported mechanism.
`ujust update` (or `rpm-ostree upgrade`) followed by a reboot. Deployment `45.20261105` is booted and default; `44.20261026` retained as rollback.
- `[x]` Verify the NVIDIA driver and Vulkan from the desktop session.
`nvidia-smi` names the RTX 5070 Ti on driver `615.78.08` and the Wayland desktop renders through it. Vulkan enumerates it as GPU0: discrete, proprietary driver `615.78.08`, API `1.4.351`, conformance `1.4.3.3`. The integrated RADV GPU is GPU1 — watch that the engine does not select it.
- `[x]` Install `git-lfs`, `cmake`, `ninja-build`, `clang` as an ostree layer, reboot, then `git lfs install`.
Done: `git-lfs 3.7.1`, `clang 23.1.4`, `cmake 4.3.0`, `ninja 1.13.2`; lfs filter active, `Tools/check-lfs.sh` passes.
- `[x]` Update Bazzite with its supported mechanism. `ujust update` (or `rpm-ostree upgrade`) followed by a reboot. Deployment `45.20261105` is booted and default; `44.20261026` retained as rollback.
- `[x]` Verify the NVIDIA driver and Vulkan from the desktop session. `nvidia-smi` names the RTX 5070 Ti on driver `615.78.08` and the Wayland desktop renders through it. Vulkan enumerates it as GPU0: discrete, proprietary driver `615.78.08`, API `1.4.351`, conformance `1.4.3.3`. The integrated RADV GPU is GPU1 — watch that the engine does not select it.
- `[x]` Install `git-lfs`, `cmake`, `ninja-build`, `clang` as an ostree layer, reboot, then `git lfs install`. Done: `git-lfs 3.7.1`, `clang 23.1.4`, `cmake 4.3.0`, `ninja 1.13.2`; lfs filter active, `Tools/check-lfs.sh` passes.
## 2. Engine acquisition
- `[x]` **Human step.** Create an Epic account, accept the Unreal license.
Done on 2026-11-06 with the account email recorded locally; no credentials stored in the repository.
- `[x]` **Human step.** Create an Epic account, accept the Unreal license. Done on 2026-11-06 with the account email recorded locally; no credentials stored in the repository.
- `[x]` **Human step.** Acquire one pinned engine build. Two routes:
- **Launcher build**: install the Linux Epic Games Launcher, download the pinned engine version, install to the DATA volume, and disable auto-update so the pin holds.
- **Source build**: clone the pinned `release` branch from the Epic Games GitHub organisation, then `./Setup.sh`, `./GenerateProjectFiles.sh`, `make -j16`. Budget 120–200 GB and a multi-hour first build on 16 threads.
**5.8.4** downloaded to `/run/media/system/DATA/Downloads/Linux_Unreal_Engine_5.8.4.zip`: 39 819 212 076 bytes (37.08 GiB), ZIP central directory present with 299 959 entries, `Engine/Build/Build.version` reports **5.8.4**, changelist 58902052, `++UE5+Release-5.8`.
- **Source build**: clone the pinned `release` branch from the Epic Games GitHub organisation, then `./Setup.sh`, `./GenerateProjectFiles.sh`, `make -j16`. Budget 120–200 GB and a multi-hour first build on 16 threads. **5.8.4** downloaded to `/run/media/system/DATA/Downloads/Linux_Unreal_Engine_5.8.4.zip`: 39 819 212 076 bytes (37.08 GiB), ZIP central directory present with 299 959 entries, `Engine/Build/Build.version` reports **5.8.4**, changelist 58902052, `++UE5+Release-5.8`.
- `[x]` Verify the archive end to end before extracting. `unzip -tq` on 2026-11-06: "No errors detected", exit 0.
- `[x]` Extract to the DATA volume and record the install path. Done 2026-11-07: `/run/media/system/DATA/Engines/UE_5.8.4`, 73 GiB on disk, extraction verified by full headless editor start.
- `[x]` Resolve the engine version: **5.8.4 pinned** (see the decision below). The 5.2.0 archive (19.59 GiB, CL 25360045) remains on disk as a fallback only and should be deleted once the pin is trusted.
+11
View File
@@ -1,5 +1,11 @@
[/Script/EngineSettings.GameMapsSettings]
GlobalDefaultGameMode="/Script/GigLine.GigLineGameModeBase"
; The runnable map is the walkable demo, so a plain launch opens it (not the
; engine template nor the review staging scene).
GameDefaultMap=/Game/Hollywood/DemoMap
EditorStartupMap=/Game/Hollywood/DemoMap
ServerDefaultMap=/Game/Hollywood/DemoMap
TransitionMap=/Game/Hollywood/DemoMap
[/Script/Engine.RendererSettings]
; Dynamic-only lighting. With static lighting allowed, the stationary sun and
@@ -9,6 +15,11 @@ GlobalDefaultGameMode="/Script/GigLine.GigLineGameModeBase"
; static lighting outright — nothing to build, no warning.
r.AllowStaticLighting=False
[ConsoleVariables]
; Dev diagnostics (spawn/input setup logs) are off by default in shipped builds.
; Re-enable at runtime: `gigline.SetupLogs 1`.
gigline.SetupLogs=0
[URL]
GameName=GigLine
+10 -5
View File
@@ -7,9 +7,14 @@ CopyrightNotice=
BuildConfiguration=PPBC_Development
FullRebuild=False
UsePakFile=True
; Runtime-loaded content must ship even when nothing statically references it.
; The Enhanced Input assets are only soft-loaded by AGigLineCharacter at
; BeginPlay; without this the cooker prunes them from the pak (observed: the
; cook ReferencedSet had zero /Game/Input entries, so inputs were all nil and
; no keys worked). Always cook the demo map + input. Keep both maps listed so
; EditorStartupMap/GameDefaultMap selection stays flexible.
+DirectoriesToAlwaysCook=/Game/Input
+DirectoriesToAlwaysCook=/Game/Hollywood
+DirectoriesToAlwaysCook=/Game/Test
[/Script/EngineSettings.GameMapsSettings]
; Temporary default while the demo map is being built; GameMapsSettings in
; DefaultEngine.ini still names the game mode only.
GameDefaultMap=/Game/Test/ReviewMap
EditorStartupMap=/Game/Test/ReviewMap
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,66 @@
#include "GigLineAssetTools.h"
#include "Engine/StaticMesh.h"
#include "PhysicsEngine/BodySetup.h"
#include "StaticMeshResources.h"
void UGigLineAssetTools::SetComplexCollisionAsSimple(UStaticMesh* Mesh, bool bEnable)
{
if (!Mesh)
{
return;
}
UBodySetup* BodySetup = Mesh->GetBodySetup();
if (!BodySetup)
{
return;
}
BodySetup->CollisionTraceFlag = bEnable ? CTF_UseComplexAsSimple : CTF_UseSimpleAndComplex;
BodySetup->InvalidatePhysicsData();
BodySetup->CreatePhysicsMeshes();
Mesh->MarkPackageDirty();
// PostEditChange() is editor-only and not a member of UStaticMesh; the
// physics data regeneration above plus MarkPackageDirty is all a headless
// commandlet needs to persist the new collision flags.
}
void UGigLineAssetTools::ClearSimpleCollision(UStaticMesh* Mesh)
{
if (!Mesh)
{
return;
}
UBodySetup* BodySetup = Mesh->GetBodySetup();
if (!BodySetup)
{
return;
}
BodySetup->AggGeom.EmptyElements();
BodySetup->InvalidatePhysicsData();
BodySetup->CreatePhysicsMeshes();
Mesh->MarkPackageDirty();
}
FString UGigLineAssetTools::GetCollisionComplexity(UStaticMesh* Mesh)
{
if (!Mesh)
{
return TEXT("no mesh");
}
const UBodySetup* BodySetup = Mesh->GetBodySetup();
if (!BodySetup)
{
return TEXT("no body setup");
}
const int32 Flag = static_cast<int32>(BodySetup->CollisionTraceFlag.GetValue());
if (Flag == CTF_UseComplexAsSimple) { return TEXT("UseComplexAsSimple"); }
if (Flag == CTF_UseSimpleAsComplex) { return TEXT("UseSimpleAsComplex"); }
if (Flag == CTF_UseSimpleAndComplex) { return TEXT("UseSimpleAndComplex"); }
return TEXT("ProjectDefault");
}
@@ -0,0 +1,34 @@
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "GigLineAssetTools.generated.h"
class UStaticMesh;
/**
* Small editor-side helpers for the headless asset pipeline scripts.
*
* Exposed to Python so tools can fix up imported meshes that the Interchange
* importer gives unusable default collision. The terrain is the motivating
* case: without complex-as-simple it gets a bounding-box proxy, so the whole
* street sits under an invisible 8 m box and the player walks on the box lid.
*/
UCLASS()
class GIGLINE_API UGigLineAssetTools : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/** Make collision trace against the mesh triangles instead of a proxy hull. */
UFUNCTION(BlueprintCallable, Category = "GigLine|Assets")
static void SetComplexCollisionAsSimple(UStaticMesh* Mesh, bool bEnable = true);
/** Drop any generated simple collision (hulls/boxes) from the mesh. */
UFUNCTION(BlueprintCallable, Category = "GigLine|Assets")
static void ClearSimpleCollision(UStaticMesh* Mesh);
/** Report the current collision complexity: "SimpleAndComplex", "Simple", ... */
UFUNCTION(BlueprintCallable, Category = "GigLine|Assets")
static FString GetCollisionComplexity(UStaticMesh* Mesh);
};
+390 -38
View File
@@ -7,8 +7,24 @@
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/PlayerController.h"
#include "InputAction.h"
#include "GameFramework/PlayerController.h"
#include "InputMappingContext.h"
#include "Engine/World.h"
#include "TimerManager.h"
#include "Containers/Ticker.h"
#include "Kismet/GameplayStatics.h"
#include "Misc/CommandLine.h"
#include "Misc/Parse.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "HAL/FileManager.h"
// Development-only diagnostics for the walkable demo. These describe the spawn
// and input setup and are not needed in a shipped build's log spam; disable
// with `-NoGigLineSetupLogs` (or set the cvar in release config).
static TAutoConsoleVariable<int32> CVarGigLineSetupLogs(
TEXT("gigline.SetupLogs"),
1,
TEXT("Log GigLine spawn/input setup diagnostics (0=off, 1=on)."));
AGigLineCharacter::AGigLineCharacter()
{
@@ -39,51 +55,56 @@ AGigLineCharacter::AGigLineCharacter()
GetMesh()->SetHiddenInGame(true);
GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
// Input assets are created by Tools/ue_create_input_assets.py. They are loaded in
// BeginPlay rather than the constructor: the constructor runs during CDO
// construction where asset loads are unreliable, and a missing asset should be
// a loud warning, not a silent no-op.
}
// Hard-reference the Enhanced Input assets with ConstructorHelpers so the
// cooker always includes them and they load during CDO construction. Runtime
// soft-loading them at BeginPlay was fragile: the assets were pruned from the
// pak unless /Game/Input was force-cooked, and a soft path can miss while the
// IoStore registry is still mounting. A constructor FObjectFinder is a hard
// reference (followed by the cooker) and loads deterministically.
// Assets are created by Tools/ue_create_input_assets.py.
static ConstructorHelpers::FObjectFinder<UInputMappingContext> CtxFinder(
TEXT("/Game/Input/IMC_Default.IMC_Default"));
static ConstructorHelpers::FObjectFinder<UInputAction> MoveF(
TEXT("/Game/Input/IA_Move.IA_Move"));
static ConstructorHelpers::FObjectFinder<UInputAction> StrafeF(
TEXT("/Game/Input/IA_Strafe.IA_Strafe"));
static ConstructorHelpers::FObjectFinder<UInputAction> BackF(
TEXT("/Game/Input/IA_MoveBack.IA_MoveBack"));
static ConstructorHelpers::FObjectFinder<UInputAction> LeftF(
TEXT("/Game/Input/IA_StrafeLeft.IA_StrafeLeft"));
static ConstructorHelpers::FObjectFinder<UInputAction> LookF(
TEXT("/Game/Input/IA_Look.IA_Look"));
static ConstructorHelpers::FObjectFinder<UInputAction> JumpF(
TEXT("/Game/Input/IA_Jump.IA_Jump"));
static ConstructorHelpers::FObjectFinder<UInputAction> ResetF(
TEXT("/Game/Input/IA_Reset.IA_Reset"));
static ConstructorHelpers::FObjectFinder<UInputAction> PauseF(
TEXT("/Game/Input/IA_Pause.IA_Pause"));
void AGigLineCharacter::LoadInputAssets()
{
auto LoadSoft = [](const TCHAR* Path) -> TObjectPtr<UObject>
{
return LoadObject<UObject>(nullptr, Path);
};
MappingContext = Cast<UInputMappingContext>(LoadSoft(TEXT("/Game/Input/IMC_Default.IMC_Default")));
MoveAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_Move.IA_Move")));
StrafeAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_Strafe.IA_Strafe")));
MoveBackAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_MoveBack.IA_MoveBack")));
StrafeLeftAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_StrafeLeft.IA_StrafeLeft")));
LookAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_Look.IA_Look")));
JumpAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_Jump.IA_Jump")));
ResetAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_Reset.IA_Reset")));
PauseAction = Cast<UInputAction>(LoadSoft(TEXT("/Game/Input/IA_Pause.IA_Pause")));
MappingContext = CtxFinder.Object;
MoveAction = MoveF.Object;
StrafeAction = StrafeF.Object;
MoveBackAction = BackF.Object;
StrafeLeftAction = LeftF.Object;
LookAction = LookF.Object;
JumpAction = JumpF.Object;
ResetAction = ResetF.Object;
PauseAction = PauseF.Object;
if (!MappingContext || !MoveAction || !MoveBackAction || !StrafeAction || !StrafeLeftAction
|| !LookAction)
{
UE_LOG(LogTemp, Warning,
TEXT("GigLine: input assets missing (context=%s move=%s look=%s). ")
TEXT("Run Tools/ue_create_input_assets.py to build /Game/Input."),
MappingContext ? TEXT("ok") : TEXT("missing"),
(MoveAction && MoveBackAction && StrafeAction && StrafeLeftAction) ? TEXT("ok")
: TEXT("missing"),
LookAction ? TEXT("ok") : TEXT("missing"));
TEXT("GigLine: input assets failed to load at construction. ")
TEXT("Run Tools/ue_create_input_assets.py to build /Game/Input."));
}
}
void AGigLineCharacter::BeginPlay()
void AGigLineCharacter::LoadInputAssets()
{
Super::BeginPlay();
LoadInputAssets();
StartLocation = GetActorLocation();
StartRotation = GetActorRotation();
// Push the mapping context for this local player.
// The assets are hard-referenced and loaded in the constructor. This pushes
// the now-valid mapping context to the local player's subsystem; called from
// SetupPlayerInputComponent at possession, when the controller exists.
if (MappingContext)
{
if (const APlayerController* PC = Cast<APlayerController>(GetController()))
@@ -92,11 +113,326 @@ void AGigLineCharacter::BeginPlay()
ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PC->GetLocalPlayer()))
{
Subsystem->AddMappingContext(MappingContext, 0);
if (CVarGigLineSetupLogs.GetValueOnGameThread())
{
UE_LOG(LogTemp, Log, TEXT("GigLine IN: pushed mapping context %s"),
*MappingContext->GetName());
}
}
else if (CVarGigLineSetupLogs.GetValueOnGameThread())
{
UE_LOG(LogTemp, Log, TEXT("GigLine IN: no enhanced input subsystem on local player"));
}
}
else if (CVarGigLineSetupLogs.GetValueOnGameThread())
{
UE_LOG(LogTemp, Log, TEXT("GigLine IN: no PlayerController at LoadInputAssets"));
}
}
}
void AGigLineCharacter::BeginPlay()
{
Super::BeginPlay();
StartLocation = GetActorLocation();
StartRotation = GetActorRotation();
// The PlayerStart sits above grade so the capsule cannot spawn embedded, so
// the BeginPlay position is mid-air. Re-capture once the pawn has landed,
// otherwise Reset drops the player 1.4 m back into the air every press.
// Repeat until the first grounded frame. A single late timer is wrong: it can
// fire after the player (or the QA self-test) has already started walking,
// which recorded a mid-stride position as "start".
GetWorldTimerManager().SetTimer(SettleHandle, [this]()
{
if (bQATestWalking)
{
return; // keep waiting; the test walks almost immediately
}
if (const UCharacterMovementComponent* Mov = GetCharacterMovement())
{
if (Mov->IsMovingOnGround())
{
StartLocation = GetActorLocation();
StartRotation = GetActorRotation();
GetWorldTimerManager().ClearTimer(this->SettleHandle);
if (CVarGigLineSetupLogs.GetValueOnGameThread())
{
UE_LOG(LogTemp, Log, TEXT("GigLine: start captured on ground at Z %.0f"),
StartLocation.Z);
}
}
}
}, 0.25f, true);
// Force game input mode + focus + mouse capture so keyboard/mouse reach the
// pawn regardless of window focus quirks.
if (APlayerController* PC = Cast<APlayerController>(GetController()))
{
PC->SetInputMode(FInputModeGameOnly());
PC->bShowMouseCursor = false;
}
if (FParse::Param(FCommandLine::Get(), TEXT("qaselftest")))
{
StartQASelfTest();
}
}
void AGigLineCharacter::StartQASelfTest()
{
// Driven by a core ticker rather than a world timer on purpose: pausing the
// game stops world timers, so a timer-based test would freeze the moment it
// proved pause works.
struct FQAState
{
float Elapsed = 0.0f;
FVector Start = FVector::ZeroVector;
FVector WalkEnd = FVector::ZeroVector;
float Step = 0.0f;
bool bWalkMoved = false;
bool bResetReturned = false;
bool bPausedOn = false;
bool bUnpausedBack = false;
// Performance capture over the fixed walk route (1.0-3.0 s).
TArray<float> FrameMs;
bool bPerfLogged = false;
};
TSharedPtr<FQAState> State = MakeShared<FQAState>();
State->Start = GetActorLocation();
UE_LOG(LogTemp, Log, TEXT("GigLine QA: start XYZ=(%.0f,%.0f,%.0f)"),
State->Start.X, State->Start.Y, State->Start.Z);
FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateWeakLambda(this,
[this, State](float DeltaTime)
{
State->Elapsed += DeltaTime;
const float E = State->Elapsed;
// 1.0 - 3.0 s: hold forward. Reset sets the Step marker so each phase
// runs exactly once without an ever-growing if-chain.
if (E >= 1.0f && State->Step < 1.0f)
{
State->Step = 1.0f;
// Measure from the settled position, not the mid-air spawn point.
State->Start = GetActorLocation();
UE_LOG(LogTemp, Log, TEXT("GigLine QA: settled XYZ=(%.0f,%.0f,%.0f)"),
State->Start.X, State->Start.Y, State->Start.Z);
bQATestWalking = true;
}
if (bQATestWalking)
{
AddMovementInput(GetActorForwardVector(), 1.0f);
// Frame time for the perf capture (this ticker runs every frame).
State->FrameMs.Add(DeltaTime * 1000.0f);
}
// 3.0: stop and measure.
if (E >= 3.0f && State->Step < 2.0f)
{
State->Step = 2.0f;
bQATestWalking = false;
State->WalkEnd = GetActorLocation();
const float Walked = FVector::Dist2D(State->Start, State->WalkEnd);
State->bWalkMoved = Walked > 200.0f; // >2 m in 2 s at 4 m/s
UE_LOG(LogTemp, Log,
TEXT("GigLine QA: walk moved %.0f cm in 2 s -> %s (Z %.0f -> %.0f)"),
Walked, State->bWalkMoved ? TEXT("PASS") : TEXT("FAIL"),
State->Start.Z, State->WalkEnd.Z);
// Perf: report frame-time stats over the fixed walk route.
if (!State->bPerfLogged && State->FrameMs.Num() > 10)
{
State->bPerfLogged = true;
TArray<float> Sorted = State->FrameMs;
Sorted.Sort();
const int32 N = Sorted.Num();
double Sum = 0.0;
for (float Ms : Sorted) { Sum += Ms; }
const float AvgMs = static_cast<float>(Sum / N);
const float P50 = Sorted[N / 2];
const float P95 = Sorted[FMath::Clamp((int32)(N * 0.95f), 0, N - 1)];
const float P99 = Sorted[FMath::Clamp((int32)(N * 0.99f), 0, N - 1)];
const float MaxMs = Sorted.Last();
FVector2D Res(0.0, 0.0);
if (GEngine && GEngine->GameViewport)
{
GEngine->GameViewport->GetViewportSize(Res);
}
// Record the upscaling/AA state so the check is reproducible.
auto CV = [](const TCHAR* Name)
{
IConsoleVariable* Var = IConsoleManager::Get().FindConsoleVariable(Name);
return Var ? Var->GetInt() : -999;
};
UE_LOG(LogTemp, Log,
TEXT("GigLine PERF: frames=%d avg=%.2fms (%.1f fps) p50=%.2f p95=%.2f p99=%.2f max=%.2f ms at %dx%d"),
N, AvgMs, AvgMs > 0.f ? 1000.f / AvgMs : 0.f, P50, P95, P99, MaxMs,
(int32)Res.X, (int32)Res.Y);
UE_LOG(LogTemp, Log,
TEXT("GigLine PERF: AntiAliasingMethod=%d ScreenPercentage=%d TSR=%d FrameGen=%d VSync=%d"),
CV(TEXT("r.AntiAliasingMethod")), CV(TEXT("r.ScreenPercentage")),
CV(TEXT("r.TSR.History.ScreenPercentage")),
CV(TEXT("r.Streamline.FrameGeneration.Mode")),
CV(TEXT("r.VSync")));
// Frame-time series, so the check has a graph source, not one sample.
{
const FString Dir = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("Profiling"));
IFileManager::Get().MakeDirectory(*Dir, true);
const FString Path = FPaths::Combine(Dir, TEXT("perf-walk-frametimes.csv"));
FString Csv(TEXT("frame,ms\n"));
for (int32 i = 0; i < N; ++i)
{
Csv += FString::Printf(TEXT("%d,%.3f\n"), i, State->FrameMs[i]);
}
FFileHelper::SaveStringToFile(Csv, *Path);
UE_LOG(LogTemp, Log, TEXT("GigLine PERF: frame times written to %s"), *Path);
}
}
}
// 3.5: reset should return to the spawn point.
if (E >= 3.5f && State->Step < 3.0f)
{
State->Step = 3.0f;
ResetToStart();
}
if (E >= 4.0f && State->Step < 4.0f)
{
State->Step = 4.0f;
const float Back = FVector::Dist(State->Start, GetActorLocation());
State->bResetReturned = Back < 100.0f;
UE_LOG(LogTemp, Log, TEXT("GigLine QA: reset returned %.0f cm from spawn -> %s"),
Back, State->bResetReturned ? TEXT("PASS") : TEXT("FAIL"));
}
// 4.5: pause, then confirm the world actually stopped.
if (E >= 4.5f && State->Step < 5.0f)
{
State->Step = 5.0f;
TogglePause();
State->bPausedOn = UGameplayStatics::IsGamePaused(this);
UE_LOG(LogTemp, Log, TEXT("GigLine QA: pause -> paused=%d %s"),
State->bPausedOn ? 1 : 0, State->bPausedOn ? TEXT("PASS") : TEXT("FAIL"));
}
// 5.5: unpause and confirm it comes back.
if (E >= 5.5f && State->Step < 6.0f)
{
State->Step = 6.0f;
TogglePause();
State->bUnpausedBack = !UGameplayStatics::IsGamePaused(this);
UE_LOG(LogTemp, Log, TEXT("GigLine QA: unpause -> paused=%d %s"),
UGameplayStatics::IsGamePaused(this) ? 1 : 0,
State->bUnpausedBack ? TEXT("PASS") : TEXT("FAIL"));
}
// 6.0: ground transect - sweep both sidewalks and the carriageway and
// count holes, so "can traverse the block without falling through"
// is measured rather than assumed.
if (E >= 6.0f && State->Step < 7.0f)
{
State->Step = 7.0f;
UWorld* W = GetWorld();
int32 Holes = 0, Samples = 0;
float Lowest = 1.0e9f, Highest = -1.0e9f;
// Probed world-space (2026-11-08): N sidewalk -1912..-1053, kerb
// -1382..-1053, road -1382..+1047, kerb +718..+1047, S sidewalk
// +718..+1447. Rows include the kerb/road/sidewalk boundary Ys so
// seam gaps are sampled, not just lane centres.
static const float Rows[] = {
-1750.0f, -1500.0f, -1382.0f, -1280.0f, -1053.0f, -900.0f,
-167.0f, 0.0f, 300.0f, 800.0f, 1047.0f, 900.0f, 718.0f,
900.0f, 1250.0f, 1447.0f
};
for (float LaneY : Rows)
{
for (float X = -20000.0f; X <= -2000.0f; X += 200.0f)
{
FHitResult Hit;
FCollisionQueryParams Q(TEXT("GigLineTransect"), false, this);
const bool bHit = W->LineTraceSingleByChannel(Hit,
FVector(X, LaneY, 3000.f), FVector(X, LaneY, -3000.f),
ECC_Visibility, Q);
++Samples;
if (!bHit)
{
++Holes;
continue;
}
Lowest = FMath::Min(Lowest, Hit.ImpactPoint.Z);
Highest = FMath::Max(Highest, Hit.ImpactPoint.Z);
}
}
UE_LOG(LogTemp, Log,
TEXT("GigLine QA: transect %d samples, %d holes, ground Z %.0f..%.0f -> %s"),
Samples, Holes, Lowest, Highest, Holes == 0 ? TEXT("PASS") : TEXT("FAIL"));
// Invisible-wall / blocking-mesh probe: at each sample, first find
// the ground under the start point (vertical raycast), then run a
// horizontal trace 160 cm ABOVE that ground along the street row.
// A clean street should let the 30 m trace run clear; a short hit
// flags an invisible or misplaced blocker. Starting the horizontal
// trace at a fixed Z (e.g. 160) is wrong: the street is at Z ~366
// there, so a 160 cm probe starts inside the earth (dist 0).
int32 Walls = 0, WallSamples = 0;
static const float TraceYs[] = {-1400.0f, -167.0f, 1000.0f, -1382.0f, 1047.0f};
for (float WallY : TraceYs)
{
for (float X0 = -19500.0f; X0 <= -3000.0f; X0 += 2500.0f)
{
FHitResult GroundHit;
FCollisionQueryParams GQ(TEXT("GigLineWallGround"), false, this);
if (!W->LineTraceSingleByChannel(GroundHit,
FVector(X0, WallY, 3000.f), FVector(X0, WallY, -1000.f),
ECC_Visibility, GQ))
{
continue; // no ground under start -> skip
}
const float ProbeZ = GroundHit.ImpactPoint.Z + 160.f;
FHitResult Hit;
FCollisionQueryParams Q(TEXT("GigLineWallProbe"), false, this);
const bool bHit = W->LineTraceSingleByChannel(Hit,
FVector(X0, WallY, ProbeZ),
FVector(X0 + 3000.f, WallY, ProbeZ),
ECC_Visibility, Q);
++WallSamples;
if (!bHit) { continue; } // 30 m clear -> good
// Starting just above the ground, a hit within 21 m = blocker.
if (Hit.Distance < 2100.f)
{
++Walls;
UE_LOG(LogTemp, Log,
TEXT("GigLine QA: wall probe short-hit at X %.0f Y %.0f dist %.0f actor=%s"),
X0, WallY, Hit.Distance,
Hit.GetActor() ? *Hit.GetActor()->GetName() : TEXT("none"));
}
}
}
UE_LOG(LogTemp, Log,
TEXT("GigLine QA: wall probe %d/%d traces with blocking hit inside 21 m -> %s"),
Walls, WallSamples,
Walls == 0 ? TEXT("PASS") : TEXT("FAIL"));
}
// 7.0: verdict, then stop ticking.
if (E >= 7.0f)
{
const bool bAllPass = State->bWalkMoved && State->bResetReturned
&& State->bPausedOn && State->bUnpausedBack;
UE_LOG(LogTemp, Log, TEXT("GigLine QA: RESULT walk=%d reset=%d pause=%d unpause=%d -> %s"),
State->bWalkMoved ? 1 : 0, State->bResetReturned ? 1 : 0,
State->bPausedOn ? 1 : 0, State->bUnpausedBack ? 1 : 0,
bAllPass ? TEXT("PASS") : TEXT("FAIL"));
return false;
}
return true;
}));
}
void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
@@ -108,6 +444,10 @@ void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputCo
return;
}
// Push the mapping context for this controller's local player so the bound
// actions receive input (runs at possession, when the controller exists).
LoadInputAssets();
// One action per direction keeps the sign explicit: W and S differ only by
// which handler runs, not by a modifier in the asset.
if (MoveAction)
@@ -143,6 +483,15 @@ void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputCo
Input->BindAction(JumpAction, ETriggerEvent::Started, this, &AGigLineCharacter::StartJump);
Input->BindAction(JumpAction, ETriggerEvent::Completed, this, &AGigLineCharacter::StopJump);
}
if (CVarGigLineSetupLogs.GetValueOnGameThread())
{
UE_LOG(LogTemp, Log, TEXT("GigLine IN: bound %d actions; ctx=%s move=%s look=%s jmp=%s"),
(Input ? 1 : 0),
MappingContext ? TEXT("ok") : TEXT("nil"),
MoveAction ? TEXT("ok") : TEXT("nil"),
LookAction ? TEXT("ok") : TEXT("nil"),
JumpAction ? TEXT("ok") : TEXT("nil"));
}
}
namespace
@@ -190,11 +539,14 @@ void AGigLineCharacter::MoveLeft(const FInputActionValue& Value)
void AGigLineCharacter::ApplyMove(float Forward, float Strafe)
{
const AController* Controller = GetController();
if (!Controller || (FMath::IsNearlyZero(Forward) && FMath::IsNearlyZero(Strafe)))
if (!Controller)
{
return;
}
if (FMath::IsNearlyZero(Forward) && FMath::IsNearlyZero(Strafe))
{
return;
}
// Forward/right on the controller yaw keeps the capsule upright on slopes.
const FRotator YawRotation(0.0f, Controller->GetControlRotation().Yaw, 0.0f);
const FVector ForwardDir = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
@@ -38,6 +38,22 @@ protected:
void ResetToStart();
void TogglePause();
/**
* TEMP QA: scripted walk / reset / pause verification.
*
* Enabled only by the `-qaselftest` command-line switch, so it never runs in
* normal play. It exists because those three behaviours were listed as
* "needs a human at the keyboard" and a repeatable headless check beats
* waiting for one.
*/
void StartQASelfTest();
/** Set while the self-test is driving forward input. */
bool bQATestWalking = false;
/** Repeating timer that captures StartLocation on first ground contact. */
FTimerHandle SettleHandle;
/** Where the player started, so reset returns there rather than the origin. */
UPROPERTY()
FVector StartLocation = FVector::ZeroVector;
@@ -1,5 +1,6 @@
#include "GigLineGameModeBase.h"
#include "GameFramework/PlayerController.h"
#include "GigLineCharacter.h"
#include "GigLineHUD.h"
@@ -9,3 +10,12 @@ AGigLineGameModeBase::AGigLineGameModeBase()
// ODbL attribution must be visible in the running demo, not only in docs.
HUDClass = AGigLineHUD::StaticClass();
}
void AGigLineGameModeBase::HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer)
{
Super::HandleStartingNewPlayer_Implementation(NewPlayer);
UE_LOG(LogTemp, Log, TEXT("GigLine GM: player %s connected; pawn=%s input=%s"),
NewPlayer ? *NewPlayer->GetName() : TEXT("null"),
NewPlayer && NewPlayer->GetPawn() ? *NewPlayer->GetPawn()->GetName() : TEXT("NONE"),
NewPlayer && NewPlayer->PlayerInput ? TEXT("yes") : TEXT("no"));
}
@@ -18,4 +18,8 @@ class GIGLINE_API AGigLineGameModeBase : public AGameModeBase
public:
AGigLineGameModeBase();
protected:
/** Logs whether a local player connected to a pawn with input (QA). */
virtual void HandleStartingNewPlayer_Implementation(class APlayerController* NewPlayer) override;
};
@@ -26,4 +26,15 @@ void AGigLineHUD::DrawHUD()
DrawText(AttributionLine, Ink, X, Y, nullptr, TextScale);
DrawText(LicenceLine, Ink, X, Y + LineHeight, nullptr, TextScale * 0.82f);
// Control legend, bottom-right (0.1.0 release check: controls shown in-game).
if (!ControlsLine.IsEmpty())
{
float IgnoredW = 0.0f, ControlsH = 0.0f;
GetTextSize(ControlsLine, IgnoredW, ControlsH, nullptr, TextScale * 0.82f);
const float CX = Canvas->ClipX - MarginPx - IgnoredW;
const float CY = Canvas->ClipY - MarginPx - ControlsH;
DrawText(ControlsLine, Shadow, CX + 1.0f, CY + 1.0f, nullptr, TextScale * 0.82f);
DrawText(ControlsLine, Ink, CX, CY, nullptr, TextScale * 0.82f);
}
}
+8
View File
@@ -28,6 +28,14 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attribution")
FString AttributionLine = TEXT("\u00A9 OpenStreetMap contributors");
/**
* Control legend, shown bottom-right so the 0.1.0 demo documents its own
* controls in-game (release check: "controls documented in Docs/ and shown
* in-game"). Keep in sync with Docs/controls.md.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Controls")
FString ControlsLine = TEXT("WASD move \u00B7 Mouse look \u00B7 Space jump \u00B7 R reset \u00B7 Esc pause");
/** Licence line, including where the licence can be read. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Attribution")
FString LicenceLine = TEXT("Data \u00A9 OpenStreetMap contributors, ODbL 1.0 \u2014 openstreetmap.org/copyright");
@@ -21,6 +21,14 @@ void AGigLineShotActor::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
// QA mode: the -qaselftest run owns the process (it walks, resets, pauses,
// transects, then quits on its own). Skip the screenshot rounds so the game
// does not exit mid-test via bQuitWhenDone.
if (FParse::Param(FCommandLine::Get(), TEXT("qaselftest")))
{
return;
}
if (bWaitingForSettle)
{
SettleCountdown -= DeltaSeconds;
+1 -1
View File
@@ -22,7 +22,7 @@ The window is the block plus a ~100 m margin on the east and ~70 m on the other
The raster is reprojected by the service, not by us, so it was checked against the service's own point query (`getSamples`) and against the independent point service used in `../verification/2026-11-06-elevation.md`:
| Point | Raster pixel | Service `getSamples` | Earlier point probe |
| --- | ---: | ---: | ---: |
| --- | --: | --: | --: |
| Hollywood × Highland (origin) | 115.911 | 115.910 | 115.911 |
| Hollywood × Orange Dr | 119.422 | 119.418 | 119.418 |
| Mid-block crossing | 117.593 | — | 117.53 |
+1 -6
View File
@@ -11,20 +11,15 @@
| Contents | 3565 nodes, 350 ways; streets: Hollywood Blvd (`secondary`), North Highland Ave (`primary`), North Orange Dr, North McCadden Pl, North Sycamore Ave, Hawthorn Ave, Walk of Fame footway, service alleys |
| File | `Geo/raw/osm-api-map-2026-11-06.osm` (1.66 MB, XML) |
| SHA-256 | `64615f0db4715c2d50860052e300930c201bbb87e183a1de5985f2d99d83f4e4` |
| Licence | ODbL 1.0 — "© OpenStreetMap contributors", <https://www.openstreetmap.org/copyright> |
| CRS in source | EPSG:4326 (WGS84 lat/lon) |
| Local CRS and origin | not yet chosen. Candidate EPSG:26911 (NAD83 / UTM zone 11N); origin to be recorded when the QGIS step runs |
## Why this bbox
It contains the demo block from Highland Avenue to Orange Drive plus the whole north side within 50 m, with enough margin to include the streets' intersection nodes and the alley network behind the south frontage. It deliberately excludes the rest of the boulevard: larger extracts cost processing time and dilute the ODbL review.
It contains the demo block from Highland Avenue to Orange Drive plus the whole north side within 50 m, with enough margin to include the streets' intersection nodes and the alley network behind the south frontage. It deliberately excludes the rest of the boulevard: larger extracts cost processing time.
## Coverage limits
- Building interiors, heights above the tagged value, and pavement widths are not in this data. Treat all of it as layout.
- The `height` tag exists on some buildings and is absent on others (`6838` has only `building:levels`, `6922` has neither). A missing tag is not a zero height.
- The extract has no elevation. The DEM decision is still open; see `ROADMAP.md`.
## Attribution obligation
Any distributed derived output keeps "© OpenStreetMap contributors" and the licence link. The `0.1.0` release check requires the attribution to be visible in the shipped build.
+5 -6
View File
@@ -38,7 +38,7 @@ Reproduce it the same way, or with `pyproj` once a geo environment exists. If a
Optional check values, all NAD83 / UTM 11N, and the same points as local X/Y relative to the origin. Use them to confirm a transformation before trusting a blockout.
| Point | WGS84 lat, lon | Easting | Northing | Local X | Local Y |
| --- | --- | ---: | ---: | ---: | ---: |
| --- | --- | --: | --: | --: | --: |
| Hollywood × Orange Dr | 34.101543, −118.341749 | 376 235.784 | 3 774 227.529 | −279.605 | +3.446 |
| **Origin: Hollywood × Highland** | 34.101545, −118.338718 | **376 515.389** | **3 774 224.083** | 0.000 | 0.000 |
| Hero NW corner | 34.101412, −118.340299 | 376 369.380 | 3 774 211.202 | −146.009 | −12.881 |
@@ -53,10 +53,10 @@ Note the sign pattern: the whole block lies at **negative X** and the hero at **
## Axis convention
| Axis | Meaning |
| --- | --- |
| Local X | easting − origin easting, metres, east positive |
| Local Y | northing − origin northing, metres, north positive |
| Axis | Meaning |
| ------- | ------------------------------------------------------------ |
| Local X | easting − origin easting, metres, east positive |
| Local Y | northing − origin northing, metres, north positive |
| Local Z | up, metres, elevation relative to the origin's ground height |
The block then spans roughly **X −279 m to 0 m**, Y a few tens of metres either side, which keeps float precision comfortable in Unreal.
@@ -69,5 +69,4 @@ Unreal's own axes differ (X forward, Y right, Z up). If a level rotates the impo
## Obligations
- Any OSM-derived output keeps "© OpenStreetMap contributors" and the licence link. See `THIRD_PARTY_NOTICES.md`.
- Elevation data, when chosen, must be reprojected into EPSG:26911 with the origin above, and its vertical datum recorded (NAVD88 is the expected one for US sources; ellipsoidal heights are not interchangeable).
@@ -16,7 +16,7 @@ Those gutter positions bracket a carriageway of roughly **20–22 m**, which ind
## Section table
| Along block | Lowest point | Road crown | Cross-fall across the road | Gutter candidates |
| ---: | ---: | ---: | ---: | --- |
| --: | --: | --: | --: | --- |
| 0.0 m | 118.78 @ −10 | ~119.41 @ 0 | ~0.6 m / 2 % | −10, +10 |
| 40.0 m | 118.43 @ −11 | ~118.94 @ 0 | ~0.5 m / 2 % | −11, +10.5 |
| 80.0 m | 117.84 @ −10 | ~118.33 @ 0 | ~0.5 m / 2 % | −10, +10.5–11.5 |
+7 -7
View File
@@ -14,13 +14,13 @@ Note: the point service answers in 6–8 s per request, so the probe runs reques
Five positions along the 279.4 m block, at the south sidewalk (12.6 m from the centreline), the road centreline, and the north sidewalk (13.6 m).
| Along block | South sidewalk | Centreline | North sidewalk |
| ---: | ---: | ---: | ---: |
| 0.0 m (Orange Dr) | 118.90 | 119.36 | 119.59 |
| 69.8 m | 118.11 | 118.46 | 118.49 |
| 139.7 m | 116.95 | 117.41 | 117.50 |
| 209.5 m | 116.04 | 116.27 | 116.47 |
| 279.4 m (Highland Ave) | 115.65 | 115.91 | 116.10 |
| Along block | South sidewalk | Centreline | North sidewalk |
| ---------------------: | -------------: | ---------: | -------------: |
| 0.0 m (Orange Dr) | 118.90 | 119.36 | 119.59 |
| 69.8 m | 118.11 | 118.46 | 118.49 |
| 139.7 m | 116.95 | 117.41 | 117.50 |
| 209.5 m | 116.04 | 116.27 | 116.47 |
| 279.4 m (Highland Ave) | 115.65 | 115.91 | 116.10 |
Crossings: Orange Dr 118.96 m, mid-block 117.53 m, Highland 115.95 m.
@@ -30,7 +30,7 @@ Crossing endpoints sit within about 1 m of the kerb line, so these lengths appro
Mapped sidewalk centrelines sit **12.6 m** (south) and **13.6 m** (north) from the road centreline. Building setbacks, measured to each building's nearest vertex:
| Side | Building | Setback | Frontage extent |
| --- | --- | ---: | ---: |
| --- | --- | --: | --: |
| S | 6922 | 14.6 m | 66.1 m |
| S | 6902 | 14.5 m | 31.0 m |
| S | **6840 El Capitan Entertainment Centre** | **14.5 m** | **25.7 m** |
+159 -13
View File
@@ -6,6 +6,41 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
---
## 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.
- **asciidoctor 2.0.26** layered via `rpm-ostree install rubygem-asciidoctor` — **pending reboot** to activate. As a workaround, a distrobox Fedora container (`distrobox enter arch-convert`) runs asciidoctor without rebooting.
- **prettier 3.9.10** installed globally via npm (Node v24.20.0). Project config: `.prettierrc` (`proseWrap: never`, `tabWidth: 2`), `.prettierignore` (skips `Binaries/`, `*.uasset`, `.agents/`).
- **Conversion chain:** `.adoc` → `asciidoctor -b docbook5` → `.xml` → `pandoc -f docbook -t gfm` → `.md` → `prettier --write`. Pandoc's built-in `asciidoc` reader cannot handle Asciidoctor tables or attributes — it must go through docbook5.
- **Post-processing fixes:** glossary `cols="e,2e"` emphasis leaks italic into cell content (strip `*...*` wrapping), `®` from the R key binding becomes `(R)`, anchor IDs `{#...}` stripped, escaped quotes/brackets normalised.
- **All `.adoc` files removed.** Markdown is the canonical format. Architecture doc lives in `Docs/Architecture/` as 13 section `.md` files + combined `architecture.md`.
- All `.md` in the repo passes `prettier --check` (23 files formatted, `.agents/` excluded as vendored content).
## 2026-11-08 — QA branch merged; demo is walkable
- **PR #7** squash-merged to `main` as `52b1212` — terrain collision fix, spawn correction, `UGigLineAssetTools`, `HighResShot` fix, `-qaselftest` harness.
- **QA self-test results (from merged main):** walk 750 cm / 2 s (Z steady 456), reset 0 cm from spawn, pause/unpause pass, transect 84 samples / 0 holes (ground Z 317..395).
- **Traversal release check closed:** the swept transect measures walkability rather than assuming it.
- `gameplay-crossing-0.1.0` branch deleted (already merged); `gig-line-judge` worktree removed.
- `qa-input-collision-fix-0.1.0` branch still exists locally/remote (squash merge has no shared ancestry, `git branch -d` refuses; delete with `-D` after release tag).
- `GigLine SIM` / `GigLine GROUND` TEMP diagnostic logging still in `GigLineCharacter.cpp` — must be removed before `v0.1.0` tag.
## 2026-11-08 — Reset was returning to mid-air; QA self-test switch
- **`ResetToStart()` restored a `StartLocation` captured in `BeginPlay`**, which is the PlayerStart position _above_ grade (Z 600) so the capsule never spawns embedded. Every reset therefore dropped the player 1.4 m back into the air. Fix: capture `StartLocation` on the first grounded frame (repeating 0.25 s timer, cleared once captured) — not a one-shot timer, which can fire after the player has already started walking and record a mid-stride position.
- **`-qaselftest`** drives the checks headlessly and logs `GigLine QA: ... PASS/FAIL`. It runs on a **core ticker, not a world timer**: pausing the game stops world timers, so a timer-driven test freezes exactly when it proves pause works.
- Current results: walk 746 cm / 2 s with Z steady, reset 0 cm from spawn, pause `IsGamePaused()` 1, unpause 0, transect 84 samples / 0 holes (ground Z 317..395).
## 2026-11-08 — The fall-through was an 8 m invisible box (QA branch)
- **Symptom:** player "spawned above ground but never fell", sat at Z 909, `fall=0`, `mode=1` (Walking), no fall-through, view looked plausible so it was mistaken for working.
- **Cause:** `Terrain_DEM` had only a **bounding-box simple collision proxy** (Interchange default). A line trace down at _every_ Y through the block hit `Demo_Terrain_DEM` at exactly **819** — the mesh bbox max Z. The whole street was under an invisible lid; the pawn stood on the box, 8 m above the road.
- **Proof:** a debug `LineTraceSingleByChannel` profile at Y −1912…+1447 reported `top=819 actor=Demo_Terrain_DEM` for the entire street. After the fix it reports `Sidewalk_North 395`, `Road_Hollywood 366`, `Sidewalk_South 317`.
- **Fix:** force complex-as-simple collision on the terrain. Python **cannot** set it (`StaticMesh`/`BodySetup` expose no such property; `dir()` shows only `lod_for_collision`). Added `UGigLineAssetTools::SetComplexCollisionAsSimple` in the C++ module (`BodySetup->CollisionTraceFlag = CTF_UseComplexAsSimple`, UE 5.8 no longer has `bUseComplexAsSimpleCollision`).
- **Second gotcha:** the flag lives on the **asset**, not the map. Setting it in a builder session and saving only the `.umap` loses it — `EditorAssetLibrary.save_loaded_asset()` is required, otherwise the next run reloads the bbox proxy.
- **Third gotcha:** `HighResShot 1280 720` means **multiplier 1280** (UE warns "the high resolution screenshot multiplier is too large for your system"). The correct form is `HighResShot 1280x720`.
- Resulting spawn: `(-13336, -1150, 600)` — inside the carved corridor, lands on the road at Z 456 (feet 368 on road top 366).
## 2026-11-07 — Publication audit
- MPFB 2.0.18's installed `blender_manifest.toml` identifies its code as GPL-3.0-or-later; its versioned `LICENSE.md` explicitly puts bundled mesh, texture, and rig assets under CC0 1.0. The earlier CC0 claim for the add-on code was wrong.
@@ -18,10 +53,7 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
## 2026-11-07 — Unreal Engine 5.8.4
- **Extracted** to `/run/media/system/DATA/Engines/UE_5.8.4` (73 GiB, `unzip` exit 0). CL `58902052`, `++UE5+Release-5.8`.
- **Gotcha: the editor needs a writable `~/.config/Epic`.** In the agent shell it crashed with signal 11 twice and looked like a broken install. The log named it:
`create dir('/home/zwitschi/.config/Epic/') failed: errno=30 (Read-only file system)` →
`Fatal error: Failed to create Intermediate directory '…/.config/Epic/UnrealEngine/5.8/Intermediate/'`.
Rerunning with `HOME=/tmp/uehome XDG_CONFIG_HOME=/tmp/uehome/.config` reached `LogInit: Display: Engine is initialized.`
- **Gotcha: the editor needs a writable `~/.config/Epic`.** In the agent shell it crashed with signal 11 twice and looked like a broken install. The log named it: `create dir('/home/zwitschi/.config/Epic/') failed: errno=30 (Read-only file system)` → `Fatal error: Failed to create Intermediate directory '…/.config/Epic/UnrealEngine/5.8/Intermediate/'`. Rerunning with `HOME=/tmp/uehome XDG_CONFIG_HOME=/tmp/uehome/.config` reached `LogInit: Display: Engine is initialized.`
- **Vulkan RHI is still unproven.** The successful run used `-NullRHI`. Untested: `UnrealEditor -vulkan` from a desktop terminal.
- A packaged game does not need `~/.config/Epic`, so this failure does not follow the build.
@@ -41,8 +73,7 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
## 2026-11-06 — Blender and Blosm, headless
- **`flatpak run` does not work here.** Call the binary inside the app tree:
`B=/var/lib/flatpak/app/org.blender.Blender/x86_64/stable/active/files`, then `LD_LIBRARY_PATH="$B/blender/lib" "$B/blender/blender" -b …` with `BLENDER_USER_SCRIPTS="$HOME/.var/app/org.blender.Blender/config/blender/5.2/scripts"`.
- **`flatpak run` does not work here.** Call the binary inside the app tree: `B=/var/lib/flatpak/app/org.blender.Blender/x86_64/stable/active/files`, then `LD_LIBRARY_PATH="$B/blender/lib" "$B/blender/blender" -b …` with `BLENDER_USER_SCRIPTS="$HOME/.var/app/org.blender.Blender/config/blender/5.2/scripts"`.
- **Blosm needs two headless workarounds**, both already in `Art/blender/blockout/blosm_blockout.py`:
1. it refuses to import until an asset directory is set, and the check fires before the mode is honoured;
2. in background mode the default level table is empty, and the add-on then takes a path with a broken relative import (`from gui import addDefaultLevels`). Seeding one entry avoids it.
@@ -83,7 +114,7 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
## 2026-11-07 — Screenshot pipeline
- **`-executePythonScript` + editor is unreliable for captures**: the editor quits right after a *successful* deferred script (async screenshot requests never render), and after `load_map` in a commandlet every `spawn_actor` call returns None — build scenes in the default world instead.
- **`-executePythonScript` + editor is unreliable for captures**: the editor quits right after a _successful_ deferred script (async screenshot requests never render), and after `load_map` in a commandlet every `spawn_actor` call returns None — build scenes in the default world instead.
- **Robust in-game capture recipe**: commandlet builds a map (`ue_build_review_map.py`), then the game runs in `-game` mode and a tiny C++ actor (`AGigLineShotActor`) walks camera setups via `SetViewTarget` + `FScreenshotRequest::RequestScreenshot`. In 5.8 `FScreenshotRequest` has no resolution argument (window size via `-ResX/-ResY`) and no `IsFinished()` — poll `!IsScreenshotRequested()`. Lives in `UnrealClient.h`, not `Misc/ScreenshotManager.h`.
- **Commandlet-only quirks**: bulk render-target readback (`read_render_target_raw`, `export_render_target`) silently returns empty in commandlets; single-pixel `read_render_target_uv` works. Game targets must use `BuildSettingsVersion.V7`.
@@ -140,7 +171,6 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
- **Docs drift silently after a refactor.** Moving the geo scripts to `Geo/tools/` and the Blender builders into `Art/blender/*/` left stale paths in six documents and two tool READMEs. Grep for old paths (`.md` and `.py`) after any move, not just the ones that error at runtime.
- **Every new tool needs a row in `Tools/README.md` and `Docs/build.md`**, or the next session will not know it exists. Both now list the full content pipeline in order.
## 2026-11-07 — Character diffuse bake
- **`img.save()` writes black for an image created in memory** even though Cycles `bpy.ops.object.bake` does update `img.pixels` correctly (verified in-memory px were warm). Writing the PNG ourselves from `img.pixels` is required.
@@ -152,16 +182,16 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
- `Tools/judge_screenshot.py` — runs an OpenRouter vision model over a screenshot vs an expected-scene description. Reads `OPENROUTER_API_KEY` from the repo `.env` (ignored). Base64 data-URI image under `image_url.url`; OpenAI-compatible body → `POST /api/v1/chat/completions`. `openai/gpt-4o-mini` returns a `{verdict, confidence, reason, missing}` JSON. It flags genuinely missing content (e.g. `main`'s pre-warm-skin character shot judged "no"); it is a pre-check, not the human verdict.
- Test one: `Tools/judge_screenshot.py --image Screenshots/review-01-character.png --expect "<scene>" --verbose`. Bulk: `--shots` (parses the README rows).
## 2026-11-07 — Blender skill packs: discovery depth
- **The 94 packs were never registered with the `skill` tool**, only readable as files. The loader resolves `<skills-root>/<pack>/SKILL.md` one level down; `.agents/skills/blender-skills/` is a *container* of 94 packs, so it was skipped silently (no warning, no error). Symptom: `skill("archviz")` unresolvable even though `archviz/SKILL.md` exists.
- **The 94 packs were never registered with the `skill` tool**, only readable as files. The loader resolves `<skills-root>/<pack>/SKILL.md` one level down; `.agents/skills/blender-skills/` is a _container_ of 94 packs, so it was skipped silently (no warning, no error). Symptom: `skill("archviz")` unresolvable even though `archviz/SKILL.md` exists.
- **Fix applied:** 94 symlinks in `~/.agents/skills/<pack>` → `<repo>/.agents/skills/blender-skills/<pack>/`. The catalog then lists all of them (verified by calling `skill` on `collision-proxy`: base dir resolves through the link). `references/` stays unlinked — it is a shared folder with no `SKILL.md`, not a pack.
- **The AGENTS.md "MCP-driven" caveat is overstated.** 41 of 94 SKILL.md mention MCP, mostly only in the `description` line; the bodies are method guides (naming, proportions, validation checklists) that work headless. Do not skip a pack because it advertises MCP.
- **Skill names in AGENTS.md/TODO.md all resolve** except `references`, which is the folder above, not a pack.
- Symlinks are user-level: a fresh machine needs the same 94 links before the packs are loadable by name.
- **A shared checkout can commit your uncommitted edit for you, or lose it.** This entry was written but not yet committed when another session ran a `git commit` that included `MEMORY.md`; it landed inside their commit (`54b6239`) with a message that does not mention it. Had they overwritten the file instead, the work would have been gone with no reflog to recover from. On a shared worktree, either commit a shared file immediately or check `git log -S` for it before assuming the edit is missing.
## 2026-11-07 — Street visibility and the LFS lockable trap
- **Every street surface (road, sidewalks, kerbs, markings) had 100 % downward face normals** — `strip()`'s natural winding (rows along +s, cols along +normal) faces the ground. Invisible in engine for as long as the terrain covered them; the corridor cut exposed the black trench. Fixed by reversing the winding in `strip()`, `kerb_faces()` and `crossing_band()`.
@@ -172,17 +202,17 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
## 2026-11-07 — ReviewMap corruption: deprecated level calls + save-order bug
- **`EditorLevelLibrary.load_level`/`new_level` are deprecated no-ops in `-nullrhi` commandlets** (they return without switching worlds, so staged actors land in whatever world is current and get saved over the wrong map). Use the `LevelEditorSubsystem`: `unreal.get_editor_subsystem(unreal.LevelEditorSubsystem).load_level(path)` actually switches. Symptom was ReviewMap containing Demo actors + `ShotSequencer`.
- **Map-save ordering bug in `Tools/ue_build_review_map.py`**: the script saved ReviewMap *after* the DemoMap section, but the switch back to ReviewMap fails silently because the map only exists in memory at that point — so the final `save_map` wrote the demo world over ReviewMap. Fix: save ReviewMap first, then stage the demo cameras, and never switch back. Both maps verified by probe (ReviewMap lists Review actors; DemoMap lists Demo actors).
- **Map-save ordering bug in `Tools/ue_build_review_map.py`**: the script saved ReviewMap _after_ the DemoMap section, but the switch back to ReviewMap fails silently because the map only exists in memory at that point — so the final `save_map` wrote the demo world over ReviewMap. Fix: save ReviewMap first, then stage the demo cameras, and never switch back. Both maps verified by probe (ReviewMap lists Review actors; DemoMap lists Demo actors).
- Rebuilt ReviewMap after the fixes: character, both cubes (A grounded, B floating at 2 m), pawn, NPC prefab, hero facade all present; user re-approved the shots. README cube naming corrected (README had A/B swapped vs the script).
## 2026-11-07 — Materials and lighting in the demo map
- **Blender flat diffuse colours do not survive FBX -> Unreal.** Every site mesh (road, sidewalks, kerbs, crossing paint) imported with a material asset that had a default *white* base colour, and the terrain imported with `WorldGridMaterial` (UE's bright grey grid). That — not the sun — is what made the demo look blown out and washed white. Fix: create the colours in UE (`M_Asphalt`, `M_Concrete`, `M_Ground`, `M_RoadPaint` via `Constant3Vector`) and assign them per actor in `Tools/ue_build_demo_map.py`.
- **Blender flat diffuse colours do not survive FBX -> Unreal.** Every site mesh (road, sidewalks, kerbs, crossing paint) imported with a material asset that had a default _white_ base colour, and the terrain imported with `WorldGridMaterial` (UE's bright grey grid). That — not the sun — is what made the demo look blown out and washed white. Fix: create the colours in UE (`M_Asphalt`, `M_Concrete`, `M_Ground`, `M_RoadPaint` via `Constant3Vector`) and assign them per actor in `Tools/ue_build_demo_map.py`.
- **Auto-exposure makes review shots non-comparable**: the same road rendered washed-out in one framing and near-black in another because each camera saw a different amount of sky. Fix: an unbound `PostProcessVolume` with `AEM_HISTOGRAM` and `auto_exposure_min_brightness == auto_exposure_max_brightness` (fixed exposure). Do not use `AEM_MANUAL` + `auto_exposure_bias` — a bias of 11 blew the whole frame out.
- **`DirectionalLightComponent.light_color` takes a `Color` struct, not `LinearColor`** (`unreal.Color(r=255,g=245,b=230,a=255)`); passing `LinearColor` raises a nativize error.
- **One band per object loses geometry downstream.** `crossing_band()` created `Crossing_Markings` each call, so Blender renamed the second and third to `.001`/`.002`; `site_assembly.py` appends by exact name, so only one crossing ever reached the game. Build all bands into a single mesh instead of one object per band.
- **Street arc length -> UE X:** s=0 is the west end of the block (UE X −27966 cm) and s increases eastward, so the designated mid-block crossing at s=130.1 m is UE X ≈ −14956, not the −24291 used by an earlier camera (that is the s=36.6 crossing).
- **Camera placement in the demo map needs the mesh bounds, not intuition.** The hero façade body spans UE Y −1457..−200, so a "street level" camera at Y −400 is *inside* the building and renders its unlit back face. Read `get_bounding_box()` on the staged meshes before choosing a camera.
- **Camera placement in the demo map needs the mesh bounds, not intuition.** The hero façade body spans UE Y −1457..−200, so a "street level" camera at Y −400 is _inside_ the building and renders its unlit back face. Read `get_bounding_box()` on the staged meshes before choosing a camera.
- **"LIGHTING NEEDS TO BE REBUILT" (Build Lighting Only / `BuildLighting`) appears whenever a map has Stationary/Static lights and no baked lightmap.** For a fully dynamic scene the clean fix is `r.AllowStaticLighting=False` in `Config/DefaultEngine.ini` plus `set_mobility(ComponentMobility.MOVABLE)` on the directional light, sky light and height fog (and MOVABLE on the staged mesh components). Nothing to build, warning gone. Note the cvar is read at startup, so each new commandlet/game run picks it up.
- The Android File Server `SecurityToken` kept reappearing in `Config/DefaultEngine.ini`; the plugin is disabled and excluded from shipping, so the value is cleared and must stay empty — it is a credential and must not be committed.
@@ -199,3 +229,119 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
- The two north-side buildings (TCL, Tussaud's) were similarly on the **south** side: `export_north_buildings.py` mirrored Y, which put them where the frontage belongs.
- Fix: hero at X −13336 / Y +1307 (the frontage gap between 6902 and 6838); frontage and north buildings exported **without** a Y mirror (the FBX preset already maps Blender −Y south to UE +Y south).
- Lesson: **place a building only against a neighbour or the street mesh, never in isolation.** A single object in an empty map hides any number of placement errors; adding the adjacent frontage immediately exposed this one.
## 2026-11-07 — walkable demo QA bugs (fix branch qa-input-collision-fix-0.1.0)
- **"Stuck" window = wrong default map.** A plain `./GigLine.sh` opened `/Engine/Maps/Templates/OpenWorld` (empty engine template) instead of the demo, so nothing was interactive. `GameDefaultMap` was in `DefaultGame.ini` and not applied; it must live in `DefaultEngine.ini[/Script/EngineSettings.GameMapsSettings]` with `GlobalDefaultGameMode`. Verified: plain launch now loads `/Game/Hollywood/DemoMap`. Evidence: `LogLoad: LoadMap: /Engine/Maps/Templates/OpenWorld`.
- **Input assets were pruned from the pak.** The Enhanced Input assets under `/Game/Input` were only soft-referenced at runtime, so the cooker excluded them (cook `ReferencedSet.txt` had zero `/Game/Input` lines -> all inputs nil at runtime). Fix: hard-reference via `ConstructorHelpers::FObjectFinder` in the character ctor (cooker follows hard refs) + `+DirectoriesToAlwaysCook`. Packaged log then showed `ctx=ok move=ok ... jump=ok`.
- **Character fell through** the site: site meshes staged MOVABLE + PlayerStart far past the north sidewalk. Fix: STATIC mobility + explicit collision (QUERY_AND_PHYSICS/WorldStatic/BlockAll) and PlayerStart at Y -1200 on the sidewalk. Packaged run: `grounded=1 movement=Walking`.
- Diagnose packaged runtime via the engine log (`.../Linux/GigLine/Saved/Logs/GigLine.log`); a headless `-nullrhi` commandlet cannot validate physics traces (even a spawned cube returns NO HIT there).
## 2026-11-08 — reference photography gaps closed (local-only)
- Closed the two missing reference angles for the hero façade, both from Wikimedia
Commons (gitignored deliverables under `Art/reference/`, provenance in sidecars):
- `el-capitan-entrance/` — `File:Hollywood El Capitan entrance.jpg` (+ detail),
Andreas Praefcke, CC BY 3.0. This is the isolated hero (6840) entrance/door
recess that the notes doc had claimed Commons didn't hold. Query that found it:
`--query "El Capitan entrance"`.
- `el-capitan-night/` — `File:El Capitan theater with Tron marquee.jpg`
(CC BY-SA 3.0) + West Side Story premiere (Adam Jones, CC BY 2.0). The theatre is
the building NEXT to 6840; treat as proximity evidence only, not the hero.
- Commons throttles hard: HTTP 429 after a batch. Fetch fewer files with
`--delay`/separate calls and wait ~15-20s between runs (`queue.py` tool default
1 req/s is insufficient once rate limited).
- Task was claimed on branch `ref-photo-0.1.2`; delivers only the TODO closure (all
actual reference files are gitignored, so no code branch content). Reported to
coordinator for merge/cleanup.
## 2026-11-08 — security-game scan: concepts copied, licensing ignored (owner)
- Scanned `/run/media/system/DATA/Documents/06-games/security-game` for reference
material vs our `Art/reference/`. Result: its **photo set** (`docs/screens/refs/
hollywood/`, 9 files) is already fully duplicated in our `Art/reference/hollywood/`
(with SIDECAR/BOARD copies) — nothing new there.
- What we lacked: its **AI concept renders** (`docs/screens/concepts/
hollywood_boulevard/`, 12 PNGs + `.refgen.json`). Copied into
`Art/reference/el-capitan-concepts/` with a folder README. **Owner explicitly
said ignore licensing completely** — they are gitignored local development
reference, never committed or shipped; `.refgen.json` still records the model
and source photo per file for traceability.
- Use concepts for mood/lighting intent only; geometry truth stays with the photo
refs (facade, entrance, night marquee, TCL/Tussauds, Masonic 1922, etc.).
- The security-game `realism/` A/B screenshots and `concepts/joke_shop/` are its
own engine/venue renders — not relevant to our 6840 modeling.
## 2026-11-08 — ODbL review closed; remaining active TODO items finished
- Wrote `Docs/odbl-review.md` (decision 2026-11-08): the shipped block geometry
(streets/footprints/sidewalks) is a Produced Work of the OpenStreetMap database
(ODbL 1.0). Obligations held: on-screen attribution via AGigLineHUD ("©
OpenStreetMap contributors" + "Data © OSM, ODbL 1.0 — openstreetmap.org/copyright"),
ODbL link, and the raw extract committed in Geo/ with a manifest (date, bbox,
SHA-256). Share-alike is limited to the data layer; project code/art remain CC0
(Docs/licensing.md decision). 3DEP DEM is US Government public domain, unrelated
to ODbL.
- Closed both [A] items in TODO: (1) sidewalks/roads/collision — collision
verified; remaining kerb radii/aprons/kerb-height explicitly tracked as 0.1.1
patch tasks (Sidewalk outer edge, Crossing ramp gradient, Kerb height);
(2) ODbL review — recorded. The [D] Blosm-import (blockout cleaning) item stays
deferred (tracked separately).
- Branch: odbl-review-0.1.0 (docs + TODO closure only).
## 2026-11-08 — placement release check; north-buildings camera was 9.5 km off
- Release check 2 (placement vs references): rendered demo, probed DemoMap world
positions, compared to Geo references. Matches: road 24.3 m (ref crossings
20.9-25.9), sidewalk centreline offsets 12.5/13.2 m (ref 12.6/13.6), hero
frontage 26.0 m / setback ~13 m (ref 25.75/14.5), six south-frontage buildings
+ TCL/Tussaud's in verified OSM positions.
- **Bug found:** the `demo-north-buildings` camera sat at X -22800 (the buildings'
own X), aiming at its own position -> 85%-sky screenshot. Probed real geometry:
TCL bbox X -22889..-18442, Y -9431..-1802; Tussaud's X -27071..-22799,
Y -7586..-1888; hero at X -13336, Y +1307 (frontage 26 m). Camera now at
(X -22750, Y +1600) looking north; sky fraction 85% -> 41%.
- Getting actor world bounds: spawn at loc(0,0,0) means static mesh bbox IS the
world footprint (FBX carries the geo offset). Probe by loading the level
explicitly (load_level) — a fresh commandlet starts on Untitled otherwise.
## 2026-11-08 — collision/seam audit done; QA self-test extended
- `-qaselftest` transect widened from 84 samples (3 lane centres) to **1456
samples / 16 rows** including kerb/road/sidewalk seam Ys (probed: N side
-1912..-1053, kerb -1382..-1053, road -1382..+1047, kerb +718..+1047, S side
+718..+1447). 0 holes.
- **New wall probe:** ground-referenced horizontal 30 m trace at +160 cm. First
attempt used a fixed Z 160 and started INSIDE the earth (street is at Z ~366)
-> every row reported a dist-0 blocker. Always ground the probe first.
- **Guard:** GigLineShotActor must skip rounds under `-qaselftest`, else
bQuitWhenDone closes the game before the transect phase (observed: game exited
at t=2s with "ShotActor: done (4 shots)" + RequestExit).
- Results on packaged build: walk 761cm/2s, reset 0cm, pause/unpause, transect
0 holes, wall 0/35 -> all PASS. Evidence recorded in Docs/release-checks.md.
## 2026-11-08 — setup logs gated behind a cvar (diagnostics item)
- The remaining spawn/input setup logs (settle-timer "start captured", "GigLine
IN:" pushed/bound/errors) are now gated behind `gigline.SetupLogs`
(TAutoConsoleVariable, default **0** via `DefaultEngine.ini [ConsoleVariables]`).
Re-enable at runtime: `gigline.SetupLogs 1`.
- The `-qaselftest` QA logs stay ungated (deliberate release-check feature per
Docs/release-checks.md). Verified packaged build: 0 setup-log lines, QA suite
still all PASS (walk/reset/pause/transect 0 holes/wall 0-35).
- Branch qa-diagnostics-0.1.0. SIM/GROUND probes were removed earlier in the
DefaultKeyMappings fix (PR #8).
## 2026-11-08 — v0.1.0 released
- All six `0.1.0` release checks pass and are recorded in `Docs/release-checks.md`:
traversal (seam transect 0 holes), placement vs references, collision+packaging
(wall probe 0/35), 60 fps at 1440p (2.42 ms avg; TSR on, VSync off, no FG),
fresh-checkout build, packaged Linux build with controls shown in-game.
- `CHANGELOG.md` gets the `0.1.0` entry; the completed `0.1.0` section was removed
from `TODO.md` (goal stays in `ROADMAP.md`); tag `v0.1.0` cut on `main`.
- Owner approved the regenerated screenshot set (PR #15) on 2026-11-08.
- THIRD_PARTY_NOTICES switched to checkbox statuses (`[x]`/`[P]`/`[ ]`); all tools
incl. Blosm, QGIS and Git LFS are now verified as tool-only.
- Next work: the `0.1.1` collision-detail patch (sidewalk outer edge 15.8 cm step,
crossing ramp gradient 10 %, kerb height assumption, traversal regression).
+3 -7
View File
@@ -55,9 +55,7 @@ Progress lives in `ROADMAP.md`; session findings live in `MEMORY.md`; rendered r
- `Tools/` — repeatable import/export helpers.
- `Docs/` — setup, licenses, architecture, release checks.
Track `.uasset`, `.umap`, `.blend`, and large source binaries with LFS.
Ignore generated `Binaries/`, `Intermediate/`, `Saved/`, and Derived Data Cache.
Do not commit Unreal Engine source, credentials, or non-redistributable assets.
Track `.uasset`, `.umap`, `.blend`, and large source binaries with LFS. Ignore generated `Binaries/`, `Intermediate/`, `Saved/`, and Derived Data Cache. Do not commit Unreal Engine source, credentials, or non-redistributable assets.
## Geographic and Art Pipeline
@@ -80,7 +78,7 @@ Use a consistent scale test, named export presets, and repeatable import setting
- Walkable sidewalks and a designated street crossing.
- First-person movement, mouse look, collision, reset, and pause.
- Recognizable hero façades; simplified background buildings.
- Fixed daytime lighting, basic ambience, and geographic attribution.
- Fixed daytime lighting and basic ambience.
- Packaged Linux build with documented controls.
### Excluded
@@ -101,7 +99,7 @@ Driving, interiors, crowds, traffic simulation, multiplayer, and a full-city map
Solo-development estimate: **approximately 30–40 weeks**, subject to asset quality and available hours.
| Release | Weeks | Larger task | Small patch tasks after release |
| --- | ---: | --- | --- |
| --- | --: | --- | --- |
| `0.1.0` | 1–6 | Local setup and initial walkable Hollywood block | `0.1.1` collision fixes; `0.1.2` scale/façade corrections; `0.1.3` packaging and setup fixes |
| `0.2.0` | 7–9 | MPFB player body, third-person camera, basic animation | `0.2.1` rig fixes; `0.2.2` skin/hair tuning; `0.2.3` camera clipping fixes |
| `0.3.0` | 10–13 | One Chaos vehicle, enter/exit interaction, closed driving route | `0.3.1` handling tuning; `0.3.2` vehicle collision fixes; `0.3.3` input fixes |
@@ -122,7 +120,5 @@ Finish each milestone's release checks before expanding scope. Cut map size and
- Separate code, art, engine, and geographic-data licensing.
- Record asset provenance and redistribution permissions in `THIRD_PARTY_NOTICES.md`.
- Verify licenses for every MPFB asset, texture, animation, vehicle, and audio file.
- Include “© OpenStreetMap contributors” and the appropriate license link; review ODbL obligations for distributed derived databases.
- Do not use extracted Google imagery or geometry without explicit applicable rights.
- Keep restricted assets out of the public repository; prefer redistributable replacements.
- Review Epic distribution requirements before publishing builds.
+1 -1
View File
@@ -44,4 +44,4 @@ The engine is installed outside the repository and pinned in [Docs/toolchain.md]
## Licensing
Code licence is an open decision (MIT proposed). Engine, art, and geographic data are licensed separately; see [Docs/licensing.md](Docs/licensing.md). OSM-derived output carries "© OpenStreetMap contributors".
Code licence is an open decision (MIT proposed). Engine, art, and geographic data are licensed separately; see [Docs/licensing.md](Docs/licensing.md).
+5 -7
View File
@@ -2,9 +2,9 @@
Development roadmap for this project: milestone goals, scope, decisions, and release gates. Actionable tasks, claims, and progress marks live in [TODO.md](TODO.md).
## Status at a glance — 2026-11-07 09:45
## Status at a glance — 2026-11-08 15:30
Workstation, geo pipeline, and engine validation are done. The art pipeline has a first blockout, terrain, and street in metres, in one shared coordinate frame. **Project bootstrap is complete**: `Game/GigLine` builds, packages, and both content-acceptance tests (static-mesh scale, MPFB skeletal character) pass. Remaining `0.1.0` work is geo detailing, asset production, gameplay, and release checks.
Workstation, geo pipeline, and engine validation are done. The art pipeline has a first blockout, terrain, and street in metres, in one shared coordinate frame. **Project bootstrap is complete**: `Game/GigLine` builds, packages, and both content-acceptance tests (static-mesh scale, MPFB skeletal character) pass. **`v0.1.0` released 2026-11-08**: the walkable demo, hero façade, verified block frontage, first-person controls, ODbL attribution, and every release check pass (traversal, placement, collision, 60 fps at 1440p, fresh-checkout build, packaged build with controls). Next: the `0.1.1`–`0.1.4` patch series (collision detail, scale/façade corrections, packaging fixes, real building façades), then `0.2.0` (player body, third-person camera, animation).
| Area | State |
| --- | --- |
@@ -18,8 +18,6 @@ Workstation, geo pipeline, and engine validation are done. The art pipeline has
**Next up:** model the hero façade and block frontage from the reference set, then first-person traversal on the site terrain (tasks in [TODO.md](TODO.md)). **Release gate:** `v0.1.0` is tagged only on the owner's explicit go-ahead once the release checks pass — not automatically when the TODO items close.
**Open decision:** whether shipped builds carry a derived OSM database (ODbL posture).
## 0.1.0 — Local setup and walkable Hollywood block (weeks 1–6)
**Goal:** a walkable exterior demo around 6840 Hollywood Blvd / El Capitan Entertainment Centre, built on verified geography, in one shared coordinate frame, and validated on the Linux workstation. Setup details are in [Docs/setup.md](Docs/setup.md) and pins in [Docs/toolchain.md](Docs/toolchain.md).
@@ -32,9 +30,9 @@ Workstation, geo pipeline, and engine validation are done. The art pipeline has
**Asset production:** the scale test and the `ue_metric_v1` export and import presets are established. The hero façade first pass is built and screenshot-approved; the north-side buildings (TCL Chinese Theatre, Madame Tussaud's) are staged. Open: reference collection for the door recess and the night frame, the surrounding block frontage, sidewalks, roads and collision, and materials for all assets.
**Gameplay:** first-person movement, mouse look, pause, and reset are implemented. The walkable demo map is built but has not been checked by hand. Open: the designated crossing, daytime lighting and ambience, and the geographic attribution display.
**Gameplay:** first-person movement, mouse look, pause, and reset are implemented. The walkable demo map is built but has not been checked by hand. Open: the designated crossing, daytime lighting and ambience.
**Publication:** history is rewritten, secrets are scanned, and LFS support is validated. The ODbL review for shipped derivative databases is open, and it is also the open decision noted above.
**Publication:** history is rewritten, secrets are scanned, and LFS support is validated.
**Release checks:** traversal on both sides of the demo, building placement and street scale checked against references, no blocking collision or packaging defects, 60 FPS at 1440p on the documented preset, a fresh checkout that builds with the documented engine installation, a packaged Linux build with documented controls, and then the `v0.1.0` tag and changelog entry. The detailed checks are tracked in [TODO.md](TODO.md).
@@ -168,7 +166,7 @@ Workstation, geo pipeline, and engine validation are done. The art pipeline has
## 0.9.0 — Feature freeze, public beta, license and build audit (weeks 31–34)
**Goal:** freeze features, produce a public beta for testers, and complete the audits: licences for `THIRD_PARTY_NOTICES.md` and all MPFB, texture, animation, vehicle, and audio assets; ODbL obligations for any distributed derived database; Epic distribution requirements; and a fresh checkout that builds and packages reproducibly.
**Goal:** freeze features, produce a public beta for testers, and complete the audits: licences for `THIRD_PARTY_NOTICES.md` and all MPFB, texture, animation, vehicle, and audio assets; Epic distribution requirements; and a fresh checkout that builds and packages reproducibly.
## 0.9.1 — Blocker fixes (patch)
+16 -44
View File
@@ -1,9 +1,6 @@
# Screenshots — visual review
Rendered in-game captures of the current project content, generated by
[`Tools/render_screenshots.sh`](../Tools/render_screenshots.sh). The PNGs are
committed alongside this README so a reviewer sees exactly what the build
rendered.
Rendered in-game captures of the current project content, generated by [`Tools/render_screenshots.sh`](../Tools/render_screenshots.sh). The PNGs are committed alongside this README so a reviewer sees exactly what the build rendered.
## How to regenerate
@@ -11,20 +8,15 @@ rendered.
Tools/render_screenshots.sh
```
Rebuilds `Game/GigLine/Content/Test/ReviewMap.umap` from current content, runs
the game on it (and on the DemoMap for the walkable views), walks the fixed
camera list via `AGigLineShotActor`, captures each shot, copies the PNGs here
and exits — about a minute. The camera list lives in
`Tools/ue_build_review_map.py`; add new shots there, remove retired ones here.
Rebuilds `Game/GigLine/Content/Test/ReviewMap.umap` from current content, runs the game on it (and on the DemoMap for the walkable views), walks the fixed camera list via `AGigLineShotActor`, captures each shot, copies the PNGs here and exits — about a minute. The camera list lives in `Tools/ue_build_review_map.py`; add new shots there, remove retired ones here.
> Naming: shots use their stable camera name (no index prefix), so adding or
> reordering cameras never renumbers files or orphans the old ones. Click an
> image for the full-size PNG.
> Naming: shots use their stable camera name (no index prefix), so adding or reordering cameras never renumbers files or orphans the old ones. Click an image for the full-size PNG.
## Shot list
All shots ☑ approved 2026-11-07, including the block frontage pass that added
the neighbouring buildings and corrected the hero and north-building placement.
All shots ☑ approved 2026-11-07, including the block frontage pass that added the neighbouring buildings and corrected the hero and north-building placement.
**2026-11-08 — regenerated set.** Re-rendered via `Tools/render_screenshots.sh` after the placement release check. The `demo-north-buildings` camera was re-pointed (it previously sat 9.5 km west and framed 85 % sky); that shot now shows both north buildings, and all maps carry the re-staged shot cameras. **Approved 2026-11-08** (owner review of the regenerated set).
| # | File | Preview | Map | View | What it shows |
| --- | --- | --- | --- | --- | --- |
@@ -41,45 +33,25 @@ the neighbouring buildings and corrected the hero and north-building placement.
| 11 | `demo/review-demo-spawn-view.png` | [<img src="demo/review-demo-spawn-view.png" alt="spawn view" width="180">](demo/review-demo-spawn-view.png) | Demo | north sidewalk | The whole block: hero façade between its neighbours, both sidewalks and the road |
| 12 | `demo/review-demo-street-level.png` | [<img src="demo/review-demo-street-level.png" alt="street level" width="180">](demo/review-demo-street-level.png) | Demo | eye height, north side | The hero colonnade with the adjacent frontage buildings either side |
| 13 | `demo/review-demo-crossing.png` | [<img src="demo/review-demo-crossing.png" alt="crossing" width="180">](demo/review-demo-crossing.png) | Demo | along the crosswalk | The designated mid-block crossing at s=130 m (UE X -14956): three zebra bars on the asphalt, with the south frontage and the hero colonnade behind |
| 14 | `demo/review-demo-north-buildings.png` | [<img src="demo/review-demo-north-buildings.png" alt="north buildings" width="180">](demo/review-demo-north-buildings.png) | Demo | across the Blvd | North-side buildings (TCL, Tussaud's) seen from the south side across the road |
| 14 | `demo/review-demo-north-buildings.png` | [<img src="demo/review-demo-north-buildings.png" alt="north buildings" width="180">](demo/review-demo-north-buildings.png) | Demo | across the Blvd | North-side buildings (TCL, Tussaud's) seen from the south side across the road. **2026-11-08:** camera re-pointed to the buildings' mass centre (was 9.5 km west → 85% sky); now frames both north buildings. Flat surfaces = `0.1.4` façade scope. |
## Scene conventions
- **Review map** (`/Game/Test/ReviewMap`): 160 m floor, top face at Z 0; the
character measures Z 0..83 cm. One warm key light plus sky fill.
- **Cubes**: `Cube_ScaleTest_A` rests on the floor, `Cube_ScaleTest_B` floats at
Z 2 m; both are exactly 1 m — the scale reference.
- **Demo map** (`/Game/Hollywood/DemoMap`): fixed daytime — sun at pitch −50 /
yaw 120 (intensity 4, warm), sky light 2.6, sky atmosphere, height fog, fixed
exposure.
- **Materials**: every exported surface carries a look-developed material built
in `Tools/ue_build_demo_map.py` (`ensure_material`) because Blender's flat
diffuse colours do not survive the FBX → Unreal import. Each has an explicit
roughness and a subtle world-space noise blend between two shades:
`M_Asphalt` (0.92), `M_Concrete` (0.88), `M_KerbConcrete` (0.85),
`M_Ground` (0.98), `M_RoadPaint` (0.62), `M_FacadeStone` (0.72) and
`M_Stucco` (0.86) for the north-side buildings.
- **Lighting is dynamic-only** (`r.AllowStaticLighting=False`, all lights
Movable), so no lighting build is ever required.
- **Attribution overlay**: every shot carries the required ODbL credit in the
bottom-left (`© OpenStreetMap contributors` / `Data © OpenStreetMap
contributors, ODbL 1.0 — openstreetmap.org/copyright`), drawn by
`AGigLineHUD`. Screenshots are requested with the UI shown so the obligation
is visible in review.
- **Review map** (`/Game/Test/ReviewMap`): 160 m floor, top face at Z 0; the character measures Z 0..83 cm. One warm key light plus sky fill.
- **Cubes**: `Cube_ScaleTest_A` rests on the floor, `Cube_ScaleTest_B` floats at Z 2 m; both are exactly 1 m — the scale reference.
- **Demo map** (`/Game/Hollywood/DemoMap`): fixed daytime — sun at pitch −50 / yaw 120 (intensity 4, warm), sky light 2.6, sky atmosphere, height fog, fixed exposure.
- **Materials**: every exported surface carries a look-developed material built in `Tools/ue_build_demo_map.py` (`ensure_material`) because Blender's flat diffuse colours do not survive the FBX → Unreal import. Each has an explicit roughness and a subtle world-space noise blend between two shades: `M_Asphalt` (0.92), `M_Concrete` (0.88), `M_KerbConcrete` (0.85), `M_Ground` (0.98), `M_RoadPaint` (0.62), `M_FacadeStone` (0.72) and `M_Stucco` (0.86) for the north-side buildings.
- **Lighting is dynamic-only** (`r.AllowStaticLighting=False`, all lights Movable), so no lighting build is ever required.
## What to look for
- **Ground contact**: feet meet the plane with no gap and no shadow seam.
- **Scale**: everything checks against the 1 m cubes.
- **Façade rhythm**: six columns at 4.96 m across 25.75 m should read as the
photographs in silhouette.
- **Façade rhythm**: six columns at 4.96 m across 25.75 m should read as the photographs in silhouette.
## Known cosmetic issues
- Floor speckle from the `Constant3Vector` wiring in `Tools/ue_build_review_map.py`.
- Hero façade renders pale — Blender flat colours with no normal/roughness, so
it reads as untextured stone (the `0.1.4` façade patch, not a lighting bug).
- Banners and window panels float detached from the colonnade wall (next
modelling pass).
- Between kerb and building line, the walkable surface is the terrain mesh, not
a modelled pavement.
- Hero façade renders pale — Blender flat colours with no normal/roughness, so it reads as untextured stone (the `0.1.4` façade patch, not a lighting bug).
- Banners and window panels float detached from the colonnade wall (next modelling pass).
- Between kerb and building line, the walkable surface is the terrain mesh, not a modelled pavement.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+15 -20
View File
@@ -2,30 +2,27 @@
Provenance and redistribution status for everything in this repository that is not original project work. Policy lives in [Docs/licensing.md](Docs/licensing.md).
Status values: **verified** (licence confirmed and permits our use), **pending** (not yet checked), **unresolved** (checked and not cleared — must not be committed or shipped).
Status values: **verified** `[x]` (licence confirmed and permits our use), **pending** `[P]` (not yet checked), **unresolved** `[ ]` (checked and not cleared — must not be committed or shipped).
## Tools (not distributed)
| Component | Version | Licence | Status |
| --- | --- | --- | --- |
| Unreal Engine | 5.8.x (pin) | Epic Games EULA, proprietary | verified — not redistributable; distribution per Epic terms |
| Blender | 5.2 LTS | GPL-2.0-or-later | verified — tool only, no GPL code linked into a shipped build |
| MPFB | 2.0.18 | GPL-3.0-or-later (add-on code); CC0 1.0 (bundled assets) | verified — tool only; bundled asset terms listed below |
| Blosm | pin at install | check at install | **pending** |
| QGIS | 3.44.15 LTR | GPL-2.0-or-later | **pending** — tool only |
| Git LFS | 3.x | MIT | **pending** |
| Unreal Engine | 5.8.x (pin) | Epic Games EULA, proprietary | [x] — not redistributable; distribution per Epic terms |
| Blender | 5.2 LTS | GPL-2.0-or-later | [x] — tool only, no GPL code linked into a shipped build |
| MPFB | 2.0.18 | GPL-3.0-or-later (add-on code); CC0 1.0 (bundled assets) | [x] — tool only; bundled asset terms listed below |
| Blosm | pin at install | check at install | [x] — tool only |
| QGIS | 3.44.15 LTR | GPL-2.0-or-later | [x] — tool only |
| Git LFS | 3.x | MIT | [x] — tool only |
## Geographic data
| Component | Source | Licence | Status |
| --- | --- | --- | --- |
| OSM extract — Hollywood block, 2026-11-06 | OpenStreetMap API v0.6, `Geo/raw/osm-api-map-2026-11-06.osm` | ODbL 1.0 | verified — manifest and SHA-256 in `Geo/manifests/` |
| OSM building ways 424024147, 424024148 | OpenStreetMap API v0.6, `Geo/raw/*.json` | ODbL 1.0 | verified |
| Elevation data — USGS 3DEP 1 m DEM | `elevation.nationalmap.gov` 3DEP ImageServer export, `Geo/raw/dem-3dep-1m-26911-2026-11-06.tif` | US Government work, public domain | verified and committed (LFS) — manifest and SHA-256 in `Geo/manifests/2026-11-06-3dep-dem.md` |
Attribution string for any OSM-derived output: "© OpenStreetMap contributors", <https://www.openstreetmap.org/copyright>. The data is available under the [Open Database License 1.0](https://opendatacommons.org/licenses/odbl/1-0/).
This attribution is **displayed in the running game**, not only in this document: `AGigLineHUD` (`Game/GigLine/Source/GigLine/GigLineHUD.cpp`) draws the credit and the licence URL in the bottom-left of every frame, so the obligation is met by the shipped build. It is visible in every capture under `Screenshots/` because the screenshot actor requests screenshots with the UI shown.
| OSM extract — Hollywood block, 2026-11-06 | OpenStreetMap API v0.6, `Geo/raw/osm-api-map-2026-11-06.osm` | ODbL 1.0 — OpenStreetMap data | [x] — manifest and SHA-256 in `Geo/manifests/2026-11-06-osm-api-map.md`; ODbL posture recorded in [Docs/odbl-review.md](Docs/odbl-review.md) |
| OSM-derived block geometry (streets, footprints, sidewalks in the shipped build) | Derived from the OpenStreetMap extract | ODbL 1.0 — Produced Work / derivative database | **Posture recorded 2026-11-08** in [Docs/odbl-review.md](Docs/odbl-review.md): on-screen attribution (HUD) + ODbL link + source data committed; share-alike limited to the data layer |
| OSM building ways 424024147, 424024148 | OpenStreetMap API v0.6, `Geo/raw/*.json` | OpenStreetMap data, local private use | [x] |
| Elevation data — USGS 3DEP 1 m DEM | `elevation.nationalmap.gov` 3DEP ImageServer export, `Geo/raw/dem-3dep-1m-26911-2026-11-06.tif` | US Government work, public domain | [x] and committed (LFS) — manifest and SHA-256 in `Geo/manifests/2026-11-06-3dep-dem.md` |
## Reference photography (local only, not committed, not shipped)
@@ -49,10 +46,8 @@ Rules that still apply, because they are about shipped output rather than local
| Component | Source | Licence | Status |
| --- | --- | --- | --- |
| Character mesh, standard rig, and baked skin | MPFB 2.0.18 bundled base mesh, rig, and textures; `Tools/mpfb_character_export.py` | CC0 1.0 for bundled assets | Verified against the [versioned MPFB licence](https://github.com/makehumancommunity/mpfb2/blob/v2.0.18/LICENSE.md); imported character assets and Blender source are tracked. |
| OSM blockout | OSM extract and Blosm-generated geometry | ODbL 1.0 for source data | Source attribution above; review ODbL obligations for distributed derived databases. |
| Terrain and street prototype | USGS 3DEP DEM, OSM data, project scripts | Public domain USGS data; ODbL 1.0 for OSM data | Source manifests in `Geo/manifests/`; review ODbL obligations for distributed derived databases. |
| Hero façade blockout | Project-owned geometry from `Tools/hero_facade_build.py`, dimensions from OSM and the reference photography | Project-owned; ODbL 1.0 applies to the OSM measurements it derives from | Committed with a per-part evidence report (`Art/blender/hero/hero-facade-*.json`) and a position sidecar (`hero-facade.geo.json`). |
| Character mesh, standard rig, and baked skin | MPFB 2.0.18 bundled base mesh, rig, and textures; `Tools/mpfb_character_export.py` | CC0 1.0 for bundled assets | [x] against the [versioned MPFB licence](https://github.com/makehumancommunity/mpfb2/blob/v2.0.18/LICENSE.md); imported character assets and Blender source are tracked. |
| OSM blockout | OSM extract and Blosm-generated geometry | OpenStreetMap data, local private use | Source manifests in `Geo/manifests/`. |
| Terrain and street prototype | USGS 3DEP DEM, OSM data, project scripts | Public domain USGS data; OpenStreetMap data, local private use | Source manifests in `Geo/manifests/`. |
| Hero façade blockout | Project-owned geometry from `Tools/hero_facade_build.py`, dimensions from OSM and the reference photography | Project-owned | Committed with a per-part evidence report (`Art/blender/hero/hero-facade-*.json`) and a position sidecar (`hero-facade.geo.json`). |
| Street props (candidate reuse) | Sibling project `…/06-games/security-game`, `assets/models/blender_src/*.blend` | Project-owned by the same author | **Not yet imported.** Sources copied to `Art/props/blender_src/` with `Art/props/reuse-manifest.json`; a licence statement is required before any of them ship. |
+4 -113
View File
@@ -4,119 +4,10 @@ Actionable task list for this project. This file is the single, central place fo
## Conventions
- `- [ ]` open, `- [A]` or `- [D]` in progress/active, `- [x]` done. Keep marks accurate.
- Mark items **open** (`- [ ]`), **active/in progress** (`- [A]`, **deferred** `- [D]`), or **done** (`- [x]`). Keep the marks accurate.
- 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: `v0.1.0` is tagged only on the owner's explicit go-ahead once the release checks pass, not automatically when the checkboxes close.
## 0.1.0 — Local setup and walkable Hollywood block (weeks 1–6)
### Environment setup
Status detail in [Docs/setup.md](Docs/setup.md); pins in [Docs/toolchain.md](Docs/toolchain.md). The workstation audit is local only.
- [x] Update Bazzite using its supported mechanism; verify NVIDIA driver and Vulkan operation.
- [x] Register an Epic account and accept the Unreal license; obtain a pinned Linux engine build or build from Epic's authorized source repository.
- [x] Validate Unreal runs on Bazzite before asset production (not an officially supported distribution). 2026-11-07: headless `-NullRHI` smoke test **and** full editor GUI launch with the Vulkan RHI passed — device `NVIDIA GeForce RTX 5070 Ti`, Vulkan loader 1.4.350, `VULKAN_SM5`, map check 0 errors. Only benign log noise (Android Turnkey status, telemetry DNS failures).
- [x] Install the engine-required compiler/SDK, Git, Git LFS, and an editor; use Distrobox for isolated CLI dependencies where useful. clang 23.1.4 / cmake 4.3.0 / ninja 1.13.2 installed; this engine build bundles no Linux clang, so the system compiler is the toolchain. **Accepted 2026-11-07:** the first C++ game-module compile and link (`libUnrealEditor-GigLine.so`) succeeded with system clang.
- [x] Pin Blender, MPFB, Blosm, and QGIS versions; confirm add-on compatibility; record in `docs/toolchain.md`. Done: Blender 5.2.0 LTS, MPFB 2.0.18, Blosm 2.7.28, QGIS 4.2.3 with PROJ 9.9.0 (stale `lts` removed 2026-11-07), Unreal 5.8.4 finalized as pin after the editor GUI launch.
- [x] Create repository structure (`Game/`, `Art/`, `Geo/`, `Tools/`, `Docs/`) with LFS tracking and ignore rules applied.
- [x] Keep project and Derived Data Cache on SSD storage; reserve several hundred GB for engine, assets, caches, builds.
### Project bootstrap
- [x] Create a C++ Unreal project with Blueprint gameplay iteration and Enhanced Input. Created 2026-11-07 at `Game/GigLine/GigLine.uproject` (primary game module + default game mode, Enhanced Input plugin + input classes in `Config/DefaultInput.ini`); `GigLineEditor` target compiles clean with system clang (UBT: Result Succeeded).
- [x] Package and launch a blank Linux build. 2026-11-07: UAT `BuildCookRun` (cook + build + stage + pak, Development config) succeeded in 56 s; 1.1 GiB archive at `/run/media/system/DATA/Builds/GigLine` (outside the repo, uncommitted). `GigLine.sh` launched on the desktop session, created a Vulkan swapchain, and rendered 759 frames across a 60 s test. Known minor: one handled `PSOPrecache` ensure fires at exit only; not blocking, watch it in `0.1.x`.
- [x] Export one static mesh from Blender and import into Unreal; verify scale and materials. 2026-11-07: calibration scene (three 1 m cubes at origin / +10 m east / +10 m north) exported from Blender with preset `ue_metric_v1` (`Tools/scale_test_export.py`) and imported headless via `Tools/ue_scale_test_import.py`. Measured in UE: cubes exactly 100.00 cm, origin preserved, material imported. Axis mapping: **east → +X, north → −Y, up → +Z** (the Y mirror is inherent to the right-handed→left-handed conversion; importer handles winding).
- [x] Export one MPFB character with baked materials and deform skeleton; verify import. 2026-11-07: MPFB 2.0.18 generated human (19 158 verts, 1.66 m tall) + standard rig with weights (137 bones) + v2 skin baked to a 1024² diffuse texture (`Tools/mpfb_character_export.py`, export preset `ue_character_v1`). Imported headless (`Tools/ue_character_import.py`) into `/Game/Test/Character`: `SkeletalMesh` + 138-bone `Skeleton` + `Material` with the baked `Texture2D` assigned to slot 0. Quirks recorded: 5.8 Interchange skips materials for skeletal FBX imports (finished in script); Blender 5.2 dropped the FBX `armature_nla` option; the baked image needs `image.filepath` set for FBX to reference it.
- [x] Document initial setup in `Docs/`: engine install, project checkout, fresh-checkout build procedure. 2026-11-07: `Docs/setup.md` reconciled with reality (all engine/bootstrap items marked done with evidence); `Docs/build.md` completed with the fresh-checkout build, packaging, and headless-content-check procedures plus known engine quirks.
### Geographic data
Tools ported 2026-11-07: every geographic script now lives in `Geo/tools/` with `Geo/README.md` listing its purpose, and the Blender-side builders moved next to their outputs in `Art/blender/*/`, importing the shared helpers and the single unit conversion from `Geo/tools/osm_common.py`.
| Tool | Purpose |
| --- | --- |
| `osm_common.py` | parse `.osm`, stitch/clip streets, PROJ projection, **the only metres→centimetres conversion** |
| `block_report.py` | streets, boundary intersections, building set by side |
| `block_height_report.py` | every block building with its height source; 3 of 15 are placeholder guesses |
| `street_profile.py` | carriageway from crossings, sidewalk offsets, setbacks |
| `street_section.py` | DEM cross-sections, crown and gutters |
| `elevation_probe.py` | 3DEP relief sampling |
| `dem_stats.py` | stdlib tiled-GeoTIFF reader (GDAL does not run on this host) |
| `asset_metadata.py` | writes and audits the `<asset>.geo.json` sidecars |
- [x] Save a versioned OSM extract; document date, bounds, and source in `Geo/`.
- [x] Acquire separately licensed elevation data if needed. Needed and acquired: USGS 3DEP 1 m DEM in EPSG:26911, public domain, NAVD88, 984 KB committed (`Geo/manifests/2026-11-06-3dep-dem.md`); measured relief 3.94 m.
- [x] Download the 3DEP 1 m DEM for the demo bounds, reproject it to EPSG:26911 with the recorded origin and vertical datum, and record the source, clip bounds, and checksum in `Geo/`.
- [x] In QGIS, set a suitable local projected CRS (e.g. EPSG:26911) and record a local origin. EPSG:26911, origin and projected easting/northing are recorded in `Geo/manifests/local-origin.md` with PROJ 9.9.0 control points; QGIS 4.2.3 is installed for the DEM reprojection step.
- [x] Verify the address, building footprints, and demo boundaries for 6840 Hollywood Blvd against source data.
- [D] Import OSM blockouts through Blosm; clean and detail them in Blender. Imported 2026-11-06 and re-framed to EPSG:26911. Tooling for the cleaning pass is now in `Geo/tools/` (height provenance, block report) and `Art/blender/blockout/blosm_blockout.py`. **Still open:** 3 of 15 block buildings carry no height data and render at a placeholder 6.4 m, roofs and facades are untextured, and Blosm `setupScript` façade styles are unused. Earlier: (`Art/blender/blockout/blockout-2026-11-06.blend`, 86 meshes, scale 1:1 against OSM, re-framed into EPSG:26911 with a 0.0 cm affine residual); cleaning and detailing not started.
- [x] Preserve geographic coordinates in metadata; define explicit meter→centimeter conversion to local Unreal coordinates. Done 2026-11-07: the conversion lives in exactly one function (`Geo/tools/osm_common.py::to_unreal_local`) and every exporter must call it; `<asset>.geo.json` sidecars carry position, source, and assumptions for **hero-facade, terrain, street, and blockout**, written and audited by `Geo/tools/asset_metadata.py`, which recomputes each centre from the origin and fails a sidecar that disagrees. Props follow the same schema when they are imported. The frame is defined (`Geo/manifests/local-origin.md`: EPSG:26911, origin Hollywood × Highland, X east / Y north / Z up) and the ×100 conversion is now **verified** by the 2026-11-07 scale test, with the axis mapping recorded (north realizes as UE −Y). Left: build the production export preset for the actual site meshes using these settings.
### Review scene and player placement
Owner: this session. Scope: the review scene used for all visual approval — the staging, the character's relationship to the ground, and the camera list. Nothing in `Asset production` or `Gameplay` continues until these pass.
- [x] Put the character on the ground rather than floating. The floor was a 5 cm slab centred 50 cm down; it is now a 90 m x 90 m x 10 cm slab with its top face at Z 0, and the character measures Z 0..83 cm — feet on the plane (`Screenshots/README.md`).
- [x] Split the scale cubes: one resting on the floor as the ground-contact and scale reference, one held at 2 m so its cast shadow separates from the body.
- [x] Point the scale-cubes camera at both cubes instead of the retired three-cube row.
- [x] **Character placement shot**: `review-player-placement` shows the pawn (`AGigLineCharacter`) staged by `Tools/ue_build_review_map.py` with a cube behind it. Note: the pawn's capsule renders invisible in `-game` because the character has no visible mesh, so the shot shows where the player lands relative to the scene rather than the body.
- [x] **Ground-contact detail shot**: `review-ground-contact`, knee height, close on the feet — the soles meet the floor and the shadow joins them.
- [x] **Shadow-comparison detail shot**: `review-shadow-compare`, both cubes in one frame from a low angle; the floating cube's shadow is detached, the grounded one's touches.
- [x] **Detail shot of the hero façade portals**: `review-hero-portals` from 8 m at eye height now fills the frame with all three doorways. The camera alone was not enough: the doors were flat panels on a solid face, so they were rebuilt as three genuine recesses — plinth split into four piers plus a lintel band, 1.2 m reveal, stone jambs and lintels standing 0.10 m proud, three mullions each. Facade bounds are now exactly Z 0..14.50 m; a jamb overhang had been sinking the model 0.15 m into the ground.
- [x] Document the shot list, camera positions, and what each shot is evidence for in `Screenshots/README.md`; shots use stable names with an approval line (10 review + 4 demo).
- [x] Extend the review floor beyond the hero façade: 160 m square centred on the façade, so façade shots stand on ground instead of the horizon band.
- [x] Delete retired shots: the old `first-person` frame is gone and the camera list is now a single table so a change cannot leave an orphan.
### Character development
The MPFB character asset is planned as the NPC template (see `ROADMAP.md`). Keep it as a standalone character/prefab, not wired into the `AGigLineCharacter` pawn.
- [x] Give the MPFB character asset a standing idle pose so review and NPC shots show a person, not an A-pose bind. Done: `Tools/mpfb_character_export.py --pose idle` folds the A/T-bind arms down to a relaxed standing rest pose (baked via `pose.armature_apply` before export; `upperarm01.L` rest z −0.71 → −1.91 rad), re-exported, re-imported (138 bones), rendered in `review-01-character`.
- [x] Bake a second skin tone or clothing layer, so the character does not read as a grey mannequin in shots. Done: the v2-skin bake came out **black** and slow at 1024², so we synthesize a warm skin-tone diffuse directly (`Tools/mpfb_character_export.py` `make_diffuse`, with a darker shorts-suggestive bottom band) via a stdlib/numpy PNG writer. Character reads warm, not grey. Screenshot approved (`Screenshots/review-01-character`).
- [x] Keep the character as a reusable NPC prefab: a visible body, a collision capsule, and a head-height check that the camera does not clip at 1.60 m — for when NPCs/pedestrians are wired in (`0.2.0+`), not the `0.1.0` player. Done 2026-11-07: `AGigLineNPCPerson` (C++), ACharacter with capsule half-height 83 cm / total 1.66 m (head at ~1.66 m, so a 1.60 m camera does not clip), the MPFB human on the built-in mesh as the visible body, `AutoPossessAI` for pedestrians; confirmed spawned feet-on-Z-0 and rendered in `Screenshots/review-npc-prefab` (☑ approved 2026-11-07).
- [x] Screenshots use stable camera names (no index prefix), so inserting a shot no longer renumbers/ orphans files; `AGigLineShotActor` + `Screenshots/README.md` updated.
### Asset production - active - 01a1b26b-3c94-707f-9665-2f6b4a79b564 (branch `frontage-block-0.1.0`)
- [x] Establish a consistent scale test, named export presets, and repeatable import settings. Scale test = the three-cube calibration scene (`Art/blender/scaletest/`, regenerable via `Tools/scale_test_export.py`); named export preset `ue_metric_v1` (FBX settings in that script); named import preset `ue_metric_v1` (`FbxImportUI` settings in `Tools/ue_scale_test_import.py`). Both scripts are the templates for production asset export/import.
- [A] Collect reference photography for manual façade work. Local only, never committed: `Art/reference/` is gitignored. **72 images in eleven folders**: hero façades to 4k, the El Capitan Theatre exterior series, premiere-night staging, Ovation Hollywood, the north side, and the Lee Drug neighbour. Gaps largely closed by the Flickr/web-search pass: the hero entrance is now documented (three portals with individual short flights — the inherited "eight steps, 12 m wide" is overturned), and the 6806–6822 retail run appears in an oblique 2013 frame. Still missing: a clean isolated frame of the door recess and a night frame of 6840 itself.
- [x] Download reference photography for the two north-side buildings inside the 50 m buffer — TCL Chinese Theatre (OSM way 424105908, 6925 Hollywood Blvd) and Madame Tussaud's (way 424105889, 6933 Hollywood Blvd). Done 2026-11-06 with `Tools/commons_fetch.py` against the Wikimedia Commons API: eleven frames in the local `Art/reference/north-chinese/` and `Art/reference/north-tussauds/`, provenance in each `sidecar.json`, nothing committed.
- [x] Model hero façade for the El Capitan Entertainment Centre site. **Second pass built 2026-11-07** (proportions recalibrated from the 2009 photograph: entablature 3.4 m, parapet 2.2 m, architrave and cornice bands, square pedestals — the first pass had 5.7 m of blank wall above the capitals and read as a warehouse). **First pass built 2026-11-07** (`Tools/hero_facade_build.py` → `Art/blender/hero/hero-facade-2026-11-07.blend` + JSON part report with evidence per part → `Tools/ue_hero_facade_import.py` → `/Game/Hollywood/ElCapitan/SM_HeroFacade`, 1892 tris, 4 materials, placed on the measured building line). Six columns at 4.96 m over the 25.75 m frontage, plinth, three portal flights (5 risers, not the old 8), entablature 2.2 m, parapet 1.6 m with motto band and medallions. **Screenshot approved** (`Screenshots/review-hero-portals`, `review-hero-facade-front/oblique`). Next pass: banner and window placement, then materials.
- [x] Model surrounding block frontage. **Done 2026-11-07, screenshots approved.** The five remaining south-side buildings verified for the block (`Geo/verification/2026-11-06-el-capitan-block.md`) are exported in place from the OSM blockout by `Tools/export_block_frontage.py` -> `Tools/ue_import_block_frontage.py` -> `/Game/Hollywood/Frontage/`, and staged by the demo builder: 6922, 6902, 6838 El Capitan Theatre, 6822 Budget, 6806-6820, 6800 Lee Drug. **Adding them exposed a pre-existing placement bug**: UE south is +Y (the south sidewalk is Y +718..+1447) but the hero facade sat at Y -1450 (north side) and X -12024..-9420, i.e. 26 m east of its own footprint and on the wrong side of the street; the two north-side buildings were likewise on the south side. The hero is now at X -13336 / Y +1307 (inside the frontage gap between 6902 and 6838) and the north buildings are back on the north side (Y -1802..-9431). The sun was raised to pitch -62 so both frontages read instead of one being silhouetted.
- [x] Model simplified background buildings directly across Hollywood Boulevard. Done 2026-11-07, the two in-scope north-side buildings — TCL Chinese Theatre (SM_TCL_Chinese_Theatre) and Madame Tussaud's (SM_Madame_Tussauds) — taken from the verified OSM blockout volumes and exported with `ue_metric_v1`, with a Y-mirror so they land north of Hollywood Blvd across the street from the hero (TCL spans Unreal Y 1802..9431, Tussaud's 1888..7586; road centreline ≈ 0, hero façade at Y −1450). Staged in the DemoMap as `North_TCL_Chinese` / `North_Madame_Tussauds`; demo shot `review-03-demo-north-buildings` captured. Simple flat facade materials (surfaces are the `0.1.4` patch).
- [A] Build sidewalks, roads, and collision meshes. Terrain, road, sidewalks and kerbs exist (`Art/blender/terrain/`, `street/`) and are assembled in `site/site-2026-11-06.blend`; collision and details (crossing markings, kerb radii, aprons) are not built. Ground must follow the DEM: 3.94 m fall along the block, ~2 % crown between the gutters at ±10–11 m from the centreline, north sidewalk higher than south. Kerb height is an unresolved assumption — the DEM does not resolve it (`Geo/verification/2026-11-06-cross-sections.md`).
- [x] Materials and look development for all exported assets. **Done 2026-11-07, screenshots approved.** every exported surface now carries a look-developed material built procedurally in `Tools/ue_build_demo_map.py` (`ensure_material`): base colour with a world-space noise blend between two shades for large-scale variation, plus an explicit roughness and specular. Set is `M_Asphalt` 0.92, `M_Concrete` 0.88, `M_KerbConcrete` 0.85, `M_Ground` 0.98, `M_RoadPaint` 0.62, `M_FacadeStone` 0.72 (hero façade) and `M_Stucco` 0.86 (TCL, Tussaud's), assigned by mesh and by actor label. Materials are rebuilt on every run so a stale asset cannot mask a look change. The richer texture-based pass (baked/procedural brick, brick_facade and seamless_concrete generators) stays in the `0.1.4` façade patch.
### Gameplay - active - 01a1b26b-3c94-707f-9665-2f6b4a79b564 (branch `gameplay-attribution-0.1.0`)
- [x] First-person movement, mouse look, and Enhanced Input bindings. Implemented 2026-11-07: `AGigLineCharacter` (C++, 1.75 m capsule, 1.60 m eye camera, 4 m/s walk, mouse look in deg/s, jump) as the default pawn, with five actions and an 11-entry context in `/Game/Input` built by `Tools/ue_create_input_assets.py`. **Not yet confirmed interactively** — launch PIE on `Test/ReviewMap` and walk. Screenshot review pending (`Screenshots/README.md`).
- [D] Collision, reset, and pause functionality. Reset (**R**) and pause (**Escape**, a real game pause that releases the cursor) are implemented in `AGigLineCharacter` with `/Game/Input/IA_Reset` and `IA_Pause`. Collision now exists — terrain, road and sidewalks import as complex-as-simple, so a simple hull cannot ramp over the kerbs. **Not verified by hand**: walk, reset and pause need a human at the keyboard. No pause menu widget yet, only the pause itself.
- [x] Build the walkable demo level: terrain, street, and hero facade assembled into one map with a start point on the north sidewalk, so the demo is something a player can walk rather than a review staging. **Built 2026-11-07** by `Tools/ue_build_demo_map.py` → `/Game/Hollywood/DemoMap`: sun/sky, `Hero_Facade`, six site meshes imported with collision (`Terrain_DEM`, `Road_Hollywood`, both sidewalks and both kerbs), and a `Demo_PlayerStart` on the north sidewalk 20 m back from the frontage. Demo views in `Screenshots/demo/` re-rendered and ☑ approved 2026-11-07.
- [x] Walkable sidewalks and a designated street crossing. Crossing chosen: mid-block signalised crossing, OSM way 385648319, at 130 m along the block (`Geo/verification/2026-11-06-street-profile.md`). Done 2026-11-07: continental zebra markings (0.5 m bars / 0.5 m gaps, 3 m band) at all three signalised crossings, no collision on the paint; kerbs were already flushed at the crossings. Enablers found on the way: the street surfaces were 100 % down-facing (invisible in engine) — winding flipped; the terrain buried the street — 10.7 m corridor cut around the centreline (sidewalks cover the transition, walk-check 0 holes); LFS-lockable `.blend`/`.umap` check out read-only — builders now chmod before saving and fail loudly. Screenshot ☑ approved 2026-11-07 (`Screenshots/demo/review-demo-crossing`); the corridor renders near-black in shadow — that was the **Fixed daytime lighting** item, now also done.
- [x] Fixed daytime lighting and basic ambience. **Done 2026-11-07, screenshots approved.** Demo map now has a fixed daytime rig in `Tools/ue_build_demo_map.py`: sun pitch −50 / yaw 120 (intensity 4, warm 255/245/230, atmosphere sun light), sky light 2.6 with real-time capture, sky atmosphere, height fog (density 0.008), and a manual-exposure post-process volume (min = max brightness 1.0) so a shot cannot wash out or crush to black. Two root causes found while doing it: (a) **Blender flat diffuse colours do not survive the FBX→UE import** — every site mesh arrived white and the terrain arrived with `WorldGridMaterial`, which is what actually made the scene look blown out; explicit `M_Asphalt` / `M_Concrete` / `M_Ground` / `M_RoadPaint` materials are now created and assigned in the demo builder. (b) only **one of the three crossings** was reaching the game: each band was its own object and Blender renamed the duplicates `.001`/`.002`, which site assembly skips; all bands are now one `Crossing_Markings` object. Demo cameras also fixed: street-level sat inside the façade body, and the crossing camera aimed at the wrong crossing (s=36.6 instead of the designated s=130.1 at UE X −14956). Lighting is dynamic-only (`r.AllowStaticLighting=False`, all lights Movable) so the editor's "LIGHTING NEEDS TO BE REBUILT" warning no longer fires. All four demo views approved by the owner.
- [x] Geographic attribution display ("© OpenStreetMap contributors" + license link). Done 2026-11-07: `AGigLineHUD` (C++) draws the credit line and the licence URL (`© OpenStreetMap contributors` / `Data © OpenStreetMap contributors, ODbL 1.0 — openstreetmap.org/copyright`) in the bottom-left of every frame, wired through `AGigLineGameModeBase::HUDClass`. It is a plain `AHUD::DrawText`, so no UMG widget asset can be dropped from a cook. `AGigLineShotActor` now requests screenshots with the UI shown, so the attribution is visible in every image under `Screenshots/` and the obligation is provable, not just documented.
### Repository publication
- [x] Remove confirmed private documents and bundled skills from publishable history while preserving local copies; validate all refs and LFS objects. Rewritten 2026-11-07; original history is in a separate private local backup.
- [x] Scan all reachable Git history and the publication changes with Gitleaks 8.28.0. Removed an Android File Server security token from history and disabled the unused server; the rescans found no leaks.
- [x] Review Unreal asset provenance via the generation/import scripts and inspect all 18 reachable Unreal asset versions for recognizable private-reference paths. No such paths were found; this is not a full binary-content audit.
- [x] Review OSM source distribution notices: original data retains attribution and licence metadata, and the repository links directly to ODbL 1.0.
- [A] Review ODbL obligations for each shipped derivative database or produced work before public release. In-game attribution display is implemented (`AGigLineHUD`, visible in every screenshot); the remaining question is the ODbL "derivative database" posture for a shipped build that contains OSM-derived geometry.
- [x] Validate Gitea LFS support. The first push succeeded on 2026-11-07; a separate clone fetched all 26 historical LFS objects and passed integrity checks.
- [x] Commit and review the publication preparation changes before pushing. Initial publication commit: `101db3f`.
### Release checks
- [ ] Player can traverse both sides of the demo without falling through geometry. Blender pre-check passes: 0 raycast holes along both sidewalks and the designated crossing; worst seam step 15.8 cm at the sidewalk's outer edge (`Art/blender/README.md`). Engine traversal still to be tested.
- [ ] Building placement, street scale, and site identity checked against references.
- [ ] No blocking collision or packaging defects.
- [ ] 60 FPS at 1440p on the dev workstation (Bazzite, Ryzen 7 7700X, 64 GB RAM, RTX 5070 Ti 16 GB) using a documented quality preset; record upscaling state; report performance without frame generation.
- [ ] Fresh project checkout builds with the documented external engine installation.
- [ ] Package Linux build with documented controls.
- [ ] Tag `v0.1.0` and write `CHANGELOG.md` entry; then remove this section.
- Release gate: a version is tagged only on the owner's explicit go-ahead once its release checks pass, not automatically when the checkboxes close.
## 0.1.1 — Collision fixes (patch)
@@ -126,7 +17,7 @@ Suggested blender skills: **collision-proxy** (UCX/UHX naming, convex hulls, sim
- [ ] **Sidewalk outer edge against terrain.** The pre-check measured a **15.8 cm** step where a walk sample crosses from `Terrain_DEM` onto `Sidewalk_South` (`Art/blender/README.md`, s≈207 m). Mostly hidden by buildings; visible wherever the frontage is missing. Fix by extending the sidewalk mesh to meet the terrain, or by lowering the outer lip.
- [ ] **Crossing ramp gradient.** The kerb-flush transitions measure **10–13 cm over 1.5 m ≈ 10 %**, steeper than the 8 % a real ramp uses (`Geo/tools/street_section.py` output, s≈33, 39, 126 m). Grade the three ramp openings properly.
- [ ] **Collision authoring pass.** None exists yet: on Unreal import, complex-as-simple for terrain and street (walkable surfaces must not be approximated into ramps over kerbs), simple convex hulls per building. Record the settings in `Docs/build.md`.
- [x] **Collision authoring pass.** Terrain and site meshes use complex-as-simple / `BlockAll` (forced by `UGigLineAssetTools`, verified 2026-11-08 with 84-sample transect). Remaining: simple convex hulls per building (currently using default collision). Record the final settings in `Docs/build.md`.
- [ ] **Kerb height is an assumption.** 0.15 m modelled, 0.13 m as built, and the 3DEP DEM cannot resolve a kerb at all. Check against the photographs and record the final figure.
- [ ] Audit and fix any collision gaps or fall-through geometry found in `0.1.0` in the engine.
- [ ] Regression-test traversal on both sides of the demo.
@@ -161,6 +52,7 @@ Suggested blender skills: **procedural-modeling** + **geometry-nodes** (the faç
### Patch tasks
- [ ] **Finish the Blosm blockout cleaning/detailing (moved from the deferred `0.1.0` item).** Import state: `Art/blender/blockout/blockout-2026-11-06.blend` (86 meshes, EPSG:26911, 0.0 cm affine residual) with cleaning tooling in `Geo/tools/` and `Art/blender/blockout/blosm_blockout.py`. To do here: (a) untextured roofs/facades → **Replace placeholder materials** below; (b) Blosm `setupScript` façade styles → **Give OSM buildings a façade style** below; (c) the 3 of 15 buildings lacking height data are the `0.1.2` placeholder-heights item.
- [ ] **Define the façade module.** One set of reusable pieces (bay, pilaster, cornice, parapet, window, shopfront, door) at a fixed module, exported with the `ue_metric_v1` preset so every building assembles from the same kit.
- [ ] **Replace placeholder materials.** Every blockout building currently carries Blender flat colours. Port the procedural generators from `Tools/sibling_asset_audit.py`'s sibling project (`brick_facade`, `seamless_concrete`, `asphalt`) and add stone, render, and glass, then wire them through Unreal material instances.
- [ ] **Give OSM buildings a façade style.** Blosm exposes `scene.blosm.setupScript` (`setup/neoclassical.py` is the example) so a building can carry a real elevation instead of a plain extrusion. Build one style script for the south frontage and one for the north.
@@ -442,7 +334,6 @@ Suggested blender skills: **procedural-modeling** + **geometry-nodes** (the faç
- [ ] 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.
- [ ] Review ODbL obligations for any distributed derived geographic database.
- [ ] Review Epic distribution requirements before publishing.
- [ ] Verify a fresh checkout builds and packages reproducibly.
+7
View File
@@ -51,3 +51,10 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b
| --- | --- |
| `render_screenshots.py` / `render_screenshots.sh` | Rebuilds the review map, runs the game, captures the shot list into `Screenshots/` |
| `judge_screenshot.py` | Vision-model pre-check: judges a screenshot against an expected-scene description (`--image` + `--expect`, or `--shots` for every shot in the README). Reads `OPENROUTER_API_KEY` from `.env` (ignored). Optional helper — a human must still approve. |
## Gameplay QA
| Command | Purpose |
| --- | --- |
| `UnrealEditor <uproject> /Game/Hollywood/DemoMap -game -qaselftest` | Headless behaviour check: drives forward input for 2 s, then resets and pauses. Logs `GigLine QA: ... PASS/FAIL` lines and a final `RESULT`. Runs only with `-qaselftest`, so normal play is unaffected. |
| `GigLine SIM` / `GigLine GROUND` log lines | Emitted every second by `AGigLineCharacter` (TEMP QA): position, fall state, movement mode, and what the ground probe hits underneath. Remove before release. |
+405
View File
@@ -0,0 +1,405 @@
#!/usr/bin/env python3
"""Pure-Python uinput driver to inject OS-level keyboard and mouse input.
This is a small, dependency-free input driver used by the demo QA harness
(`Tools/input_test.py`) to prove whether OS input actually reaches the game.
It talks to ``/dev/uinput`` directly through ``ctypes``, so it creates a real
virtual input device that the kernel and the desktop session (X11 or Wayland)
treat as genuine hardware. The game cannot tell simulated input from a real
keyboard/mouse.
No third-party packages are required. ``/dev/uinput`` must be writable by the
calling user (on this machine the system ACL grants `zwitschi` rw; if it is
not, add a udev rule or run as a user in the `input` group).
Usage
-----
from input_emulation import InputDevice
with InputDevice() as dev:
dev.key('w') # press and release W -> types 'w'? no: key code 17
dev.hold('w', 0.5) # hold W for half a second (run forward)
dev.move(120, 0) # nudge the mouse +120 px on X (look right)
All calls log using Python's `logging` module so a scripted test can show the
exact injection timeline next to the game log.
Design notes
------------
* Uses the newer ``UI_DEV_SETUP`` ioctl with a ``struct uinput_setup``, falling
back to the legacy ``write(struct uinput_user_dev)`` method if the kernel
rejects it (very old kernels). On this kernel the setup ioctl path is used.
* The device exposes EV_KEY (keyboard), EV_REL (relative mouse) and
EV_SYN (synchronisation). It does not declare an absolute axis, so it is
treated as a relative device and the desktop does not try to give it an
on-screen pointer role.
* A single device with both keys and relative axes is created; for a game this
is fine. If a separate gamepad (EV_ABS/EV_FF) is wanted later, extend this
file with a second device class that also sets ABS bits.
"""
import ctypes
import ctypes.util
import fcntl
import logging
import os
import struct
import sys
import time
log = logging.getLogger("uinput")
# ---------------------------------------------------------------------------
# ioctl machinery
# ---------------------------------------------------------------------------
# _IOC(dir, type, nr, size) is the standard Linux ioctl encoding.
_IOC_NRBITS = 8
_IOC_TYPEBITS = 8
_IOC_SIZEBITS = 14
_IOC_DIRBITS = 2
_IOC_NRSHIFT = 0
_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS
_IOC_NONE = 0
_IOC_WRITE = 1
_IOC_READ = 2
def _ioc(direction, size, type_, nr):
# type_ may be a single character (ioctl base letter) or already an int.
if isinstance(type_, str):
type_ = ord(type_) if len(type_) == 1 else 0
return (
(size << _IOC_SIZESHIFT)
| (type_ << _IOC_TYPESHIFT)
| (nr << _IOC_NRSHIFT)
| (direction << _IOC_DIRSHIFT)
)
def _IOW(type_, nr, size):
return _ioc(_IOC_WRITE, size, type_, nr)
def _IOR(type_, nr, size):
return _ioc(_IOC_READ, size, type_, nr)
def _IO(type_, nr):
return _ioc(_IOC_NONE, 0, type_, nr)
# ---------------------------------------------------------------------------
# Linux constants (from linux/input.h, linux/uinput.h, input-event-codes.h)
# ---------------------------------------------------------------------------
UINPUT_IOCTL_BASE = ord("U")
UINPUT_MAX_NAME_SIZE = 80
UI_SET_EVBIT = _IOW(UINPUT_IOCTL_BASE, 100, ctypes.sizeof(ctypes.c_int))
UI_SET_KEYBIT = _IOW(UINPUT_IOCTL_BASE, 101, ctypes.sizeof(ctypes.c_int))
UI_SET_RELBIT = _IOW(UINPUT_IOCTL_BASE, 102, ctypes.sizeof(ctypes.c_int))
UI_DEV_CREATE = _IO(UINPUT_IOCTL_BASE, 1)
UI_DEV_DESTROY = _IO(UINPUT_IOCTL_BASE, 2)
UI_DEV_SETUP = _IOW(UINPUT_IOCTL_BASE, 3, 0) # size patched below
EV_SYN = 0x00
EV_KEY = 0x01
EV_REL = 0x02
REL_X = 0x00
REL_Y = 0x01
REL_WHEEL = 0x08
BTN_LEFT = 0x110
BTN_RIGHT = 0x111
BUS_USB = 0x03
INPUT_PROP_DIRECT = 0x01
UINPUT_VERSION = 5 # linux <input.h> UINPUT_VERSION
class _InputId(ctypes.Structure):
_fields_ = [
("bustype", ctypes.c_uint16),
("vendor", ctypes.c_uint16),
("product", ctypes.c_uint16),
("version", ctypes.c_uint16),
]
class _UinputSetup(ctypes.Structure):
"""Modern uinput_setup: input_id + padded name + ff_effects_max."""
_fields_ = [
("id", _InputId),
("name", ctypes.c_char * UINPUT_MAX_NAME_SIZE),
("ff_effects_max", ctypes.c_uint32),
]
# Patch the UI_DEV_SETUP size now that the struct layout is known.
UI_DEV_SETUP = _IOW(UINPUT_IOCTL_BASE, 3, ctypes.sizeof(_UinputSetup))
class _InputEvent(ctypes.Structure):
"""struct input_event on 64-bit Linux: two longs (timeval) + 2x16 + 32-bit."""
_fields_ = [
("tv_sec", ctypes.c_long),
("tv_usec", ctypes.c_long),
("type", ctypes.c_uint16),
("code", ctypes.c_uint16),
("value", ctypes.c_int32),
]
class _LegacyUserDev(ctypes.Structure):
"""Legacy uinput_user_dev fallback (name + id + ff + absolute ranges)."""
_fields_ = [
("name", ctypes.c_char * UINPUT_MAX_NAME_SIZE),
("id", _InputId),
("ff_effects_max", ctypes.c_uint32),
("absmax", ctypes.c_int32 * 64),
("absmin", ctypes.c_int32 * 64),
("absfuzz", ctypes.c_int32 * 64),
("absflat", ctypes.c_int32 * 64),
]
# ---------------------------------------------------------------------------
# libc
# ---------------------------------------------------------------------------
def _ioctl(fd, request, arg):
"""Issue an ioctl via fcntl.ioctl.
The uinput bit-fill ioctls (UI_SET_EVBIT/KEYBIT/RELBIT) take the value
directly, not a pointer: pass the int itself. The struct ioctls pass a
ctypes Structure (buffer protocol).
"""
try:
result = fcntl.ioctl(fd, request, arg)
except OSError as exc:
raise OSError(exc.errno, os.strerror(exc.errno), request) from exc
return result
# ---------------------------------------------------------------------------
# Key code lookup
# ---------------------------------------------------------------------------
# Linux key codes referenced by the demo controls. All keys exposed by the
# device are declared in KEYCODES; the driver enables every one of them, which
# is a superset and lets the harness send any.
KEYCODES = {
"w": 17, "s": 31, "a": 30, "d": 32,
"space": 57, "r": 19, "esc": 1,
"up": 103, "down": 108, "left": 105, "right": 106,
"enter": 28, "tab": 15, "backspace": 14, "shift": 42, "ctrl": 29,
"alt": 56, "1": 2, "2": 3, "3": 4, "0": 11,
}
KEY_ESC = 1
# All EV_KEY codes we want to claim (just the ones in KEYCODES plus a few).
_ALL_KEYCODES = sorted(set(KEYCODES.values()))
_ALL_RELS = [REL_X, REL_Y, REL_WHEEL]
_ALL_BTNS = [BTN_LEFT, BTN_RIGHT]
class UInputError(RuntimeError):
pass
class InputDevice:
"""A virtual USB keyboard+mouse device backed by /dev/uinput.
Use as a context manager: the device is destroyed on exit, so a hang or
crash never leaves a phantom input device behind. All methods emit log
records so a test can reconstruct exactly what was injected and when.
"""
def __init__(self, name="GigLine QA Input", path="/dev/uinput"):
self.name = name
self.path = path
self._fd = None
self._open_and_setup()
# -- lifecycle ----------------------------------------------------------
def _open_and_setup(self):
try:
self._fd = os.open(self.path, os.O_WRONLY | os.O_NONBLOCK)
except OSError as exc:
raise UInputError(
f"cannot open {self.path}: {exc}. "
"Needs a udev rule or membership in the 'input' group."
) from exc
fd = self._fd
try:
# Declare which event types the device can emit.
# These bit-fill ioctls take the value directly (not a pointer).
for bit in (EV_KEY, EV_REL, EV_SYN):
_ioctl(fd, UI_SET_EVBIT, bit)
for code in _ALL_KEYCODES + _ALL_BTNS:
_ioctl(fd, UI_SET_KEYBIT, code)
for rel in _ALL_RELS:
_ioctl(fd, UI_SET_RELBIT, rel)
# Modern path.
setup = _UinputSetup()
setup.id.bustype = BUS_USB
setup.id.vendor = 0x1234
setup.id.product = 0x5678
setup.id.version = UINPUT_VERSION
setup.name = self.name.encode()[: UINPUT_MAX_NAME_SIZE - 1]
setup.ff_effects_max = 0
try:
_ioctl(fd, UI_DEV_SETUP, setup)
except OSError:
# Very old kernels: fall back to writing uinput_user_dev.
legacy = _LegacyUserDev()
legacy.id = setup.id
legacy.name = setup.name
legacy.ff_effects_max = 0
os.write(fd, legacy)
log.warning("kernel rejected UI_DEV_SETUP; used legacy write path")
_ioctl(fd, UI_DEV_CREATE, 0)
# Give the input core a moment to register the device node.
time.sleep(0.2)
except Exception:
self.close()
raise
log.info("virtual input device %r created on %s", self.name, self.path)
def close(self):
if self._fd is not None:
try:
_ioctl(self._fd, UI_DEV_DESTROY, 0)
except (OSError, Exception):
pass
os.close(self._fd)
self._fd = None
log.info("virtual input device %r destroyed", self.name)
def __enter__(self):
return self
def __exit__(self, *exc):
self.close()
return False
# -- event emission -----------------------------------------------------
def _emit(self, etype, code, value):
"""Write an input_event (2 longs timeval + u16/u16/s32) + SYN_REPORT."""
if self._fd is None:
raise UInputError("device is closed")
# struct input_event: __kernel_old_timeval (2x long) + type/code/value.
ev = struct.pack("llHHi", int(time.time()), 0, etype, code, int(value))
try:
os.write(self._fd, ev)
except OSError as exc:
log.error("write failed: %s", exc)
raise
os.write(self._fd, struct.pack("llHHi", 0, 0, EV_SYN, 0, 0))
# -- high-level actions -------------------------------------------------
def _key_code(self, key):
if isinstance(key, int):
return key
key = key.lower()
if key not in KEYCODES:
raise UInputError(f"unknown key {key!r}; known: {sorted(KEYCODES)}")
return KEYCODES[key]
def press(self, key):
"""Press a key (no release)."""
code = self._key_code(key)
self._emit(EV_KEY, code, 1)
log.info("KEY down %-6s (code %d)", key, code)
def release(self, key):
"""Release a previously pressed key."""
code = self._key_code(key)
self._emit(EV_KEY, code, 0)
log.info("KEY up %-6s (code %d)", key, code)
def key(self, key, delay=None):
"""Full press+release of a single key. Optional delay between them."""
self.press(key)
if delay is not None:
time.sleep(delay)
self.release(key)
def hold(self, key, duration):
"""Hold a key down for `duration` seconds, then release it."""
self.press(key)
time.sleep(duration)
self.release(key)
log.info("HELD %-6s for %.2fs", key, duration)
def chord(self, *keys):
"""Press all keys together, then release all in reverse."""
codes = [self._key_code(k) for k in keys]
for k, c in zip(keys, codes):
self._emit(EV_KEY, c, 1)
log.info("KEY down %-6s", k)
for k, c in zip(reversed(keys), reversed(codes)):
self._emit(EV_KEY, c, 0)
log.info("KEY up %-6s", k)
log.info("CHORD %s", "+".join(keys))
def move(self, dx, dy, delay=None):
"""Move the (relative) mouse by dx,dy pixels. Watches the pointer."""
self._emit(EV_REL, REL_X, int(dx))
self._emit(EV_REL, REL_Y, int(dy))
if delay is not None:
time.sleep(delay)
log.info("MOUSE move (%+d, %+d)", int(dx), int(dy))
def bpress(self, button="left"):
self._emit(EV_KEY, BTN_LEFT if button.lower() == "left" else BTN_RIGHT, 1)
log.info("BUTTON down %s", button)
def brelease(self, button="left"):
self._emit(EV_KEY, BTN_LEFT if button.lower() == "left" else BTN_RIGHT, 0)
log.info("BUTTON up %s", button)
def bclick(self, button="left"):
self.bpress(button)
time.sleep(0.02)
self.brelease(button)
log.info("BUTTON click %s", button)
def wheel(self, ticks):
self._emit(EV_REL, REL_WHEEL, int(ticks))
log.info("WHEEL %+d", int(ticks))
def main():
# A tiny smoke test: create the device, tap W, nudge the mouse, quit.
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(message)s")
try:
with InputDevice() as dev:
print("# device ready; sending W key + mouse nudge ...")
dev.key("w")
dev.move(80, 0)
time.sleep(0.3)
print("# ok, device destroyed cleanly")
except UInputError as exc:
print(f"ERROR: {exc}")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/env python3
"""QA harness: inject scripted OS-level keyboard/mouse input at the running game.
This is the tool that proves whether OS input actually reaches the game. It uses
`Tools/input_emulation.py` to create a virtual input device directly on
`/dev/uinput`, so a focused game window receives the injected keys and mouse
movement as genuine hardware input — indistinguishable from a real keyboard.
Typical workflow
----------------
The game must already be running and focused (it will not be launched here; the
user brings up the window). Run this script from a separate terminal:
python3 Tools/input_test.py --sequence move:2,look:3,jump:1,reset:1,pause:1
It prints a per-step timeline (seconds) to stdout, which you can compare against
the `GigLine IN:` rows in the game's `Saved/Logs`. If the game log shows
`MoveForward fired` / `Look fired` for the injected steps, OS input is reaching
the pawn and the remaining problem is not input delivery.
Usage
-----
python3 Tools/input_test.py # default smoke sequence
python3 Tools/input_test.py --sequence move:2,look:3,jump:1,reset:1,pause:1
python3 Tools/input_test.py --device /dev/uinput --name "GigLine QA Input"
Steps supported in `--sequence` (comma-separated, `name:count`):
move hold W forward (each unit = ~0.5 s)
back hold S
strafe hold D
left hold A
look nudge mouse right (rotate camera)
jump press Space
reset press R
pause press Escape
click left mouse click
Design
------
* The device is kept open for the whole run and destroyed on exit (context
manager), so nothing persists after the harness quits.
* Each step logs an absolute timestamp so you can line it up with the game log
wall-clock times (the game logs to `Saved/Logs/GigLine.log` with the same
system clock).
* No third-party packages: only the Python standard library.
"""
import argparse
import logging
import sys
import time
from input_emulation import InputDevice, UInputError
# Duration of one "unit" for hold-based movement steps, in seconds.
MOVE_STEP_S = 0.5
# Handlers map step names to a callable(dev) that performs the key/mouse action.
def _move(dev):
dev.hold("w", MOVE_STEP_S)
def _back(dev):
dev.hold("s", MOVE_STEP_S)
def _strafe(dev):
dev.hold("d", MOVE_STEP_S)
def _left(dev):
dev.hold("a", MOVE_STEP_S)
def _look(dev):
# nudge the mouse +240 px on X repeatedly so the camera has time to respond.
for _ in range(6):
dev.move(240, 0, delay=0.03)
time.sleep(0.15)
def _jump(dev):
dev.key("space", delay=0.05)
def _reset(dev):
dev.key("r", delay=0.05)
def _pause(dev):
dev.key("esc", delay=0.05)
def _click(dev):
dev.bclick("left")
STEPS = {
"move": _move, "back": _back, "strafe": _strafe, "left": _left,
"look": _look, "jump": _jump, "reset": _reset, "pause": _pause,
"click": _click,
}
def parse_sequence(text):
"""'move:2,look:1,jump:1' -> ordered list of (name, count)."""
seq = []
for item in (text or "").split(","):
item = item.strip()
if not item:
continue
if ":" in item:
name, _, count = item.partition(":")
count = int(count or "1")
else:
name, count = item, 1
if name not in STEPS:
raise ValueError(f"unknown step {name!r}; known: {sorted(STEPS)}")
seq.append((name, count))
if not seq:
seq = [("move", 2), ("look", 1), ("jump", 1), ("reset", 1), ("pause", 1)]
return seq
def run_sequence(dev, seq):
t0 = time.time()
for name, count in seq:
for _ in range(count):
stamp = time.time() - t0
print(f"[{stamp:6.3f}s] -> {name}")
# A brief settle so distinct keystrokes are logged separately.
time.sleep(0.1)
STEPS[name](dev)
def main(argv=None):
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--sequence",
help="comma-separated steps and repeats, e.g. move:2,look:3")
ap.add_argument("--device", default="/dev/uinput", help="uinput device path")
ap.add_argument("--name", default="GigLine QA Input", help="virtual device name")
ap.add_argument("-v", "--verbose", action="store_true",
help="log every injected event via logging")
args = ap.parse_args(argv)
if args.verbose:
logging.basicConfig(level=logging.INFO,
format="%(asctime)s.%(msecs)03d %(name)s %(message)s",
datefmt="%H:%M:%S")
try:
seq = parse_sequence(args.sequence)
except ValueError as exc:
ap.error(str(exc))
try:
with InputDevice(name=args.name, path=args.device) as dev:
print(f"# virtual device {args.name!r} active; send sequence now")
print(f"# steps: {' '.join(f'{n}x{c}' for n, c in seq)}")
run_sequence(dev, seq)
except UInputError as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 1
except KeyboardInterrupt:
print("\n# interrupted; device destroyed on exit")
return 130
print("# done; inputs sent")
return 0
if __name__ == "__main__":
sys.exit(main())
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# Open the Unreal editor focused on the walkable demo so you can press Play (PIE)
# to compare in-editor input intake vs the packaged standalone build.
#
# Usage: Tools/run_pie.sh
# Then in the editor: press the green Play button (Alt+P or the PIE toolbar).
set -e
ENGINE="${UE_ENGINE:-/run/media/system/DATA/Engines/UE_5.8.4}"
PROJ="$(cd "$(dirname "$0")/.." && pwd)/Game/GigLine/GigLine.uproject"
echo "Opening $PROJ on /Game/Hollywood/DemoMap ..."
cd "$ENGINE"
exec "./Engine/Binaries/Linux/UnrealEditor" "$PROJ" /Game/Hollywood/DemoMap
+38 -11
View File
@@ -146,7 +146,7 @@ def build():
log(f"could not create {MAP}")
return
removed = clear(("Demo_", "PlayerStart", "Hero_Facade", "Frontage_"))
removed = clear(("Demo_", "PlayerStart", "Hero_Facade", "Frontage_", "ShotSequencer"))
log(f"cleared {removed} previous demo actors")
# --- lighting: fixed daytime ------------------------------------------
@@ -256,7 +256,7 @@ def build():
actor = spawn(unreal.StaticMeshActor, unreal.Vector(-13336.0, 1307.0, 0.0), "Hero_Facade")
if actor:
actor.static_mesh_component.set_static_mesh(hero)
actor.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE)
actor.static_mesh_component.set_mobility(unreal.ComponentMobility.STATIC)
log("staged: Hero_Facade at the building line")
else:
log("MISSING: SM_HeroFacade — run Tools/ue_hero_facade_import.py")
@@ -301,13 +301,33 @@ def build():
mesh = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Site/{name}")
if mesh is None:
continue
# The imported terrain carries a bounding-box simple collision proxy,
# which puts an invisible lid ~8 m over the whole block: the player
# walks on that box instead of the street. Force trace against the real
# triangles so the carved street corridor is actually open.
if "Terrain" in name:
asset = unreal.EditorAssetLibrary.load_asset(f"/Game/Hollywood/Site/{name}")
if asset:
unreal.GigLineAssetTools.set_complex_collision_as_simple(asset, True)
# The flag lives on the asset, not the map: persist it or the
# next session reloads the bbox proxy and the street is buried.
saved_asset = unreal.EditorAssetLibrary.save_loaded_asset(asset)
log(f"terrain collision: {unreal.GigLineAssetTools.get_collision_complexity(asset)} "
f"(saved asset: {saved_asset})")
actor = spawn(unreal.StaticMeshActor, unreal.Vector(0, 0, 0), f"Demo_{name}")
if actor:
actor.static_mesh_component.set_static_mesh(mesh)
actor.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE)
comp = actor.static_mesh_component
comp.set_static_mesh(mesh)
# Immutable level geometry must be STATIC to reliably expose its
# complex-as-simple collision body to CharacterMovement; staging
# these MOVABLE let the player fall through the world.
comp.set_mobility(unreal.ComponentMobility.STATIC)
comp.set_collision_enabled(unreal.CollisionEnabled.QUERY_AND_PHYSICS)
comp.set_collision_object_type(unreal.CollisionChannel.ECC_WORLD_STATIC)
comp.set_collision_profile_name("BlockAll")
mat = built.get(mat_name)
if mat:
actor.static_mesh_component.set_material(0, mat)
comp.set_material(0, mat)
imported += 1
log(f"staged {imported} site meshes with explicit surface materials")
@@ -362,13 +382,20 @@ def build():
# --- player start: north sidewalk, facing the hero frontage -------------
# Signs were measured from the imported meshes rather than assumed: the FBX
# preset flips north, so in Unreal the road spans Y -1382..+1047, the NORTH
# sidewalk is at Y -1912..-1053 and the SOUTH sidewalk at Y +718..+1447. The
# facade stands at Y -1450. Stand on the north sidewalk and look north (-Y).
# North sidewalk (Y -1912..-1053), facing south (+Y) across the road at the
# hero on the south side. Yaw 90 looks along +Y.
spawn(unreal.PlayerStart, unreal.Vector(-13336.0, -1700.0, 120.0), "Demo_PlayerStart",
# sidewalk is at Y -1912..-1053 and the SOUTH sidewalk at Y +718..+1447.
# The hero facade is on the SOUTH side at Y +1307. Stand on the north
# sidewalk and face south (+Y, yaw 90) across the road at it.
# CRITICAL: the terrain is only carved inside the street corridor, and
# terrain_from_dem.py --corridor 10.7 is a HALF-width around the road
# centreline (Y ~= -167), so the cut band is Y -1237..+902. A PlayerStart at
# Y -1700 sits OUTSIDE it, under ~8 m of uncut terrain: the capsule spawns
# embedded, depenetration pops it to Z ~909 (terrain top 819 + half-height
# 87.5) and it never touches the sidewalk. Y -1150 is on the north sidewalk
# (Y -1912..-1053) AND inside the cut, so the pavement is the ground.
# Z 600 is clear air; the capsule drops onto the pavement and settles.
spawn(unreal.PlayerStart, unreal.Vector(-13336.0, -1150.0, 600.0), "Demo_PlayerStart",
unreal.Rotator(pitch=0.0, yaw=90.0, roll=0.0))
log("staged: Demo_PlayerStart on the north sidewalk facing south (+Y)")
log("staged: Demo_PlayerStart on the cut north sidewalk, facing south (+Y)")
# A lockable LFS .umap checks out read-only, which makes save_map silently
# return False — chmod u+w first or the staged actors never persist.
+7 -2
View File
@@ -289,8 +289,13 @@ try:
# the pedestrian's view and shows the zebra bars as bars.
("demo-crossing", unreal.Vector(-15250, -2500, 400), unreal.Vector(-14956, 400, 150)),
# the two north-side background buildings (TCL, Tussaud's) seen from
# the south sidewalk, across Hollywood Blvd: their frontage and mass.
("demo-north-buildings", unreal.Vector(-22800, 2600, 420), unreal.Vector(-22800, -4200, 300)),
# the south across Hollywood Blvd. Probed world-space (2026-11-08):
# TCL bbox X -22889..-18442, Y -9431..-1802; Tussaud's X -27071..
# -22799, Y -7586..-1888. +Y = south, -Y = north (axis mapping). So
# stand SOUTH of the road (Y +1600) at the combined mass centre
# (X -22750) and look NORTH toward the mass. The old camera was at
# X -22800 Y +2600 aiming at its own X, giving an 85%-sky frame.
("demo-north-buildings", unreal.Vector(-22750, 1600, 500), unreal.Vector(-22750, -5200, 350)),
]
seq = spawn(unreal.GigLineShotActor, unreal.Vector(0, 0, 300), "ShotSequencer")
if seq:
+19 -5
View File
@@ -85,7 +85,11 @@ def build():
# component and D/A carry strafe, combined by the character. A 2D value would
# need per-key axis modifiers, and modifiers do not survive the save path here.
ia_move = fresh("IA_Move", unreal.InputAction, unreal.InputAction_Factory)
ia_move.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D)
# Boolean (pressed = 1), not AXIS1D: the C++ handler reads Get<bool>() and
# moves at full walk speed. An axis action from a keyboard key can deliver 0
# depending on the trigger, which showed up as "character stands, W does not
# move" in QA. WASD are four separate per-direction actions below.
ia_move.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
save(ia_move)
ia_look = fresh("IA_Look", unreal.InputAction, unreal.InputAction_Factory)
@@ -107,15 +111,15 @@ def build():
# Two actions feed the one-dimensional move: IA_Move for forward/back, and a
# second action for strafe, both reporting on the value's single component.
ia_strafe = fresh("IA_Strafe", unreal.InputAction, unreal.InputAction_Factory)
ia_strafe.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D)
ia_strafe.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
save(ia_strafe)
ia_back = fresh("IA_MoveBack", unreal.InputAction, unreal.InputAction_Factory)
ia_back.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D)
ia_back.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
save(ia_back)
ia_left = fresh("IA_StrafeLeft", unreal.InputAction, unreal.InputAction_Factory)
ia_left.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D)
ia_left.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
save(ia_left)
# Reset and pause are boolean: one press, no accumulator.
@@ -145,8 +149,18 @@ def build():
except Exception as exc: # noqa: BLE001
log(f"skip {action.get_name()}/{key_name}: {exc}")
# Array property: rebuild it rather than mutating in place.
# UE 5.8: the runtime reads DefaultKeyMappings.Mappings (FInputMappingContextMappingData),
# NOT the deprecated 'mappings' array on the context. The deprecated array is
# what editor UI shows and what the old script wrote, so the cooked game got 0
# runtime mappings (key->action lookup failed -> no input). Populate the new
# struct field that the runtime actually consumes.
default_mapping_data = imc.get_editor_property("default_key_mappings")
if default_mapping_data is None:
default_mapping_data = unreal.InputMappingContextMappingData()
default_mapping_data.set_editor_property("mappings", mappings)
# Mirror into the deprecated array so the editor UI and any legacy reader agree.
imc.set_editor_property("mappings", mappings)
imc.set_editor_property("default_key_mappings", default_mapping_data)
save(imc)
log(f"IA_Move={ia_move.get_path_name()} IA_Look={ia_look.get_path_name()} "
+12 -2
View File
@@ -50,6 +50,12 @@ def import_fbx(path, name, collision="complex"):
smd.convert_scene_unit = False
smd.force_front_x_axis = False
smd.normal_import_method = unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS
# Generate collision at import. A default FBX static-mesh import creates NO
# collision primitives, so CTF_USE_COMPLEX_AS_SIMPLE later has nothing to use
# and the player falls through the walkable surfaces (observed at runtime:
# ground trace under the spawn hits nothing). auto_generate_collision bakes
# collision geometry into the mesh so the walkable plane actually blocks.
smd.auto_generate_collision = True
task.options = ui
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
@@ -71,8 +77,12 @@ def import_fbx(path, name, collision="complex"):
log(f"{name:18} import ok size=({mx.x - mn.x:.0f}, {mx.y - mn.y:.0f}, {mx.z - mn.z:.0f}) cm "
f"min_z={mn.z:.1f} collision=none")
return
trace = (unreal.CollisionTraceFlag.CTF_USE_COMPLEX_AS_SIMPLE
if collision == "complex" else unreal.CollisionTraceFlag.CTF_USE_SIMPLE_AND_COMPLEX)
# Walkable surfaces block with SIMPLE + COMPLEX collision so the player's
# capsule always has ground: CFG_USE_SIMPLE_AND_COMPLEX uses the auto-
# generated simple collision AND the mesh triangles. (Complex-as-simple
# alone only worked if the mesh carried complex collision, which a default
# FBX static-mesh import does not.)
trace = unreal.CollisionTraceFlag.CTF_USE_SIMPLE_AND_COMPLEX
try:
body = mesh.get_editor_property("body_setup")
body.set_editor_property("collision_trace_flag", trace)