Skip to content

Path tracing toggle: F9 cycle, CLI overrides, render scale 0.75 - #11

Merged
proggeramlug merged 11 commits into
mainfrom
feat/path-tracing-toggle
Jul 14, 2026
Merged

Path tracing toggle: F9 cycle, CLI overrides, render scale 0.75#11
proggeramlug merged 11 commits into
mainfrom
feat/path-tracing-toggle

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

Game-side integration for the engine hardware path tracer (Bloom-Engine/engine#92):

  • F9 cycles path tracing: off -> progressive (stand still and the image converges) -> realtime (SVGF-denoised gameplay mode). No-op on devices without hardware ray query. The HUD debug line shows the mode plus a build tag (v10) so a stale binary is identifiable at a glance.
  • CLI overrides for A/B testing: --render-scale 0.25|0.5|0.75|1, --output-scale (same steps), --pt prog|rt. Values are string-matched, never parsed (perry-quirks Round-2 integration: gameplay + flicker fixes land in main #5); they land in the live settings array so the video menu reflects them.
  • Default render scale 0.5 -> 0.75: re-measured on the current engine - 0.5 reads visibly upscale-soft at 4K while 0.75 is dramatically sharper at ~30 fps raster (1.0 = ~19 fps, selectable in the video menu). The realtime trace grid is budget-capped engine-side, so the raster scale no longer multiplies ray cost.

Verification

  • Launch main.exe --pt rt --render-scale 0.25: HUD reads the injected mode with zero keypresses and the image shows the quarter-res scale.
  • RT stills + mid-strafe captures clean after the engine SVGF drop; shimmer meter has RT temporally quieter than the raster baseline.

https://claude.ai/code/session_018574BdCfSjdpNK3WLgx1hP

Ralph Kuepper added 11 commits July 13, 2026 09:49
…H-046)

Every alien and the player rendered with a base colour texture and NOTHING else:
no normal map, no roughness map, flat roughness 0.9 / metallic 0. They read as
matte toys standing on a terrain that has triplanar PBR and a detail normal. It
was the weakest surface in frame.

The Unvanquished source ships authored _n (normal) and _s (specular) maps for
every single character. convert-aliens-anim.ts only ever read the diffuse. It now
reads all three, so this cost nothing but the reading of it.

_s -> metallicRoughness is NOT a swap, and getting it wrong is what the tuning in
here is about. A Quake-lineage spec map stores specular INTENSITY; glTF wants
ROUGHNESS in green and METALNESS in blue. Mapping intensity straight to gloss —
my first attempt — drove bright texels to roughness ~0 and the battlesuit came out
looking wet. The mapping is now conservative: roughness spans 1.0 -> 0.35, never a
mirror. The point is surface VARIATION, which there was none of; it is not to make
anything glossy.

The battlesuit alone takes metalness from the spec map. It is armour plate, and a
dielectric with a tight white highlight reads as wet plastic — metalness is what
makes it read as metal, because the reflection is tinted by the base colour rather
than staying white. The aliens stay dielectric: metallic chitin looks like tinfoil.

The _adv upgrade skins ship no _n/_s of their own (they are recolours of the base
on the same mesh and UVs), so they borrow the base skin's maps.

Also lands docs/asset-sourcing.md — where better free animated models can actually
come from. The headline finding is uncomfortable and worth keeping: our five
DISTINCT NON-HUMANOID creatures with full animation sets are precisely the asset
class that does not exist for free. The free ecosystem is rich in humanoid
animation libraries and stylized low-poly monsters, and thin in exactly what we
already have. Also: D3 freed us on licence but NOT on redistribution — a git repo
IS redistribution, which rules Mixamo out as repo content no matter the licence.

Verified by screenshot at 4K, cropped: plate definition and edge wear where there
was flat matte, and no wet look after the retune. ~0.5 MB per model.

Claude-Session: https://claude.ai/code/session_01V3MihNxaRwMR8GjMPvMkRb
…SH-036 / A4)

Six real tracks in, mastered, and the game streams them. SH-036 is closed.

Suno ignored two things.

LENGTH, comprehensively: asked for 60-90 s beds and 2-4 s stingers; got 162/73/195 s
beds and 9/24/49 s "stingers". tools/music-master.ts handles it. Note the victory
sting's swell does not LAND until 0:29 — "take the first 4 seconds" would have
shipped an intro. The stinger windows are read off the rendered waveforms
(tools/music-inspect.ts), not guessed.

KEY: asked for D minor, got A# minor — and it does not matter, for exactly the
reason the rule existed. The beds are keyed to EACH OTHER, which is the only thing
the crossfade cares about. That is VERIFIED, not assumed: estimateKey() runs a
chroma + Krumhansl-Schmuckler estimate and calm, combat and menu all agree. Had
they disagreed the crossfade would have been unfixable and they'd need regenerating
— which is why the tool checks rather than trusting the prompt.

The loop point is SEARCHED, not assumed. Candidate (start, length) pairs are scored
by how similar the music actually sounds at `start` and at `start + length`, which
is what makes a crossfade inaudible, and which self-corrects tempo error. That last
part matters: at hop 512 the autocorrelation bins around 120 BPM are 119.7 and
122.3, so calm and combat — both 120 — landed in DIFFERENT BINS and appeared to
disagree by 2%. I nearly time-stretched a track that did not need it. Parabolic
interpolation now resolves that, and the search does not depend on it anyway.

Also: candidates step by the BAR (a seam anywhere else is mid-bar and no crossfade
saves it); loops containing the intro/outro fade are rejected (they would get
quieter every wrap); and among acceptable seams the LONGEST loop wins — I first
wrote a scoring function that traded a 63 s loop for a 31 s one to buy a 1 dB seam
improvement nobody could hear. A short loop is a cost the player notices.

Result: calm 63.1 s, combat 47.8 s, menu 55.9 s, all seams verified (wrap-jump vs
p99 sample delta, and level either side of the join within 3 dB). Stingers 4-5 s,
all ending in true silence.

bun tools/music-loopcheck.ts writes each loop played TWICE with the join at the
midpoint — the only test that really counts is whether a human can hear it, so the
tool exists to let one try.

Raw renders kept in assets/music/src/; mastering is re-runnable and never touches
them. assets/music/*.wav is build output — edit the tool, not the files.

Claude-Session: https://claude.ai/code/session_01V3MihNxaRwMR8GjMPvMkRb
… with the floor

Reported as "the animation is not there, the player just slides". The animation was
playing the entire time. The thresholds were wrong.

The clip was picked by crossing a speed threshold: run above 7.0 m/s, else walk.
But MOVE_SPEED is 6.0, so normal movement ALWAYS took the walk clip — and the walk
clip is authored for ~2.6 m/s. Covering 6 m/s of ground with it meant playing it at
the 2.2x clamp, and even at the clamp the stride only covers ~5.7 m/s. So the feet
could never keep up with the ground. That is the slide: a walk cycle at double
speed, permanently behind the floor.

Sprint (9.0 target, ~7.4 measured) only just cleared 7.0, so the run clip flickered
at the boundary instead of being a state you could feel. Hence "shift doesn't run".

Fixed twice, and the second one is the real fix:

1. The clip follows what the player is DOING — isSprinting() runs, moving walks,
   standing idles. No threshold to sit on. (isSprinting() was exported by player.ts
   and imported by nobody. That was the clue.)

2. MOVE_SPEED 6.0 -> 4.5. Six metres per second is a RUN speed wearing a walk
   animation, and no amount of clip selection fixes that. SPRINT_MUL 1.5 -> 2.0
   absorbs it, so TOP SPEED IS STILL 9.0 m/s — nothing about outrunning a dragoon
   pounce or a mantis dart has changed. Only the cruise is slower, and now holding
   shift is the difference between a walk and a run.

The clamp is the canary: playback rate is (actual speed / authored clip speed), and
if that ratio ever REACHES the clamp then the movement speed and the clip no longer
belong together — the fix is the speed constant, not a wider clamp. Measured after
the change: walk ~1.4x, sprint ~1.1x, both well inside it.

Diagnosed by making the game auto-start and walk itself on rails, then logging
playerSpeed() from the loop — playerSpeed() was returning 4.8-5.3 the whole time,
which killed the obvious theory (a Perry miscompile of a one-line getter) before I
could waste a day on it.

Claude-Session: https://claude.ai/code/session_01V3MihNxaRwMR8GjMPvMkRb
TEX_MAX was 512. The Unvanquished source is 2048² (player) and 4096² (aliens). We
have been shipping every character at a fraction of its authored detail, on models
the camera is rarely more than a few metres from.

Now 1024 for the aliens, 2048 for the player — it is on screen 100% of the time and
earns more. 4x the texels on the aliens, 16x on the player.

Cost: none that matters. 53 fps before, 53 fps after. This was never fill-bound, it
was texture-starved. Models grew 28 MB -> 65 MB on disk.

Between this and the normal/spec maps that landed earlier today, the characters now
carry roughly 64x the surface information they did this morning, and not one byte of
it was downloaded — it was all sitting in vendor/.

SH-046 part 2 (replace the player character) is closed as WON'T-DO, and this time on
evidence rather than a guess: searched Sketchfab authenticated via the Download API,
across cc0/by/by-sa, downloadable+animated, ten queries, 43 candidates. The
realistic ones ship 3-5 animations. The well-animated ones are low-poly stylized.
The one high-poly well-animated hit is a Black Myth: Wukong rip — someone else's IP
whatever the licence label says.

The battlesuit has 34 purpose-built clips (idle, walk, run, 4 strafes, 5 crouch
states, jump, land, pain, die, attack, per-weapon pose deltas). Trading that for a
4-animation astronaut is trading a complete locomotion set for a prettier T-pose.

Claude-Session: https://claude.ai/code/session_01V3MihNxaRwMR8GjMPvMkRb
Joins the F5-F8 render-pass toggle bar. Shows 'n/a' and stays inert on
devices without hardware ray query. The engine-side modes are landing on
engine branch feat/path-tracer.
Re-measured on the 4K dev box after the engine perf rounds: 0.5 reads
visibly upscale-soft (the user called it out), 0.75 is dramatically
sharper at ~30 fps raster, 1.0 (~19 fps) stays selectable in the video
menu for stills. Mobile keeps 0.5. The PT realtime ray budget is
decoupled engine-side so path tracing does not slow down with this.
--render-scale 0.25|0.5|0.75|1 and --output-scale (same steps) override
settings.json for one launch; --pt prog|rt starts in that path-tracing
mode without F9 taps. Values are string-matched, never parsed
(perry-quirks #5). Verified: launch with --pt rt --render-scale 0.25
shows HUD PT RT v9 and a visibly quarter-res image, no keys pressed.

Claude-Session: https://claude.ai/code/session_018574BdCfSjdpNK3WLgx1hP
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@proggeramlug, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a86d5b98-5c77-448b-967f-23debc96ca60

📥 Commits

Reviewing files that changed from the base of the PR and between 62e3ac3 and ea5e8cf.

⛔ Files ignored due to path filters (12)
  • assets/music/music_calm.wav is excluded by !**/*.wav
  • assets/music/music_combat.wav is excluded by !**/*.wav
  • assets/music/music_menu.wav is excluded by !**/*.wav
  • assets/music/src/music_calm.wav is excluded by !**/*.wav
  • assets/music/src/music_combat.wav is excluded by !**/*.wav
  • assets/music/src/music_menu.wav is excluded by !**/*.wav
  • assets/music/src/sting_death.wav is excluded by !**/*.wav
  • assets/music/src/sting_victory.wav is excluded by !**/*.wav
  • assets/music/src/sting_wave_clear.wav is excluded by !**/*.wav
  • assets/music/sting_death.wav is excluded by !**/*.wav
  • assets/music/sting_victory.wav is excluded by !**/*.wav
  • assets/music/sting_wave_clear.wav is excluded by !**/*.wav
📒 Files selected for processing (20)
  • assets/models/enemy_adv_dragoon.glb
  • assets/models/enemy_adv_marauder.glb
  • assets/models/enemy_dragoon.glb
  • assets/models/enemy_dretch.glb
  • assets/models/enemy_mantis.glb
  • assets/models/enemy_marauder.glb
  • assets/models/enemy_tyrant.glb
  • assets/models/player_bsuit.glb
  • assets/music/SUNO-PROMPTS.md
  • docs/ASSET-TODO.md
  • docs/asset-sourcing.md
  • docs/tickets.md
  • src/main.ts
  • src/player.ts
  • src/settings.ts
  • tools/audio.ts
  • tools/convert-aliens-anim.ts
  • tools/music-inspect.ts
  • tools/music-loopcheck.ts
  • tools/music-master.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/path-tracing-toggle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@proggeramlug
proggeramlug merged commit d5de83d into main Jul 14, 2026
1 check passed
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