World and entity receivers now choose the nearest ready torch map within the existing tolerance. Previously an earlier neighbouring fixture won even when the light's own map was present at its exact position. Add an opt-in attached spell-light capture fixture and correlation trace.
Verified on Windows with the app library gate, play build and inn/blacksmith captures. The inn trace reproduces first slot 3 versus exact-position slot 11. Visual 60056 movement and doorway transitions still require an in-game retest.
Co-Authored-By: GPT-6 Astra (Codex)
Reject rock, stone, boulder, pebble, gravel and rubble model filenames from retained foliage. Give matching ground-detail models zero bend height so neither grass wind nor player parting moves them. Limit the name veto to the filename to preserve vegetation in stone-named zones.
Verified on Windows: two foliage classifier tests and one rigid-model test pass; benilla-app lib tests have 4313 passed, 0 failed, 10 ignored (no-install run); play build passes. Elwynn wind captures at 0 and 3 seconds preserve coloured foliage motion. The tester's specific small-rock assets still need an in-game retest.
Co-Authored-By: GPT-6 Astra (Codex)
Move the foliage wind marker from bit 14 to bit 15 in its Rust producer and WGSL consumer so distance-fade twins cannot trigger a downstream fragment effect. Keep bit 14 reserved for those extensions.
Verified on Windows: cargo build -p benilla --profile play; cargo test -p benilla-app --lib (4313 passed, 0 failed, 10 ignored; no-install run). Public-base and fixed captures at the reported Elwynn position are pixel-identical; downstream integration still needs its in-game retest.
Co-Authored-By: GPT-6 Astra (Codex)
Letter and punctuation keys were named by their US position, so on AZERTY the key labelled Z bound as W, and Ctrl+A sat on the key labelled Q.
1.12.1 on Windows names a key from the VK its layout produced. `VK_A`–`VK_Z` and digits are named as themselves, and other keys by `MapVirtualKeyA` (`0x42da39`); a key with no character is dropped (`0x42da49`). The EditBox's Ctrl chords switch on that same name (`0x77b1fb`). The Mac client names keys by `KeyTranslate`.
Now:
- Windows takes the same path through `MapVirtualKeyExW`.
- The Mac names keys through the current layout, or the ASCII-capable layout it pairs with.
- Linux follows one documented rule.
- Bindings, Key Bindings capture and Ctrl/Cmd+A/C/X/V all follow the layout.
- A character above U+00FF names its key, as a deviation.
Tests cover AZERTY, Hebrew, Greek, Lithuanian, Turkish, Bulgarian, Hungarian and Dvorak names, layout switches and chords.
Fixes#621Fixes#746🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A crowd of arriving players was composited in one frame, on the main thread, under the archive lock: 40 geared looks took 76–115 ms.
In 1.12.1 an unforced full composite never waits on its section loads (`0x44b430`), and a unit whose first composite is pending is not drawn: ShouldRender `0x607da0` feeds the draw flag `0x710c50`, and the flag goes to the mount model when mounted. It appears dressed once the composite is done.
Now an arriving body composites on the async pool, one task per look, reading outside the lock. The body, and its mount, are built once the atlas lands. Arriving now costs the main thread 0.26–0.45 ms for 40 looks. The appear fade runs from arrival, as `FadeTo` is armed at create.
A re-dress, a rig heal and the glue and dressing-room previews stay in the frame. Baked NPC skins are unchanged.
Fixes#609🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Volumetric light: the shadow rig's directional light (sun by day, moon at
night) is ray-marched through its cascades at half resolution (16 Medium /
32 High jittered samples from 0.35 yd, height-thinned air, HG g 0.6), then a
depth-aware blur and upsample composite it over the fog pass. Density and
colour follow the zone fog; no shafts in WMO interiors. The volumetric fog
pass retires its own midpoint shafts while this is on. Cvars volumetricLight
0/1/2 and volumetricLightStrength, env WOW_VOLLIGHT; Graphics Preset Medium 1,
High/Ultra 2.
Moonlight: a cool, dim directional term from the visible moon added to the
stock night on terrain, models, WMO exteriors and doodads, shadowed by the
moon's map (shadow_hook::realtime_shadow_moonlit .z), ramping with the moon's
elevation and handing back to the sun at dawn. MonkeyFrame grows one row
(per-frame blob 8800 B). Cvar moonLight 0..2 (0 = the reference night).
Advanced Graphics rows (EN + RU), capture scenarios vol-*, docs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A meshed doodad counted as drawn whenever one of its parts had a `Visibility` other than `Hidden`. Bevy's frustum culling writes only `ViewVisibility`, so a doodad behind the camera kept an `Inherited` part and went on animating and ticking its particles.
The reference animates (`0x714260`) and ticks (`0x718960`) only the M2 scene's worklist, which `0x707680` builds. A doodad is admitted to that worklist only after the 6-plane frustum test `0x683700`.
`gate_doodad_anim` now also tests a meshed host's fade sphere against the world camera's frustum. That sphere is the doodad's world bounding sphere, fixed at spawn. A meshless host already had the same test, and both branches now share one closure. The `Visibility` term stays, because it carries the fade, exterior and room verdicts.
Test: `a_meshed_host_outside_the_frustum_is_parked`. Two meshed hosts with `Inherited` parts sit one behind the camera and one ahead of it. Only the host behind is parked.
Fixes#619🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A placement whose model load failed (a file missing from the chain, or a model the parser rejects) stayed unspawned for ever. The spawner treats `m2s.get(h) == None` as "still loading", the progress count holds it as pending, and `scene_ready` needs zero pending, so the loading screen never cleared.
`spawn_loaded_placements` now first retires any unspawned placement (M2 or WMO root) or WMO prop whose handle's `LoadState` is `Failed`. A retired entry spawns nothing, is flagged spawned, and leaves `pending_spawns`, with one warning naming the path. That is the same fallback the WDT load already takes. Handles that are still loading keep waiting. WMO groups read inline in the WMO loader, so the root's state covers them.
`a_failed_model_load_retires_the_placement` and `a_failed_prop_load_retires_the_prop` load unresolvable paths through a real `AssetServer`. Both fail with the failure check removed. Built and gated on macOS. What draws in place of a missing model stays #330.
Fixes#616🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- On Linux a key the keyboard layout remaps (X11's caps:escape) acts as the key it is remapped to, as in the real client
- A layout-moved named key also acts as that key on Windows, where the reference reads the layout's virtual key; macOS keeps its physical table
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
**#549: the stock video window's CVars.** Its check boxes, `ffx` and `gxRefresh` are registered at the reference's defaults. `pixelShaders` and `specular` ship as `overridden` "1" (`hwDetect`, `0x639a60`), and `gxFixLag` ships "1".
- `ffx`, `ffxGlow` and `ffxDeath` gate the full-screen effect passes every frame, as the reference's gate does (`0x6cd890`; `CFFXGlow` reads `ffxGlow` at `0x6cc5a8`, `CFFXDeath` reads `ffxDeath` at `0x6cdf10`).
- `gxMaximize` is latched. At the `RestartGx` commit it makes a windowed client borderless over the monitor, as the reference's window rebuild does (`0x58cf10`).
- The rest are registered with no effect, and each row says why: `useWeatherShaders` (snow's fallback leg is a separate CPU draw), `lod`, `M2UseShaders`, `M2UsePixelShaders`, `movieSubtitle`, `gxTripleBuffer`, `gxCursor`, `gxRefresh`, `DesktopGamma`.
- `cvars.rs` is split into the runtime and the table.
**#329: Spell Detail.** benilla's options window gains 1.12's Spell Detail Level slider on the Graphics page, over `spellEffectLevel` on the stock 0-2 grid.
**#583: `FrameXML.log`.** Load failures now go to `benilla-config/Logs/FrameXML.log`, as the reference's go to `Logs\FrameXML.log`, and no longer to chat.
- The log uses the reference's lines ("Couldn't open", "Error loading", "Couldn't parse XML in") and its banners.
- A UI load that reports something rewrites the file, and a runtime `LoadAddOn` appends to it (`0x490187`, `0x46aac0`).
Verified:
- New tests for each switch, the maximize latch, every box's stored value with no unknown-CVar warning, the Spell Detail row and the log file's rewrite and append, each failing on main.
- A region dump of the options window shows the new row, and the rows below it moved 35 px, and nothing else.
- `scripts/gates.sh` and `scripts/smoke.sh` are green.
- A live run unticked Full Screen Glow in the stock window, which turned the glow pass off and saved `ffxGlow = "0"`, and a missing addon file produced its four log lines.
Built on macOS; no platform seam touched.
Fixes#549Fixes#329Fixes#583🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The stock video options window (`OptionsFrame`, off the player's chain) now opens, saves, restores defaults and cancels with no Lua error, as on 1.12.1.
- The CVars it reads are registered at the reference's defaults: `SmallCull` "0.04" (`0x68854a`), `shadowLevel` "1" (`0x6885bc`), `baseMip` "0" (`0x6887aa`), `DesktopGamma` "0" (`0x402d4d`), `spellEffectLevel` "2" (`0x688900`), plus `doodadAnim` and `texLodBias`, which the new getters read. `SetWorldDetail` keeps `SmallCull` in step (`0x488e56`), and the wrong comment that called the getter its only reader is gone.
- `spellEffectLevel`'s handler (`0x689510`): the value is clamped to 0-2, "Spell effect level set to %d." is printed, the particle emission scalar is set to 0.33/0.66/1.0, and the shard emitter's rate follows the level (`0x6eb95e`).
- The 21 functions #337 lists: the six video getter/setter pairs (`0x488ec0`-`0x489300`), `GetNumFrames`/`EnumerateFrames` in creation order (`0x705f00`, `0x705f60`), and the client queries, including `GetDebugStats` and `OpeningCinematic`'s empty `CMSG_OPENING_CINEMATIC` (0xf9, `0x48c8c9`).
Verified: a headless test walks the stock window's 18 boxes and 9 sliders, then Okay, Defaults and Cancel, with no error (it fails on main at `OptionsFrame.lua:146`), plus unit tests for each binding and handler; 12 mutations each fail a test. A live run opened the window, saved, restored defaults and cancelled with no Lua error. `scripts/gates.sh` green. Built on macOS; no platform seam touched.
Fixes#548Fixes#337
Refs #329 (benilla's own options window has no Spell Detail row yet)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A dev build finds its project folder, where its `WoW` link, `benilla-config/` and `.probe-identity` live, from its launcher, not from a benilla crate's own `CARGO_MANIFEST_DIR`. For a crate built on top of benilla, where benilla is a git dependency, that manifest dir is cargo's checkout of benilla.
- `benilla_buildstamp::emit()` stamps `BENILLA_PROJECT_DIR`, the launcher's workspace root (the nearest folder up holding `Cargo.lock`), only when the launcher's `dev` feature is on. A player build stamps it empty and carries no source path. The shims pass it in as `BuildId::project_dir`.
- `benilla_formats::project_folder` is the one home for the folder. Each entry point records the stamped folder once, before anything resolves from it. The install's dev rung, the state folder (which keeps its step from a linked worktree to the primary checkout) and the probe identity all read it. With nothing recorded (a test, an example, a tool) it is benilla's checkout, as before, and in a player build it is `None`.
- Live `assets/ui` and the addon corpus stay on benilla's own source.
- The example launcher's header now says a dev build of a crate on top keeps these in its own folder.
- The comment above the stamp's resource names the build banner, the stamp's reader beside the panel footer.
Fixes#494Fixes#493🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A crate built on top of benilla now adds its own Bevy plugins through `benilla_app::run_with(build, extend)`: `extend` gets the client fully built, after the game plugins, the asset loaders and the `ExtractSchedule` executor flip, and before the probe fleet, which sees what it adds. `run` is `run_with` with nothing added, so the stock client is unchanged.
- A client started through `run_with` carries `extended` in its build id (`BuildId::summary`), so the startup banner, the crash report and the debug panel's footer all say so, and a report from an extended client reads apart from stock. The entry point sets the flag; a launcher leaves it to `..Default::default()`.
- `crates/benilla-app/examples/extended_launcher.rs` is a launcher with a plugin of its own, compiled by the gates. Its header is the shape a crate of its own copies: the manifest (benilla-app without default features and `dev` forwarded, the workspace's bevy, and the root's `[patch]` and `[profile.dev]`, which cargo reads from the root manifest only), the build script's stamp, and a `Cargo.lock` started from benilla's.
- `tests/launcher_shape.rs` holds that documented manifest to the root `Cargo.toml` and the `benilla` launcher's, so a patch or profile added later cannot leave the documented shape building a client that is not benilla's.
- `docs/CONTRIBUTING.md`: a feature 1.12.1 does not have lives in its own crate on top of benilla, started through `run_with`.
Fixes#483🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Two conflicts: docs/MAP.md (our map plus upstream's cfg_categories/wow_ini modules
and WOW_CHAR row) and the Update ambiguity ceiling in game_plugins.rs (kept ours,
re-measured in a follow-up commit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The startup line, crash reports and the debug panel now name the release a build is, so a bug report carries it: `benilla build 0.2.0 · b17be27 · 2026-09-27 · release` on a release, `0.2.0+12 · …` twelve commits past its tag.
- `benilla-buildstamp` also stamps `git describe --tags --long --match 'v[0-9]*'` and watches `packed-refs` and `refs/tags`, so a fetched tag restamps without a new commit.
- `BuildId` carries the workspace version and the describe. `release()` reads the version, with `+N` when the describe's tag is that version's; with no tag in reach (a source archive, a shallow clone) or the previous version's tag (the release commit before its tag exists) it reads the version alone.
- The bug template's example line shows the new form.
Checked: the four new `build_id` tests fail with the distance parser broken and pass restored; the describe command in a scratch clone reads empty with no tag, `v0.1.0-443-g…` over a launch tag, `v0.2.0-0-g…` on the release commit, and skips tags that are not `v`-numbered; the build script's output carries the new watches and variable. Gates green on macOS; no platform seam touched.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The owner's "wind does not work" was the motion being too small to see (WXL's
0.06 yd grass tips on a 6 s wave, 0.1 yd tree crowns). Retuned at the 9 yd/s
base speed:
- grass: tips about +-0.2-0.3 yd, 2.5 s main wave (182 cycles/wrap), 1.1 s
cross flutter (410); 55 yd gust bands roll downwind (68), swelling the sway
to 1.2x and lulling it to 0.55x; a bent tip dips (no stretched blades).
Parting around units unchanged.
- trees/bushes: 4 s crown sway (114), 5.5 s side wave (82), 0.8 s leaf
flutter (569); crown weight grows with height (0.43x at 1.5 yd, 1x at 6 yd,
1.3x cap), so canopies sway about +-0.3 yd while bushes nod and bases stay
planted.
- new cvar foliageWindStrength 0.25..3 (default 1, not on the preset ladder):
Advanced Graphics slider under Foliage Wind (EN+RU label/tooltip), greyed
while Foliage Wind is Off; carried in sway_strength, so the parting ignores
it; a storm adds up to +60 % sway.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lamp_terrain_occlusion_scan takes an optional light height and ray clearance, counts blocked
samples within 15 yd, prints the ground height, and ranks by the near count. Used to pick the
leftovers B1 scenes (Elwynn campfires, lampposts, bridge lamp): the gather and the terrain
receiver work, the terrain-blocked ground is grazing or at the rim, so the lit image is unchanged.
LIGHTING.md says so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review report (grass and tree sway not moving in the play build) did not
reproduce: the release/play exe publishes grass and tree strength 1 with
foliageWind absent from the config, and capture pairs show sway. The tier is
now logged at boot and on every change so a player run shows what the
receivers were given, and a test holds the no-pin default path: moving
travel, grass and tree rows set, tier 0 clearing them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SKYBOX_FORCE_SUN_SHAFTS (0x8) and SKYBOX_NO_SUN_FOG (0x10) constants, CameraSkybox::flag_weight
(heaviest main layer carrying a flag). update_fog_model scales sun_fog_strength by 1 - weight of a
0x10 sky. LIGHTING.md documents both flags (0x8 consumer lands in the next commit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Casting a targeted spell ends repair mode, as 1.12.1's targeting arm writes its cursor over Repair
- A right mouse-down on the world ends repair mode, as 1.12.1's WorldFrame hook resets the cursor to Point
Fixes#420Fixes#422
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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 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 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>
The comment and string passes' last leftovers, and one lookup made to match the reference.
**Comments that stated a wrong reference fact**, now stating the right one:
- A cast failure clears the GCD, and a failed cooldown-on-event spell also drops its parked record (`0x6e73cc`).
- `SetUnit` is `0x505d70`; `0x476cb0` is the component clone it reaches.
- Interior sound follows the character, where the listener sits, not the camera.
- Template inheritance re-runs `LoadXML` on the one object against each template (`0x76985c`), and reads `name` and `id` from the instance alone (`0x769770`).
- On the ghost-to-alive edge the reference clears its corpse cache and sends nothing (`0x5ee990`, `0x491f50`).
- The name cache has no high-bit miss. An empty-name answer evicts the record in the reference (`0x55f6f0`).
- The combat-log deferred queue (`0xc4e208`) waits only on an item name. A unit name never waits.
- A flying spline's segments are paced by curve length (`0x454320`, `0x453760`), not by chord length.
- A stop within 3 yd of its stop point keeps the arrow nocked (`0x6019e4`).
- A missile's sound id is at `CMissile+0x40`.
- A nil or unresolved `AssistUnit` token is a gap against `0xb8` ERR_GENERIC_NO_TARGET, not a deviation.
**Strings:**
- Test, log and tool messages lose leftover history wording and placeholder labels.
- The extractor's scan summaries describe the current tint and sequence-clock paths.
- The smoke script's failure text carries no private numbers.
**Line continuations:** twelve log, panic and assert strings get back the `\` continuations they had lost. Each printed a run of spaces mid-message. Fixes#106.
**One lookup:** a region's `inherits=` is looked up as one verbatim name, matched case-insensitively (`0x6ee6f0`, `_strnicmp` at `0x6ee747`), as `framexml::expand` already did for frames. The gate before it split on commas and matched case-sensitively, so a texture or font string whose `inherits=` spelled its template in another case never received the template. `a_region_takes_a_mis_cased_template` fails before this change and passes after.
**Tests:** the quest tests stop loading three FrameXML files that no quest file uses.
**In game:** nothing changes for the stock UI. Log lines read as one sentence, and an addon texture that names its template in another case now gets it.
Verified with `scripts/check.sh` on the changed crates, plus the loader and quest tests with the install present.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <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>
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>
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
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
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>