Skip to content

D2LoD Vanilla+0.0.6-R52

Pre-release
Pre-release

Choose a tag to compare

@VTSTech VTSTech released this 31 Oct 06:58
· 265 commits to main since this release
image

D2LoD Vanilla+ 0.0.6 (R46 → R52)

The post-release repair era. Seven source rounds — R46 through R52, all landed 2026-10-30/31 — driven entirely by field reports against the shipped 0.0.6 build, closing five GitHub issues (#11, #10, #12, #9, #14) and shipping the two-site fix for the sixth (#8, field pass pending). The D2LoD-Vanilla-Plus-0.0.6 release page carries the 0.0.6 cut itself (build R45); everything after it lives here. Same drop-in layout throughout: each build is a rebuilt d2.exe on the 0.0.6 tree — extract over the install and go; the bundled glide3x.dll (Glide3xVK 0.4.6) is unchanged; every opt-in feature still ships OFF — with no d2vp.ini the game behaves like retail; and every build self-identifies on the first d2boot.log line (Vanilla+0.0.6-R46 … Vanilla+0.0.6-R52), which pins the exact binary before any analysis. Publish state as of these notes: the R46 build rides the D2LoD-Vanilla-Plus-0.0.6 release page, R47–R49 are tagged pre-releases, R50–R52 await the owner's tag pass — the builds and their overlays exist; the tags are the owner's ritual.

Build index

Build Round What it fixes Issue Field verdict Shipped as Overlay
R46 Rev 47 Glide -3dfx first-frame divide-by-zero (texture-memory init + upload guards restored) #11 — CLOSED "ok Glide i working now" D2LoD-Vanilla-Plus-0.0.6 release page (asset D2LoD-Vanilla-Plus-0.0.6-R46.7z) vanilla-plus-0.0.6-R46-glide-texmem-fix.zip
R47 Rev 50 Auto Gold Pickup dead trigger (re-parented VP block) #10 — CLOSED "Great job. Auto Gold Pickup working again." tag R47 vanilla-plus-0.0.6-R47-auto-gold-fix.zip
R48 Rev 52 720p deposit-gold dialog widgets (wrong-branch riders) #12 — CLOSED "Great job. 12 Closed, R48 Field Verified." tag R48 vanilla-plus-0.0.6-R48-gold-dialog-branch-fix.zip
R49 Rev 53 Unique monsters base-colored (RandTransforms dead path) #9 — CLOSED "R49 Field Tested, Confirmed Unique Monsters are Unique again." tag R49 vanilla-plus-0.0.6-R49-track-c-unique-tint.zip
R50 Rev 55 Skeleton gold, site 1 of 2 — roster pet-recolor polarity #8 (with R51) necessary, not sufficient — "Necro Skeletons are still yellow" not yet released vanilla-plus-0.0.6-R50-skeleton-recolor-polarity.zip
R51 Rev 56 Skeleton gold, site 2 of 2 — spawn-time ownership polarity #8 — field pass pending pending not yet released vanilla-plus-0.0.6-R51-skeleton-spawn-polarity.zip
R52 Rev 57 720p belt hover popup collapse + tooltip anchor; d2boot.log overwrite #14 — fix confirmed "R52, Hover belt confirmed fix" not yet released vanilla-plus-0.0.6-R52-belt-hover-y-virtualization.zip

Fixed

  • (R46) Glide (-3dfx) died ~0.5 s after the first frame with an integer divide-by-zero (0xC0000094). The merge port that carried the 0.0.6 tree to its upstream base (d6affdd, 2026-10-30) resolved RENDERER_GLIDE_InitTextureMemory (D2Glide/Renderer/Glide.cpp) to the fork's pre-merge version of the function, silently dropping the retail texture-size assignments (nSubtileTexSize = 0x400, nFloorTexSize = 0x8000) together with the 3.5 MB VRAM gate, the debug-font hookup, the TMU≥2 perspective memory split, and two glide upload guards (the fix-3 class-slot and fix-2 stale-nodeIdx checks). The floor/subtile cache initializers then divided (nVramEnd - nVramStart) / dwSize by an uninitialized stack slot — the exact first-frame death, right after the init-time grChromakeyValue call; the Vulkan wrapper (swapchain, slang chain) was never at fault. Both function bodies are restored byte-exact from the pre-port commit. Field-verified on the owner's build ("ok Glide i working now") — root cause + fix documented in #11.
  • (R47) Auto Gold Pickup never triggered. With [features] auto_gold=1 set, walking over gold piles collected nothing, while clicking piles still picked them up. The same merge port had re-inserted the VP [auto-gold] trigger block one brace too early inside DRAW_UI (D2Client/UI/Draw.cpp) — inside if (GFX_bShowUIFlag.HelpMenu), the full-screen help-overlay block — so the per-frame AUTO_GOLD_Tick() only ever ran while the help overlay was open; in normal play the trigger was dead, and every presence-based check saw the call as intact. The block is back at its per-frame top-level placement (the last field-verified pre-port tree, 44e3518), with an invariant comment and a gate row that fails if it ever moves again. This fix named a new casualty class — the re-parented VP marker — and its house rule: presence is not placement. Field-verified on the owner's build ("Great job. Auto Gold Pickup working again") — closed in #10.
  • (R48) At 720p, the stash deposit-gold dialog's widgets sat misaligned. The modal's background rode the wide-layout offsets, but the edit box, OK and Cancel buttons and the spinner stayed vanilla-800-space. Third casualty class of the same merge port: wrong-branch resolution — the upstream regeneration had inverted the widget-alloc null-check shape, and the port resolved each VP-marked widget rider onto the constructor call that LOOKED like the original, now in the allocation-FAILED arm and immediately followed by the same-LHS = 0 null-out: a dead store in normal play (the raw 800-space constants ran — the field symptom) and a latent OOM null-deref. All four riders are moved into the allocation-success branches. Field-verified on the owner's build ("Great job. 12 Closed, R48 Field Verified.") — closed in #12.
  • (R49) Unique monsters rendered in base colors. The rolled unique/champion tint indices (9–29) resolve exclusively through the global RandTransforms.dat table, but the transcribed PALSHIFT_GetMonsterTransform (D2Client/GAME/PalShift.cpp) early-returned nullptr for any monster without a palshift.dat entry BEFORE the palette-index switch — every random unique looked its tint up through a dead path and drew its base palette. Not a merge casualty: a baseline transcription defect, verbatim at the last field-verified pre-port tree — no marker audit could ever catch it. The unconditional early-out is deleted and behavior-identical guards re-homed inside the two entry-consuming arms; the owner's own proposed fix (a MonStats2 component memcpy) was evaluated first and retired with the 0xAC wire-width proof. Field-verified on the owner's build ("R49 Field Tested, Confirmed Unique Monsters are Unique again.") — closed in #9.
  • (R50) Necro skeletons spawned gold, part 1: the roster pet-recolor wrote the inverted hostility flag. ROSTER_UpdateHostileColors/ROSTER_UpdateMercHostileColors wrote (uint32_t)!bHostile into both the palshift index and its re-sync latch shadow — feeding every own raised skeleton the yellow HOSTILE palshift sub-table instead of bone-white sub-table 0. Baseline transcription defect, verbatim at 44e3518; every upstream branch carries it, so there was nothing to cherry-pick. All four writes flipped to the raw flag. The R50 field pass proved this fix necessary but NOT sufficient ("Necro Skeletons are still yellow") — the spawn-time write below decides the first render.
  • (R51) Skeletons gold, part 2: the spawn-time ownership fallback carried the same inversion. CreateObject's MonStats2 Utrans==0xFF ownership fallback (D2Client/UNIT/CUnit.cpp — the packet path every client-visible monster takes, single-player included) transcribed nPalShiftIdx = (uint32_t)!uMercType: the same inverted polarity, at the site that runs FIRST, and single-player summons never trigger the event-driven roster recolors — so idx 1 (yellow) stuck for the unit's whole life. Spawn now writes the raw flag; both write sites agree on the raw-flag convention (own/ally = 0 → bone-white sub-table 0, hostile owner = 1 → yellow sub-table 1). Awaiting its field pass on #8 — the manifest's last open item (multiplayer note: hostile-owner summons now correctly spawn yellow; pre-fix they spawned white — the tell was inverted on both sites).
  • (R52) At 720p, the expanded belt hover popup collapsed on the next mouse event. The shared expanded-belt hit-test funnel PANEL_CalculateBeltSlotIndex (D2Client/UI/Panel.cpp, ~30 call sites) had mouse X virtualized into 800-space (site-5) but compared mouse Y raw against the Belts.txt 720p rect's 600-absolute bounds — at 1280x720 the real cursor sat one height-delta (60 px) BELOW the tested rect, every in-rect test failed, and the popup closed on the next mouse event (the small-HUD open path is resolution-independent and worked, hence opens-then-instantly-closes). Fix: nVirtualMouseY = nMouseY - VPRES_GetWideUiOffsetY() feeds both Y bounds — offset 0 at 640x480/800x600, retail byte-identical. Same-class secondary defect fixed in the same round: the belt tooltip anchor was raw 600-space and would have drawn 60 px high once the popup stayed open; it now rides the re-centered rect. Field-verified on the first pass ("R52, Hover belt confirmed fix") — #14 carries the record (ISSUE-14-CLOSE-COMMENT.md is the paste-ready close comment).

Internals

  • Gate ledger, 194 → 206. Six rows pin the restored glide texture-memory surface (R46 → 200), then one row per fix: auto-gold trigger placement (R47 → 201), gold-dialog riders in the alloc-success branch (R48 → 202), palshift rand-transform reachability (R49 → 203), roster pet-recolor polarity (R50 → 204), spawn ownership polarity (R51 → 205), belt hover Y virtualization + belt tooltip Y anchor + boot log overwrite (R52 → 208). At Rev 58 the two version-self-ID pins (banner + build version) were retired on owner directive — "gate is for features and fixes" — leaving 206/206; the build check is manual (first d2boot.log line, SMOKE-CHECKLIST.md item 0).
  • The suite is a manual merge audit again. It left CI at Rev 48 (between the R46 and R47 builds) on owner call — a red gate must block a merge, not a build — after a d6affdd CI run showed exactly the two glide casualties the fix rounds then healed.
  • Three casualty classes, three instruments. The era named and instrumented: the re-parented VP marker (scripts/rev47_vp_placement_audit.py; rule "presence is not placement"), wrong-branch resolution (scripts/rev48_wrong_branch_audit.py), and the baseline transcription defect no marker audit can catch (R49/R50/R51 — provenance-first method, scripts/rev49_trackc_provenance.sh). docs/merge-upstream.md carries the war stories; merge step 4 runs the suite plus both audits.
  • The d6affdd merge-port ledger closed at five healed casualties: glide texture-memory + upload guards (R46), auto-gold placement (R47), gold-dialog branches (R48) — after the sound FP constants (rev41) and the inventory shift order (rev44) before the era.
  • d2boot.log now overwrites every run (R52 rider, owner call: the log should never accumulate) — GENERIC_WRITE + CREATE_ALWAYS, fresh log at the first mark; hangwatch/crash writes ride the same handle. Gate row boot: log overwrites per run.
  • SMOKE-CHECKLIST.md added (R52): the per-build in-game smoke pass as a living checklist; item 13b (belt hover) is CONFIRMED and stays as the per-build regression check.
  • Docs shipped through the era: paste-ready close comments for every closed issue (#11, #10, #12, #9, and #14 — the last in-tree as ISSUE-14-CLOSE-COMMENT.md), README field-status rewrites per fix, and the handoff trims to HISTORY.md (H26 = revs 40–56 + the closed tracks; H27 = the belt-hover arc).

Known issues

  • Necro/minion skeletons can spawn gold (#8) — the only open item. One baseline defect, two inverted-polarity write sites, fixed across R50 (roster pet-recolor) + R51 (spawn-time fallback); awaiting its field pass on an R51+ build: raise skeletons → bone-white AT SPAWN (no roster event involved); in multiplayer, hostile-owner summons must spawn yellow. If still yellow, the next discriminator is live: force idx 2 (the TransLvl path) on a skeleton and compare its render against the idx 0/1 renders — the remaining suspect family is the palshift.dat entry parse itself (loader table order vs retail).
  • Glide regression watch (closed, keep an eye): after any rebuild or wrapper pass, the H26 §4.3 step 4 checklist — world visible (not black) at the 720p stop, fps overlay reads "Video: Glide", cutscenes resize to 640x480 first (retail behavior), the 800x600 stop byte-identical.

Upgrade notes

  • Extract over any 0.0.6-era install (0.0.6 through R51); saves, realm logins and d2vp.ini carry over. If d2vp.ini is missing, a documented all-defaults template is written next to the exe on the next start. The bundled glide3x.dll (Glide3xVK 0.4.6) is unchanged across the whole era.
  • Pin the build before any analysis: the first d2boot.log line must read the Vanilla+0.0.6-Rxx you meant to run (table above). From R52 the log starts fresh every run — old marks no longer accumulate.
  • -3dfx is playable end-to-end again (R46): run d2vp.exe -3dfx with the bundled glide3x.dll beside it.
  • auto_gold=1 picks up gold piles on walk-over again (R47; each pile vanishes within ~0.2 s of entering the instant-pick radius) and coexists with alt_toggle=1.
  • 720p regression sweep per build: inventory cells and skill flyouts (0.0.6), deposit-gold dialog (R48), unique/champion tints (R49), belt hover popup + slot tooltip (R52 — SMOKE-CHECKLIST.md item 13b). 800x600 and 640x480 stay retail byte-identical.
  • The one outstanding field ask: #8 on an R51+ build — skeletons bone-white at spawn.

Full Changelog: D2LoD-Vanilla-Plus-0.0.6...R49 — tags for R50–R52 are the owner's release call; R52 is the current working tree.