Review leftover #17. animate_skyboxes keeps its per-model clocks across frames (a path String is
cloned only when a model first shows), poses into reused buffers (SkyRig::pose_into), and
re-skins and re-uploads a skinned batch only when its model's pose changed (a generation
counter). resolve_camera_skybox compares paths with an allocation-free norm_eq and owns only the
winning WMO skybox name. Test: norm_eq agrees with norm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fold mask came from the two long wave bands only, which cross in a regular diamond lattice:
every crest broke into the same elongated patch. The mask adds the next two bands' compression,
a slow domain-warped gust field moves the break threshold and calms patches, and ~6 yd warped
segments break each crest into short runs. Geometry is unchanged; crest foam stays on steep waves.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The erosion weight 4a(1-a) was 0.84 in dense cloud (a = 0.7), so one low base-octave detail cell
(~5 degrees) cut a round hole in the mass, and the detail's mean (0.573, not 0.5) lifted the
plateau around it: a hard ring near the sun, rimmed by the silver lining. Erosion is now centred
on the detail's mean and weighted 6.75a(1-a)^2 (thin edges keep the cauliflower, dense cores
keep their shape); thin cloud is not self-shadowed, and the silver lining reads a wider
footprint ahead and is capped. Cloud behind the glow is a diffuse brightening again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Water writes no depth, so the haze pass measured the seabed behind it, and water with no seabed
behind it (depth 0 below the horizon) read as sky and took no haze. The underwater slope of an
island drew a hard-edged wedge across the sea (water-ocean at High). With an ocean footprint in
reach and the eye above sea level, a point below sea level is now hazed at the ocean plane.
Samples past the cascade's far bound take the ray's mean covered visibility instead of 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
update_bleed_fixtures walked every interior source in the world four times per
doorway every frame (~1.0 ms/frame main thread, traced, Northshire at High).
Sources are now bucketed per placement once (same rows, same order, same sums:
another placement's claimant weighs 0), the evaluation runs at 10 Hz with
arrivals and dial changes bypassing the cadence, and the light is written only
on a real change. Captures inn-noon and perf-abbey-night are pixel-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A probe window opens unfocused and WinitSettings::game() drives an unfocused
window at reactive 1/60 s, so every FPS_PROBE read ~16.7 ms whatever the scene
cost (Classic at 720p with 0.4 ms of GPU read 57 fps). Probes now run
Continuous in both modes. Adds perf-northshire-night / perf-abbey-night.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Frames the Westfall sea from 30 yd up so the 64-80 yd fine/coarse ring crosses the frame
centre; used to verify the stitched LOD edges at WOW_WATER=2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A separate system added 10 unordered pairs to Update (schedule ambiguity
ceiling test). Same behaviour; the recapture is identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by the first runtime capture of the grade: a capture has no avatar, so
CurrentArea stayed None and no zone was ever graded; and the day/night weight
read WorldTime (the server clock, noon offline), so a manual/capture night
graded with the day LUT. Adds CaptureCameraArea (area under the camera, only
under WOW_CAPTURE, never read by the area authority) and grades by the
rendered GameClock minute. LIGHTING.md notes the preset/capture changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- farclip High seed 777 declared in SEEDED_DEVIATIONS (owner: High is the
default); test walks the seeded column against every reference row (#6)
- WOW_CAPTURE_CVARS=<file> replaces BENILLA_HOME as the capture cvar fixture (#13)
- label derivation skips session-owned rows, so an env lever no longer
persists graphicsQuality/lightingQuality = Custom (#14)
- /console help strings: restored line continuations + a test (#15)
- WOW_FOLIAGE_WIND read only in a dev build under WOW_CAPTURE (#16)
- fog node skipped at volumetricFog 0 with no lamp; fog and SSAO uniforms
persistent per view, written in prepare (#17)
- tests: every governed graphics row through its observer; OptionsFrame
farclip slider tied to view::FARCLIP_RANGE
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The grading port carries the same attribution header as the other WarcraftXL
ports (used with the author's permission, attribution required).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: sun_shafts prepare keeps the integration warm loop on the polish
format-only blur key (mask pipelines are queued at init); sky_fx keeps the
clock ordered before LightingConsumeSet plus the polish clock buffer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
STAGE_AO flags a two-sided depth spike (the smaller one-sided step two pixels out, capped by the
second difference: ~0 on planes, grazing planes, silhouettes and contact creases) and stores the
smoothness in alpha. STAGE_BLUR turns the spike density of its 4x4 window into a foliage mask,
widens the depth tolerance there (up to 11x) and scales the occlusion by 1 - 0.4 * mask. Smooth
windows are unchanged. WOW_AO_DEBUG=5 shows the mask. LIGHTING.md documents this and the two
earlier followups fixes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On load, Cvars::reapply_saved_presets writes the saved graphicsQuality rung (and the saved
lightingQuality rung, else the Graphics rung's) over every governed row the file does not carry,
so a row registered after the save follows the preset instead of its registered default. Rows the
file carries and session-owned rows stay; Custom writes nothing. Tests for both ladders.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The static world caster rebuilds only on 16 yd of camera drift. The refresh that invalidates it
when streamed geometry arrives lived in the volumetric-fog plugin and was gated on that cvar, so
with volumetricFog 0 a standing camera never picked up trees and doodads that streamed in late.
The refresh now lives in world_shadow.rs and is gated on worldShadows only (Classic unaffected).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It reads MonkeyFrame.wetness while update_fog_model writes MonkeyFrame; the
undeclared pair put the ambiguity count one over the 5,582 ceiling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The detail drift was `(0.011, 0.0043) * t`, so the field jumped to a new
pattern when the sky clock wrapped at 86400 s. `value_noise_plane` now takes
a lattice period and hashes cell indices modulo it; the detail plane tiles
every CLOUD_TILE = 64 cells (octave o every 64 << o, the half-rate domain
warp every 32), and the drift is whole tiles per wrap, CLOUD_DRIFT_TILES =
(15, 6), i.e. 0.01111 and 0.00444 cells/s (+1% and +3% over WarcraftXL's
rates). The shader takes the drift as `fract(tiles * t / SKY_WRAP) * tile`,
so the field at the wrap equals the field at 0, and the drift term stays
below 64 instead of growing to ~950 in f32. One tile spans 1.78 of the
sheet's UV, wider than the dome shows, so no spatial repeat is visible.
A test reads `sky_fx.wgsl` and pins SKY_WRAP to SKY_CLOCK_WRAP_S, an even
tile, whole tiles of drift and the rates within 5% of the originals.
Verified: cargo test -p benilla-world --lib (644 passed, 8 ignored);
cloud.wgsl (SKY_FX_CLOUDS) and sky.wgsl (SKY_FX) compose and validate under
naga_oil 0.20 / naga 27 offline. No GPU or WoW data here; built on Linux only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6yUWgPosSakvL23gtgvpH
At skyQuality >= 1 the dome and cloud materials were rewritten every frame
only because `fx.y` carried the sky clock, so each frame re-prepared both
material bind groups. The clock now lives in `SkyClockBuffer`, a 16-byte
STORAGE | COPY_DST buffer created beside the shared light buffer
(AssetSet::Open) and bound read-only by both materials (sky binding 101,
cloud binding 103), the same shape as the materials' shared `light_buf`.
The render world writes `SkyClock::secs` into it in PrepareResources each
frame. `fx.y` is now reserved (0), so `write_gated` only touches a material
when a real input moves. The clock is still our own f64 accumulator with
the seamless 86400 s wrap (Bevy's globals.time wraps hourly); capture mode
still freezes it. A headless build with no RenderDevice gets no buffer and
spawns no sky or cloud dome.
Verified: cargo check -p benilla; cargo test -p benilla-world --lib (643
passed, 8 ignored); sky.wgsl (Classic and SKY_FX) and cloud.wgsl (plain and
SKY_FX_CLOUDS + VERTEX_COLORS) compose and validate under naga_oil 0.20 /
naga 27 offline against stubbed bevy_pbr imports and the real fog_hook,
monkey_frame and sky_fx modules. No GPU or WoW data here; built on Linux only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6yUWgPosSakvL23gtgvpH
Graphics Preset ladder + Render Distance (farclip ceiling 1497). Conflict in
options_tests: the Advanced page now has 33 rows (preset + render distance lead,
then the 31 programme rows). The three round-3 rows join the ladder:
ambientOcclusion 0/0/1/2/2, zoneSkyboxes 0/0/1/1/1, lampFog 0/0/1/2/2
(Classic/Low/Medium/High/Ultra); the preset tooltip names them (EN+RU).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The grade snapped to the new zone's LUT pair on the frame `CurrentArea`
changed. `GradeBlend` now holds the current and previous pairs and fades
between them over GRADE_FADE_S = 2 s: the view binds a second day/night
pair (bindings 6, 7; identity when there is none) and `control.z` is the
current pair's weight, `control.w` the previous pair's strength. The shader
runs the previous pair through the same day/night blend at its own
strength and mixes, only while z < 1. A zone with no row fades to identity
(strength 0) instead of cutting; stepping back across the border mid-fade
reverses the fade from the image on screen. The first graded zone after
login, and a disabled grade, snap. Missing LUTs still bind the identity cube.
Verified: cargo test -p benilla-app --lib post:: (4 passed, including the
new zone_change_crossfades_and_a_quick_return_reverses); grading.wgsl
composes and validates under naga_oil 0.20 / naga 27 offline. No GPU or
WoW data here; built on Linux only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6yUWgPosSakvL23gtgvpH
wet_hook gains shelter_taps/shelter_cell/shelter_amount (bilinear over
four cells; sheltered when a covered top is >= ~1 yd above the fragment,
the roof's own surface stays open). terrain, wow_model and static_gx
mirror the region after monkey and scale wet exposure by 1 - shelter
(wet_puddles takes exposure, so no puddles under cover); enhanced_water
suppresses rain rings under bridges/roofs via its row view. The lookup
runs only while wetness > 0 and the header is active, so a dry frame is
unchanged.
Verified: the wet_hook + receiver/water lookup snippets composed and
validated with naga_oil 0.20 / naga 27 (scratch harness; unpack2x16float
avoided - it needs a float16 capability). Full receiver shaders were not
compiled here (no GPU, no game data).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8t89gSUuo1uvbXKdh1mJt
128x128 cells of 0.75 yd around the camera, recentred in 16-cell steps.
Filled by vertical rays against the collision world (the precip ground
oracle's SpatialQuery + body filter): top surface, then down each hit
until an air gap >= 1.2 yd marks the column covered. Cover is eroded two
cells (drip margin) and 3x3 blurred (soft edge), packed with the top
height into one u32 per cell. 192 casts/frame while filling (nearest
first), 32/frame refresh once full; idle and header off when dry or
rainSurfaces=0.
The grid rides the shared light buffer as a new region right after the
MonkeyFrame block; the prop-probe region (and everything chained from it)
moves up by 65568 B. Uploads only the header and changed rows.
Verified: cargo test -p benilla-world --lib (652 pass, 9 new shelter
tests: column probe, transform, recentre, budget, porch shelter/drip,
packing, region alignment); cargo check -p benilla. Linux only; no
platform seams.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R8t89gSUuo1uvbXKdh1mJt
Advanced Graphics gains a Graphics Preset row (cvar graphicsQuality:
Classic / Low / Medium / High / Ultra / Custom) and a Render Distance slider
on farclip (177..1497, same cvar as Graphics' Terrain Distance). The ladder is
GRAPHICS_PRESETS in cvars.rs, one line per governed row with a column per
rung; lightingQuality is itself a row, so the lighting ladder's members follow
it. The lighting ladder gains an Ultra rung (4096 sun map, 16/8 torch maps,
High water, High volumetric fog). The label is re-derived every frame beside
lightingQuality, so editing a governed row shows Custom.
A player whose config.toml names no graphicsQuality is seeded to High at
startup over the rows the file does not carry; captures, tests and malformed
files never seed, and the rows keep their per-lane registered defaults. The
page's Defaults lands on High too. ambientOcclusion, zoneSkyboxes and lampFog
are commented one-line TODO rows until their cvars land.
Verified: cargo test -p benilla-app --lib -- options cvars preset (136
passed, incl. 7 new preset tests); full benilla-app lib: 3722 passed, 5
pre-existing failures unrelated to this change (spell envelope float, schedule
ambiguity ceiling, pipe_warm fog/shaft/shadow lanes, dev-plane cfg). The
options UI tests need WoW data and were skipped; no captures (no install in
the cloud). Built on Linux only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Toc8dZYuTKW1JWBFbMb5S1
FARCLIP_RANGE now ends at FARCLIP_MAX = 1497 (the 1.12 slider grid of 60
carried to n = 22), a documented deviation: Modern fog already stretches the
fog end past 777 with farclip, so a longer view is visible. Terrain residency,
the model/WMO and particle walls follow farclip; at 1497 residency is at most
7x7 ADT tiles against 777's 5x5, which is the memory bound. The art-scope
sweep radius rises to 7 tiles to stay clear of the widest resident reach.
Verified: cargo test -p benilla-world --lib (643 passed). Built on Linux only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Toc8dZYuTKW1JWBFbMb5S1
Colour grading and sun shafts built a fresh `UniformBuffer` (a new GPU buffer)
inside their render-graph node every frame. Each view now owns one small
UNIFORM | COPY_DST buffer, created the first frame the effect is on and
rewritten in place with `RenderQueue::write_buffer` in
`RenderSystems::PrepareResources`, the same shape as bloom's prepare-time
uniform. The node only binds it. The pass still gates on the extracted view
component, so the kept buffer never runs a disabled pass.
Verified: cargo check -p benilla-app; rustfmt clean on both files. No GPU
here; built on Linux only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6yUWgPosSakvL23gtgvpH
The radial blur read the scene depth 28 times per full-resolution pixel. A
new mask pre-pass (`fs_mask`, R8Unorm at ceil(w/2) x ceil(h/2)) loads the
depth once per half-res texel and thresholds it with the same sky test
(depth <= 1e-7 on infinite reverse-Z); the blur then takes 28 clamp-to-edge
bilinear taps of the mask. Only the mask stage is MSAA-specialised (single
and multisampled depth layouts, one pipeline each); the blur pipeline is
keyed by the target format alone.
Verified: cargo check -p benilla; all four WGSL variants (blur, blur +
MULTISAMPLED, SHAFT_MASK, SHAFT_MASK + MULTISAMPLED) compose and validate
under naga_oil 0.20 / naga 27 offline. No GPU or WoW data in this
environment, so the image is not captured; built on Linux only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y6yUWgPosSakvL23gtgvpH
Zone Skyboxes row moved to the end of the Advanced Graphics page and added to
OPTIONS_PAGE_ROWS (the lane had left it out of the list).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RenderSubmesh carries stage 1 (texture, UV set, transform, Mod/Mod2x) for
textureCount 2 batches; M2Vertex reads UV1. WowModelExt gains a stage1
uniform + texture binding 94/95 and the WOW_STAGE1 shader def, so every
one-texture batch compiles the old code. The skybox lane binds stage 1 on
its own material copies and drives its transform rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>