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>
ShadowCasterMaterial and CutoutShadowCasterMaterial get one warm rig each (production attribute
sets, layers 0+31: the world camera mints the forward pipeline, a live sun rig's shadow views the
depth one). The cutout pair (MAY_DISCARD opaque + prepass) otherwise first compiles when the first
leaf card enters sun-rig reach, after the cover lifts. Fixes the failing
pipe_warm::menagerie::every_material_lane_has_a_warm_contributor census for real.
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>
0x8: the sky's weight lifts the daylight fade so a low/just-set sun keeps full shafts.
Mask: water writes no depth, so the sea under the horizon read as sky and got a shaft lift
(Westfall sunset: up to 9.7 levels on the water, 0 above the horizon after). The mask pass now
binds the view uniform and keeps depth-0 only where the view ray is above -0.01 elevation.
Adds the leftovers-sea-sunset capture scenario.
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>
Build Blizzard-derived sky assets only from the user's own CASC install, merge lighting DBCs from mounted winners, and pack a preserved patch-Z candidate. Include bilingual install and recovery guidance plus project-owned grading data.
Verified all 46 default stage payloads byte-identical to the reviewed stage; moderated WoW Forever LightIntBand byte-identical; band and mounted-chain checks pass on a hard-linked client copy. No Cargo command run.
Co-Authored-By: GPT-5.6 Sol (Codex)
- A repair the purse cannot cover is refused as in 1.12.1: the not-enough-money error and its voice line, no repair sound, nothing sent; Repair All tests the worn gear alone
- Tidy three comments: the gift-wrap getter's reader, the repair-reset test's claim, the merchant module doc's width
Fixes#412
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Repair costs take the vendor's price discount, 10% off at Honored and up to 10% more by honor rank at a PvP-flagged vendor, rounded as 1.12.1 rounds them
- Flight map fares take the flight master's price discount, as 1.12.1 shows them, while an express flight still sends the full fare
Fixes#411Fixes#434
Co-Authored-By: Claude Opus 5.5 (1M context) <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>
- Right-clicking a bag or worn item puts a held item back first, as 1.12.1 clears the cursor before any use
- The bag frame's end-to-end test: a right-click with a held item puts it back, then uses the slot
Fixes#413Fixes#421
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- fix: clear a standing AFK when /dnd is sent
- The AFK clear runs once, ahead of the type arms, and a test drives /afk then /dnd through the real drain
Fixes#185
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
- Fix trainer tooltip on first hover
- The spell-tooltip feed keeps its in-game gate, and a test holds it after the trainer feed
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
- fix: repair mode
- repair mode: a right-click puts a held payload back, then repairs the item
- repair clicks sound ITEM_REPAIR as they are sent, under the kit's no-duplicates gate
- the repair-all button is no longer driven from Rust
- repair mode: the click bindings' order, for every cursor payload
- worn-item repair clicks drain beside the bag's, in no system of their own
Fixes#147Fixes#148
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
The dawn/weather density curve read WorldTime, the server clock, which stays at its noon default
offline, so a manual or capture time never got the dawn mist. It now reads the rendered minute
through grading's rendered_minute (the same rule as ac9a1ef3). Baselines move: volfog-dawn is
mistier (MAE 8.5), volfog-night is unchanged (0.07).
Co-Authored-By: Claude Opus 5.5 <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 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>
A maintainer reviews it against the reference and usually finishes it on the contributor's branch
(a rebase, a fix, a test, a comment) rather than sending it back, so "Allow edits by maintainers"
stays ticked; it lands as one squash-merged commit with the contributor as its author, or, when
what lands would be mostly ours, as our own commit crediting them as co-author.
Docs only; no gate reads it.
Co-Authored-By: Claude Opus 5.5 (1M context) <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>
The gates of scripts/gates.sh that need neither the install nor a display (fmt, clippy, the
workspace tests without an install, the doc-link and render-pass lints, the player build and its
tests), on ubuntu-latest, so a red pull request is seen before anyone builds it. A branch of this
repository skips the job: it runs the full chain before it lands. The job reads the repository
only, keeps no credentials in the checkout, pins its two actions by commit, drops debuginfo for
the runner's disk and caches dependencies. gates.sh and CONTRIBUTING say what runs where.
actionlint passes; the system packages are the ones CONTRIBUTING names for Linux. The workflow's
first run is a manual one on main once it lands.
Co-Authored-By: Claude Opus 5.5 (1M context) <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>
One comment corrected: the stock unit menu greys Trade and Duel on `HasFullControl()` (`UnitPopup.lua:474-477`, `:502-505`), not follow and trade.
Comment only.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
One test message corrected. A pet's combat-stats snapshot carries no defense pair, because `UnitDefense("pet")` computes the pet's level × 5 itself (`CGUnit_C` `0x613680`). The message added in #391 said benilla had no pet defense, which is wrong.
Verified: the `ui_pet_doll` tests pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <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>
Quests printed no chat lines: no "Quest accepted: …", "… completed.", "Experience gained: …" or "Received …". Nothing raised the reference's quest messages.
Fixes#69.
- **Accept** (`0x5ddd80`): a quest-log slot watcher raises "Quest accepted: <title>" when a slot gains a quest, or keeps its quest and loses the fail bit (`0x5dde17`-`0x5dde34`); a slot that swaps quests says nothing. An uncached quest prints when its record arrives. The QUESTADDED sound now comes from that message row.
- **Turn-in** (`0x5dc400`), in order: "<title> completed." only when the quest has no follow-up in its chain; "Experience gained: N." when the XP is non-zero; "Received …" with the paid coins from the COPPER/SILVER/GOLD globals; then one "Received item: [link]." per fixed reward, and one for the chosen reward, which the client remembers from `GetQuestReward` (`0x50161a`) because the packet does not carry it (vmangos `Player.cpp:14500`).
- Every line comes from the install's strings. A line whose quest or item record is still in flight waits for it; an unknown record is dropped.
- The money line joins only the paid coins ("1 Gold, 5 Copper"). The reference's separator test prints "1 Gold, , 5 Copper" when the middle coin is zero, a quirk not copied.
Tests: six new tests across the quest log, the turn-in handler and the line queue; `scripts/check.sh` is green.
To see it: accept a quest from any giver and read "Quest accepted: <title>"; turn it in and read the completed, experience, money and item lines.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A ghost could swing, got a lit action bar, a sword cursor over enemies, and an Attack button that did not refuse. benilla tested death as health 0 only, and a released ghost has health 1.
Fixes#246. Fixes#248. Fixes#250. Fixes#251.
- **One predicate** (`0x605f30`): dead or ghost is health 0 or below, or a player with the `PLAYER_FLAGS` ghost bit `0x10`. It lives on the object fields and every site uses it: the attack check (`0x612e10`), the usable walk's first check (`0x6e3dad`), summon, trade and the death screen.
- **#248:** the cast ladder refuses a dead or ghost caster with `SPELL_FAILED_CASTER_DEAD` and sends nothing, unless the spell's `Attributes` has `0x800000` (`0x6e4c49`-`0x6e4c68`); the Attack action button takes it first ("You are dead").
- **#251:** the sword cursor also needs a live, unmounted, unpacified player in control (`0x482648`-`0x4826cb`). A right-click now decides attack from the reference's CanInteract/CanAttack fork (`0x60c18c`-`0x60c247`), not from the cursor kind, so a ghost right-clicking a hostile NPC with a service flag selects it and does nothing else, instead of opening its window.
Tests: seven tests, each with a ghost at health 1; `scripts/check.sh` is green.
To see it: release spirit. The action bar stays grey; Attack Target reads "Can't attack while dead."; hovering a hostile mob shows the pointer; right-clicking a hostile vendor selects it and opens nothing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Party and raid join and leave lines, and the join chime, follow the reference: it composes them from two stores, the party slots and the raid roster, where benilla ran one ungated diff over the whole member list.
Fixes#264. Fixes#268. Fixes#269. Fixes#270.
- **Party lines** (`0x5e6a40`): join lines print only onto a group already held (an occupied slot, `0x5e6afb`, or a raid roster, `0x5e6b29`); forming a group as leader prints one line, for the member the list names last (`0x5e6c94`-`0x5e6cc1`); a raid list prints no party member lines (`0x5e6c01`, `0x5e6cd1`).
- **Raid lines** (`0x4ba5f0`, `0x4ba550`): arrivals print only onto a held roster (`0x4ba725`), departures from `0x4ba7a0`; "You have joined a raid group" when none was held (`0x4ba83a`), and "You have left the raid group" when a non-raid list ends a held raid (`0x4ba55f`).
- **Ending the group**: the leave acknowledgement, `SMSG_GROUP_UNINVITE` and `SMSG_GROUP_DESTROYED` empty the party slots first (`0x4e84a0`), so the empty list that follows names nobody.
- **The chime** (`igPlayerInviteAccept`): queued when a member is seated into no held group (`0x5e6dc6`) or joins a held party (`0x5e6c24`); a raid arrival plays nothing.
Tests: eight new tests fail on the old composition and pass now; `scripts/check.sh` is green.
To see it (two or more characters): accept a party invite and hear the chime, with no "joins the party." lines for members already there; `/leave` prints only "You leave the group."; convert a party of four to a raid and party members see only "You have joined a raid group".
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Floating combat text picks the wrong outcome word for mitigated hits, partly mitigated spell hits, damage-over-time ticks and one spell-miss code. Each now follows the reference's own emitter.
Fixes#42. Fixes#43. Fixes#89. Fixes#349.
- **Melee partials (#42):** tested resist (`0x629efd`), then block (`0x629f15`), then absorb (`0x629f30`); benilla tested absorb first.
- **Direct spell hits (#43):** a landed hit words its first partial as `0x62cd80` does: `SPELL_RESISTED`, else `SPELL_BLOCKED` (carrying the blocked amount), else `SPELL_ABSORBED`, else `SPELL_DAMAGE`; a zero-damage hit tests absorbed, blocked, resisted. The wider cause: benilla applied the direct words to every `SMSG_SPELLNONMELEEDAMAGELOG`. The reference's handler (`0x5e85e0`) sends split damage to `SPLIT_DAMAGE` (`0x62de60`) and a periodic log, gated on `CombatLogPeriodicSpells` (`0x62d9ae`), to the periodic emitter; the text now routes the same three ways.
- **Damage ticks (#89):** a landed tick words `RESIST`, else `ABSORB`, else `DAMAGE` (`0x628100`: `0x628354`, `0x62836c`, `0x62837f`); a zero-damage tick words `SPELL_ABSORBED`, else `SPELL_RESISTED`.
- **Spell-miss codes (#349):** the jump table at `0x62be10` sends code 10, 1 and 12 and up to `SPELL_MISSED` (`0x62bdc3`), and code 0 fires nothing.
Tests: six tests in `combat_log/text.rs`; the two over existing functions fail on the old code, and the new routing is covered by three more. `scripts/check.sh` is green.
To see it (Floating Combat Text on, "Show Damage Reduction" on): a damage-over-time tick now scrolls as damage, "-N", and a partly resisted tick as "N (M resisted)"; a partly resisted spell hit reads "300 (100 resisted)" instead of "-300".
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <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>
Closing a loot window left the looted corpse targeted, and changing target left the old corpse's loot window open.
Fixes#238. Fixes#71.
- **One close routine** (`0x48f200`): every close (CloseLoot, the last row or the coins taken, the server's release response, stepping away) runs the same routine, then deselects the source when it is a unit or player with health 0 or below (`0x48f34f`-`0x48f369`). Before, only the movement close asked, and it keyed on the loot type rather than health. A skinned corpse now loses the selection; a live pick-pocket target keeps it.
- **A selection change closes the old target's loot** (`0x4936cc` → `0x493910` → `0x493974`): Tab, Esc, a click-off or the target's death sends the release before the new selection. A chest window survives a Tab.
- Declaring the loot drain's order against the target update also clears about ninety older undeclared schedule pairs, so the ceiling follows down.
Tests: four new or extended tests fail with the behaviours stubbed out and pass now; `scripts/check.sh` is green.
To see it: kill a mob, loot it and close the window: the target frame clears. Loot a corpse and press Tab with the window open: the window closes and the next mob is targeted.
One wire difference stays and is documented at the seam: when Tab closes a window on a dead target, the reference also sends a `CMSG_SET_SELECTION` of 0 before the new selection (`0x493a2a`). The server ends in the same state.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>