Skip to content

D2LoD Vanilla+0.0.7-R98

Pre-release
Pre-release

Choose a tag to compare

@VTSTech VTSTech released this 03 Nov 10:13
· 68 commits to main since this release
image image image

Release notes — R88 → R98

R88 — Vanilla+ 0.0.7-R88 — 2026-11-02 — tag R88 @ 9526633c (merge 1d401785)

The second upstream sync of the 0.0.7 line — and the SUPERSESSION LEDGER OPENS.

Upstream c8445e73..bc47c6bc (9 commits) merged with the Vanilla+ surface intact:
Ghidra DB 1839 → 1851, byte match 6,232 → 6,242 of 14,642 functions (19.50% →
19.54% of bytes), 22 conflicts / 27 hunks resolved. Where upstream recovered the
retail form of a fork fix, upstream wins and the fork keeps the root-cause note:

  • fix 16 (Defect 13: SFX silent, music OK) superseded by upstream's
    D2FloatIntUnion — same root cause, retail-faithful form.
  • The R54/R85 quest-gossip hand-tables (#13,
    #22) superseded by
    upstream's verbatim retail tables — all 39 aNpcGossipData externs now sized
    retail-exact (28 were unsized); the track-J singular-table watch resolves.
  • Sound constants take upstream's exact decoded decimals (the -2000.0 centibel
    diagnosis preserved).
  • Checked and NOT superseded: the issue #11
    [glide fix 11] hook (the wrapper ships from the Glide3xVK branch, which
    upstream does not carry).

Gates 326/326 (10 stale expectations re-pinned to the superseding state).
Field-run green 2026-11-03 riding the R91 pass.

R89 — Vanilla+ 0.0.7-R89 — 2026-11-02 — tag R89 @ 4014ef65

The monster-components data-model fix (issue #8 hardening).

Raised skeleton/monster component bytes were overlaying pUnitDataPlayer->szCharName[4..];
they now write the monster unit-data union (pUnitDataMonster->Components[0x10],
->pAiGeneral) with null guards (SKILLNEC_SetSkeletonComponentsByMasteryLvl,
D2Game/UNIT/Monster.cpp). Skeleton classes 0x16b/0x16c get their palshift.dat
ownership polarity forced from the live hostility check regardless of byUtrans
(D2Client/UNIT/CUnit.cpp). Field-verified 2026-11-03 riding the R90b run —
"Skeletons are finally white again" (issue #8).

R90 / R90b — Vanilla+ 0.0.7-R90(b) — 2026-11-03 — tags R90 @ fff470d7 + R90b @ 3726f3ce (merge 77e87e02)

The third upstream sync + two Windows-build blockers fixed.

Upstream bc47c6bc..57380e21 (2 commits) merged: Ghidra DB 1851 → 1855, byte match
6,273 of 14,642 functions (19.51% of bytes), 3 conflicts / 9 hunks. The ledger grows:
upstream rebuilt the D3D ground-tile index buffers in retail form (stack
aStripIndices[60] prefill → properly-extent gpD3DGroundTileVertexBuffer; fan
gdwRendererMode = {0,5,0x23,0x1e} byte-match verified), superseding fork fixes 24
(fullscreen black ground) and 25 (perspective floor diagonal bars) — DrawGroundTile
and InitGroundTileCache are now byte-identical to upstream. D2UnitFindDataStrc.h
deleted upstream (structs relocated to D2Common/Unit.h).

R90b fixes the owner's first MSYS2 build of R90: a stale D2UnitFindDataStrc.h
include in VanillaPlusCore.cpp (kitchen-sink block missed by the merge) and a
RENDERER_D3D_DrawGlyph splice referencing float globals the upstream regen deleted.
Cross-verified in the sandbox with llvm-mingw (whole-tree include audit: 14,915
quoted includes → 0 unresolvable; d2.exe links pei-i386). Field-run green
2026-11-03
(see R91).

R91 — Vanilla+ 0.0.7-R91 — 2026-11-03 — commit fd46e40a (tag pending)

The field-verify round — one report, two closures.

The owner's MSYS2 MINGW32 build of R90b went green and the game runs:
"Skeletons are finally white again" — issue #8
RESOLVED IN THE FIELD (the R50 roster-recolor polarity + R51 spawn-polarity write +
R89 component hardening all confirmed on one build; GitHub close pending the owner).
R90/R90b field verification closed with it. Docs-only otherwise; regular rounds
return to the overlay-zip workflow (git bundles reserved for upstream-merge rounds).
Gates 326/326.

R92 — Vanilla+ 0.0.7-R92 — 2026-11-03 — commit fc84bd67 (tag pending)

Issue #26 fix round 1 — the 720p panel blackness root-caught.

"[720p] With only Left or Right UI Panel open, Black triangles on that side of
screen" — root cause: every panel-open adjustment in the world pipeline is a
fraction of the window width, tuned for 640x480/800x600 where the open 800-class
panel reaches the screen edge. The Vanilla+ wide modes center that rigid 800 px UI
(240 px margins at 1280x720), so the culls still assumed edge-hugging geometry and
the strip beyond the open panel was culled to the black backbuffer. Fixed for wide
layouts only (retail byte-identical): the tile walk keeps the full panned span
(Path.cpp), the perspective cull rects open the margin side (D3D + Glide), unit
clicks reject only the real panel rectangle (CUnit.cpp — margin units clickable
again), town-portal fade and weather clamps span the full width. Gates 332/332.
Field result: PARTIAL — left side fixed; right side and both-panels still black.

R93 — Vanilla+ 0.0.7-R93 — 2026-11-03 — commit ab34c7ea (tag pending)

Issue #26 fix round 2 — the three remaining family members.

The border-3 ViewDraw2 world-draw skip (Game.cpp): with both panels open the
whole world pass never ran — every renderer, both perspective modes. The
bottom-band vertical cuts: non-perspective builders anchor-cut at h-0xf,
rendering the perspective-OFF bottom strip black. Plus the case-2 pan-anchor flip.
4 files; gates +6. Field result: PARTIAL — non-perspective single panels
verified; both panels still black; the perspective right margin survived.

R94 — Vanilla+ 0.0.7-R94 — 2026-11-03 — commit a6ef88b9 (tag pending)

Issue #26 fix round 3 — both panels closed.

Fixed the both-panels artifacts (the border-3 world-draw delivery + the D3D/Glide
bottom-band anchors): "both panels open, no more blackness" — green in BOTH
perspective modes and BOTH renderers. Field result: MOSTLY GREEN — the
perspective-ON right-panel margin and the world-slide question moved to R95.

R95 — Vanilla+ 0.0.7-R95 — 2026-11-03 — commit d1e7e350 (tag pending)

Issue #26 round 4 + the no_panel_shift feature — "Can we open the panel without
moving anything?"

Answer: the quarter-screen world slide with one panel open is VANILLA
(TypeOfUiOverlayOverWorld pans the world by ∓ScreenSizeX/4 to re-center the
action in the uncovered strip; exact only when the open panel reaches the screen
edge — 640x480/800x600). At 720p the floating 800-class UI makes the slide drag the
world out from under the visible margins. R95 adds the opt-in no_panel_shift
d2vp.ini feature: while 1, a single open panel takes the border-0 world pipeline
verbatim (Path.cpp camera gate + a neutralizer at the 11 world-draw origins) — the
renderers receive 0 and take their own full-screen no-pan path; zero renderer edits;
retail dims byte-identical; 9 files; gates +10. Field-verified in Glide: "no
shifting is observed when opening a panel ... All blackness appears fixed in Glide
as well" — issue #26 field-green in Glide.

R96 — Vanilla+ 0.0.7-R96 — 2026-11-03 — commit ce77c056 (tag pending)

Issue #26 round 5 — the D3D perspective-toggle triage (diagnostics only, zero
behavior change).

Owner report: "toggling perspective mode in D3D crashes the game ... Glide can
toggle." R96 ships five one-shot d2boot.log probes bracketing the toggle (videoopt
load/apply, world frame enter/drawn, first D3D perspective DrawGroundTile) plus a
read-out matrix (SMOKE §L item 46). The first field pass named the death: D3D
800x600, ~6.5 s of perspective-ON play, then videoopt: perspective apply ... active=1 -> 0 and halt report: line=200 in the same 15 ms tick — Redivivus "Fog's
unrecoverable-error halt", no access violation.

R97 — Vanilla+ 0.0.7-R97 — 2026-11-03 — commit b4211fbe (tag pending)

Issue #26 round 6 — the promote guards: degrade instead of dying.

line=200 exists at exactly two sites: the retail nIndex > nMaxNodes range halt
inside D3DFLOOR_PromoteNodeToMRU / D3DSUBTILE_PromoteNodeToMRU (the D3D
floor/subtile texture caches). Every static link audited retail-clean, so R97 turns
the death into a logged, self-healing degradation (dead cache → skip the draw;
out-of-range slot word → bootlog mark + heal to the alloc path) and re-arms the
probes as per-flip transition marks (world: perspective now %d, view frame [R97],
d3d: perspective now %d, floor tile pass ... [R97]). Gates 356 → 362. The field
pass returned outcome (b) exactly as designed: the guard fired —
subtile promote out-of-range idx=4097 ... healed to uncached [R97] — the game
continued one step, then AV READ at FF600060. The mark's numbers were the
root-cause data.

R98 — Vanilla+ 0.0.7-R98 — 2026-11-03 — commit 1be20222 (tag pending)

Issue #26 round 7 / #27
— the D3D perspective-toggle death ROOT-CAUSED AND ROOT-FIXED.

Both field numbers decode to one defect: RENDERER_D3D_BuildWallTileVerticesNoPerspective
(the D3D flat wall builder — the pass that first runs on the first frame after a
perspective 1 → 0 flip) walked the tile's pSubTiles array by byte offset with a
uint8_t iterator (+= 0x14 for 0x14-byte records — wraps at 256 after 12
records; a decompiler artifact merging the dead nParam4 argument slot; every
sibling iterator in the tree is full-width int). From the 13th subtile the loop
read +4-shifted pseudo-records: the fake index word was the real record's DT1 format
word (idx=4097 = 0x1001 — the word that halted line=200 in R96), and the fake
pixel pointer was the next real record's packed screen offsets (AV READ at FF600060 = nOffsetY −160 / nOffsetX +96), which the R97 heal then dereferenced.
Latent since the original decompilation; perspective-ON walls and both shadow
builders index the array properly, which is why Glide always toggled clean. R98
widens the iterator to int, keeps the R97 guards as defense in depth, and adds a
bounded wide-tile probe (d3d: flat wall subtiles=<n> tile=<p> [R98]). Gates
362 → 364. Field-verified by the owner the same day: "R98 fixed Perspective
Toggle"
— both directions, D3D.

Full Changelog: R91...R98