Skip to content

rounds 5-7: real tree textures, terrain detail, audio pass + F12 capture tool#8

Merged
proggeramlug merged 2 commits into
mainfrom
round5-8/integration
Jul 11, 2026
Merged

rounds 5-7: real tree textures, terrain detail, audio pass + F12 capture tool#8
proggeramlug merged 2 commits into
mainfrom
round5-8/integration

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Lands the rounds 5–7 content pass and the new batch-capture tool. The game-side counterpart of Bloom-Engine/engine#86 — the two headline fixes from that PR (scene-wide missing shadows; the wave-2 "distorted dragon") are engine-side, so this PR carries no gameplay-code fixes for them, only the content that was tuned against that engine state.

Visuals (rounds 5–6)

  • Trees rebuilt on external CC0 packs (documented in assets/textures/external/SOURCES.md): ShareTextures Platanus 2K / Robinia 4K branch atlases, ambientCG LeafSet004 scanned-leaf compositor fallback, Poly Haven pine bark embedded as JPEG. Three species variants; fallback chain atlas → compositor → procedural keeps fresh checkouts building without the packs.
  • tools/png.ts: full 8-bit PNG decoder (color types 0/2/3/4/6) powering the compositors; tools/build-props.ts gains the atlas + compositor paths.
  • Terrain: macro moisture patches, riverbed mud blend gated on the carved channel + waterline; grass root→tip gradient; rebuilt house.

Audio (round 7)

  • Weapon-specific fire, positional alien vocals per kind (attack/pain/3 deaths — Unvanquished's own GPL assets, mapping in assets/sounds/SOURCES.md), hit confirms, ricochets, player pain/death, wading splashes; listener follows the camera.

Tools

  • tools/f12-shot.ps1: takeScreenshot is broken on Windows and geisterhand's key endpoint ignores F-keys — posting VK_F12 to the game window triggers the engine's own screenshot handler for true 4K captures in batch runs.

Verification

Batch F12 captures at title + gameplay (all five alien kinds forced into wave 1 via a temporary, reverted hack): posed shadows under player and every alien, tree/building shadows across the terrain, marauder/tyrant/dragoon correctly shaped and scaled. Run against the engine branch in Bloom-Engine/engine#86 — merge that first (or rebuild the engine lib from it) before relinking.

Summary by CodeRabbit

  • New Features

    • Added richer positional audio, including weapon fire, impacts, ricochets, splashes, and character reactions.
    • Added planar water reflections, improved shoreline effects, and more natural splash and wake visuals.
    • Enhanced terrain with rivers, wet and dry variation, riverbeds, and pebble detail.
    • Added varied, clumped grass and multiple tree variants with improved textures.
    • Added automated screenshot capture tooling.
  • Documentation

    • Documented audio and external texture sources and licensing.
  • Bug Fixes

    • Improved water depth and reflection behavior.
    • Corrected enemy model orientation and scaling.

Ralph Kuepper added 2 commits July 11, 2026 05:07
Visuals (rounds 5-6):
- Trees rebuilt around external CC0 texture packs (SOURCES.md in
  assets/textures/external): ShareTextures Platanus 2K / Robinia 4K
  branch atlases stamped whole via atlasCard, ambientCG LeafSet004
  scanned-leaf compositor as fallback, Poly Haven pine bark embedded
  as JPEG. Three species variants (prop_tree, prop_tree2, prop_tree3);
  fallback chain atlas -> leaf compositor -> procedural keeps fresh
  checkouts building without the packs.
- tools/png.ts grows a full 8-bit PNG decoder (color types 0/2/3/4/6)
  for the compositors; build-props gains the atlas + compositor paths.
- Terrain: macro moisture patches (~45 m), riverbed mud blend gated on
  the carved channel + waterline, straw/lush sweeps; grass blades get
  the squared root->tip gradient; house.glb rebuilt.

Audio (round 7):
- Weapon-specific fire sounds, positional alien vocals (attack/pain/
  three death variants per kind, from Unvanquished's own GPL assets -
  see assets/sounds/SOURCES.md), flesh-impact hit confirm, ricochets,
  player pain/death, wading splashes on a cooldown.
- Audio listener follows the camera every frame so playSound3D pans
  and attenuates correctly.

Requires the engine round8 branch (Bloom-Engine/engine#86) for the
material per-view wiring, multi-primitive skinning, cached rotated
draws, and per-cascade shadow uniforms these assets were tuned against.
takeScreenshot is broken on Windows and geisterhand /key ignores
F-keys; posting VK_F12 WM_KEYDOWN/UP to the game window triggers the
engine screenshot handler and yields true 4K captures in redirected
batch runs. Snapshots the existing screenshot set first and reports
only the new file - never deletes.
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR updates terrain, water, and grass shaders; expands procedural asset generation; adds tree variants and external texture support; revises forest rendering; introduces positional audio and river testing; and adds asset provenance and screenshot tooling.

Changes

Rendering, assets, and gameplay feedback

Layer / File(s) Summary
Shader surface and water effects
assets/materials/grass_instanced.wgsl, assets/materials/terrain.wgsl, assets/materials/water.wgsl
Grass uses a stronger root-to-tip gradient, terrain adds moisture and riverbed variation, and water updates impulse sampling, depth linearization, reflections, Fresnel composition, glints, wake brightening, and alpha output.
Procedural textures and generated models
tools/png.ts, tools/build-props.ts, assets/models/house.glb, assets/textures/external/SOURCES.md
PNG decoding and external texture ingestion support new leaf and bark inputs, procedural fallbacks, parameterized tree variants, JPEG MIME detection, and multiple generated tree GLBs.
Engine rendering and material integration
src/main.ts
Material parameters, planar reflection probes, grass geometry and placement, forest caching, tree scaling, GI proxies, and animation yaw handling are revised.
Positional audio and gameplay feedback
src/main.ts
Weapon, enemy, impact, ricochet, splash, and player sounds use expanded variants and positional playback; river feedback is cooldown-gated and a scripted water test harness is added.
Asset provenance and capture tooling
assets/sounds/SOURCES.md, tools/f12-shot.ps1
Sound provenance and conversion details are documented, and a script automates game launch, F12 capture, process termination, and screenshot selection.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Player
  participant GameLoop
  participant AudioSystem
  participant WaterSystem
  participant WaterShader
  participant ReflectionProbe
  Player->>GameLoop: Move, fire, or hit an enemy
  GameLoop->>AudioSystem: Play positional weapon, impact, enemy, or splash sound
  GameLoop->>WaterSystem: Submit cooldown-gated river impulse
  WaterSystem->>WaterShader: Provide impulse and water parameters
  ReflectionProbe-->>WaterShader: Provide planar reflection
  WaterShader->>WaterShader: Compose refraction, absorption, Fresnel, and reflection
Loading

Possibly related PRs

  • Bloom-Engine/shooter#2: Updates the same water shader’s reflection, refraction, Fresnel, foam, and shoreline behavior.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: rounds 5-7 content, tree textures, terrain/audio updates, and the F12 capture tool.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 round5-8/integration

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tools/png.ts (1)

84-169: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

decodePng adds throw new Error(...) — confirm the no-throw rule doesn't extend to build tooling.

The coding guideline for {src/**/*.ts,tools/**/*.ts} says to return { ok, error } rather than throwing. decodePng introduces four new throw sites (invalid signature, unsupported bit depth/interlace/colour type). These are build-time Bun tools (not Perry FFI runtime), and encodePng/resolveTexture already throw here, so this may be intentionally out of scope — but the path pattern matches, so please confirm.

Decoder logic itself (signature, chunk walk, inflate, Sub/Up/Avg/Paeth unfilter, palette/tRNS handling) is correct.

As per coding guidelines: "Return { ok: boolean, error: string } instead of throwing exceptions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/png.ts` around lines 84 - 169, Confirm the no-throw guideline for
tools/**/*.ts and align decodePng with the project’s error-handling convention:
replace its four exception paths for invalid PNGs and unsupported formats with a
typed { ok, error } result, then update callers to check and propagate failures;
if build tooling is explicitly exempt, document or verify that exception and
leave decodePng consistent with encodePng and resolveTexture.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main.ts`:
- Around line 2162-2176: Update the pain-bark condition in the enemy-hit
handling block to use the shot counter rather than the fixed enemy slot: replace
the `(i & 3) === 0` gate with an equivalent check based on `shotsFired`,
preserving the existing non-lethal-hit logic and pain sound call.

---

Nitpick comments:
In `@tools/png.ts`:
- Around line 84-169: Confirm the no-throw guideline for tools/**/*.ts and align
decodePng with the project’s error-handling convention: replace its four
exception paths for invalid PNGs and unsupported formats with a typed { ok,
error } result, then update callers to check and propagate failures; if build
tooling is explicitly exempt, document or verify that exception and leave
decodePng consistent with encodePng and resolveTexture.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a169ad9-7c43-47cd-aee9-595f1da2a4ba

📥 Commits

Reviewing files that changed from the base of the PR and between add7e99 and 5c73c17.

⛔ Files ignored due to path filters (62)
  • assets/sounds/alien0_attack.wav is excluded by !**/*.wav
  • assets/sounds/alien0_die1.wav is excluded by !**/*.wav
  • assets/sounds/alien0_die2.wav is excluded by !**/*.wav
  • assets/sounds/alien0_die3.wav is excluded by !**/*.wav
  • assets/sounds/alien0_pain.wav is excluded by !**/*.wav
  • assets/sounds/alien1_attack.wav is excluded by !**/*.wav
  • assets/sounds/alien1_die1.wav is excluded by !**/*.wav
  • assets/sounds/alien1_die2.wav is excluded by !**/*.wav
  • assets/sounds/alien1_die3.wav is excluded by !**/*.wav
  • assets/sounds/alien1_pain.wav is excluded by !**/*.wav
  • assets/sounds/alien2_attack.wav is excluded by !**/*.wav
  • assets/sounds/alien2_die1.wav is excluded by !**/*.wav
  • assets/sounds/alien2_die2.wav is excluded by !**/*.wav
  • assets/sounds/alien2_die3.wav is excluded by !**/*.wav
  • assets/sounds/alien2_pain.wav is excluded by !**/*.wav
  • assets/sounds/alien3_attack.wav is excluded by !**/*.wav
  • assets/sounds/alien3_die1.wav is excluded by !**/*.wav
  • assets/sounds/alien3_die2.wav is excluded by !**/*.wav
  • assets/sounds/alien3_die3.wav is excluded by !**/*.wav
  • assets/sounds/alien3_pain.wav is excluded by !**/*.wav
  • assets/sounds/alien4_attack.wav is excluded by !**/*.wav
  • assets/sounds/alien4_die1.wav is excluded by !**/*.wav
  • assets/sounds/alien4_die2.wav is excluded by !**/*.wav
  • assets/sounds/alien4_die3.wav is excluded by !**/*.wav
  • assets/sounds/alien4_pain.wav is excluded by !**/*.wav
  • assets/sounds/blaster_fire.wav is excluded by !**/*.wav
  • assets/sounds/impact_flesh.wav is excluded by !**/*.wav
  • assets/sounds/player_die1.wav is excluded by !**/*.wav
  • assets/sounds/player_die2.wav is excluded by !**/*.wav
  • assets/sounds/player_pain1.wav is excluded by !**/*.wav
  • assets/sounds/player_pain2.wav is excluded by !**/*.wav
  • assets/sounds/ricochet1.wav is excluded by !**/*.wav
  • assets/sounds/ricochet2.wav is excluded by !**/*.wav
  • assets/sounds/rifle_fire2.wav is excluded by !**/*.wav
  • assets/sounds/splash1.wav is excluded by !**/*.wav
  • assets/textures/external/forrest_ground_01/forrest_ground_01_diff_2k.jpg is excluded by !**/*.jpg
  • assets/textures/external/forrest_ground_01/forrest_ground_01_nor_gl_2k.jpg is excluded by !**/*.jpg
  • assets/textures/external/forrest_ground_01/forrest_ground_01_rough_2k.jpg is excluded by !**/*.jpg
  • assets/textures/external/leafset/LeafSet004/LeafSet004_2K-PNG_Color.png is excluded by !**/*.png
  • assets/textures/external/leafset/LeafSet004/LeafSet004_2K-PNG_Opacity.png is excluded by !**/*.png
  • assets/textures/external/pine_bark/pine_bark_diff_1k.jpg is excluded by !**/*.jpg
  • assets/textures/external/pine_bark/pine_bark_diff_2k.jpg is excluded by !**/*.jpg
  • assets/textures/external/pine_bark/pine_bark_nor_gl_1k.jpg is excluded by !**/*.jpg
  • assets/textures/external/pine_bark/pine_bark_nor_gl_2k.jpg is excluded by !**/*.jpg
  • assets/textures/external/pine_bark/pine_bark_rough_1k.jpg is excluded by !**/*.jpg
  • assets/textures/external/pine_bark/pine_bark_rough_2k.jpg is excluded by !**/*.jpg
  • assets/textures/external/platanus/PlatanusOccidentalis_1-2K/PlatanusOccidentalis_1_ambientocclusion-2K.png is excluded by !**/*.png
  • assets/textures/external/platanus/PlatanusOccidentalis_1-2K/PlatanusOccidentalis_1_basecolor-2K.png is excluded by !**/*.png
  • assets/textures/external/platanus/PlatanusOccidentalis_1-2K/PlatanusOccidentalis_1_height-2K.png is excluded by !**/*.png
  • assets/textures/external/platanus/PlatanusOccidentalis_1-2K/PlatanusOccidentalis_1_metallic-2K.png is excluded by !**/*.png
  • assets/textures/external/platanus/PlatanusOccidentalis_1-2K/PlatanusOccidentalis_1_normal-2K.png is excluded by !**/*.png
  • assets/textures/external/platanus/PlatanusOccidentalis_1-2K/PlatanusOccidentalis_1_opacity-2K.png is excluded by !**/*.png
  • assets/textures/external/platanus/PlatanusOccidentalis_1-2K/PlatanusOccidentalis_1_roughness-2K.png is excluded by !**/*.png
  • assets/textures/external/platanus/PlatanusOccidentalis_1-2K/PlatanusOccidentalis_1_translucency-2K.png is excluded by !**/*.png
  • assets/textures/external/robinia/RobiniaViscosa_2-4K/RobiniaViscosa_2_ambientocclusion-4K.png is excluded by !**/*.png
  • assets/textures/external/robinia/RobiniaViscosa_2-4K/RobiniaViscosa_2_basecolor-4K.png is excluded by !**/*.png
  • assets/textures/external/robinia/RobiniaViscosa_2-4K/RobiniaViscosa_2_height-4K.png is excluded by !**/*.png
  • assets/textures/external/robinia/RobiniaViscosa_2-4K/RobiniaViscosa_2_metallic-4K.png is excluded by !**/*.png
  • assets/textures/external/robinia/RobiniaViscosa_2-4K/RobiniaViscosa_2_normal-4K.png is excluded by !**/*.png
  • assets/textures/external/robinia/RobiniaViscosa_2-4K/RobiniaViscosa_2_opaciy-4K.png is excluded by !**/*.png
  • assets/textures/external/robinia/RobiniaViscosa_2-4K/RobiniaViscosa_2_roughness-4K.png is excluded by !**/*.png
  • assets/textures/external/robinia/RobiniaViscosa_2-4K/RobiniaViscosa_2_translucency-4K.png is excluded by !**/*.png
📒 Files selected for processing (13)
  • assets/materials/grass_instanced.wgsl
  • assets/materials/terrain.wgsl
  • assets/materials/water.wgsl
  • assets/models/house.glb
  • assets/models/prop_tree.glb
  • assets/models/prop_tree2.glb
  • assets/models/prop_tree3.glb
  • assets/sounds/SOURCES.md
  • assets/textures/external/SOURCES.md
  • src/main.ts
  • tools/build-props.ts
  • tools/f12-shot.ps1
  • tools/png.ts
👮 Files not reviewed due to content moderation or server errors (1)
  • assets/models/house.glb

Comment thread src/main.ts
Comment on lines +2162 to +2176
// Per-kind death screech, positional, variant by slot.
playSound3D(sfxAlienDie[enKind[i] * 3 + (i % 3)],
enX[i], enY[i] + 1, enZ[i]);
} else if ((i & 3) === 0) {
// Occasional pain bark so sustained fire isn't monotone.
playSound3D(sfxAlienPain[enKind[i]], enX[i], enY[i] + 1, enZ[i]);
}
break;
}
}
if (!struckEnemy) {
// World hit — ricochet ping (alternating variants).
playSound3D(sfxRicochet[shotsFired & 1],
hit.point.x, hit.point.y, hit.point.z);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Pain-bark gate is deterministic per-enemy, not "occasional" across shots.

(i & 3) === 0 gates on the enemy's fixed array slot i, not on the shot/frame. This means enemies occupying slots 0,4,8,... will bark on every non-lethal hit, while all others will never bark — the comment's intent ("Occasional pain bark so sustained fire isn't monotone") isn't achieved; the outcome is fixed per-slot rather than varied per-hit.

🎯 Proposed fix: gate on shot count instead of enemy index
-            } else if ((i & 3) === 0) {
+            } else if ((shotsFired & 3) === 0) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Per-kind death screech, positional, variant by slot.
playSound3D(sfxAlienDie[enKind[i] * 3 + (i % 3)],
enX[i], enY[i] + 1, enZ[i]);
} else if ((i & 3) === 0) {
// Occasional pain bark so sustained fire isn't monotone.
playSound3D(sfxAlienPain[enKind[i]], enX[i], enY[i] + 1, enZ[i]);
}
break;
}
}
if (!struckEnemy) {
// World hit — ricochet ping (alternating variants).
playSound3D(sfxRicochet[shotsFired & 1],
hit.point.x, hit.point.y, hit.point.z);
}
// Per-kind death screech, positional, variant by slot.
playSound3D(sfxAlienDie[enKind[i] * 3 + (i % 3)],
enX[i], enY[i] + 1, enZ[i]);
} else if ((shotsFired & 3) === 0) {
// Occasional pain bark so sustained fire isn't monotone.
playSound3D(sfxAlienPain[enKind[i]], enX[i], enY[i] + 1, enZ[i]);
}
break;
}
}
if (!struckEnemy) {
// World hit — ricochet ping (alternating variants).
playSound3D(sfxRicochet[shotsFired & 1],
hit.point.x, hit.point.y, hit.point.z);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main.ts` around lines 2162 - 2176, Update the pain-bark condition in the
enemy-hit handling block to use the shot counter rather than the fixed enemy
slot: replace the `(i & 3) === 0` gate with an equivalent check based on
`shotsFired`, preserving the existing non-lethal-hit logic and pain sound call.

@proggeramlug proggeramlug merged commit 92ef0f9 into main Jul 11, 2026
1 check passed
proggeramlug pushed a commit that referenced this pull request Jul 13, 2026
…thorable)

`terrain.wgsl` computed its four layer weights procedurally — slope for rock, a
moisture FBM for the dry/lush grass split, distance to a hardcoded river line for
dirt. Good defaults, and no authoring story at all: `TerrainLayer.weights` was
written by nothing and read by nothing.

The editor now paints those weights. They arrive as one RGBA8 texel per terrain
cell and are mixed OVER the procedural blend by COVERAGE (the sum of the four
weights):

  - a cell nobody painted has zero coverage and keeps the procedural look exactly,
    which is why neither shipped arena changed by a pixel;
  - erasing lowers coverage rather than zeroing the ground, so paint fades back
    into the procedural blend instead of leaving a bald patch.

Layer order is the ABI and it is the WORLD FILE'S, not ours: layer i's textureRef
becomes array slice i and its weights become splat channel i (normal found by the
_albedo.png -> _normal.png convention). No layers = the built-in four.

The splat needed an array slot and all three were taken, so SH-010's detail normal
moved to slice 4 of the normal array — all five normal maps are 512² and a texture
array wants a uniform extent anyway. Transport is engine EN-049.

Verified in the game, not just the editor: tools/paint-test-world.ts writes what
the brush writes, and the shooter renders it.

Two bugs found on the way, both mine-adjacent and both silent:
  - engine EN-050: Perry miscompiled `clamp`, so every splat weight quantised to 0
    and painted terrain loaded unpainted (docs/perry-quirks.md #8);
  - the splat_uv params landed at float 24 instead of 28 — four per-layer tints are
    16 floats, not 12 — which overwrote the detail-normal scales and left the UV
    transform zero. That reads as "the splat map is empty", not as an off-by-one.

perry-quirks #8 also records the five probes that gave confidently WRONG answers
before one gave a right one — constant folding, shadowing, calls nested in log
concatenations, a bogus `as` cast, and worst of all stale builds: Perry does not
rebuild when only an imported module changed.

Claude-Session: https://claude.ai/code/session_01V3MihNxaRwMR8GjMPvMkRb
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