Skip to content

Round-2 integration: gameplay + flicker fixes land in main#5

Merged
proggeramlug merged 11 commits into
mainfrom
round2/integration
Jul 8, 2026
Merged

Round-2 integration: gameplay + flicker fixes land in main#5
proggeramlug merged 11 commits into
mainfrom
round2/integration

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Consolidates all round-2 shooter work into main: the open round-2 PRs (already merged into this integration branch) plus the 2026-07-06/07 lag + flicker + gameplay fixes that are not in any individual PR.

Round-2 PRs subsumed (all already merged here)

New fixes landed on top

  • SSGI stays enabled (engine amortized the camera-follow GI bakes that caused the fullscreen freezes).
  • Building gray-line flicker: antialiased the procedural mortar bands (fwidth distance-field) that aliased into ~11 crawling lines under half-res TSR; removed the coplanar building_floor.glb double-draw.
  • Gameplay: enemies follow terrain height (were walking into hills); player model faces the camera/aim direction (was 180 off).
  • Dormant PERFTEST instrumentation harness retained (PERFTEST=false) for future regressions.

Depends on engine round2/integration (PR Bloom-Engine/engine#83). Verified via native F12 swapchain screenshots.

Summary by CodeRabbit

  • New Features

    • Added a performance overlay shortcut showing per-pass CPU/GPU timing.
    • Introduced a new diagnostics mode for profiling and automated performance checks.
    • Expanded enemy wave variety and counts in one arena scenario.
  • Bug Fixes

    • Improved water, terrain, grass, tree, and building rendering for more stable motion, lighting, and reflections.
    • Reduced z-fighting and visual mismatches in building and water rendering.
    • Adjusted enemy movement so it follows terrain height more accurately.

Ralph Kuepper added 11 commits July 4, 2026 09:49
… shafts, title

Validated on the audit poses (region-luma vs the round-2 baselines):
- water at grazing: clean-water rect now 1.00x sky luma (was 1.16-1.23x —
  the river measured BRIGHTER than the sky); the fat white band at the
  waterline is gone
- backlit treeline: canopy/background 1.35 (audit) -> 1.26, and crowns
  now read as shaded volumes (lit tops, darker sides, saturated green)
  instead of flat pale-mint blobs; the remainder is sun-struck top facets

water.wgsl (audit F2):
- re-land the lost grazing calibration as min(schlick, 0.60)
- lift reflected rays off the horizon (r.y >= 0.08): the GGX-prefiltered
  horizon mip is a pale wash that does not exist in the real sky
- fade crest foam by view angle (measured ~5% of the milk stripe)
- screen UV from the fragment position, not a per-vertex perspective
  divide (wrong snapshot texel at grazing distances)

tree.wgsl (audit F3):
- crown self-occlusion on the sky fill (sides 35%, tops 100%) — env
  irradiance by normal alone gave side facets half the sky dome
- rim-gated, shadowed, cloud-modulated leaf transmission (a full-crown
  version brightens exactly the silhouettes the audit flagged)
- direct term carries sun_dir.w; leaf shadow floor deepens with distance

main.ts:
- fog heightRef 0 -> -0.6, density 0.024 -> 0.016: the reference sat at
  water level, so grazing views integrated the densest slab down the
  whole valley — that WAS the visible milk band (it spilled over banks)
- sun shafts 0.4/0.96 -> 0.18/0.90 (up to ~+0.14 HDR silhouette veil)
- title centering via measureText (exact in the current engine; the
  0.58 hand estimate was itself the off-centering)
- stop engaging the drifted inline water fallback (stale lighting AND a
  mismatched params layout); SH-005 codegen is the durable fix

Adds docs/audit-round2.md — the full round-2 audit with the evidence
index (tools/.testout/audit2/) these numbers come from.
…of truth

Round-2 audit F11 items — the gameplay-shaped findings:

Waves: with one kind per wave and BODIES_PER_KIND=2 pool slots, the
shipped game never showed more than 2 enemies at once — "19 enemies"
was only ever a total. build-world.ts now accepts a comma-separated
kind cycle per wave (an explicit spawn sequence when the list length
equals count), arena_02 authors 3 mixed waves (22 enemies, dragoons +
a tyrant finale that never appeared before), and MAX_CONCURRENT rises
4 -> 6 so the cap is the real limit again.

Camera: the orbit-shortening raycast now hits STATIC geometry only.
Enemy bodies (MOVING layer) used to shorten the orbit, pulling the
camera inside the mob whenever the player got surrounded — near-plane
clipping filled the screen with polygon soup (see the audit's combat
captures). Enemies briefly occluding the camera reads far better.

River: the world file authored six overlapping zig-zag water volumes
(with a 2 m gap) that the runtime silently ignored in favour of
hardcoded constants — two drifted sources of truth. The world file now
carries the one real 80x5 band and main.ts reads extents from it.

Boot-smoked 20 s at 4K on the round-2 engine lib; world generator
output verified (1 water volume, 3 waves / 22 enemies, sequence
matches the authored cycles).
Round-2 audit F8: every world material wrote velocity = (0,0), so TAA's
motion-adaptive clamp never engaged for terrain / building / trees /
grass — the primary mechanism behind TSR-0.5 shimmer on thin grass and
sway ghosting. With the engine half (Bloom-Engine/engine PR #82:
per-slot prev transforms + abi_motion_vector), the four world
materials now output real motion vectors:

- terrain.wgsl / building.wgsl: static geometry — previous clip =
  view.prev_view_proj x world (pure camera reprojection).
- tree.wgsl: wind sway re-evaluated at frame.time - frame.delta_time,
  projected by the previous VP — sway finally moves the velocity
  buffer instead of ghosting through TAA at velocity 0.
- grass_instanced.wgsl + the inline copy in main.ts (kept in sync,
  as the SH-005 note requires): per-blade sway one frame back.

All velocity writes go through the ABI's abi_motion_vector (the core
path's exact NDC-delta x 0.5 convention).

Requires the engine lib from PR #82 (older libs compile these shaders
fine — prev_view_proj and prev_mvp always existed in the ABI — but
prev_mvp was filled with the current mvp there, so object motion would
read zero; camera reprojection works either way).

Smoked at 4K on the live game: all four materials compile and render
clean with wind sway active — no smearing or warp, which a wrong
velocity sign/scale would show immediately under TAA.
- CLAUDE.md: Windows-specifics build section (bun world build,
  --debug-symbols, engine rebuild + relink/perry-cache rules,
  self-reporting crashes, batch-run note), five quirks incl. the
  EN-020 string rule, inline-grass sync warning, real main.ts size.
- perry-quirks.md: Impact section now describes the shipped
  build-time world generator (not the pre-generator hardcoded-world
  era); quirk-verification section updated; quirk #5 cross-refs.
- tickets.md: round-2 PR status header; SH-012 shipped (round-2 tree
  shading, evolved form); SH-022 shipped (planar-reflection water +
  round-2 recalibration); SH-021 drift warning now cites the real
  EN-022 incident.
- audit-round2.md: status addendum — PR roster, F1/EN-020 root-caused
  and fixed (toggle correlation was a red herring; per-frame string
  parsing was the trigger), decisions D1-D3 still open.
- engine-notes.md: all three prerequisites are landed in the engine
  repo; doc kept as the record of why.
- visual-quality.md: status header (tiers 1-4 + round-2 pass landed;
  numbers in tier tables are historical; where current truth lives).
- README.md: stale engine-patch prerequisite replaced, F3 profiler
  row added, Windows --debug-symbols pointer.
…anded)

Companion to engine 713e2a1 (fullscreen lag + flicker root causes).

- SSGI remains enabled: the Lumen SW-GI camera-follow bakes that stalled
  the GPU 1-2.4s per ~5s of mouse-look on the 760M are now amortized
  engine-side (binned + sliced clipmap bake). Measured here: rotating
  gameplay locked at 59-60fps with GI on, startup freeze gone.
- PERFTEST harness (dormant, PERFTEST=false): mode 0 staged feature
  bisect, mode 1 gameplay timeline with per-phase frame brackets
  (begin/A/B/C/D/E attribution; E=endDrawing catches GPU submit stalls),
  PERFSPIKE dumps, scripted enemy kills, camera-rotation load, optional
  swapchain screenshot schedule. This instrumentation found the clipmap
  stall, the stale shadow VPs, the AE hunting cycle, the EN-022 jitter
  velocity, and the GTAO phase pulse -- keep it for future regressions.
- Notes-to-future-self in comments: injected keys never reach
  isAnyInputPressed (edge consumed by the pump) -- flip gameState
  directly; F3 profiler overlay costs ~5.5ms/frame at 4K, do not judge
  perf with it open.
- Enemies were steered in XZ only and kept their spawn Y forever —
  on any slope they walked INTO the hill and attacked the player from
  inside the ground. New terrainHeightAt() bilinearly samples the same
  heightfield grid the Jolt collider uses; spawn and the per-frame AI
  step snap enY to it.
- Player model yaw offset was -pi/2, which faced the character INTO
  the camera (180 degrees off the aim direction). Verified empirically
  with windowed screenshots at yaws 0.00/0.31/0.78/4.29: +pi/2 lines
  the bsuit front up with camera forward at every heading.
…hell

THE building flicker (user-visible gray lines/areas): the category-1
skip in the static-mesh loop only covered MODEL_IS_BOX placeholders, so
the real building_floor.glb fell through to drawModel — coplanar with
the procedural matBuilding shell. The two surfaces z-fought and the
GLB window-slat texture rows flickered through the plaster whenever
TAA jitter flipped the per-pixel depth winner. Root-caused from native
F12 screenshots (4x zoom showed the slat rows, not shadow acne or text).
The skip now covers every branch of the loop.
…ker)

Root cause of the reported building flicker, found via native F12
swapchain screenshots: the mortar bands were pow(max(cos(y*f),0),
band.w*8) — a razor-sharp procedural pulse with zero filtering.
Re-evaluated each frame at TAA-jittered positions on the half-res
buffer, its hard edges produced an aliasing beat pattern (~11 crawling
lines out of 2-3 real bands) that flickered across the whole face,
worst up close. Replaced with a distance-field band antialiased over
one pixel via fwidth, with a sub-pixel coverage fade. The face is now
stable plaster with a single clean band; verified by consecutive-frame
diff (face went from bright-lines-everywhere to near-zero).
@proggeramlug proggeramlug merged commit 7d8cd05 into main Jul 8, 2026
1 check was pending
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bed3a67e-e6ef-466d-9e8f-fcfc21ef8ecf

📥 Commits

Reviewing files that changed from the base of the PR and between af98dbe and cad3dc9.

⛔ Files ignored due to path filters (1)
  • src/generated/world.ts is excluded by !**/generated/**
📒 Files selected for processing (16)
  • .gitignore
  • CLAUDE.md
  • README.md
  • assets/materials/building.wgsl
  • assets/materials/grass_instanced.wgsl
  • assets/materials/terrain.wgsl
  • assets/materials/tree.wgsl
  • assets/materials/water.wgsl
  • assets/worlds/arena_02.world.json
  • docs/audit-round2.md
  • docs/engine-notes.md
  • docs/perry-quirks.md
  • docs/tickets.md
  • docs/visual-quality.md
  • src/main.ts
  • tools/build-world.ts

📝 Walkthrough

Walkthrough

This PR adds real motion vectors to building, grass, terrain, and tree materials, recalibrates water reflection/foam and tree lighting, simplifies world water config and multi-enemy wave spawning, adds terrain-following enemy movement, camera/model fixes, a PERFTEST harness, and expands documentation on Perry FFI quirks and build workflow.

Changes

Motion vectors and material shading updates

Layer / File(s) Summary
Motion vectors added to shaders
assets/materials/building.wgsl, assets/materials/grass_instanced.wgsl, assets/materials/terrain.wgsl, assets/materials/tree.wgsl, src/main.ts
VsOut structs gain curr_clip/prev_clip, vertex shaders compute previous-frame positions, and fragment shaders output real velocity via abi_motion_vector; the inline grass shader copy in src/main.ts is kept in sync.
Building mortar, tree lighting, water reflection tuning
assets/materials/building.wgsl, assets/materials/tree.wgsl, assets/materials/water.wgsl
Mortar banding switches to an antialiased distance-field, tree lighting adds backlit transmission and crown AO, and water removes the screen_uv varying, recomputing it per-fragment while capping Fresnel and adding grazing foam fade.

Gameplay, world data, and main.ts systems

Layer / File(s) Summary
Fog/sun-shaft tuning and water wiring
src/main.ts
Fog and sun-shaft parameters are retuned, SSGI stays enabled with amortization notes, water material always uses compiled file output, and river geometry reads from generated world data.
World spawn/water config and generator
assets/worlds/arena_02.world.json, tools/build-world.ts
Wave configs gain larger multi-enemy rosters, water is simplified to a single river box, and the generator parses comma-separated enemy lists cyclically.
Terrain-following enemies, camera, yaw, title centering
src/main.ts
Adds terrainHeightAt for enemy Y positioning, raises concurrency cap, restricts camera collision mask, flips player model yaw sign, skips building meshes during static draw, and uses measureText for title centering.
PERFTEST diagnostic harness
src/main.ts
Adds stage-based feature bisect, automated gameplay driving with deterministic kills/screenshots, and per-phase timing capture around the draw loop.

Documentation and repo hygiene updates

Layer / File(s) Summary
Docs, tickets, audit report, gitignore
.gitignore, CLAUDE.md, README.md, docs/*
Adds ignore patterns, Windows dev workflow and FFI numeric-crossing rules, a new audit-round2.md report, and status updates to engine-notes, tickets, and visual-quality docs.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Vertex as VertexShader
  participant Frame as FrameUniforms
  participant View as ViewUniforms
  participant Fragment as FragmentShader
  Vertex->>Frame: read frame.time - frame.delta_time
  Vertex->>Vertex: recompute previous wind sway / world position
  Vertex->>View: project prev position with view.prev_view_proj
  Vertex->>Fragment: pass curr_clip, prev_clip
  Fragment->>Fragment: velocity = abi_motion_vector(curr_clip, prev_clip)
Loading
sequenceDiagram
  participant MainLoop
  participant TerrainHeightAt
  participant EnemyState
  participant Camera
  MainLoop->>TerrainHeightAt: query terrainHeightAt(enX, enZ)
  TerrainHeightAt-->>EnemyState: bilinear-sampled Y
  EnemyState-->>MainLoop: updated enY per tick
  MainLoop->>Camera: raycast with Layer.NON_MOVING mask
  Camera-->>MainLoop: orbit distance excluding enemies
Loading
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch round2/integration

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant