streaming: split dense passage-junction grid fills; heavy-first order + far-parent deferral in Unreal (L85)
Tiles with passage-overlap score >= 9 split grid fill over up to 4 helper lanes (disjoint rows, same per-sample order, join before MC) when workers are idle. FI junction tile fill 173 -> 99 ms; estimated serial floor ~0.55-0.60 s. Readiness essentially unchanged (FI/TN LOD0-2 ~2.15 s @W12): the limit is now aggregate work, not single tiles. Unreal: LOD0-2 ordered by passage overlap before travel/distance (L80 modes kept); far parents built after LOD0-1, LOD2 gated on them; helper budget via ParallelFor. Unreal null-RHI FI arrival: LOD0-2 resident at 3.65 s. All tile hashes identical to L84 (four whole views). Fast tier PASS; Linux UE host builds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
0a98da6fd0
commit
06f558dc91
17 files changed
+1561
-54
No files matched your search
@@ -0,0 +1,61 @@
|
||||
# Linux 85 — split heavy grid fills
|
||||
|
||||
## Split and floor
|
||||
|
||||
Full-resolution passage tiles with overlap score **9 or higher** can split density-grid fill over up to four helper lanes. Lanes copy the tile’s worker-local sampling caches and passage mask, write disjoint grid rows, and join before marching cubes. Each sample keeps the same evaluation order. The helper budget is limited by active tile workers under the 12-CPU affinity, so saturated W12/W16 work stays serial inside each tile.
|
||||
|
||||
Matched W8 profiles show the junction tile `(0,1,-93,lod0)` improved. Times are medians of three L84/L85 profiles; each tile materialized 42,875 samples.
|
||||
|
||||
| View | Junction service, L84 → L85 | Junction grid fill, L84 → L85 | Slowest full-grid service / fill, L84 → L85 | Estimated floor, L84 → L85 |
|
||||
|---|---:|---:|---:|---:|
|
||||
| FI | 243 → 167 ms | 173 → 99 ms | 243 / 173 → 224 / 167 ms | 0.560 → 0.547 s |
|
||||
| TN | 168 → 159 ms | 121 → 100 ms | 243 / 172 → 246 / 186 ms | 0.593 → 0.595 s |
|
||||
|
||||
Floor = median base-shape time + median far-parent time + median per-run slowest full-grid LOD0 service. The junction fill fell 43% in FI and 18% in TN, but the estimated floor moved only slightly: the slowest tile can be a different, unsplit tile. Readiness also stayed close to L84 overall, with mixed W8 results and nearly unchanged FI/TN W12 results.
|
||||
|
||||
Cold LOD0–2-ready / whole-view seconds. L85 uses one warmup then three interleaved W8/W12 pairs per stratum; L84 values are from the L84 report.
|
||||
|
||||
| Stratum | L84 W8 | L85 W8 | L84 W12 | L85 W12 |
|
||||
|---|---:|---:|---:|---:|
|
||||
| SurfaceWorld | 1.021 / 2.209 | 0.976 / 2.166 | 0.990 / 2.081 | 0.961 / 2.019 |
|
||||
| FloatingIslands | 2.264 / 3.429 | 2.286 / 3.469 | 2.180 / 3.341 | 2.162 / 3.306 |
|
||||
| TunnelNetwork | 2.269 / 3.377 | 2.323 / 3.377 | 2.146 / 3.153 | 2.147 / 3.140 |
|
||||
| VerticalShafts | 1.127 / 2.080 | 1.243 / 2.229 | 1.086 / 2.023 | 1.080 / 2.000 |
|
||||
|
||||
## Unreal and brick look
|
||||
|
||||
`VoxelWorld.cpp` now orders outstanding LOD0–2 tiles by passage overlap before its existing travel/distance order, while keeping L80 speed-aware modes and the collision-critical ring. Initial compile omits far parents; the Unreal scheduler builds them after desired LOD0–1 tiles are loaded and gates LOD2 until publication. The Core task adapter passes the available helper budget to Unreal `ParallelFor`.
|
||||
|
||||
The null-RHI FI-start trace reached 1,216/1,216 resident LOD0–2 tiles at **3.650 s**, before movement began; the center was `(3,0,-93)`. Trace: `Saved/Linux/l85/unreal-fi/fi-arrival-l85.trace.jsonl`.
|
||||
|
||||
Per-tile passage masks prune passage candidates at tile scope. They do not prove the sign of the complete density field inside a brick, so they do not make a cheap, sound brick skip available; no new brick audit was run.
|
||||
|
||||
## Hashes and checks
|
||||
|
||||
L84 and L85 whole-view and per-tile density, mesh, payload and topology hashes match exactly; zero tile mismatches.
|
||||
|
||||
| Stratum | Tiles | Density | Mesh | Payload | Topology |
|
||||
|---|---:|---|---|---|---|
|
||||
| SurfaceWorld | 2,850 | `40b76d0072a2adcd` | `8559df0850d4d7f1` | `7700965ff31ac702` | `0e7f2a16b4cc71a9` |
|
||||
| FloatingIslands | 2,249 | `99ca213b21ce86e4` | `2977525405b79fff` | `3d7d2efb581710bd` | `af1916c949224871` |
|
||||
| TunnelNetwork | 2,233 | `8d23c26f5c092be1` | `15c3aad2e29d9ea0` | `3d48f68d511c21df` | `5b1584676884edd8` |
|
||||
| VerticalShafts | 2,329 | `876b44981d106be3` | `aaef57cb08bd6845` | `b19617df9f4acbb1` | `e8eee991260adff4` |
|
||||
|
||||
Fast tier: **PASS**, 274 checks, 286.5 s. Linux `VFHostEditor`: **PASS**, final post-timing UBT invocation used `-NoUBA -MaxParallelActions=4`, taskset CPUs 0–11, and no address-space limit. The prior full compile built 35/35 actions; three existing RealtimeMesh deprecation warnings remain. The per-tile W8 profile and timing runners used taskset and the 6 GiB address-space limit. Headless processes in hash and fast runs used both limits.
|
||||
|
||||
## Source files
|
||||
|
||||
- `Source/VoxelForge/Private/VoxelWorld.cpp` — Unreal tile ordering, far-parent deferral and helper-lane budget.
|
||||
- `Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp`, `Source/VoxelForge/Public/VoxelMarchingCubesMesher.h` — parallel grid-fill hooks and worker joins.
|
||||
- `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h`, `Standalone/Core/src/VoxelForgeCore.cpp` — scheduler API, worker-local context and overlap scoring.
|
||||
- `Standalone/Core/src/ShapeWorldInternal.h`, `Standalone/Core/src/WorldAssemblerInternal.h` — cloneable passage mask and cache-mode access.
|
||||
- `Standalone/Source/StreamingWorld.h`, `Standalone/Source/StreamBenchmarkPipeline.h`, `Standalone/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp`, `Standalone/Source/VoxelForge/Public/VoxelMarchingCubesMesher.h` — headless scheduling and split fill.
|
||||
|
||||
## Failed runs
|
||||
|
||||
- The first split smoke hit SIGBUS because helper threads wrote to their empty thread-local density buffer. Workers now write into the parent-owned grid; final hashes match.
|
||||
- Splitting every passage overlap was too broad and regressed FI/TN throughput. The final score-9 threshold and active-worker budget narrow it to dense junction tiles with spare capacity.
|
||||
- The first UBT attempt stopped in UnrealHeaderTool because `<cstddef>` followed the generated header include. Include order was fixed; final builds passed.
|
||||
- The first fast-tier launch lacked its round-specific binary. Building the final-stamp binary fixed it; the 274-check rerun passed.
|
||||
|
||||
No run exceeded 30 minutes. No install, commit, push or stash was made.
|
||||
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compare L84 and L85 per-tile and whole-view hashes for all four full views."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l85/hash-check-final"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BINARIES = {
|
||||
"L84": ROOT / "Saved/Linux/l84/bin/VoxelForgeCoreHeadless-l84-clang19",
|
||||
"L85": ROOT / "Saved/Linux/l85/bin/VoxelForgeCoreHeadless-clang19",
|
||||
}
|
||||
STAMPS = {
|
||||
"L84": "c433a039b0255ce3c4794bf20313b8a5a11442feeb07b6555f585688949741a5",
|
||||
"L85": "2967bb8aaea5a7beac0cd0735c0c2e5cccd7b31a9622dd33395e81ff1e5f2589",
|
||||
}
|
||||
CAMERAS = {
|
||||
0: (8.0, 16.0, -372.2626953125),
|
||||
1: (25.8369140625, 3.19921875, -737.0),
|
||||
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||
3: (-20.0625, -5.068359375, -900.353515625),
|
||||
}
|
||||
EXPECTED_WHOLE = {0: 2850, 1: 2249, 2: 2233, 3: 2329}
|
||||
STAMP = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=(\S+)$", re.M)
|
||||
TILE_HASH = re.compile(
|
||||
r"^\[PipelineTileHash\] lod=(-?\d+) x=(-?\d+) y=(-?\d+) z=(-?\d+) "
|
||||
r"density=(0x[0-9a-f]+) mesh=(0x[0-9a-f]+) payload=(0x[0-9a-f]+) "
|
||||
r"topology=(0x[0-9a-f]+)$", re.M)
|
||||
WHOLE_HASH = re.compile(
|
||||
r"^\[PipelineWholeViewHash\] tiles=(\d+) density=(0x[0-9a-f]+) "
|
||||
r"mesh=(0x[0-9a-f]+) payload=(0x[0-9a-f]+) topology=(0x[0-9a-f]+)$", re.M)
|
||||
|
||||
|
||||
def run(strate: int, version: str) -> dict:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
log = OUT / f"{version.lower()}-s{strate}.log"
|
||||
camera = ",".join(f"{v:.12f}" for v in CAMERAS[strate]) + ",0,0"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"env", "VOXELFORGE_L81_PASSAGE_BOUND_PADDING_VOXELS=0", "timeout", "1740s",
|
||||
str(BINARIES[version]), str(DATA), "--world-assembler-prototype",
|
||||
"--stream-benchmark", "--stream-pipeline", "--workers=8", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||
"--stream-scheduler-policy=3", "--stream-hash-tiles",
|
||||
]
|
||||
started = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||
output = log.read_text(encoding="utf-8", errors="replace")
|
||||
source, whole = STAMP.search(output), WHOLE_HASH.search(output)
|
||||
hashes = {
|
||||
(int(match.group(1)), int(match.group(2)), int(match.group(3)), int(match.group(4))):
|
||||
match.group(5, 6, 7, 8)
|
||||
for match in TILE_HASH.finditer(output)
|
||||
}
|
||||
if result.returncode or source is None or whole is None:
|
||||
raise RuntimeError(f"hash run failed: exit={result.returncode} {log}")
|
||||
if source.group(1) != STAMPS[version]:
|
||||
raise RuntimeError(f"source stamp mismatch in {log}: {source.group(1)}")
|
||||
if int(whole.group(1)) != EXPECTED_WHOLE[strate] or len(hashes) != EXPECTED_WHOLE[strate]:
|
||||
raise RuntimeError(f"incomplete whole-view hashes in {log}: tiles={len(hashes)} whole={whole.group(1)}")
|
||||
row = {
|
||||
"version": version, "strate": strate, "exit_code": result.returncode,
|
||||
"wall_s": round(time.monotonic() - started, 3), "tile_count": len(hashes),
|
||||
"whole_hash": whole.group(1, 2, 3, 4, 5), "hashes": hashes,
|
||||
"source_stamp": source.group(1), "log": str(log.relative_to(ROOT)),
|
||||
}
|
||||
print("[L85HashRun] " + json.dumps(
|
||||
{key: value for key, value in row.items() if key != "hashes"}, sort_keys=True),
|
||||
flush=True)
|
||||
return row
|
||||
|
||||
|
||||
def main() -> int:
|
||||
for binary in (*BINARIES.values(), DATA):
|
||||
if not binary.is_file():
|
||||
raise SystemExit(f"missing input: {binary}")
|
||||
rows, comparisons = [], []
|
||||
for strate in range(4):
|
||||
pair = {version: run(strate, version) for version in ("L84", "L85")}
|
||||
before, after = pair["L84"], pair["L85"]
|
||||
mismatches = sorted(key for key in set(before["hashes"]) | set(after["hashes"])
|
||||
if before["hashes"].get(key) != after["hashes"].get(key))
|
||||
comparison = {
|
||||
"strate": strate, "tile_count": len(before["hashes"]),
|
||||
"tile_hashes_identical": not mismatches,
|
||||
"whole_hash_identical": before["whole_hash"] == after["whole_hash"],
|
||||
"mismatch_count": len(mismatches), "first_mismatches": mismatches[:12],
|
||||
"l84_whole_hash": before["whole_hash"], "l85_whole_hash": after["whole_hash"],
|
||||
"logs": {version: pair[version]["log"] for version in pair},
|
||||
}
|
||||
comparisons.append(comparison)
|
||||
rows.extend({key: value for key, value in row.items() if key != "hashes"}
|
||||
for row in pair.values())
|
||||
(OUT / "runs-partial.json").write_text(json.dumps(
|
||||
{"comparisons": comparisons, "runs": rows}, indent=2) + "\n", encoding="utf-8")
|
||||
print("[L85HashComparison] " + json.dumps(comparison, sort_keys=True), flush=True)
|
||||
return 0 if all(row["tile_hashes_identical"] and row["whole_hash_identical"]
|
||||
for row in comparisons) else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python3
|
||||
"""L80-protocol W8/W12 readiness pairs for the L85 split-grid candidate."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l85/scaling-final"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BINARY = ROOT / "Saved/Linux/l85/bin/VoxelForgeCoreHeadless-clang19"
|
||||
WORKERS = (8, 12)
|
||||
CAMERAS = {
|
||||
0: (8.0, 16.0, -372.2626953125),
|
||||
1: (25.8369140625, 3.19921875, -737.0),
|
||||
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||
3: (-20.0625, -5.068359375, -900.353515625),
|
||||
}
|
||||
EXPECTED_NEAR = {0: 1408, 1: 1216, 2: 1344, 3: 1344}
|
||||
EXPECTED_WHOLE = {0: 2850, 1: 2249, 2: 2233, 3: 2329}
|
||||
SOURCE_STAMP = "2967bb8aaea5a7beac0cd0735c0c2e5cccd7b31a9622dd33395e81ff1e5f2589"
|
||||
PIPELINE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M)
|
||||
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||
NEAR = re.compile(r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) cold_ready_s=([0-9.]+)$", re.M)
|
||||
STAMP = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=(\S+)$", re.M)
|
||||
|
||||
|
||||
def run(strate: int, workers: int, phase: str, rep: int) -> dict:
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
log = OUT / f"s{strate}-w{workers}-{phase}{rep}.log"
|
||||
camera = ",".join(f"{v:.12f}" for v in CAMERAS[strate]) + ",0,0"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"env", "VOXELFORGE_L81_PASSAGE_BOUND_PADDING_VOXELS=0", "timeout", "1740s",
|
||||
str(BINARY), str(DATA), "--world-assembler-prototype", "--stream-benchmark",
|
||||
"--stream-pipeline", f"--workers={workers}", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||
"--profile-lod-work", "--stream-scheduler-policy=3",
|
||||
]
|
||||
start = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||
output = log.read_text(encoding="utf-8", errors="replace")
|
||||
pipeline_match, near_match, stamp_match = (
|
||||
PIPELINE.search(output), NEAR.search(output), STAMP.search(output))
|
||||
if result.returncode or pipeline_match is None or near_match is None:
|
||||
raise RuntimeError(f"readiness run failed: exit={result.returncode} {log}")
|
||||
if stamp_match is None or stamp_match.group(1) != SOURCE_STAMP:
|
||||
raise RuntimeError(f"source stamp mismatch in {log}")
|
||||
pipeline = dict(FIELDS.findall(pipeline_match.group(1)))
|
||||
near_done, near_total = int(near_match.group(1)), int(near_match.group(2))
|
||||
whole_done, whole_total = (int(v) for v in pipeline["whole_tiles"].split("/"))
|
||||
if (near_done, near_total) != (EXPECTED_NEAR[strate], EXPECTED_NEAR[strate]):
|
||||
raise RuntimeError(f"near readiness incomplete in {log}: {near_done}/{near_total}")
|
||||
if (whole_done, whole_total) != (EXPECTED_WHOLE[strate], EXPECTED_WHOLE[strate]):
|
||||
raise RuntimeError(f"whole readiness incomplete in {log}: {whole_done}/{whole_total}")
|
||||
row = {
|
||||
"strate": strate, "workers": workers, "phase": phase, "replicate": rep,
|
||||
"exit_code": result.returncode, "wall_s": round(time.monotonic() - start, 3),
|
||||
"lod02_ready_s": float(near_match.group(3)),
|
||||
"whole_view_ready_s": float(pipeline["whole_view_ready"].removesuffix("s")),
|
||||
"shape_s": float(pipeline["shape"].removesuffix("s")),
|
||||
"far_shape_s": float(pipeline["far_shape"].removesuffix("s")),
|
||||
"first_tile_ready_s": float(pipeline["first_tile_ready"].removesuffix("s")),
|
||||
"neighborhood_ready_s": float(pipeline["neighborhood_ready"].removesuffix("s")),
|
||||
"full_detail_ready_s": float(pipeline["full_detail_ready"].removesuffix("s")),
|
||||
"near_tiles": near_total, "whole_tiles": whole_total,
|
||||
"source_stamp": stamp_match.group(1), "log": str(log.relative_to(ROOT)),
|
||||
}
|
||||
print("[L85ScalingRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
return row
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not BINARY.is_file() or not DATA.is_file():
|
||||
raise SystemExit(f"missing input: {BINARY} or {DATA}")
|
||||
rows = []
|
||||
for strate in range(4):
|
||||
for workers in WORKERS:
|
||||
run(strate, workers, "warmup", 0)
|
||||
for rep, order in enumerate(((8, 12), (12, 8), (8, 12)), 1):
|
||||
for workers in order:
|
||||
rows.append(run(strate, workers, "measured", rep))
|
||||
(OUT / "runs-partial.json").write_text(
|
||||
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
|
||||
summary = []
|
||||
for strate in range(4):
|
||||
for workers in WORKERS:
|
||||
subset = [row for row in rows
|
||||
if row["strate"] == strate and row["workers"] == workers]
|
||||
item = {"strate": strate, "workers": workers, "measured_runs": len(subset)}
|
||||
for metric in ("lod02_ready_s", "whole_view_ready_s", "shape_s", "far_shape_s",
|
||||
"first_tile_ready_s", "neighborhood_ready_s", "full_detail_ready_s"):
|
||||
values = [row[metric] for row in subset]
|
||||
item[metric] = values
|
||||
item[f"{metric}_median"] = statistics.median(values)
|
||||
item[f"{metric}_min"] = min(values)
|
||||
item[f"{metric}_max"] = max(values)
|
||||
summary.append(item)
|
||||
(OUT / "summary.json").write_text(
|
||||
json.dumps({"runs": rows, "summary": summary}, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Interleaved W16 tile and shape-stage profiles for L85."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l85/serial-floor-final"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BINARIES = {
|
||||
"L85": ROOT / "Saved/Linux/l85/bin/VoxelForgeCoreHeadless-clang19",
|
||||
}
|
||||
STAMPS = {
|
||||
"L85": "2967bb8aaea5a7beac0cd0735c0c2e5cccd7b31a9622dd33395e81ff1e5f2589",
|
||||
}
|
||||
CAMERAS = {
|
||||
1: (25.8369140625, 3.19921875, -737.0),
|
||||
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||
}
|
||||
NAMES = {1: "FI", 2: "TN"}
|
||||
EXPECTED_NEAR = {1: 1216, 2: 1344}
|
||||
EXPECTED_WHOLE = {1: 2249, 2: 2233}
|
||||
STAMP = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=(\S+)$", re.M)
|
||||
NEAR = re.compile(r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) cold_ready_s=([0-9.]+)$", re.M)
|
||||
PIPELINE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M)
|
||||
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||
SHAPE_FIELDS = re.compile(r"([a-zA-Z0-9_+\-]+)=([0-9.]+)")
|
||||
TIMELINE = re.compile(
|
||||
r"^\[CpuTileTimeline\] event=finish key=\(([-0-9]+),([-0-9]+),([-0-9]+),lod([0-9]+)\) (.*)$", re.M)
|
||||
STAGES = re.compile(
|
||||
r"^\[CpuTileStages\] key=\(([-0-9]+),([-0-9]+),([-0-9]+),lod([0-9]+)\) (.*)$", re.M)
|
||||
SHAPE_STAGE = re.compile(r"^\[ShapeStage\] (.*)$", re.M)
|
||||
FAR_STAGE = re.compile(r"^\[FarShapeStage\] (.*)$", re.M)
|
||||
|
||||
|
||||
def run(strate: int, version: str, rep: int) -> dict:
|
||||
out_dir = OUT / version.lower()
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
log = out_dir / f"{NAMES[strate].lower()}-w16-r{rep}.log"
|
||||
camera = ",".join(f"{v:.12f}" for v in CAMERAS[strate]) + ",0,0"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"env", "VOXELFORGE_L81_PASSAGE_BOUND_PADDING_VOXELS=0", "timeout", "1740s",
|
||||
str(BINARIES[version]), str(DATA), "--world-assembler-prototype",
|
||||
"--stream-benchmark", "--stream-pipeline", "--workers=16", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||
"--profile-lod-work", "--instrument-tile-drain", "--stream-scheduler-policy=3",
|
||||
]
|
||||
started = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||
output = log.read_text(encoding="utf-8", errors="replace")
|
||||
source = STAMP.search(output)
|
||||
near = NEAR.search(output)
|
||||
pipeline = PIPELINE.search(output)
|
||||
if result.returncode or source is None or near is None or pipeline is None:
|
||||
raise RuntimeError(f"profile run failed: exit={result.returncode} {log}")
|
||||
if source.group(1) != STAMPS[version]:
|
||||
raise RuntimeError(f"source stamp mismatch in {log}: {source.group(1)}")
|
||||
near_done, near_total = int(near.group(1)), int(near.group(2))
|
||||
p = dict(FIELDS.findall(pipeline.group(1)))
|
||||
whole_done, whole_total = (int(value) for value in p["whole_tiles"].split("/"))
|
||||
if near_total != EXPECTED_NEAR[strate] or near_done != near_total:
|
||||
raise RuntimeError(f"incomplete near tiles in {log}: {near_done}/{near_total}")
|
||||
if whole_total != EXPECTED_WHOLE[strate] or whole_done != whole_total:
|
||||
raise RuntimeError(f"incomplete whole view in {log}: {whole_done}/{whole_total}")
|
||||
|
||||
stage_rows = {}
|
||||
for match in STAGES.finditer(output):
|
||||
key = tuple(int(value) for value in match.group(1, 2, 3, 4))
|
||||
stage_rows[key] = dict(FIELDS.findall(match.group(5)))
|
||||
tiles = []
|
||||
for match in TIMELINE.finditer(output):
|
||||
key = tuple(int(value) for value in match.group(1, 2, 3, 4))
|
||||
fields = dict(FIELDS.findall(match.group(5)))
|
||||
if key[3] > 2:
|
||||
continue
|
||||
stage = stage_rows.get(key, {})
|
||||
tiles.append({
|
||||
"key": key, "lod": key[3], "service_ms": float(fields["service_ms"]),
|
||||
"queued_ms": float(fields["queued_ms"]), "dispatch_ms": float(fields["dispatch_ms"]),
|
||||
"finish_ms": float(fields["finish_ms"]), "priority_rank": int(fields["priority_rank"]),
|
||||
"queue_depth": int(fields["queue_depth"]), "active_workers": int(fields["active_workers"]),
|
||||
"samples": int(stage.get("samples", "0")),
|
||||
"candidate_cells": int(stage.get("candidate_cells", "0")),
|
||||
"surface_cells": int(stage.get("surface_cells", "0")),
|
||||
"tile_class": stage.get("tile_class", "unknown"),
|
||||
"mc_grid_ms": float(stage.get("mc_grid_ms", "0")),
|
||||
"mc_grid_workers": int(stage.get("mc_grid_workers", "1")),
|
||||
"mc_grid_workers": int(stage.get("mc_grid_workers", "1")),
|
||||
"mc_cell_ms": float(stage.get("mc_cell_ms", "0")),
|
||||
"mc_gradient_ms": float(stage.get("mc_gradient_ms", "0")),
|
||||
"mc_skirt_ms": float(stage.get("mc_skirt_ms", "0")),
|
||||
})
|
||||
if len(tiles) != near_total:
|
||||
raise RuntimeError(f"incomplete near tile timeline in {log}: {len(tiles)}/{near_total}")
|
||||
by_lod = {}
|
||||
for lod in range(3):
|
||||
subset = [tile for tile in tiles if tile["lod"] == lod]
|
||||
services = [tile["service_ms"] for tile in subset]
|
||||
by_lod[lod] = {
|
||||
"tiles": len(subset), "p50_service_ms": statistics.median(services),
|
||||
"p90_service_ms": sorted(services)[int((len(services) - 1) * 0.90)],
|
||||
"p95_service_ms": sorted(services)[int((len(services) - 1) * 0.95)],
|
||||
"max_service_ms": max(services),
|
||||
}
|
||||
slow = sorted(tiles, key=lambda tile: tile["service_ms"], reverse=True)[:12]
|
||||
stages = [dict(SHAPE_FIELDS.findall(match.group(1))) for match in SHAPE_STAGE.finditer(output)]
|
||||
far_stages = [dict(FIELDS.findall(match.group(1))) for match in FAR_STAGE.finditer(output)]
|
||||
far_shape_seconds = (float(p["far_shape"].removesuffix("s"))
|
||||
if "far_shape" in p else
|
||||
(float(far_stages[-1]["total"]) / 1000.0 if far_stages else 0.0))
|
||||
row = {
|
||||
"version": version, "strate": strate, "name": NAMES[strate], "replicate": rep,
|
||||
"workers": 16, "exit_code": result.returncode,
|
||||
"wall_s": round(time.monotonic() - started, 3),
|
||||
"near_ready_s": float(near.group(3)), "pipeline_shape_s": float(p["shape"].removesuffix("s")),
|
||||
"pipeline_far_shape_s": far_shape_seconds,
|
||||
"shape_stages": stages, "far_shape_stages": far_stages,
|
||||
"tile_count": len(tiles), "by_lod": by_lod, "slow_tiles": slow,
|
||||
"source_stamp": source.group(1), "log": str(log.relative_to(ROOT)),
|
||||
}
|
||||
print("[L85SerialFloorRun] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
return row
|
||||
|
||||
|
||||
def main() -> int:
|
||||
for binary in BINARIES.values():
|
||||
if not binary.is_file():
|
||||
raise SystemExit(f"missing binary: {binary}")
|
||||
rows = []
|
||||
for rep in range(1, 4):
|
||||
order = (1, 2) if rep % 2 else (2, 1)
|
||||
for strate in order:
|
||||
rows.append(run(strate, "L85", rep))
|
||||
(OUT / "runs-partial.json").write_text(
|
||||
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
|
||||
summary = []
|
||||
for strate in (1, 2):
|
||||
for version in ("L85",):
|
||||
subset = [row for row in rows
|
||||
if row["strate"] == strate and row["version"] == version]
|
||||
item = {"strate": strate, "name": NAMES[strate], "version": version,
|
||||
"runs": [row["log"] for row in subset]}
|
||||
for metric in ("near_ready_s", "pipeline_shape_s", "pipeline_far_shape_s"):
|
||||
values = [row[metric] for row in subset]
|
||||
item[f"{metric}_runs"] = values
|
||||
item[f"{metric}_median"] = statistics.median(values)
|
||||
item[f"{metric}_min"] = min(values)
|
||||
item[f"{metric}_max"] = max(values)
|
||||
item["shape_stage_medians_ms"] = {}
|
||||
for stage, key in (("local_shapes", "local-shapes"),
|
||||
("intervals_signs", "intervals+signs"),
|
||||
("surface_band", "surface-band"),
|
||||
("far_parents", "far-parents")):
|
||||
values = [float(shape_row[key]) for row in subset
|
||||
for shape_row in row["shape_stages"] if key in shape_row]
|
||||
item["shape_stage_medians_ms"][stage] = (
|
||||
statistics.median(values) if values else None)
|
||||
slow_per_run = [tile for row in subset for tile in row["slow_tiles"]]
|
||||
item["slowest_tiles_all_runs"] = slow_per_run
|
||||
summary.append(item)
|
||||
(OUT / "summary.json").write_text(
|
||||
json.dumps({"runs": rows, "summary": summary}, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Interleaved L84/L85 W8 profiles for the FI/TN heavy-tile floor."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import statistics
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
OUT = ROOT / "Saved/Linux/l85/tile-profiles-final"
|
||||
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||
BINARIES = {
|
||||
"L84": ROOT / "Saved/Linux/l84/bin/VoxelForgeCoreHeadless-l84-clang19",
|
||||
"L85": ROOT / "Saved/Linux/l85/bin/VoxelForgeCoreHeadless-clang19",
|
||||
}
|
||||
STAMPS = {
|
||||
"L84": "c433a039b0255ce3c4794bf20313b8a5a11442feeb07b6555f585688949741a5",
|
||||
"L85": "2967bb8aaea5a7beac0cd0735c0c2e5cccd7b31a9622dd33395e81ff1e5f2589",
|
||||
}
|
||||
CAMERAS = {
|
||||
1: (25.8369140625, 3.19921875, -737.0),
|
||||
2: (19.166015625, -14.2001953125, -753.3740234375),
|
||||
}
|
||||
NAMES = {1: "FI", 2: "TN"}
|
||||
EXPECTED_NEAR = {1: 1216, 2: 1344}
|
||||
EXPECTED_WHOLE = {1: 2249, 2: 2233}
|
||||
STAMP = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=(\S+)$", re.M)
|
||||
NEAR = re.compile(r"^\[WorldAssemblerNearLODReady\] lods=0-2 tiles=(\d+)/(\d+) cold_ready_s=([0-9.]+)$", re.M)
|
||||
PIPELINE = re.compile(r"^\[StreamPipeline\] (.*)$", re.M)
|
||||
FIELDS = re.compile(r"([a-zA-Z0-9_]+)=([^ ]+)")
|
||||
TIMELINE = re.compile(
|
||||
r"^\[CpuTileTimeline\] event=finish key=\(([-0-9]+),([-0-9]+),([-0-9]+),lod([0-9]+)\) (.*)$", re.M)
|
||||
STAGES = re.compile(
|
||||
r"^\[CpuTileStages\] key=\(([-0-9]+),([-0-9]+),([-0-9]+),lod([0-9]+)\) (.*)$", re.M)
|
||||
|
||||
|
||||
def run(strate: int, version: str, rep: int) -> dict:
|
||||
out_dir = OUT / version.lower()
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
log = out_dir / f"{NAMES[strate].lower()}-w8-r{rep}.log"
|
||||
camera = ",".join(f"{v:.12f}" for v in CAMERAS[strate]) + ",0,0"
|
||||
command = [
|
||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||
"env", "VOXELFORGE_L81_PASSAGE_BOUND_PADDING_VOXELS=0", "timeout", "1740s",
|
||||
str(BINARIES[version]), str(DATA), "--world-assembler-prototype",
|
||||
"--stream-benchmark", "--stream-pipeline", "--workers=8", "--clip-radius=3",
|
||||
"--max-clip-level=8", "--max-level=8", f"--camera={camera}",
|
||||
"--profile-lod-work", "--instrument-tile-drain", "--stream-scheduler-policy=3",
|
||||
]
|
||||
started = time.monotonic()
|
||||
with log.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(command, cwd=ROOT, stdout=stream,
|
||||
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||
output = log.read_text(encoding="utf-8", errors="replace")
|
||||
source, near, pipeline = STAMP.search(output), NEAR.search(output), PIPELINE.search(output)
|
||||
if result.returncode or source is None or near is None or pipeline is None:
|
||||
raise RuntimeError(f"profile run failed: exit={result.returncode} {log}")
|
||||
if source.group(1) != STAMPS[version]:
|
||||
raise RuntimeError(f"source stamp mismatch in {log}: {source.group(1)}")
|
||||
if (int(near.group(1)), int(near.group(2))) != (EXPECTED_NEAR[strate], EXPECTED_NEAR[strate]):
|
||||
raise RuntimeError(f"incomplete LOD0-2 in {log}")
|
||||
fields = dict(FIELDS.findall(pipeline.group(1)))
|
||||
done, total = (int(value) for value in fields["whole_tiles"].split("/"))
|
||||
if (done, total) != (EXPECTED_WHOLE[strate], EXPECTED_WHOLE[strate]):
|
||||
raise RuntimeError(f"incomplete whole view in {log}: {done}/{total}")
|
||||
|
||||
stages = {}
|
||||
for match in STAGES.finditer(output):
|
||||
key = tuple(int(value) for value in match.group(1, 2, 3, 4))
|
||||
stages[key] = dict(FIELDS.findall(match.group(5)))
|
||||
tiles = []
|
||||
for match in TIMELINE.finditer(output):
|
||||
key = tuple(int(value) for value in match.group(1, 2, 3, 4))
|
||||
if key[3] > 2:
|
||||
continue
|
||||
stage = stages.get(key, {})
|
||||
tile = dict(FIELDS.findall(match.group(5)))
|
||||
tiles.append({
|
||||
"key": key,
|
||||
"service_ms": float(tile["service_ms"]),
|
||||
"samples": int(stage.get("samples", "0")),
|
||||
"mc_grid_ms": float(stage.get("mc_grid_ms", "0")),
|
||||
"mc_grid_workers": int(stage.get("mc_grid_workers", "1")),
|
||||
})
|
||||
if len(tiles) != EXPECTED_NEAR[strate]:
|
||||
raise RuntimeError(f"incomplete near tile profile in {log}: {len(tiles)}")
|
||||
full = [tile for tile in tiles if tile["samples"] == 42875]
|
||||
slowest = max(full, key=lambda tile: tile["service_ms"])
|
||||
target = next((tile for tile in full if tile["key"] == (0, 1, -93, 0)), None)
|
||||
if target is None:
|
||||
raise RuntimeError(f"target junction tile missing from profile: {log}")
|
||||
row = {
|
||||
"version": version, "strate": strate, "name": NAMES[strate], "replicate": rep,
|
||||
"workers": 8, "lod02_ready_s": float(near.group(3)),
|
||||
"whole_view_ready_s": float(fields["whole_view_ready"].removesuffix("s")),
|
||||
"shape_s": float(fields["shape"].removesuffix("s")),
|
||||
"far_shape_s": float(fields["far_shape"].removesuffix("s")),
|
||||
"full_grid_tile_count": len(full), "slowest_full_grid_tile": slowest,
|
||||
"target_tile": target, "source_stamp": source.group(1),
|
||||
"log": str(log.relative_to(ROOT)),
|
||||
"wall_s": round(time.monotonic() - started, 3),
|
||||
}
|
||||
print("[L85TileProfile] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
return row
|
||||
|
||||
|
||||
def main() -> int:
|
||||
rows = []
|
||||
for strate in (1, 2):
|
||||
for rep in range(1, 4):
|
||||
order = ("L84", "L85") if rep % 2 else ("L85", "L84")
|
||||
for version in order:
|
||||
rows.append(run(strate, version, rep))
|
||||
(OUT / "runs-partial.json").write_text(
|
||||
json.dumps(rows, indent=2) + "\n", encoding="utf-8")
|
||||
summary = []
|
||||
for strate in (1, 2):
|
||||
for version in ("L84", "L85"):
|
||||
subset = [row for row in rows
|
||||
if row["strate"] == strate and row["version"] == version]
|
||||
item = {"strate": strate, "name": NAMES[strate], "version": version,
|
||||
"runs": [row["log"] for row in subset]}
|
||||
for metric in ("lod02_ready_s", "whole_view_ready_s", "shape_s", "far_shape_s"):
|
||||
values = [row[metric] for row in subset]
|
||||
item[f"{metric}_runs"] = values
|
||||
item[f"{metric}_median"] = statistics.median(values)
|
||||
for metric in ("service_ms", "mc_grid_ms"):
|
||||
values = [row["slowest_full_grid_tile"][metric] for row in subset]
|
||||
item[f"slowest_full_grid_{metric}_runs"] = values
|
||||
item[f"slowest_full_grid_{metric}_median"] = statistics.median(values)
|
||||
item["slowest_full_grid_keys"] = [row["slowest_full_grid_tile"]["key"]
|
||||
for row in subset]
|
||||
item["slowest_full_grid_workers"] = [
|
||||
row["slowest_full_grid_tile"]["mc_grid_workers"] for row in subset]
|
||||
for metric in ("service_ms", "mc_grid_ms"):
|
||||
values = [row["target_tile"][metric] for row in subset]
|
||||
item[f"target_{metric}_runs"] = values
|
||||
item[f"target_{metric}_median"] = statistics.median(values)
|
||||
item["target_grid_workers"] = [row["target_tile"]["mc_grid_workers"]
|
||||
for row in subset]
|
||||
item["floor_s"] = item["shape_s_median"] + item["far_shape_s_median"] \
|
||||
+ item["slowest_full_grid_service_ms_median"] / 1000.0
|
||||
summary.append(item)
|
||||
(OUT / "summary.json").write_text(
|
||||
json.dumps({"runs": rows, "summary": summary}, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""One Unreal null-RHI streaming trace starting in the FI stratum."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[4]
|
||||
HOST = Path("/home/jahni/VFHost")
|
||||
EDITOR = Path("/home/jahni/UnrealEngine/Engine/Binaries/Linux/UnrealEditor-Cmd")
|
||||
OUT = ROOT / "Saved/Linux/l85/unreal-fi"
|
||||
HOST_PLUGIN_SAVED = HOST / "Plugins/VoxelForge/Saved/Linux/l85"
|
||||
HOST_SAVED = HOST / "Saved/Linux/l85"
|
||||
SPAWN_M = (25.8369140625, 3.19921875, -737.0)
|
||||
SAMPLE_SECONDS = 0.25
|
||||
MOVE_START_S = 6.0
|
||||
MOVE_DURATION_S = 12.0
|
||||
DWELL_S = 5.0
|
||||
EXIT_S = MOVE_START_S + MOVE_DURATION_S + DWELL_S
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not EDITOR.is_file():
|
||||
raise SystemExit(f"missing Unreal commandlet executable: {EDITOR}")
|
||||
label = "fi-arrival-l85"
|
||||
trace = OUT / f"{label}.trace.jsonl"
|
||||
abslog = OUT / f"{label}.log"
|
||||
stdout = OUT / f"{label}.stdout.log"
|
||||
output = HOST_PLUGIN_SAVED / "output" / label
|
||||
userdir = HOST_SAVED / "user" / label
|
||||
zendata = HOST_SAVED / "zen" / label
|
||||
ddc = HOST_SAVED / "DDC"
|
||||
for directory in (OUT, output, userdir, zendata, ddc):
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
spawn_cm = ",".join(f"{value * 100.0:.3f}" for value in SPAWN_M)
|
||||
command = [
|
||||
str(EDITOR), str(HOST / "VFHost.uproject"), "-nullrhi", "-unattended",
|
||||
"-nop4", "-nosplash", "-NoSound", "-corelimit=12", "-NoZenAutoLaunch",
|
||||
"-notraceserver", "-nocrashreports", "-DisablePlugins=AndroidFileServer",
|
||||
"-ddc=NoZenLocalFallback", f"-userdir={userdir}", f"-ZenDataPath={zendata}",
|
||||
f"-LocalDataCachePath={ddc}", "-run=VoxelForgeExplore", "-seed=0",
|
||||
"-archetype=TunnelNetwork", "-slot=1", "-modes=export", "-gameconfig=1",
|
||||
"-coreworldtest=1", "-ownerfourstrateplan=1", "-corevoxelsizecm=25",
|
||||
"-coreislandtest=0", "-corefallbacktest=0", "-coreofftest=0", "-coreplantest=0",
|
||||
"-settingsref=/Game/VoxelForge/DA_Settings.DA_Settings", "-corehybridmesher=0",
|
||||
f"-out={output}", "-voxel.StreamingCoarseFirst=1",
|
||||
"-voxel.StreamingDynamicNearReserve=1", "-voxel.StreamingLegacyPendingResultCap=0",
|
||||
"-voxel.CoreRegionConcurrentAdmission=1", "-voxel.StreamingResultBacklogCap=256",
|
||||
"-voxel.StreamingReadyQueueDepth=8", "-voxel.StreamingBackgroundWorkerCap=1",
|
||||
"-voxel.CoreColumnCacheDirectMapped=0", "-voxel.CoreMCGradientMemoization=1",
|
||||
"-voxel.StreamingCollisionLookahead=1", "-voxel.TestMove=1",
|
||||
"-voxel.TestMoveSpeedCmPerSecond=140.0",
|
||||
f"-voxel.TestMoveStartSeconds={MOVE_START_S:.1f}",
|
||||
f"-voxel.TestMoveDurationSeconds={MOVE_DURATION_S:.1f}",
|
||||
f"-voxel.TestExitSeconds={EXIT_S:.1f}", "-voxel.TestMoveDirection=1,0,0",
|
||||
"-voxel.StreamingSchedulerPolicy=3", "-voxel.TestExitOnSteady=0",
|
||||
"-coreworldstreamtrace=1", f"-voxel.StartupTraceSpawnCm={spawn_cm}",
|
||||
"-voxel.StartupTraceUntilEndPlay=1", "-voxel.StartupTraceThroughCrossing=1",
|
||||
"-voxel.StartupTraceMaxTiles=16384", f"-voxel.StartupTraceFile={trace}",
|
||||
f"-abslog={abslog}",
|
||||
]
|
||||
wrapped = ["taskset", "-c", "0-11", "timeout", "1740s", *command]
|
||||
started = time.monotonic()
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
with stdout.open("w", encoding="utf-8") as stream:
|
||||
result = subprocess.run(wrapped, cwd=ROOT, stdout=stream,
|
||||
stderr=subprocess.STDOUT, check=False, timeout=1740)
|
||||
events = []
|
||||
if trace.is_file():
|
||||
for line in trace.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||
if line.startswith("{"):
|
||||
try:
|
||||
events.append(json.loads(line))
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
samples = [event for event in events if event.get("type") == "streaming_sample"]
|
||||
arrival = [event for event in samples if float(event.get("t_s", 0.0)) < MOVE_START_S]
|
||||
full = []
|
||||
for event in arrival:
|
||||
desired = sum(event.get("desired_by_lod", [0] * 9)[:3])
|
||||
resident = sum(event.get("resident_by_lod", [0] * 9)[:3])
|
||||
if desired and resident >= desired:
|
||||
full.append(event)
|
||||
break
|
||||
row = {
|
||||
"label": label, "exit_code": result.returncode,
|
||||
"wall_s": round(time.monotonic() - started, 3),
|
||||
"sample_count": len(samples), "arrival_sample_count": len(arrival),
|
||||
"first_lod02_full_ready_s": full[0]["t_s"] if full else None,
|
||||
"first_lod02_full_desired": (sum(full[0].get("desired_by_lod", [0] * 9)[:3])
|
||||
if full else None),
|
||||
"first_lod02_full_resident": (sum(full[0].get("resident_by_lod", [0] * 9)[:3])
|
||||
if full else None),
|
||||
"trace": str(trace.relative_to(ROOT)), "abslog": str(abslog.relative_to(ROOT)),
|
||||
"stdout": str(stdout.relative_to(ROOT)),
|
||||
}
|
||||
failure = re.search(r"\[VoxelForgeCoreWorldTest\] failed: ([^\r\n]+)",
|
||||
stdout.read_text(encoding="utf-8", errors="replace"))
|
||||
if failure:
|
||||
row["world_test_failure"] = failure.group(1)
|
||||
(OUT / "summary.json").write_text(json.dumps(row, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
print("[L85UnrealFITrace] " + json.dumps(row, sort_keys=True), flush=True)
|
||||
if result.returncode or len(samples) < 40:
|
||||
raise SystemExit("Unreal FI trace did not complete successfully")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user