Commit Graph
782 Commits
Author SHA1 Message Date
pkuzicandClaude Opus 5.5 07d16048b2 MONKEY (gfx seam W): retained character wetness is wet on every facing (wet_body); rain keeps the up-facing law
Open graphics, cherry-pickable to lighting together with 517868a2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:33:38 +01:00
pkuzicandClaude Opus 5.5 8d5c76887c MONKEY (gfx seams): per-rig wet surface row + plugin water foam spawn
Seam W: InstanceSurfaceParams (whole_wet, lower_wet, waterline_y, feather) per rig
slot, a 2048-row table in the light buffer, applied in wow_model.wgsl through the
existing wet_surface/wet_sheen (max with rain wetness, opaque/alpha-test + fade
twins only). Zero row = stock image. Self-contained: cherry-pickable to lighting.

Seam F: WaterFoamSpawn message; plugin foam records live in their own 96-record
pool so they never evict or throttle the stock unit wake/ring records.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:33:33 +01:00
Paul Marcus K e72e12ddc5 Add Ko-fi funding option for marcusaga 2026-10-10 22:52:12 +01:00
pkuzic f545081e9f MONKEY (planar water): planar replaces screen-space reflections - the Water Reflections option is gone
Owner: planar is cheap enough (~1 ms CPU, GPU about even) to replace SSR outright. High water
always reflects with the planar mirror; the waterReflections cvar, its preset rows, the options
row (EN/RU) and the debug knob are removed. The screen-space march stays only as the mirror's
fallback for water at another height than the mirror plane; WOW_WATER_REFLECT=0 remains a dev
A/B switch.
2026-10-09 19:06:59 +01:00
pkuzic 52bc8e2f94 Merge branch 'gfx/1009-water' into gfx/1009-upstream 2026-10-09 17:34:52 +01:00
pkuzic 8279d3b92d MONKEY (planar water): SSR stretch fixed; planar water reflections (Water Reflections: Screen Space / Planar)
SSR: the march accepted rays up to 2 + 0.1*dist yd behind the scene depth and returned the
bisected pixel unchecked, so rays passing BEHIND a thin dock deck converged on its front
silhouette - a vertical smear. After bisection a hit may now sit at most
0.25 + 1.5%*dist + the last interval behind; deeper = passing behind an occluder, march on.

Planar: liquid/mirror.rs (WaterMirrorPlugin) renders a half-res mirrored camera (oblique
near plane at the water) before the world camera into binding 107; no water, no sun
shadows, no post. The plane is voted by a 7x6 ray fan with hysteresis; water off that
plane fades back to SSR. Cost ~+1 ms CPU, GPU about even (SSR march skipped) -> its own
cvar waterReflections (0 Screen Space / 1 Planar, EN+RU option row), on for High/Ultra.
The High preset (= registered defaults) now carries High water (waterQuality 2); Low and
Medium keep Enhanced. Classic and Enhanced captures byte-identical.
2026-10-09 17:34:52 +01:00
pkuzic 2965d30c57 Merge branch 'gfx/1009-unlit' into gfx/1009-upstream 2026-10-09 17:28:45 +01:00
pkuzic ebd40495d8 Merge branch 'gfx/1009-shadows' into gfx/1009-upstream 2026-10-09 17:28:45 +01:00
pkuzic 1a502177d0 MONKEY (wmo lamp pick): enhanced exterior WMO lamps are ranked at the fragment, not the provoking vertex
The enhanced exterior WMO point-light term evaluated falloff per fragment but used the
flat lamp selection of the provoking vertex (snapped to its 33 yd MCNK cell), so a large
wall or paving triangle took one vertex's whole lamp pool and neighbours differed: long
straight-edged dark wedges on Stormwind's gate ramp and yard walls at dusk/night. Ranks
the closest exterior lamps at the fragment (static_gx + fallback wow_model), shared by
the shadowed and unshadowed evaluations. Classic (all light gains 0) keeps the vertex
pick and its packed frame bits; capture scenarios stormwind-training-yard-*/gate-ramp-*.
2026-10-09 17:28:38 +01:00
pkuzic a3badd0fba MONKEY (mcsh model shade): models drop the MCSH 0.5 sun dim while the world shadow lane replaces the bake
A doodad standing on its own baked terrain shadow (Lakeshire gryphon roosts, most forest
trees) was dimmed twice by day: by the MCSH 0.5 sun band AND by the realtime shadow map,
on ground that no longer shows the bake. wow_model.wgsl and static_gx.wgsl now use
terrain.wgsl's own gate (sh_c16.w, weighted by the sun lane so the bake returns at dusk).
Lane off = reference bits. Capture scenarios lakeshire-statue*/lakeshire-gryphons*,
WOW_CAPTURE_EYE/LOOK/MINUTE re-aim overrides.
2026-10-09 16:53:12 +01:00
pkuzic ee6c76b988 Merge upstream main (samwhosung/benilla c2fc3cf3) into the graphics line
Conflicts: capture fixtures/scenarios (both sides appended: kept both), options_tests
row counts (upstream 80->81 rows: ours 100->101 and 99->100), docs/MAP.md regenerated
with scripts/genmap.sh.
2026-10-09 12:50:37 +01:00
pkuzic fcd4d37a15 MONKEY (r19 F4, lighting): per-frame light scan replaced by a room light index (Naxxramas stutter)
Lighting part of benilla-everwood 736e6a27 (monkey_room_index, global_light, static_gx shortlist).

(cherry picked from commit 736e6a27, capture + physics hunks omitted)
2026-10-09 12:44:04 +01:00
pkuzic 78a23dc446 MONKEY (r19, Brill interior light): strict room-visibility test for the torch shell in static_gx
Shader part of benilla-everwood 20e19334 (the rest of that commit is non-graphics).

(cherry picked from commit 20e19334, static_gx.wgsl only)
2026-10-09 12:42:52 +01:00
pkuzic cc1dde91e2 MONKEY (r18 A1 B004 streaming, world crate): chunk-cache key, nearest-first admission, stale-ready latch, emergency ground hold + collider timeout
World-crate part of benilla-everwood e9247623 (terrain_stream, spawn, wmo_portal).

(cherry picked from commit e9247623, benilla-app hunks omitted)
2026-10-09 12:39:41 +01:00
pkuzic ef6b5e673e MONKEY (r18 A1 B002/60056): per-fragment point-light falloff on terrain + exterior props, interior retained props drop the unrelated exterior light sum, shadow-map identity = nearest valid within 0.1 yd, WOW_POINTS_DUMP=all room/light trace
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 801087822f0925a43352bb925e8c1154383c4377)
2026-10-09 12:38:43 +01:00
pkuzic cbfcc3cada MONKEY (r17 V1): B035 - world shadow casters reach-tested by batch bounds, not placement origin (Stormwind gate pop); exterior WMO point lights summed per fragment (blocky hoof glow on bridge paving)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit aade8f16c7798de1320cb189f9d834db8b8f744c)
2026-10-09 12:38:43 +01:00
pkuzic c88dda33a2 MONKEY (r16 M2): Stranglethorn cages are rigid, not wind foliage (B023)
foliage_wind_batch matched 'thorn' anywhere in the path, so Stranglethorn/ActiveDoodads/Cages
swayed. rigid_model basename rejection now also lists cage and prison.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit add0a8edb2b2524b05bf60f045e96a5a6f290112)
2026-10-09 12:38:43 +01:00
pkuzic 10abf2f4db MONKEY (r16 E3): separate MDDF/MODF placement namespaces so same-UID M2 and WMO both stream
Placements.by_id was keyed by the raw 32-bit UID, so an M2 (MDDF) and a WMO (MODF) carrying
the same numeric UID merged: the second registration only bumped the first one's refcount and
never spawned its model (approach-order dependent; e.g. UID 54418 farm.wmo vs redridgefallentree01,
54705 humantwostory.wmo vs elwynnbush09, 55333 redridgedocks02.wmo vs elwynnbush09).
Key is now u64: M2 = uid, WMO = (1<<32)|uid, carried through tile refs, refcount/release, WMO
readiness, straddler bookkeeping, prop merge and collider weld. Spawn metadata keeps the 32-bit UID.
Regression: monkey_mddf_and_modf_with_the_same_uid_stream_independently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit a55cfa184377f87734a352c6b399adc8833b25eb)
2026-10-09 12:38:42 +01:00
Sam SungandClaude Opus 5.5 c2fc3cf304 OnShow and OnHide fire children first, and after OnLoad, as in 1.12.1 (#842)
Show and hide propagated a whole subtree first, then fired from a snapshot, parent first: a parent's `OnShow` ran before its children's, and a child hidden by an earlier handler still got its `OnShow`. A frame visible at the end of its load got no `OnShow` at all.

In 1.12.1 show `0x76ae10` and hide `0x76ad50` mark the frame, walk the live child list re-checking each child's gate, and fire the frame's own handler last (`0x76aef5`, `0x76adfd`). The post-load hook fires `OnShow` after `OnLoad` for a visible frame (`0x76a3b3`), from XML and `CreateFrame` alike.

Now handlers run inside a live post-order walk, and the post-load `OnShow` fires. Tests cover the order, a sibling hidden mid-walk, and one or two `OnShow`s after a load; they fail without the fix. Four existing tests' expected orders flip to the reference's.

Fixes #350
Fixes #59

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:44:15 +07:00
Sam SungandClaude Opus 5.5 dc63a06178 The UI scale is UIParent's own scale over a 768-unit root (#841)
The UI scale was folded into the pixel seam, with the root and `UIParent` at scale 1. Frames outside `UIParent` (dropdown menus, need/greed rolls, the world map) shrank with it, `UIParent:GetScale()` answered 1, and `GetCursorPosition()` was off by the scale.

In 1.12.1 the scale is `UIParent`'s own frame scale (`0x494550` → `0x76ac10`, floored at 0.64) over a 768-unit root. `GetScreenWidth`/`GetScreenHeight` divide by it (`0x494590`), and `GetCursorPosition` (`0x48b820`) does not.

Now the seam is `height/768`, and the scale is applied to `UIParent` at the load's end and on a resize. The nameplates' compensation is gone. Backdrops, hit insets and minimap pings now follow a frame's own scale (`0x76b580`, `0x77e8d0`); tooltips at the cursor and `OnCursorChanged` divide by it (`0x530b20`, `0x77db23`). Tests cover each, and a stock-UI comparison shows nothing inside `UIParent` moves.

Fixes #172

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:17:23 +07:00
Sam SungandClaude Opus 5.5 f0dfa86148 PLAYER_LOGIN fires at world entry, once the player exists (#840)
`PLAYER_LOGIN` fired at the end of the UI load, before the player existed: its handlers saw `UnitExists("player")` nil and level, health and XP 0, at every login and `/reload`. Ace2's `OnEnable` runs there.

In 1.12.1 the load only arms a one-shot (`0x49011d`). The world-enter cascade fires `PLAYER_LOGIN` if armed (`0x49094b`), then `PLAYER_ENTERING_WORLD` (`0x49096a`), inside a sound-suppression bracket (`0x4908d5`–`0x490a56`). It runs from the player's own create (`0x5deb60`), or on `/reload` from the load (`0x490168`) with the player still in the world. A worldport re-arms nothing.

Now the load arms it, and the unit feed fires both, silenced, once the player is seated. A `/reload` loads under the live player and no longer plays the target-select sound. Tests cover login, worldport and reload; they fail without the fix.

Fixes #189
Fixes #191

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:38:06 +07:00
Sam SungandClaude Opus 5.5 87706792cb A frame or region takes no name or id from its template (#839)
An instance with no `name=` took its template's name: `framexml::merge` laid the instance's attributes over the template's, so `name` and `id` carried through. Stock `UIOptionsFrame.xml`'s unnamed `<Button inherits="UIPanelCloseButton">` became the global `UIPanelCloseButton`, with `GetName()` answering it, and any unnamed addon frame did the same to its template's name.

In 1.12.1, `PreLoadXML` (`0x769770`) reads `name` and `id` from the instance node before `LoadXML` walks the templates (`0x76985c`). The texture and font string factories also read `name` once, outside that walk (`0x6f2751`, `0x6f27e1`). So neither is ever inherited.

The merge now drops the template's `name` and `id`. Tests cover an unnamed frame, a named frame (no template id), an unnamed texture and `CreateFrame` with a nil name. All four fail without the fix.

Fixes #394

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:18:42 +07:00
Sam SungandClaude Opus 5.5 d0d1b5def9 WORLD_MAP_UPDATE fires inside the verb that moves the map, as in 1.12.1 (#829)
Reopening the world map after zooming out showed the old map for a frame before the current zone.

In 1.12.1, every Lua verb that moves the map selection (`SetMapZoom`, `SetMapToCurrentZone`, a `ProcessMapClick` hit) goes through the setter `0x4a67a0`. The setter stores the selection, rebuilds the landmark list, then fires `WORLD_MAP_UPDATE` in place before returning (`0x4a6ce4`). So `WorldMapFrame`'s OnShow repaints the tiles before the first draw.

benilla queued the event to the next tick, so the first frame painted the previous map. The three verbs now run the setter whole. The landmark list is projected from per-row sources inside the setter, so the new tiles never show the old map's markers. The engine's own pushes still queue, as a narrowed deviation.

Tests replay the report's steps on the stock UI and check the event fires inside the verb with landmarks rebuilt first; they fail before the fix.

Fixes #811

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:24:25 +07:00
Sam SungandClaude Opus 5.5 208b1e0bb0 Your own name stays blue or green in FFA areas, as in 1.12.1 (#828)
In a free-for-all area such as the Gurubashi battle ring, your own overhead name and selection ring turned red.

1.12.1 colours a player's name and ring through `0x605960` by mutual attackability: `CanAttack` (`0x606980`) in both directions. Both true reads red, only theirs soft blue, only yours yellow. Neither splits on PvP: yourself green or blue, a party member pale, others green or blue. `UnitReaction` (`0x6061e0`) answers friendly for a unit toward itself before the FFA rung (`0x606200`), so you can never attack yourself.

benilla stood in a reaction-rank threshold for that matrix, and its reaction lacked the identity rung. The selector is now the reference's four-arm matrix, `CanAttack` runs in both directions, and a unit is friendly to itself. `UnitReaction`, `UnitCanAttack` and the cursor follow for yourself.

Tests pin each arm and yourself in FFA, on the shipped faction templates too; they fail before the fix.

Fixes #814

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:00:24 +07:00
Sam SungandClaude Opus 5.5 1c5a568191 Escape on the realm list closes only the list, as in 1.12.1 (#827)
Escape on the realm list closed the list and then also quit the game from the login screen, or went back to login from character select.

1.12.1 walks the keyboard frames once per press, strata high to low, and stops at the first handler that takes it (`0x765f10`, leaving at `0x765f64`/`0x765f86`). The realm list is DIALOG strata (`RealmList.xml:246`), so its Escape never reaches the screen beneath, even though it hides itself.

benilla's list and screens read the keys in no fixed order. A screen only checked whether the list was still shown, and the list's own Escape had just hidden it. Who owns a frame's keys is now decided once, before any of them runs (`Realms::owns_input`, set in `PreUpdate`).

Tests run the real list and screen systems in both orders, and fail before the fix with the reported symptoms.

Fixes #817

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 10:30:51 +07:00
Sam SungandClaude Opus 5.5 ea7e0d087a Zone names stay uncoloured on PvE realms outside capitals, as in 1.12.1 (#825)
On a PvE realm, zone names were still coloured friendly, hostile or contested.

In 1.12.1, `GetZonePVPInfo` (`0x48d540`) answers a type only when its display gate is open (`0x48d5c2`/`0x48d5cc`). The gate opens on the realm's PvP flag or in a zone with Flags `0x10`, which is the six capitals. The realm flag is read from `Cfg_Configs.dbc` for the selected realm's type and stored once per world entry (`0x4015de`). A type with no row skips the store. Otherwise the type is nil, so the stock zone splash shows cream and the minimap zone text gold.

benilla now reads `Cfg_Configs.dbc` instead of a transcription that stopped at type 8, so realm type 10 lists as PvP. It latches the realm flag at world entry and applies the gate.

Tests cover the gate, the six capitals and every shipped Cfg_Configs row; each failed before the fix.

Fixes #816

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:53:16 +07:00
Sam SungandClaude Opus 5.5 63e917965d The quest log counts party members on each quest, as 1.12.1 does (#824)
The quest log never showed the `[n]` count of party members on a quest, because `IsUnitOnQuest` was a stub that always answered nil.

1.12.1 (`0x4dfe10`) checks both arguments first. It reads the row's quest id (`0x4df150`; a header or out-of-range row answers nil before the unit is resolved), resolves the token, and admits only you or one of the four party slots, by guid (`0x4e7f70`). It then needs the unit held as a player and scans the 20 `PLAYER_QUEST_LOG` ids for that quest. The answer is 1 or nil. The ids reach party members on the wire. `UNIT_QUEST_LOG_CHANGED` (`0x51bc63` → `0x51bd90`) fires once whenever any slot of a unit's window changes, so an open log recounts.

Unit snapshots now carry the window, the binding follows those steps, and the event fires. Tests cover each path and run the stock `QuestLogFrame.xml` to a `[2]` row; they fail against the stub.

Fixes #818

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:09:41 +07:00
80b0848628 fix: trainer services show their spell's description, as 1.12.1 does (#803)
- capture: trainer scenarios shoot the service description field (#793)
- fix: trainer services show their spell's description, as 1.12.1 does (#793)
- review: reuse the caster closure, cut repeated comments, pin the profession rows
- trainer: the pet's skill value is the tooltip's one helper, not a copy
- trainer: the recipe row's real-data test reads a product that has text

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
2026-10-08 07:16:27 +07:00
Sam SungandClaude Opus 5.5 2e82d34c1b The meeting-stone query runs at every world entry, as 1.12.1 sends it (#810)
The meeting-stone status query (`CMSG 0x296`) and its text reset ran once per UI VM, and the text drop only on leaving the world. So after a cross-map worldport benilla kept the old text and never asked again.

In 1.12.1 the world-enter cascade's meeting-stone init (`0x490a14` → `0x4c9f40`) resets the text to `UNKNOWN` and sends `0x296`, right after the battlefield init, on every pass: login, every cross-map worldport and every `/reload`. The leave-world sweep `0x490a80` drops the text (`0x490b2f` → `0x4c9f80`) on a worldport, a `/reload` and leaving the world.

The enter leg now runs on the world-enter cascade after the battlefield status. The sweep is one edge, written whenever its latch is spent, and the drop reads it. Tests cover the cascade, a worldport, a reload and the schedule order; a live trace shows `0x296` after `0x2D3` and its reply at login, on `ReloadUI()` and after `.tele`.

Fixes #99

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:29:18 +07:00
Sam SungandClaude Opus 5.5 85980939f5 A crate on top finds its own benilla-config/ through config_dir (#809)
A crate built on benilla through `run_with` has its own `benilla-config/`, but could not ask where it is: `local_state::home` is crate-private. A crate that ships an addon has to write it into that folder's `AddOns/`, where benilla discovers addons and resolves their art and fonts, so without the path it needed a fork.

- `benilla_app::config_dir()` answers `local_state::home()`, asked from `run_with` on, since a dev build resolves it from the launcher's folder set there.
- CONTRIBUTING's "Building on top" says a crate's files and addons go there, and names `run_with` and this folder as the two seams opened for crates alone.
- The example launcher logs its `AddOns/` folder through it, so the example, which clippy and the tests build, is the outside caller that keeps it public.

No 1.12.1 behaviour changes.

Fixes #807

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:12:02 +07:00
Sam SungandClaude Opus 5.5 f8493febb0 The out trace logs every world send, the login handshake's included (#806)
The `out` trace tag logged only what the long-lived writer sent, drained after each command. Everything the session sends before the reader and writer split never reached it: `CMSG_AUTH_SESSION`, `CMSG_CHAR_ENUM`, `CMSG_PLAYER_LOGIN`, character create and delete, the cinematic ack and logout. So a login trace could not show where `CMSG_PLAYER_LOGIN` sits against `SMSG_LOGIN_VERIFY_WORLD`, which wire-timing work needs.

The world socket has one write path, `send_packet`. It now reports each packet to an observer right after the write, and the app installs the trace there only when the `out` tag is on. The writer's sent-log and its drains are gone; the line format is unchanged.

A protocol test over the fake server checks that the handshake, a pre-split send and a writer send are all observed, in order. A live login trace now opens with `CMSG_AUTH_SESSION`, `CMSG_CHAR_ENUM` and `CMSG_PLAYER_LOGIN`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 08:45:22 +07:00
ffdc206ac2 World-enter queries wait for the player's create, as 1.12.1 sends them (#801)
At login benilla sent `CMSG_SET_ACTIVE_MOVER`, `CMSG_QUERY_TIME`, `MSG_QUERY_NEXT_MAIL_TIME` and `CMSG_BATTLEFIELD_STATUS` right after `CMSG_PLAYER_LOGIN`, before the server had seated the player. cmangos drops them; after a worldport or `/reload` the time and mail queries never went again.

In 1.12.1 all four follow the local player's own create (`0x5dea50`): `SetActiveMover` (`0x6006e0`), then the world-enter cascade (`0x5deb60 call 0x4908c0`), whose inits send `0x1CE`, `0x284` and `0x2D3` in that order. A cross-map worldport re-creates the player and takes the same route; `/reload` re-enters the cascade alone (`0x490168`).

The four now ride the self create, in that order, and `/reload` re-sends the three. Tests cover login, the create, a worldport, the schedule order and the reload; a live trace shows the order at login, after `.tele` and on `ReloadUI()`.

Fixes #789
Fixes #100

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Elias Miettinen <eliaspmiettinen@gmail.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
2026-10-05 22:28:59 +07:00
bb84f1f1fb Unit tooltips title the unit with its PvP name, as 1.12.1 does (#802)
A unit tooltip's title read the bare name where 1.12.1 decorates it.

The tooltip calls the name builder `0x609370` itself (`0x52a1ab`, or `0x52a1db` when there is no second part) with the decoration flag a literal `1`, the same call `UnitPVPName` makes, so no CVar gates it. With the flag on, a ranked player reads `UNIT_PVP_NAME` of the gendered `PVP_RANK_<rank>_<team>` and the name, a city protector's `PVP_MEDAL<n>` follows on a second line (`0x6093ef`), and a civilian kill (`0x612550`) reads the `PVP_RANK_CIVILIAN` prefix (`0x609461`).

The title now runs the builder `UnitPVPName` already uses. The test covers rank, gender, medal, civilian, an unranked player and a creature; an on-demand `ui-tooltip-rank` capture shows a ranked player's tooltip.

Fixes #794

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
2026-10-05 22:08:33 +07:00
Mathih13andClaude Opus 5.5 ab909df6d9 fix: TargetUnit("npc") selects the interaction NPC, as 1.12.1 does (#800)
`TargetUnit("npc")`, `AssistUnit("npc")` and `SpellTargetUnit("npc")` did nothing with a merchant, gossip or quest window open: the app's unit-token resolver answered nobody for `npc`.

In 1.12.1 the resolver (`0x515970`) matches `npc` as a whole token (`0x515bec`, an unbounded compare, so `npctarget` stays unknown) and takes the interaction NPC's guid `[0xb4e2d0]`, which `0x4930d0` writes when the window opens.

`npc` now resolves to the held unit `InteractNpc` names. Tests cover the three verbs, a closed window and a streamed-out NPC, and `/script TargetUnit("npc")` typed into the stock chat box.

Fixes #707

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 21:57:08 +07:00
622e1c3e7a fix: overhead names carry the player's PvP title, as 1.12.1 does (#792)
- capture: a name-rank scenario shoots a ranked player's overhead name line
- Overhead names carry the player's PvP title under UnitNamePlayerPVPTitle, as 1.12.1 does (#550)
- review: bundle the rank-title inputs; doc the snapshot and rank-19 limits
- nameplates: read rank strings per VM; test the composed name line
- pvp: correct the rank-title docs; convert the credit team without a cast
- Fix the titles row comment; name-rank reuses WOLF_POS, cites :900
- rank strings: the honor feed reads them off the VM; nameplates only reads the value

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
2026-10-04 11:04:32 +07:00
aa103d5a65 fix: level-up gains read signed, so a loss prints -2, as 1.12.1 does (#791)
- Change `LevelUpInfo` to signed to match vanilla behavior
- Add parse test for negative values in SMSG_LEVELUP_INFO, update comments
- grammar
- level-up gains: cite the %d fire and vmangos's wrapped deltas; pin the reported line

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
2026-10-04 10:18:24 +07:00
4a389add47 fix: ignore a loot right-click while moving, as 1.12.1 does (#790)
- A loot right-click while moving does nothing, as in 1.12.1 (#760)
- loot gate: name the emitter's drain and write the landing deviation

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
2026-10-04 10:01:50 +07:00
9bc7276dd8 fix: spell text $g takes the player's gender, as 1.12.1 does (#787)
- spell text: $g/$G take the active player's gender and the chosen form is space-trimmed, so a female character reads "her" (0x508180, 0x508214)
- spell text: trim the $g docs, drop the colon-less $g pin
- spell tooltips: $g reads the view's player store; nets cover gender
- item tooltips: re-substitute $-texts when the gender byte moves
- tests: share one real-spells loader beside Spells

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
2026-10-04 08:29:04 +07:00
pkuzicandGPT-6 Astra (Codex) 4fda6cad50 MONKEY (gfx): contain interior lighting behind exterior walls
Exclude interior sources from unshadowed lamp fog. Require a ready matching shadow map for exterior WMO spill and remove unoccluded room fill on that path. Verified the fog-picker regression in 4314 passing app tests, Windows play build and clean inn/exterior-night captures.

Co-Authored-By: GPT-6 Astra (Codex)
2026-10-04 01:16:10 +01:00
pkuzicandGPT-6 Astra (Codex) bd48bb5478 MONKEY (gfx): repair alpha-blended eye glow materials
Use additive RGB, black fog and no depth writes for named unlit mode-2 eye-glow batches. This preserves the background behind opaque black glow-texture backing. Verified two focused tests including installed Undead models, Windows play build and the app test gate.

Co-Authored-By: GPT-6 Astra (Codex)
2026-10-04 01:16:09 +01:00
pkuzicandGPT-6 Astra (Codex) 6d2804d28b MONKEY (gfx): keep slow carried lights on the live shadow path
Match motion detection to the exact-position geometry key so small hand sway uses the existing stable-mesh moving path. Cover 30, 60, 144 and 240 fps plus settling. Verified Windows play build, 4314 app tests passing, and pixel-identical inn capture.

Co-Authored-By: GPT-6 Astra (Codex)
2026-10-04 01:16:06 +01:00
pkuzicandGPT-6 Astra (Codex) c573ecc5d0 MONKEY (gfx): choose the nearest shadow map for overlapping lights
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)
2026-10-03 21:07:04 +01:00
pkuzicandGPT-6 Astra (Codex) f0e586642d MONKEY (gfx): include terrain in directional shadow occlusion
Append resident ADT triangles to the cached directional caster and invalidate it when terrain residency changes. Hills were absent from the map used by volumetric lighting. Extend the cache refresh regression for terrain arriving independently of retained models.

Verified on Windows with the app library gate, play build, and Northshire/forest captures. The fixed caster contains terrain; the sampled views are compatibility checks, not closure of the tester's original mountain camera.

Co-Authored-By: GPT-6 Astra (Codex)
2026-10-03 21:07:03 +01:00
pkuzicandGPT-6 Astra (Codex) f5547a63bd MONKEY (gfx): keep named rock doodads and ground clutter rigid in wind
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)
2026-10-03 11:48:53 +01:00
cf891dc375 fix: close a loot window that opens while you move, as 1.12.1 does (#786)
- A loot window that opens while you move closes, as in 1.12.1 (#782)
- Set auto_loot in the LootConfig initializer (clippy field_reassign_with_default)
- Close a window opened on the move in the feed's pass, so it never paints

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
2026-10-03 17:36:21 +07:00
pkuzicandGPT-6 Astra (Codex) fbeb0f02f6 MONKEY (gfx): separate fading foliage from downstream material markers
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)
2026-10-03 11:22:53 +01:00
pkuzicandClaude Opus 5.5 494d232c11 Merge upstream benilla main (b396bbf6) into everwood-graphics
105 upstream commits (v0.2.0 ba7fe6e2 -> b396bbf6) onto the graphics programme.

Conflicts resolved, keeping both sides:
- OptionsFrame.xml: upstream renamed every layer global (Options* -> BenillaOptions*,
  OPTIONS_* -> BENILLA_OPTIONS_*, KeyBindings* -> BenillaKeyBindings*, ...) and moved handlers
  to `this`; the Advanced Graphics page (rows, strings, presets) is carried over onto the new
  names, and upstream's Click-to-Move / Spell Detail rows and nameplate store are kept.
- cvars.rs -> cvars/{mod,table}.rs split: our REGISTERED rows go into table.rs, the preset
  ladder + tests into mod.rs beside upstream's ffx/gxMaximize tests.
- video.rs: lighting arms + upstream gxMaximize/ffx arms; VideoConfig keeps both field sets.
- ffx_glow.rs: SkyDither and FfxSwitches both registered/extracted; dither lane on
  upstream's switched player state.
- dest_fx.rs: our AreaLightPlan radius + upstream's spellEffectLevel shard scale.
- fixtures.rs: seed_perf_crowd + upstream's set_enemy_plates.
- options_tests.rs: tipped/described counts 100/99 (upstream 80 + ours 20/19); our Advanced
  Graphics tests use the renamed globals and `this`.
- game_plugins.rs: Update actionable ceiling 4,999 = upstream 4,980 + 19 ours (measured).
- docs/MAP.md regenerated with scripts/genmap.sh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:35:23 +01:00
b396bbf69a Click to Move walks to the ground, along the sky and into melee (#783)
With Click to Move on, benilla walked only to things you interact with. In 1.12.1 a right-click on the ground walks to the point (`0x5e0378` → `0x6102b0`, 0.5 yd short, speed-scaled, no leash, held in a deck's frame). A right-click on the sky with an empty cursor walks along the ray (`0x492dca` → `0x610100`), pitching while swimming. An attack on an enemy out of melee walks to where it stood (`0x60c22b` → `0x60fcc0`, mode 10): the stop is about 1.9 yd because the arm names the player's own guid, the radii are unscaled, and the 80 yd leash says ERR_AUTOFOLLOW_TOO_FAR. Inside melee the player turns to face it.

Deviations, each written where it lives: the melee point keeps the enemy's height, does not snap to the reference's fixed arrival heading, and rides the deck it was armed on.

Tests: the router, the steering, the gates, the leash and the deck frames.

Fixes #758

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Mathih13 <15229325+Mathih13@users.noreply.github.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
2026-10-02 21:03:36 +07:00
Mathih13andClaude Opus 5.5 9821765451 A loot window opening on a unit selects it, as in 1.12.1 (#779)
In 1.12.1 the `SMSG_LOOT_RESPONSE` handler selects a looted unit before it opens the window (`0x5ebc35` → `0x48f3a0`, SetSelection at `0x48f3be`). benilla opened the window without selecting it. So a Tab pressed during a Click-to-Move walk to a corpse left the Tab target selected when the loot opened.

The handler now admits the response and arms the latch, then selects a unit through the one SetSelection path, then opens the window. A refusal, a chest or an item selects nothing. SetSelection's re-swing now uses StartAttack's target gate (alive, then CanAttack, `0x5ecc16`), so switching to a body mid-swing never swings at it.

Tests: the handler's select, dedup, refusals, teardown order and swing stop, a hostile body looted mid-swing, and the stock TargetFrame naming the looted body.

Fixes #763

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 17:33:06 +07:00
HEOJUNFOandClaude Opus 5.5 6c25640a37 Light bands with out-of-order keys interpolate as a cycle, as in 1.12.1 (#768)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 14:14:41 +07:00