Compare commits
1
Commits
v0.1.2
...
d6d3d54810
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6d3d54810 |
No files matched your search
+21
-1
@@ -8,6 +8,25 @@ Format: [Keep a Changelog](https://keepachangelog.com/) style, versions follow [
|
||||
|
||||
Nothing yet.
|
||||
|
||||
## [0.1.3] — 2026-11-09
|
||||
|
||||
Packaging and setup fixes. No gameplay or visual scope; this patch makes the pipeline and its documentation tell the truth about what `0.1.x` taught.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Map imports could report success while saving nothing.** `.umap` files are LFS-tracked and check out read-only, so `save_map()` returned `False` with no error and the staged actor vanished on the next load. New `Tools/ue_common.py` (`make_writable`, `require_saved`) clears the read-only bit and makes the failure loud; `ue_hero_facade_import.py` and `ue_import_north_buildings.py` now use it. Verified from a forced `0o444` map: chmod logged, `save_map: True`, exit 0.
|
||||
|
||||
### Changed
|
||||
|
||||
- `Docs/build.md`: content-pipeline table now lists every step as it exists (seven site FBXs, frontage, north buildings, hero, map) and the shared FBX preset; packaging archive corrected to `Builds/GigLineQA`; the packaged log location and `-qaselftest` usage are documented; new **traps** table covers the read-only save, the hero `--out`-is-the-FBX mistake, the MPFB add-on path, the shot actor quitting under QA, and the null-RHI trace trap.
|
||||
- `Docs/setup.md`: the fresh-checkout build is marked verified (2026-11-08) and its blocker closed; a new section documents the two optional keys (`OPENROUTER_API_KEY`, `MAPBOX_ACCESS_TOKEN`) and that neither is needed by the build, the tests or a release.
|
||||
- `Docs/Architecture/07_deployment_view.md` and the combined `architecture.md`: build-output path corrected and marked as living outside the repository.
|
||||
|
||||
### Verified
|
||||
|
||||
- 54 tool tests pass (5 new for `ue_common`).
|
||||
- Packaged build still launches into `DemoMap` and `-qaselftest` passes every phase.
|
||||
|
||||
## [0.1.2] — 2026-11-09
|
||||
|
||||
Scale and façade correction patch. Buildings and the hero elevation now match the reference photography and the cross-checked height data; no new gameplay scope.
|
||||
@@ -110,7 +129,8 @@ The first playable release: a walkable exterior demo of the **6840 Hollywood Blv
|
||||
- Kerb height (0.13 m as built vs 0.15 m assumed), kerb radii, driveway aprons and graded crossing ramps are deferred to `0.1.1`; crossing ramps are kerb flushes.
|
||||
- One block only; no vehicles, NPCs, audio, or settings UI yet.
|
||||
|
||||
[Unreleased]: https://git.allucanget.biz/zwitschi/gig-line/compare/v0.1.2...main
|
||||
[Unreleased]: https://git.allucanget.biz/zwitschi/gig-line/compare/v0.1.3...main
|
||||
[0.1.3]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.3
|
||||
[0.1.2]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.2
|
||||
[0.1.1]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.1
|
||||
[0.1.0]: https://git.allucanget.biz/zwitschi/gig-line/releases/tag/v0.1.0
|
||||
@@ -7,7 +7,7 @@
|
||||
| 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) |
|
||||
| Build output | `/run/media/system/DATA/Builds/GigLineQA/Linux` (packaged Development build, ~1.1 GiB; outside the repository) |
|
||||
|
||||
Motivation
|
||||
Single-machine development. No build farm, no CI pipeline yet. The engine, toolchain, and GPU are all on the same host.
|
||||
|
||||
@@ -300,7 +300,7 @@ Source data: `Art/blender/` (`.blend` files), `Geo/` (OSM extracts, DEM, verific
|
||||
| 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) |
|
||||
| Build output | `/run/media/system/DATA/Builds/GigLineQA/Linux` (packaged Development build, ~1.1 GiB; outside the repository) |
|
||||
|
||||
Motivation
|
||||
Single-machine development. No build farm, no CI pipeline yet. The engine, toolchain, and GPU are all on the same host.
|
||||
|
||||
+45
-37
@@ -1,6 +1,6 @@
|
||||
# Build
|
||||
|
||||
Fresh-checkout build procedure. Completed 2026-11-07 after the pinned engine was installed and a blank `Game/GigLine` build was packaged and launch-tested.
|
||||
Fresh-checkout build, content-pipeline and packaging procedure for the walkable demo. Begun 2026-11-07 from a blank build and extended through the `0.1.x` patches; the release checks that exercise it are in [release-checks.md](release-checks.md).
|
||||
|
||||
## Constraints
|
||||
|
||||
@@ -10,8 +10,7 @@ Fresh-checkout build procedure. Completed 2026-11-07 after the pinned engine was
|
||||
|
||||
## Engine
|
||||
|
||||
Pinned install: **Unreal Engine 5.8.4** (CL 58902052, promoted build) at
|
||||
`/run/media/system/DATA/Engines/UE_5.8.4`. The engine bundles **no** Linux clang; the system clang (23.1.4, Fedora 45) is the game-code toolchain. Game targets must use `BuildSettingsVersion.V7` to share the installed editor's build environment.
|
||||
Pinned install: **Unreal Engine 5.8.4** (CL 58902052, promoted build) at `/run/media/system/DATA/Engines/UE_5.8.4`. The engine bundles **no** Linux clang; the system clang (23.1.4, Fedora 45) is the game-code toolchain. Game targets must use `BuildSettingsVersion.V7` to share the installed editor's build environment.
|
||||
|
||||
## Fresh-checkout build
|
||||
|
||||
@@ -42,20 +41,14 @@ cd /run/media/system/DATA/Engines/UE_5.8.4
|
||||
Engine/Build/BatchFiles/RunUAT.sh BuildCookRun \
|
||||
-project="/run/media/system/DATA/Documents/06-games/gig-line/Game/GigLine/GigLine.uproject" \
|
||||
-noP4 -platform=Linux -clientconfig=Development \
|
||||
-cook -allmaps -build -stage -pak \
|
||||
-archive -archivedirectory="/run/media/system/DATA/Builds/GigLine" \
|
||||
-cook -build -stage -pak \
|
||||
-archive -archivedirectory="/run/media/system/DATA/Builds/GigLineQA" \
|
||||
-unattended
|
||||
# Expected: "BUILD SUCCESSFUL" (~1 min for the blank project; archive ~1.1 GiB)
|
||||
# Launch test (plain, no -map): /run/media/system/DATA/Builds/GigLine/Linux/GigLine.sh
|
||||
# Expected: "AutomationTool exiting with ExitCode=0 (Success)" and "EXIT=0", ~90 s
|
||||
# Launch test (plain, no -map): /run/media/system/DATA/Builds/GigLineQA/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.
|
||||
**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
|
||||
|
||||
@@ -76,9 +69,7 @@ Both content-acceptance tests run headless and are the template for all asset im
|
||||
|
||||
## Content pipeline (current)
|
||||
|
||||
The hero block and the demo level are built by a fixed sequence. Each step writes
|
||||
a result file under `/tmp/gigline/`; read it, because the commandlet's own stdout
|
||||
can be swallowed.
|
||||
The hero block and the demo level are built by a fixed sequence. Each step writes a result file under `/tmp/gigline/`; read it, because the commandlet's own stdout can be swallowed.
|
||||
|
||||
| Step | Tool | Output |
|
||||
| --- | --- | --- |
|
||||
@@ -86,21 +77,23 @@ can be swallowed.
|
||||
| Terrain | `Art/blender/terrain/terrain_from_dem.py` | `Art/blender/terrain/terrain-*.blend` |
|
||||
| Street | `Art/blender/street/street_mesh.py` | `Art/blender/street/street-*.blend` |
|
||||
| Site assembly | `Art/blender/site/site_assembly.py` | `Art/blender/site/site-*.blend` |
|
||||
| Site export | `Art/blender/site/export_site_fbx.py` | six FBXs, one per mesh |
|
||||
| Hero façade | `Tools/hero_facade_build.py` | `Art/blender/hero/hero-facade-*.blend` + JSON report |
|
||||
| Site export | `Art/blender/site/export_site_fbx.py` | seven FBXs, one per mesh (terrain, road, both sidewalks, both kerbs, crossing) |
|
||||
| Hero façade | `Tools/hero_facade_build.py` | `Art/blender/hero/hero-facade-*.blend` + JSON part report |
|
||||
| Block frontage | `Tools/export_block_frontage.py` | six frontage FBXs from the blockout |
|
||||
| North buildings | `Tools/export_north_buildings.py` | TCL and Tussaud's FBXs from the blockout |
|
||||
| Site import | `Tools/ue_import_site.py` | `/Game/Hollywood/Site/*` |
|
||||
| Frontage import | `Tools/ue_import_block_frontage.py` | `/Game/Hollywood/Frontage/*` |
|
||||
| North import | `Tools/ue_import_north_buildings.py` | `/Game/Hollywood/North/*` |
|
||||
| Hero import | `Tools/ue_hero_facade_import.py` | `/Game/Hollywood/ElCapitan/SM_HeroFacade` |
|
||||
| Demo map | `Tools/ue_build_demo_map.py` | `/Game/Hollywood/DemoMap` |
|
||||
| Review shots | `Tools/ue_build_review_map.py` + `Tools/render_screenshots.sh` | `Screenshots/` |
|
||||
|
||||
Every Blender exporter calls [`Tools/fbx_export_preset.py`](../Tools/fbx_export_preset.py) (`ue_metric_v1` / `ue_character_v1`) instead of repeating the FBX argument block — one place for the scale conversion, the axis mapping and `mesh_smooth_type="FACE"` (without which every Unreal import warns about missing smoothing groups and round geometry arrives faceted).
|
||||
|
||||
Two rules that the pipeline enforces rather than documents:
|
||||
|
||||
- **One conversion.** Metres to Unreal centimetres happens only in
|
||||
`Geo/tools/osm_common.py::to_unreal_local`. Sidecars (`<asset>.geo.json`) record
|
||||
the result and `Geo/tools/asset_metadata.py audit` re-derives it.
|
||||
- **Collision is decided at import,** not later: terrain, road and sidewalks are
|
||||
complex-as-simple; kerbs and props stay simple. A simple hull over a kerbed
|
||||
street ramps straight across the kerb.
|
||||
- **One conversion.** Metres to Unreal centimetres happens only in `Geo/tools/osm_common.py::to_unreal_local`. Sidecars (`<asset>.geo.json`) record the result and `Geo/tools/asset_metadata.py audit` re-derives it.
|
||||
- **Collision is decided at import,** not later: terrain, road and sidewalks are complex-as-simple; kerbs and props stay simple. A simple hull over a kerbed street ramps straight across the kerb.
|
||||
|
||||
## Known engine quirks (2026-11-07)
|
||||
|
||||
@@ -110,20 +103,35 @@ 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).
|
||||
Current QA archive: `/run/media/system/DATA/Builds/GigLineQA`. A plain `./GigLine.sh` opens `/Game/Hollywood/DemoMap`; controls are in [controls.md](controls.md) and shown in-game bottom-right. The packaged log is written **relative to the wrapper's working directory**, i.e. `<archive>/Linux/GigLine/Saved/Logs/GigLine.log` — not into the repository.
|
||||
|
||||
Behaviour check without a human at the keyboard:
|
||||
|
||||
```bash
|
||||
cd /run/media/system/DATA/Builds/GigLineQA/Linux
|
||||
./GigLine.sh -windowed -ResX=1280 -ResY=720 -nosplash -qaselftest
|
||||
# then read <archive>/Linux/GigLine/Saved/Logs/GigLine.log for "GigLine QA:" lines
|
||||
```
|
||||
|
||||
`-qaselftest` walks for 2 s, resets, pauses/unpauses, sweeps a 16-row seam transect, fires a ground-referenced 21 m wall probe along the street, fires 40 m façade-occlusion traces sideways from the road centre wherever a building is staged, and writes a frame-time series to `Saved/Profiling/`. Every phase logs `... PASS`/`FAIL` and the run ends with `RESULT`.
|
||||
|
||||
## Traps that cost real time in `0.1.x`
|
||||
|
||||
| Symptom | Cause | Fix |
|
||||
| --- | --- | --- |
|
||||
| A builder reports success but the asset is unchanged | `.blend`/`.umap` are LFS-tracked and check out **read-only**; Blender and Unreal fail the save quietly | `chmod u+w <file>` first — `ue_build_*`/`ue_hero_facade_import` and every Blender builder need it |
|
||||
| `File format is not supported` when reopening a `.blend` | `hero_facade_build.py --out` writes the **FBX**; putting the `.blend` path there overwrites it with FBX bytes | `--out /tmp/…fbx --save-blend Art/…blend --report Art/…json` |
|
||||
| `Add-on not loaded: "bl_ext.blender_org.mpfb"` | `mpfb_character_export.py` needs the MPFB add-on visible to Blender | export `BLENDER_USER_SCRIPTS` (see the invocation in [../Art/blender/README.md](../Art/blender/README.md)) |
|
||||
| The packaged run exits after a couple of seconds during QA | `GigLineShotActor` runs its screenshot list and quits (`bQuitWhenDone`) | it skips its rounds when `-qaselftest` is on the command line — keep that guard |
|
||||
| Headless `-nullrhi` traces report no hits for everything | the null RHI has no physics scene | verify traces in the **packaged** run (`-qaselftest`), never in a commandlet |
|
||||
| Import logs "No smoothing group information" for every mesh | `mesh_smooth_type="OFF"` in the FBX export | use `Tools/fbx_export_preset.py` (`FACE`) |
|
||||
|
||||
## 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.
|
||||
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.
|
||||
- `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`.
|
||||
- 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`.
|
||||
+10
-16
@@ -1,36 +1,30 @@
|
||||
# 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.
|
||||
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` | |
|
||||
| Move back | `S` | |
|
||||
| Strafe right | `D` | |
|
||||
| Strafe left | `A` | |
|
||||
| Look | Mouse | Captured on launch; `Escape` releases it |
|
||||
| Jump | `Space` | |
|
||||
| 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`.
|
||||
- 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.:
|
||||
`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.
|
||||
It drives the focused game window exactly like real hardware, which is how the controls were regression-tested on the packaged build.
|
||||
+9
-26
@@ -37,22 +37,12 @@ Important nuance: ODbL governs the **database** (the OSM data as a collection an
|
||||
|
||||
## 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 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).
|
||||
- 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:
|
||||
|
||||
@@ -63,20 +53,13 @@ Release checklist items this lands:
|
||||
|
||||
## 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).
|
||||
- **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).
|
||||
- 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`.
|
||||
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`.
|
||||
+38
-89
@@ -34,21 +34,18 @@ Measured at 1440p on the development workstation: Bazzite, Ryzen 7 7700X, 64 GB
|
||||
|
||||
## Check-record template
|
||||
|
||||
| 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 |
|
||||
| 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` | 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).
|
||||
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)
|
||||
|
||||
@@ -80,78 +77,43 @@ probed from the DemoMap (commandlet, 2026-11-08).
|
||||
- **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/`.
|
||||
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.
|
||||
- **Facade occlusion probe:** from the road centre, 40 m traces sideways at walking
|
||||
height toward each frontage, required only where the map stages a building (the
|
||||
spans are listed in `AGigLineCharacter` and mirror `Tools/ue_build_demo_map.py`).
|
||||
Result **0/9 directions without a blocking wall** — the player cannot walk
|
||||
through the hero or the frontage. This probe found the hero façade had **no
|
||||
simple collision** at all (complex-only, so traces and capsule sweeps passed
|
||||
through it); it is now `UseComplexAsSimple`. Auto-generated convex hulls were
|
||||
tried first and rejected: on a 95-part, 26 m façade they bulged into the street
|
||||
(the wall probe went 0/35 → 2/35).
|
||||
(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).
|
||||
- **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.
|
||||
- **Facade occlusion probe:** from the road centre, 40 m traces sideways at walking height toward each frontage, required only where the map stages a building (the spans are listed in `AGigLineCharacter` and mirror `Tools/ue_build_demo_map.py`). Result **0/9 directions without a blocking wall** — the player cannot walk through the hero or the frontage. This probe found the hero façade had **no simple collision** at all (complex-only, so traces and capsule sweeps passed through it); it is now `UseComplexAsSimple`. Auto-generated convex hulls were tried first and rejected: on a 95-part, 26 m façade they bulged into the street (the wall probe went 0/35 → 2/35). (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.
|
||||
**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`.
|
||||
**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).
|
||||
**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.
|
||||
**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 |
|
||||
| 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.
|
||||
**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.
|
||||
**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:**
|
||||
|
||||
@@ -165,8 +127,7 @@ 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:
|
||||
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
|
||||
@@ -179,35 +140,23 @@ 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`.
|
||||
- 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.
|
||||
- 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).
|
||||
**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.
|
||||
**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`.
|
||||
|
||||
- `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.**
|
||||
+13
-2
@@ -83,13 +83,24 @@ Without `git lfs install`, adding an LFS-matched file does not error — it comm
|
||||
|
||||
Sizes: the engine lives outside the repository (pinned install path recorded in `toolchain.md`). The repository holds the Unreal project, Blender sources, geo sources, and tooling.
|
||||
|
||||
**Fresh checkout not yet tested end to end.** The build procedure in [build.md](build.md) was produced from a working tree, not from a clean clone. Exercise it before `v0.1.0` is tagged — it is a release check on the board.
|
||||
**Fresh checkout verified 2026-11-08.** A clean clone of `main` with `git lfs pull` built both `GigLineEditor` and `GigLine` against the documented external engine (recorded in [release-checks.md](release-checks.md)). Re-run it before a release tag if the engine pin or the module's dependencies change.
|
||||
|
||||
### Optional keys (local only)
|
||||
|
||||
Two tools talk to paid services and read their keys from the gitignored `.env`:
|
||||
|
||||
| Variable | Used by | Needed for |
|
||||
| --- | --- | --- |
|
||||
| `OPENROUTER_API_KEY` | `Tools/judge_screenshot.py`, `Tools/facade_detail_concepts.py` | vision pre-check and AI concept plates |
|
||||
| `MAPBOX_ACCESS_TOKEN` | `Geo/tools/mapbox_buildings.py` | the second building-height source |
|
||||
|
||||
Nothing in the build, the tests or the release path needs either key. Never commit `.env`; `git check-ignore .env` should succeed.
|
||||
|
||||
## Known blockers
|
||||
|
||||
| Blocker | Owner | Needed for |
|
||||
| --- | --- | --- |
|
||||
| Delete the fallback 5.2.0 engine archive (`/run/media/system/DATA/Downloads/Linux_Unreal_Engine_5.2.0.zip`) once the 5.8.4 pin is trusted through asset production | agent | Disk hygiene (19.6 GiB), pin safety |
|
||||
| Fresh-checkout build exercised against the documented procedure | agent | `v0.1.0` release check |
|
||||
| ~~Fresh-checkout build exercised against the documented procedure~~ | — | Done 2026-11-08 |
|
||||
|
||||
Closed 2026-11-07: Epic account and engine download, QGIS `lts` removal, Blosm and MPFB on Blender 5.2 (headless invocation fix in `Art/blender/README.md`), engine validation on Bazzite, full project bootstrap. Earlier closed: Bazzite update and reboot, driver and Vulkan verification, `git-lfs`/`clang`/`cmake`/`ninja` layering.
|
||||
Binary file not shown.
Binary file not shown.
@@ -507,3 +507,21 @@ Scope: facts and traps that cost time to establish. Planned work lives in [TODO.
|
||||
scan: banners, windows and the parapet produce different edge sets at different
|
||||
heights and the pairwise match is meaningless (it reported a 418 px "drift").
|
||||
Verify it geometrically in the camera list instead.
|
||||
|
||||
## 2026-11-09 — 0.1.3 packaging/setup fixes
|
||||
|
||||
- **`Tools/ue_common.py`**: `make_writable()` clears the read-only bit an LFS
|
||||
`.umap` checks out with (otherwise `save_map()` returns False with no error and
|
||||
the staged actors vanish), and `require_saved()` makes that failure visible.
|
||||
`ue_hero_facade_import.py` and `ue_import_north_buildings.py` never did this —
|
||||
which is why importing the hero appeared to succeed while saving nothing.
|
||||
Verified by forcing `chmod u-w` on DemoMap first: the run logged
|
||||
`map writable: chmod u+w … (was 0o444)`, then `save_map: True`, exit 0.
|
||||
- Gotcha while adding it: `ue_hero_facade_import.py` had **no** `import os`/`sys`
|
||||
(only `unreal`), so the sys.path line raised `NameError: name 'sys' is not
|
||||
defined` inside the commandlet — an exit code of 255 with no obvious cause.
|
||||
- Docs: `build.md` gained the current pipeline table (7 site FBXs, frontage, north,
|
||||
hero, map), the `Builds/GigLineQA` archive, the packaged-log path, `-qaselftest`
|
||||
usage and a **traps** table; `setup.md` marks the fresh-checkout build verified and
|
||||
documents the two optional API keys; architecture 07 + combined doc corrected the
|
||||
build-output path.
|
||||
@@ -9,16 +9,6 @@ Actionable task list for this project. This file is the single, central place fo
|
||||
- When all steps in a version section are done: write a `CHANGELOG.md` entry, then remove the version section from this file.
|
||||
- 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.3 — Packaging and setup fixes (patch)
|
||||
|
||||
Suggested blender skills: **export-pipeline** and **unreal-export** (scale/pivot/normals/materials/collision verification) if any packaging defect traces back to the Blender export presets.
|
||||
|
||||
### Patch tasks
|
||||
|
||||
- [ ] Fix any packaging or build issues encountered in `0.1.x`.
|
||||
- [ ] Update `Docs/` setup instructions for anything that changed.
|
||||
- [ ] Tag `v0.1.3` and write `CHANGELOG.md` entry; then remove this section.
|
||||
|
||||
## 0.1.4 — Proper building façades (patch)
|
||||
|
||||
Suggested blender skills: **procedural-modeling** + **geometry-nodes** (the façade module kit — bay, pilaster, cornice, parapet, window, shopfront, door — as reusable node groups), **archviz** (real-world scale and façade composition), **materials** (procedural brick, concrete, stone, render, glass — the ported generators), **texture-workflow** (AO/curvature/normal bakes and decals for the motto band and medallions), **uv-workflow** (texel density and modular UV layouts for the kit), **scene-assembly** (assembling kit pieces onto the fifteen block buildings), **lookdev** (screenshot-comparison loop until shading reads correctly), **historical-worlds** or **realistic-style** (1920s Hollywood Blvd neoclassical direction), **asset-optimization** + **unreal-export** (pre-export validation, `ue_metric_v1` preset), **qa-review** (final gate before the `Tools/render_screenshots.sh` review).
|
||||
|
||||
@@ -16,6 +16,7 @@ Geographic tools live in [`Geo/tools/`](../Geo/README.md) and the Blender-side b
|
||||
| Tool | Purpose |
|
||||
| --- | --- |
|
||||
| `check-lfs.sh` | Fails if the `lfs` filter is missing or an LFS-tracked file was committed as a plain blob |
|
||||
| `ue_common.py` | Shared helpers for the Unreal commandlet scripts: `make_writable()` clears the read-only bit an LFS `.umap` checks out with (otherwise `save_map()` returns False silently), and `require_saved()` makes that failure explicit. |
|
||||
| `fbx_export_preset.py` | The **single** FBX export preset (`ue_metric_v1` / `ue_character_v1`): metres→centimetres via `FBX_SCALE_NONE`, axis mapping east +X / north −Y / up +Z, `mesh_smooth_type="FACE"` (smoothing groups survive the import). Every Blender exporter imports it instead of copying the argument block; `preset_kwargs()` is bpy-free and unit-tested. |
|
||||
| `sibling_asset_audit.py` | Reads the sibling project's prop library from GLB headers into `Art/props/reuse-manifest.json`; copies nothing |
|
||||
| `scale_test_export.py` / `ue_scale_test_import.py` | The 1 m = 100 uu scale test; the named presets `ue_metric_v1` live in these two files |
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Tests for Tools/ue_common.py — the shared Unreal commandlet helpers.
|
||||
|
||||
Pure stdlib: these helpers exist to turn two silent failures into loud ones, so
|
||||
they are tested without starting Unreal.
|
||||
"""
|
||||
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
sys.path.insert(0, os.path.join(ROOT, "Tools"))
|
||||
|
||||
import ue_common # noqa: E402
|
||||
|
||||
|
||||
class MakeWritableTest(unittest.TestCase):
|
||||
def test_read_only_file_is_made_writable(self):
|
||||
# Exactly how an LFS-tracked .umap checks out: 0o444.
|
||||
with tempfile.NamedTemporaryFile(delete=False) as handle:
|
||||
path = handle.name
|
||||
try:
|
||||
os.chmod(path, 0o444)
|
||||
self.assertFalse(os.access(path, os.W_OK))
|
||||
note = ue_common.make_writable(path)
|
||||
self.assertIsNotNone(note)
|
||||
self.assertIn("chmod u+w", note)
|
||||
self.assertTrue(os.access(path, os.W_OK))
|
||||
finally:
|
||||
os.chmod(path, 0o644)
|
||||
os.unlink(path)
|
||||
|
||||
def test_writable_file_reports_nothing(self):
|
||||
with tempfile.NamedTemporaryFile(delete=False) as handle:
|
||||
path = handle.name
|
||||
try:
|
||||
os.chmod(path, 0o644)
|
||||
self.assertIsNone(ue_common.make_writable(path))
|
||||
finally:
|
||||
os.unlink(path)
|
||||
|
||||
def test_missing_file_raises(self):
|
||||
# A typo must not look like a permissions problem.
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
ue_common.make_writable(os.path.join(tmp, "absent.umap"))
|
||||
|
||||
def test_read_bits_are_preserved(self):
|
||||
with tempfile.NamedTemporaryFile(delete=False) as handle:
|
||||
path = handle.name
|
||||
try:
|
||||
os.chmod(path, 0o400)
|
||||
ue_common.make_writable(path)
|
||||
mode = os.stat(path).st_mode
|
||||
self.assertTrue(mode & stat.S_IRUSR)
|
||||
finally:
|
||||
os.chmod(path, 0o644)
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
class RequireSavedTest(unittest.TestCase):
|
||||
def test_passes_the_save_result_through(self):
|
||||
self.assertTrue(ue_common.require_saved(True, "/Game/X"))
|
||||
self.assertFalse(ue_common.require_saved(False, "/Game/X"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Small shared helpers for the Unreal commandlet scripts.
|
||||
|
||||
These run inside `UnrealEditor-Cmd -run=pythonscript`, where a Python `print()`
|
||||
is swallowed and a failed save reports `False` rather than raising. Both are traps
|
||||
that cost time in `0.1.x`, so they live here once instead of in each script.
|
||||
|
||||
import os, sys
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import ue_common
|
||||
|
||||
ue_common.make_map_writable("/run/media/.../Content/Hollywood/DemoMap.umap")
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import stat
|
||||
|
||||
|
||||
def is_writable(path: str) -> bool:
|
||||
"""True when the current user may write `path` (existing file)."""
|
||||
return os.access(path, os.W_OK)
|
||||
|
||||
|
||||
def make_writable(path: str) -> str | None:
|
||||
"""Clear read-only bits on `path` so a save cannot fail silently.
|
||||
|
||||
Content is LFS-tracked and `.umap`/`.blend` check out read-only, which makes
|
||||
`EditorLoadingAndSavingUtils.save_map()` return False with no error — the
|
||||
staged actors then vanish on the next load. Returns a note when the mode was
|
||||
changed, `None` when it was already writable, and raises when the file is
|
||||
missing so a typo cannot masquerade as a permissions problem.
|
||||
"""
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(path)
|
||||
mode = os.stat(path).st_mode
|
||||
if mode & stat.S_IWUSR:
|
||||
return None
|
||||
os.chmod(path, mode | stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH |
|
||||
stat.S_IWGRP)
|
||||
return f"chmod u+w {path} (was {oct(mode & 0o777)})"
|
||||
|
||||
|
||||
def require_saved(saved: bool, what: str) -> bool:
|
||||
"""Turn a silent `save_map(False)` into a loud failure.
|
||||
|
||||
Use right after a save call: `ue_common.require_saved(saved, LEVEL)`. A
|
||||
commandlet cannot be relied on to exit non-zero, so the caller should log this
|
||||
and make the failure obvious in the result file.
|
||||
"""
|
||||
return bool(saved)
|
||||
@@ -12,8 +12,14 @@
|
||||
# centreline, from the street profile), and the hero's east edge at
|
||||
# X = -12024.5 cm (projected control point in local-origin.md).
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import unreal
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import ue_common # noqa: E402
|
||||
|
||||
FBX = "/tmp/gigline/hero-facade.fbx"
|
||||
DEST = "/Game/Hollywood/ElCapitan"
|
||||
ACTOR_LABEL = "Hero_Facade"
|
||||
@@ -56,6 +62,19 @@ def import_fbx():
|
||||
unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True, True)
|
||||
|
||||
|
||||
def _make_map_writable(level: str) -> None:
|
||||
"""Clear the read-only bit an LFS .umap checks out with.
|
||||
|
||||
Without this `save_map()` returns False and the staged actor is lost on the
|
||||
next load — no exception, no non-zero exit (see MEMORY: 0.1.x trap).
|
||||
"""
|
||||
content = unreal.Paths.project_content_dir()
|
||||
rel = level[len("/Game/"):] if level.startswith("/Game/") else level
|
||||
umap = os.path.join(content, rel + ".umap")
|
||||
note = ue_common.make_writable(umap)
|
||||
if note:
|
||||
log(f"map writable: {note}")
|
||||
|
||||
def measure_and_place():
|
||||
asset = unreal.EditorAssetLibrary.load_asset(f"{DEST}/SM_HeroFacade")
|
||||
if asset is None:
|
||||
@@ -99,9 +118,12 @@ def measure_and_place():
|
||||
comp.set_static_mesh(asset)
|
||||
comp.set_mobility(unreal.ComponentMobility.STATIC)
|
||||
log(f"placed {ACTOR_LABEL} at {PLACEMENT_CM} (cm) on {LEVEL}")
|
||||
_make_map_writable(LEVEL)
|
||||
saved = unreal.EditorLoadingAndSavingUtils.save_map(
|
||||
unreal.EditorLevelLibrary.get_editor_world(), LEVEL)
|
||||
log(f"save_map {LEVEL}: {saved}")
|
||||
if not ue_common.require_saved(saved, LEVEL):
|
||||
log(f"FAILED to save {LEVEL} — staged content will not persist")
|
||||
|
||||
|
||||
try:
|
||||
|
||||
@@ -15,6 +15,9 @@ import os
|
||||
|
||||
import unreal
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import ue_common # noqa: E402
|
||||
|
||||
SRC = "/tmp/gigline/north"
|
||||
DEST = "/Game/Hollywood/North"
|
||||
LEVEL = "/Game/Hollywood/DemoMap"
|
||||
@@ -72,11 +75,27 @@ def stage():
|
||||
mn, mx = box.min, box.max
|
||||
log(f"{label}: mesh world-space X {mn.x:.0f}..{mx.x:.0f} Y {mn.y:.0f}..{mx.y:.0f} size "
|
||||
f"{mx.x-mn.x:.0f}x{mx.y-mn.y:.0f} cm")
|
||||
_make_map_writable(LEVEL)
|
||||
saved = unreal.EditorLoadingAndSavingUtils.save_map(
|
||||
unreal.EditorLevelLibrary.get_editor_world(), LEVEL)
|
||||
log(f"save_map {LEVEL}: {saved}")
|
||||
if not ue_common.require_saved(saved, LEVEL):
|
||||
log(f"FAILED to save {LEVEL} — staged content will not persist")
|
||||
|
||||
|
||||
def _make_map_writable(level: str) -> None:
|
||||
"""Clear the read-only bit an LFS .umap checks out with.
|
||||
|
||||
Without this `save_map()` returns False and the staged actor is lost on the
|
||||
next load — no exception, no non-zero exit (see MEMORY: 0.1.x trap).
|
||||
"""
|
||||
content = unreal.Paths.project_content_dir()
|
||||
rel = level[len("/Game/"):] if level.startswith("/Game/") else level
|
||||
umap = os.path.join(content, rel + ".umap")
|
||||
note = ue_common.make_writable(umap)
|
||||
if note:
|
||||
log(f"map writable: {note}")
|
||||
|
||||
def main():
|
||||
for fn, asset_name in IMPORTS:
|
||||
task = unreal.AssetImportTask()
|
||||
|
||||
Reference in new issue
Block a user