Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .agents/skills/test-writer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ modules/
world-core/ # chunk, block, block_registry, coordinates, lighting
world-worldgen/ # terrain generation, biomes, caves, decorations
world-meshing/ # chunk_storage, chunk_mesh, meshing/, GPU block buffers
world-lod/ # LOD chunks, meshes, scheduler, renderer, manager
world-runtime/ # world facade, streaming, renderer, mutation, GPU mesher
world-persistence/# level data, region files, save manager
src/
Expand Down
83 changes: 7 additions & 76 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ on:
description: Benchmark duration per preset (seconds)
required: false
default: "60"
capture_gpu_culling:
description: Capture the bounded gpu-culling-scale extreme/traversal CPU-vs-GPU source pair
required: false
type: boolean
default: false

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand Down Expand Up @@ -104,7 +99,7 @@ jobs:
name: Benchmark
timeout: ${{ github.event_name == 'schedule' && '55m' || github.event_name == 'workflow_dispatch' && '55m' || '20m' }}
log-file: benchmark.log
command: mkdir -p "$MESA_SHADER_CACHE_DIR" && devenv shell --profile graphics -- bash scripts/run_benchmark.sh --duration "$BENCHMARK_DURATION" --presets low,medium,high --scenarios stationary,traversal,rapid-turn,teleport-eviction --compact-modes off,auto --benchmark-world overworld --output-dir benchmark-results --per-preset-timeout 600
command: mkdir -p "$MESA_SHADER_CACHE_DIR" && devenv shell --profile graphics -- bash scripts/run_benchmark.sh --duration "$BENCHMARK_DURATION" --presets low,medium,high --scenarios stationary,traversal,rapid-turn,teleport-eviction --benchmark-world overworld --output-dir benchmark-results --per-preset-timeout 600
env:
XDG_RUNTIME_DIR: /tmp/runtime-runner
WAYLAND_DISPLAY: headless
Expand All @@ -114,58 +109,13 @@ jobs:
# PR/push runs are bounded canaries. Scheduled and manually requested
# captures are longer acceptance evidence, not synthetic fixtures.
BENCHMARK_DURATION: ${{ github.event_name == 'schedule' && '60' || github.event_name == 'workflow_dispatch' && github.event.inputs.duration || '5' }}
ZIGCRAFT_BENCHMARK_GPU_ADAPTER: Lavapipe (Mesa software Vulkan)
ZIGCRAFT_BENCHMARK_GPU_DRIVER: Mesa Lavapipe supplied by the pinned devenv profile
ZIGCRAFT_BENCHMARK_RUNNER: blacksmith-2vcpu-ubuntu-2404 GitHub Actions runner
ZIGCRAFT_BENCHMARK_ZIG_TOOLCHAIN: Zig 0.16.0 supplied by the pinned devenv inputs

- name: Run Phase 5 long-session streaming stress gate
if: steps.gate.outputs.run == 'true'
uses: ./.github/actions/run-with-log
with:
name: Phase 5 Streaming Stress
timeout: 15m
log-file: phase5-streaming-stress.log
command: devenv shell --profile unit -- zig build phase5-stress-gate -Dphase5-stress-iterations="$PHASE5_STRESS_ITERATIONS"
env:
# The scheduled run is longer by operation count, never by a flaky
# elapsed-time threshold. PR feedback stays bounded.
PHASE5_STRESS_ITERATIONS: ${{ github.event_name == 'schedule' && '768' || '64' }}

- name: Validate compact benchmark evidence matrix
- name: Validate benchmark results
if: steps.gate.outputs.run == 'true'
run: python3 scripts/benchmark_baseline.py validate-compact-matrix benchmark-results

- name: Capture dedicated CPU-vs-GPU culling sources
# Scheduled captures always retain a fresh pair. Manual runs opt in so
# ordinary canaries stay fast; each source is independently bounded.
if: steps.gate.outputs.run == 'true' && (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.capture_gpu_culling == 'true'))
uses: ./.github/actions/run-with-log
with:
name: GPU Culling Baseline Sources
timeout: 32m
log-file: gpu-culling-benchmark.log
command: mkdir -p "$MESA_SHADER_CACHE_DIR" && devenv shell --profile graphics -- bash -c 'scripts/run_benchmark.sh --duration 60 --presets extreme --scenarios traversal --compact-modes auto --gpu-culling off --gpu-culling-threshold 128 --benchmark-fixture gpu-culling-scale --benchmark-horizon-distance 4096 --benchmark-lod-memory-budget-mb 2048 --benchmark-require-gpu-candidates 1024 --benchmark-world flat --output-dir gpu-culling-results/cpu --per-preset-timeout 900 && scripts/run_benchmark.sh --duration 60 --presets extreme --scenarios traversal --compact-modes auto --gpu-culling on --gpu-culling-threshold 128 --benchmark-fixture gpu-culling-scale --benchmark-horizon-distance 4096 --benchmark-lod-memory-budget-mb 2048 --benchmark-require-gpu-candidates 1024 --benchmark-world flat --output-dir gpu-culling-results/gpu --per-preset-timeout 900'
env:
XDG_RUNTIME_DIR: /tmp/runtime-runner
WAYLAND_DISPLAY: headless
MESA_SHADER_CACHE_DIR: /tmp/mesa_shader_cache
SDL_AUDIODRIVER: dummy
ZIGCRAFT_SAFE_MODE: "1"
ZIGCRAFT_BENCHMARK_GPU_ADAPTER: Lavapipe (Mesa software Vulkan)
ZIGCRAFT_BENCHMARK_GPU_DRIVER: Mesa Lavapipe supplied by the pinned devenv profile
ZIGCRAFT_BENCHMARK_RUNNER: blacksmith-2vcpu-ubuntu-2404 GitHub Actions runner
ZIGCRAFT_BENCHMARK_ZIG_TOOLCHAIN: Zig 0.16.0 supplied by the pinned devenv inputs

- name: Validate fresh CPU-vs-GPU culling pair
if: steps.gate.outputs.run == 'true' && (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.capture_gpu_culling == 'true'))
run: |
python3 scripts/benchmark_baseline.py assemble-gpu-culling --overwrite \
--output gpu-culling-results/gpu-culling-baseline.json \
gpu-culling-results/cpu/auto/extreme/traversal.json \
gpu-culling-results/gpu/auto/extreme/traversal.json
python3 scripts/benchmark_baseline.py validate-gpu-culling \
gpu-culling-results/gpu-culling-baseline.json
for result in benchmark-results/*/*.json; do
bash scripts/validate_benchmark_artifact.sh --result "$result"
done

- name: Stop headless Wayland compositor
if: always() && steps.gate.outputs.run == 'true'
Expand All @@ -179,20 +129,6 @@ jobs:
exit 1
fi

- name: Compare against baseline
# A 5-second canary must not be numerically compared with a 60-second
# acceptance capture. The compatibility command also enforces this.
if: steps.gate.outputs.run == 'true' && github.event_name != 'schedule' && github.event_name != 'workflow_dispatch'
id: compare
run: |
if ! python3 scripts/benchmark_baseline.py compatibility docs/benchmarks/baseline.json benchmark-results/auto/low/stationary.json --preset low --scenario stationary; then
printf 'Skipping numeric baseline comparison: checked reference hardware/provenance differs from this CI runner. Matrix, SLO, validation, and Bencher gates still apply.\n'
exit 0
fi
for preset in low medium high; do for scenario in stationary traversal rapid-turn teleport-eviction; do
bash scripts/compare_benchmarks.sh docs/benchmarks/baseline.json "benchmark-results/auto/${preset}/${scenario}.json" --preset "$preset" --scenario "$scenario"
done; done

- name: Install Bencher CLI
if: steps.gate.outputs.run == 'true' && env.BENCHER_API_TOKEN != '' && env.BENCHER_PROJECT != ''
uses: bencherdev/bencher@30a740a2e4246560b1a5fd424057d84aa2b188d6
Expand All @@ -210,7 +146,7 @@ jobs:
--token "$BENCHER_API_TOKEN" \
--adapter json \
--err \
"cat benchmark-results/auto/${preset}/${scenario}.json"
"cat benchmark-results/${preset}/${scenario}.json"
done; done

- name: Comment Bencher dashboard
Expand All @@ -232,10 +168,7 @@ jobs:
name: benchmark-results
path: |
benchmark-results
gpu-culling-results
benchmark.log
gpu-culling-benchmark.log
phase5-streaming-stress.log
weston.log
retention-days: 30

Expand All @@ -244,12 +177,10 @@ jobs:
uses: actions/github-script@v9
env:
BENCHMARK_RUN_OUTCOME: ${{ steps.run_benchmark.outcome }}
BENCHMARK_COMPARE_OUTCOME: ${{ steps.compare.outcome }}
with:
script: |
const runOutcome = process.env.BENCHMARK_RUN_OUTCOME;
const compareOutcome = process.env.BENCHMARK_COMPARE_OUTCOME;
const failed = runOutcome === 'failure' || compareOutcome === 'failure';
const failed = runOutcome === 'failure';
const description = failed
? 'Benchmark regression or runtime failure'
: 'Benchmark completed';
Expand Down
38 changes: 3 additions & 35 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,6 @@ jobs:
log-file: unit-test-${{ matrix.optimize }}.log
command: devenv shell --profile unit -- zig build -Doptimize=${{ matrix.optimize }} test

- name: Run Phase 5 compact LOD gate
if: needs.changes.outputs.code_changes == 'true' && matrix.optimize == 'Debug'
run: devenv shell --profile unit -- zig build phase5-gate

- name: Run bounded Phase 5 streaming stress gate
if: needs.changes.outputs.code_changes == 'true' && matrix.optimize == 'Debug'
run: devenv shell --profile unit -- zig build phase5-stress-gate -Dphase5-stress-iterations=64

- name: Upload unit test log
if: failure()
uses: actions/upload-artifact@v7
Expand Down Expand Up @@ -291,38 +283,15 @@ jobs:
ZIGCRAFT_SMOKE_FRAMES: "3"
ZIGCRAFT_SAFE_MODE: "1"

- name: Run Phase 5 production visual-motion and saved-world gate
if: needs.changes.outputs.code_changes == 'true'
uses: ./.github/actions/run-with-log
with:
name: Phase 5 Visual Motion and Saved-World Reload Gate
timeout: 35m
log-file: phase5-visual-gate.log
command: devenv shell --profile graphics -- zig build phase5-visual-gate
env:
XDG_RUNTIME_DIR: /tmp/runtime-runner
WAYLAND_DISPLAY: headless

- name: Upload Phase 5 visual evidence
if: always() && needs.changes.outputs.code_changes == 'true'
uses: actions/upload-artifact@v7
with:
name: phase5-visual-evidence
path: |
zig-out/phase5-visual-smoke/
phase5-visual-gate.log
if-no-files-found: ignore
retention-days: 14

- name: Fail on Vulkan validation log errors
if: needs.changes.outputs.code_changes == 'true'
run: |
set -euo pipefail
if rg -n -e 'Vulkan validation error|Validation Error:|VUID-' integration-test.log world-smoke-test.log phase5-visual-gate.log; then
echo "Vulkan validation errors were found in integration or Phase 5 visual logs." >&2
if rg -n -e 'Vulkan validation error|Validation Error:|VUID-' integration-test.log world-smoke-test.log; then
echo "Vulkan validation errors were found in integration logs." >&2
exit 1
fi
echo "No Vulkan validation errors found in integration or Phase 5 visual logs."
echo "No Vulkan validation errors found in integration logs."

- name: Stop headless Wayland compositor
if: always() && needs.changes.outputs.code_changes == 'true'
Expand All @@ -336,7 +305,6 @@ jobs:
path: |
integration-test.log
world-smoke-test.log
phase5-visual-gate.log
weston.log
if-no-files-found: ignore
retention-days: 7
Expand Down
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ zigcraft-minidumps/
*.spv
!assets/shaders/vulkan/lpv_inject.comp.spv
!assets/shaders/vulkan/lpv_propagate.comp.spv
!assets/shaders/vulkan/lod_compact_terrain.frag.spv
!assets/shaders/vulkan/lod_compact_water.frag.spv
!assets/shaders/vulkan/water.frag.spv
!assets/shaders/vulkan/water.vert.spv
wiki/
Expand Down
13 changes: 5 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
devenv shell zig fmt src/ modules/
devenv shell zig fmt --check src/ modules/
```
- `zig build test` is the broad suite: aggregate/module tests, fuzz roots, shader compilation/validation, SPIR-V size checks, shadow ABI checks, and Phase 5 policy tests.
- `zig build test` is the broad suite: aggregate/module tests, fuzz roots, shader compilation/validation, SPIR-V size checks, and shadow ABI checks.
```bash
devenv shell zig build test
devenv shell zig build test -Dtest-filter="name"
Expand All @@ -25,11 +25,9 @@
```bash
devenv shell zig build test-integration -Dskip-present=true
devenv shell zig build test-robustness
devenv shell zig build phase5-gate
devenv shell zig build phase5-visual-gate
```
- `-Dskip-present=true` only suppresses presentation; SDL/Vulkan initialization still needs a display/compositor and driver. Use the repo skills `headless-crash-test`, `headless-screenshot`, `headless-benchmark`, or `headless-graphics-verification`, and always bound game/graphics commands with a timeout.
- For deterministic startup checks, combine `-Dskip-present`, `-Dauto-world=<normal|overworld|overworld-v2|flat|test>`, and `-Dstartup-diagnostic-seconds=N`. `-Dchunk-debug-mode` disables LOD, water, caves, and decorations; selectively restore `lod,water,watergen,waterrender,caves,decorations` with `-Dchunk-debug-enable=`.
- For deterministic startup checks, combine `-Dskip-present`, `-Dauto-world=<normal|overworld|overworld-v2|flat|test>`, and `-Dstartup-diagnostic-seconds=N`. `-Dchunk-debug-mode` disables water, caves, and decorations; selectively restore `water,watergen,waterrender,caves,decorations` with `-Dchunk-debug-enable=`.

## Benchmarks and generated artifacts

Expand All @@ -40,28 +38,27 @@
-Dbenchmark-duration=60 -Dbenchmark-output=zig-out/benchmark-low.json
```
Scenarios are `stationary`, `traversal`, `rapid-turn`, and `teleport-eviction`. Prefer the `headless-benchmark` skill for bounded runs.
- Focused CPU-only tools: `devenv shell zig build worldgen-report` and `devenv shell zig build lod-bench`. Pass climate snapshot arguments after `--`, e.g. `devenv shell zig build worldgen-climate-snapshot -- --seed 42 ...`.
- Focused CPU-only tools: `devenv shell zig build worldgen-report` and `devenv shell zig build worldgen-climate-snapshot`. Pass climate snapshot arguments after `--`, e.g. `devenv shell zig build worldgen-climate-snapshot -- --seed 42 ...`.
- Building/tests compile GLSL and write tracked `*.spv` files beside sources in `assets/shaders/vulkan/`. After intentional shader-size changes, run `./scripts/update_spirv_baseline.sh`; `docs/shaders/spirv-sizes.json` and shadow runtime SPIR-V parity are test-enforced.
- New/changed textures go through `./scripts/process_textures.sh <pack> 512`; preserve licensing/attribution for placeholder assets.

## Architecture boundaries

- `src/main.zig` and `src/game/app.zig` wire the executable. Reusable session/player/settings/benchmark logic belongs in `modules/game-core`; screens belong in `modules/game-ui`.
- `engine-rhi` owns rendering contracts and opaque handles; `engine-graphics` owns the Vulkan backend, render passes, resources, and pipelines. Vulkan is currently the only backend, so interface changes usually require both the RHI vtable/contracts and Vulkan implementation.
- `world-core` owns shared block/chunk/coordinate types; world generation is split across `world-worldgen-*` packages; `world-meshing` owns chunk mesh/storage; `world-lod` owns distant terrain; `world-runtime` coordinates streaming, lighting, GPU meshing, mutation, and rendering; `world-persistence` owns saves/regions.
- `world-core` owns shared block/chunk/coordinate types; world generation is split across `world-worldgen-*` packages; `world-meshing` owns chunk mesh/storage; `world-runtime` coordinates streaming, lighting, GPU meshing, mutation, and rendering; `world-persistence` owns saves/regions.
- Never call RHI/SDL from worker jobs. Pin chunks while background jobs hold them and synchronize shared mesh/chunk state with the project mutex abstraction.
- Convert global coordinates with `worldToChunk`/`worldToLocal` from `world-core`; chunk coordinates are floor-divided, so ad-hoc truncating division is wrong for negative positions.
- GPU-shared structs and shader blocks are ABI contracts. Use explicit layouts (`extern`/packed as appropriate), assert sizes/offsets, and update CPU, GLSL, descriptors, and backend bindings together.

## Graphics hazards

- `ZIGCRAFT_LOD_COMPACT=auto` is qualified only through immutable per-layer descriptors plus expanded fallback for unsupported shoreline topology; `off` remains the explicit supported fallback. `force` is diagnostic only. Do not weaken the saved-world RADV reload, validation, robustness, and Phase 5 qualification requirements based only on generated-world captures.
- Dedicated transfer queues cannot upload exclusive graphics buffers safely with semaphore synchronization alone; queue-family release/acquire ownership transfers are required. Until implemented, keep these uploads on the graphics family.
- A descriptor set update affects all previously recorded commands that bind that set when execution happens. Terrain/water or direct/indirect streams that need different buffers require immutable/per-layer descriptor sets, not sequential updates to one set.

## Verification and CI

- Match verification to the change, but graphics/RHI/shader/runtime work normally requires: format check, `zig build test`, ReleaseFast build, integration/robustness as relevant, and the appropriate headless graphics skill. CI additionally runs Debug and ReleaseSafe tests, `phase5-gate`, Lavapipe/Weston integration smoke tests, and `phase5-visual-gate`.
- Match verification to the change, but graphics/RHI/shader/runtime work normally requires: format check, `zig build test`, ReleaseFast build, integration/robustness as relevant, and the appropriate headless graphics skill. CI additionally runs Debug and ReleaseSafe tests and Lavapipe/Weston integration smoke tests.
- Install the repo pre-push hook with `./scripts/setup-hooks.sh`, but do not treat it as CI parity: it formats only `src/` and omits ReleaseSafe and graphics jobs.

## Git workflow
Expand Down
Loading
Loading