Files
benilla-octo/docs/UPSTREAM_SYNC.md
T
gromandClaude Opus 5 44c0b1269c docs: record the upstream sync, the divergence, and what was refused
A record so this is not re-derived. The measurement (349 upstream commits since
the 2026-10-12 base, 589 files a full merge conflicts in), why most of it cannot
be cherry-picked (upstream replaced mlua with its own Lua 5.0 VM, and ~200
commits are the deferred-to-inline conversion of the app/VM contract), the two
resolution traps this sync hit, and all 27 refusals with the reason each.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-10-26 00:02:57 -06:00

144 lines
11 KiB
Markdown

# Upstream sync — samwhosung/benilla
This fork's history descends from [samwhosung/benilla](https://github.com/samwhosung/benilla), by
way of the Everwood graphics fork (`upstream`, `codeberg.org/chairmanpow/benilla-octo`). That
original is not a configured remote; fetch it by URL:
```
git fetch --no-tags https://github.com/samwhosung/benilla main
```
## Where we stand
Measured **2026-10-25**, against merge base `2af91704` (2026-10-12):
| | |
|---|---|
| upstream commits since the base | **349** |
| ours since the base | 983 |
| files a full merge conflicts in | **589** (318 `benilla-app`, 208 `benilla-ui`) |
Of those 349, **11 applied with no conflict** (and all but a few of those were docs or CI). The
sync landed in this tree took **22** after hand resolution and refused **27** with a reason each,
listed below. The rest sit behind the two campaigns in the next section.
The fork's recorded policy for a real sync, from merge `706cd717`, is *"upstream structure wins,
local-unique re-instated."*
## Why most of it cannot be cherry-picked
Upstream is in the middle of two things that make absorbing it a **re-baseline**, not a patch pull.
**1. mlua is gone.** Upstream wrote its own Lua VM — `benilla-lua`, ~26k lines, the client's real
**5.0** dialect — behind `benilla-mlua`, a shim whose own doc says it "answers to the name `mlua`,
so code written against mlua runs on benilla's own interpreter unchanged". Our bindings and the
whole `script/compat/` layer would port on that promise. The catch is the dialect: we run mlua's
Lua **5.1**, and the ClassicAPI work was costed on 5.1 syntax being free (`ClassicAPI.dll`'s other
half is a 5.1-syntax backport for a 5.0 client). On a faithful 5.0 VM that stops being free.
**2. ~200 commits of "verbs act inside their call, events fire at their packet."** Upstream has
been converting the entire client off a deferred/queued app→VM contract onto an inline one: getters
read live state, verbs apply inside the Lua call, events fire at their packet's place. That is the
root-cause class behind several open play-test defects here, which is exactly why those fixes are
unreachable piecemeal — the tooltip rebuild (#1515, #2050, #2072), live aura getters (#2397) and
the combat-log/combat-text pass (#2047, #2081, #2096) are 15-65 conflicting files *each*.
## Resolving conflicts here: two traps
- **`git checkout --theirs <path>` takes the whole file, not the conflicting hunk.** On a conflict
that printed as two lines it cut 1454 lines out of this fork's `OptionsFrame.xml` and replaced
`ScriptLogFrame.xml` outright — 180 test failures, caught only by the suite. Resolve with
`git checkout -m -- <path>` and edit the markers, then diff the file against `main` before
committing. (Reverted in `7a8765aa`; reverting a cherry-pick can itself over-revert, and did.)
- **A clean apply is not a compiling one.** #1809 cherry-picked with no conflict and still broke
the build, naming `crate::client_state::ClientState` — a module upstream renamed and we did not.
A 3-way merge cannot see a path that moved on the other side. Build after every clean apply.
A conflict hunk also drags in neighbouring tests from *other* upstream commits: check each added
test against `git show <sha> -- <file>` before keeping it.
## Not taken, and why
Each of these was fetched, applied and read before being refused. Nothing here is untried.
- **A new nameplate draws its first WORLD batch in the frame it's built (#1341)**
`7923d67a` — Rides upstream's later batch architecture (order::DrawEntry.scope, DrawClips, layer_batch, nameplate::awaits_batch) which we do not have; the fix cannot be separated from that refactor.
- **Worn items answer their cooldown; a spent charged item drops it (#1447)**
`ff3dbc94` — Deferred, not refused: a genuine union merge. Upstream refactors the item feed's cooldown block into use_cooldown() and adds consumable/spell_0, where our side carries nampower's cooldown_legs and the charge pool in the same block (487-line conflict), ramifying into tooltip_item, char_stats, container and doll. Worth a dedicated pass for equipped-trinket cooldowns.
- **GlobalStrings reads no longer hold the UI model borrow (#1467)**
`43dfb27a` — An architecture change, not a separable fix: it rewrites callers onto upstream's app_data_mut/model.now borrow discipline (ready_check_tick's signature) and a talent struct that has diverged (exceptional vs our rank_line).
- **Character select notices a server that drops the link (#1563)**
`70635496` — Upstream rewrote the character-select park loop around a unified ParkRequest channel with in-place rename/enumerate and a Warden refusal state; our park predates all of it, so the dropped-link tick cannot be lifted out.
- **Quest item toasts come from their packet, not from bag-count changes (#1606)**
`f438c19f` — The toast fix arrives bundled with upstream's quest-log fold carry-over rework (fold_dying_vm_quest_folds / seed_quest_folds replacing our collapsed + header_keys), which came in an unpicked commit.
- **A refused shot no longer chains Auto Shot (#1638)**
`87f3b24f` — Needs upstream's QueuedMeleeSpell slot model and pending.current()/report_pops from unpicked commits, and the conflict sits in the exact cast_result block carrying our ClassicAPI cast-event feed.
- **SPELLCAST_STOP fires from the cast result, for every kind of cast (#1704)**
`ec971695` — A wholesale rewrite of the cast slot (PendingCast state->slot, committed->current, new caster_is_player, QueuedMeleeSpell and SpellTargeting joined into one slot), 13 hunks across the file nampower's cast_seq lives in. Re-baseline territory.
- **A dead player or ghost cannot use an object; its cursor grays (#1709)**
`71321043` — Auto-skipped on size: target/click.rs was rewritten upstream (the world-pick campaign), ~42KB of conflict.
- **Chat filters use the client's word and space classes, not Unicode's (#1729)**
`f24320c3` — Wants a new fancy-regex dependency and leans on the NameFilter / NameLists character-name check from an unpicked commit; the gain is profanity-filter word boundaries, which is not worth a new dependency here.
- **A character its font lacks draws nothing, not a box (#1736)**
`f5992d00` — Auto-skipped on size: 371 conflict lines across 2 files — the region was rewritten upstream, so the fix cannot be lifted out of it.
- **Outrunning terrain streaming stalls the frame, not a loading screen (#1824)**
`8731395b` — Auto-skipped on size: 431 conflict lines across 3 files — the region was rewritten upstream, so the fix cannot be lifted out of it.
- **The world's edge holds the player, as in 1.12.1 (#1841)**
`f199fbd3` — Does not build here: it needs TerrainStreamer.one_object (the WDT MPHD bit 0 accessor WdtFile::is_one_object) from an unpicked commit — our WdtFile exposes only global_wmo(), the registered placement, which is a different predicate. 929 new lines reaching into collider residency and the stream stage, for a map-edge case; not worth perturbing terrain streaming in the fork whose value is the renderer.
- **A unit the player cannot select shows no name, as 1.12.1 does (#1895)**
`ecb49a5f` — Needs the PvpNameStrings resource, which exists nowhere in this fork (an unpicked commit), and it refolds the whole show-name ladder into a helper where our SuperWoW NameplateRange cull and UnitXP occlusion cull are applied — while the system's param tuple is already at Bevy's 16 ceiling holding WorldCollision.
- **RAID_TARGET_UPDATE fires after the unit snapshots, so a new mark reaches the target frame at once (#1897)**
`2f9dd94a` — Auto-skipped on size: 304 conflict lines across 3 files — the region was rewritten upstream, so the fix cannot be lifted out of it.
- **GetAuctionItemInfo answers the miss shape for a row not yet cached (#1950)**
`7ab0596a` — Auto-skipped on size: 298 conflict lines across 2 files — the region was rewritten upstream, so the fix cannot be lifted out of it.
- **A nameplate's health fill shrinks with the unit's health again (#1970)**
`866e2cac` — Auto-skipped on size: 525 conflict lines across 3 files — the region was rewritten upstream, so the fix cannot be lifted out of it.
- **A key moved off an action button takes its label along (#1974)**
`f52df3a1` — The fix is a Lua deviation inside assets/ui/FrameXMLFixes.xml, a layer file this fork does not carry at all (our benilla.toc lists its own adapters instead), so porting means creating the file and wiring it into the UI load order. It also patches stock ActionButton_UpdateHotkeys, which pfUI replaces outright, so it would not run on this setup. Available if we adopt the layer.
- **A level-60 character shows the max-level bar, not the XP bar (#1976)**
`05e7cf4b` — Auto-skipped on size: 237 conflict lines across 6 files — the region was rewritten upstream, so the fix cannot be lifted out of it.
- **UnitDebuff's dispelType is SpellDispelType's column 11 on every locale, as 1.12.1 returns it (#1975)**
`cda0affa` — Needs the locale subsystem: upstream splits the dispel class into a localized Name (columns 1-8, via dbc::loc_str and chain.locale()) for the tooltip and the English column 11 for UnitDebuff's dispelType, and both locale_routing.rs and ui_tooltip/spell_feed.rs are upstream-only here. On an enUS install column 1 and column 11 hold the same English string, so the column change alone is a no-op for this client.
- **A moved bar item takes its green border along and leaves none behind (#1977)**
`9969b845` — Auto-skipped on size: 574 conflict lines across 6 files — the region was rewritten upstream, so the fix cannot be lifted out of it.
- **A frame in a saved variable writes as its name, as 1.12.1 writes it (#1996)**
`abcdced1` — Auto-skipped on size: 247 conflict lines across 2 files — the region was rewritten upstream, so the fix cannot be lifted out of it.
- **math.sin, math.cos and math.tan answer past the x87's range as 1.12.1 (#2019)**
`18ab7d84` — Lands in crates/benilla-lua/src/lib_math.rs — upstream's own Lua interpreter, which this fork does not have (we run mlua's Lua 5.1, whose math comes from C). Structurally unreachable without the VM re-baseline.
- **A gather's owed loot close no longer shuts the window its response opens (#2234)**
`ff69fb08` — Auto-skipped on size: 933 conflict lines across 2 files — the region was rewritten upstream, so the fix cannot be lifted out of it.
- **A standing jump keeps its air nudge for a key pressed mid-arc (#2420)**
`3664771b` — Not applicable: the bug is a second 'let moving' shadowing the direction-key one, introduced upstream by #2311's pitch-key integration. This fork has neither — no swim::integrate_pitch_keys, no pitch_emitter, and a single 'let moving' in control() — so there is nothing to un-shadow, and the hunk would instead import #2311's pitch block.
- **A BLP UI texture the device path refuses is the solid green (#2424)**
`10685032` — Auto-skipped on size: 885 conflict lines across 2 files — the region was rewritten upstream, so the fix cannot be lifted out of it.
- **An outline at a 1.5 display scale rings one texel a pass, as 1.12.1's (#2443)**
`f57549d5` — Auto-skipped on size: 196 conflict lines across 1 files — the region was rewritten upstream, so the fix cannot be lifted out of it.
- **Reloading dead keeps the DEATH popup, now a recorded deviation (#2445)**
`a1f2f36c` — Auto-skipped on size: 220 conflict lines across 1 files — the region was rewritten upstream, so the fix cannot be lifted out of it.