Repository navigation
D2LoD Vanilla+0.0.7-R67
Pre-release
D2LoD Vanilla+ — Release 0.0.7
Release notes — R59 → R66
Release: D2LoD-Vanilla-Plus-0.0.7 — the first 0.0.7 build, cut at round R66 (build self-ID Vanilla+0.0.7-R66, confirmed via line 1 of d2boot.log).
Headlines:
- The 1-page shared stash (track N round 1, R63–R66) — the first new gameplay feature of the Vanilla+ line: one retail-shaped stash page shared by every single-player character on the PC, reached from a "Shared" tab on the stash panel (no hotkey), carrying both items and gold, stored in the pool's own side file
_d2vp_shared.sss. Character saves stay retail-compatible by construction. - The save-and-re-enter crash is fixed (issue #19, R60–R61) — "Saving a game and then trying to enter a game again crashes the game": root-caused, fixed, and field-verified by the owner in one day.
- Upstream sync completed through Ghidra 1783 (Rev 74–78) — the 1776–1783 regen absorbed with Vanilla+ intact, field-proven on the first post-sync build (R62: "Builds. Runs. No obvious regressions." plus a full SMOKE pass, items 0–20 green).
Everything ships under the standing invariants: .d2s saves remain byte-retail (the engine gains zero new blocks; fork content rides side files and in-memory snapshots), and every change is opt-in via d2vp.ini — with all keys at their defaults the game behaves as retail.
For the rounds before this window (R46→R52 and R52→R59 — the 720p follow-ups, Den of Evil #13, alt-toggle #15, the item-ilvl feature itself), see the 0.0.6-era round notes and the release table in README.md. R59 is included below because its field verdict and its docs round landed inside this window.
Build index (rounds covered by this release)
| Round | Date | What shipped | Track / issue | Field verdict (owner, verbatim) | Shipped as |
|---|---|---|---|---|---|
| R59 | 2026-10-31 | item-ilvl polish: the gap before (N) is always exactly one space |
track K | "Great job. iLvl spacing appears fixed." | tag R59; overlay …R59-release-notes-r52-r59.zip (docs) |
| R60 | 2026-11-01 | issue #19 instrumentation: roster probes + save-path guards, zero behavior change | issue #19 | probe log delivered; exit state provably clean | overlay vanilla-plus-0.0.6-R60-issue-19-instrumentation.zip |
| R61 | 2026-11-01 | issue #19 root-caught + fixed (the VP Resolution row mutated the static esc-menu table) | issue #19 | "R61 Confirmed fixes #19" | overlay vanilla-plus-0.0.6-R61-issue-19-rootcause-fix.zip |
| Rev 74–78 | 2026-11-01 | upstream sync: Ghidra 1776–1783 regen absorbed + two sync fixes (compile, link) | upstream | proven via R62 below | merges 3d645e7, 19f9791, d6d4b53 |
| R62 | 2026-11-01 | first post-1783 build stamp; full SMOKE checklist (items 0–20) green | — | "Good job. Builds. Runs. No obvious regressions." | overlay vanilla-plus-0.0.6-R62-sync-stamp.zip |
| R63 | 2026-11-01 | shared stash round 1 CODED — 1 page, stash-panel tab, gold + items, _d2vp_shared.sss |
track N | — | overlay vanilla-plus-0.0.6-R63-shared-stash-1p.zip |
| R64 | 2026-11-01 | build fix: mingw32 uint32_t* → LPDWORD in the .sss read out-param |
track N | owner build green (13/14 TUs were already clean) | overlay vanilla-plus-0.0.6-R64-stash1p-fix.zip |
| R65 | 2026-11-01 | Shared tab anchor fix (0x1f8→0x180); mechanism field-proven blind |
track N | owner toggled the page blind: page swap, gold + item deposit, character switch — both returned | overlay vanilla-plus-0.0.6-R65-stash1p-tabfix.zip |
| R66 | 2026-11-01 | Shared tab visibility fixed (0x180→0x130, out of the right-panel art's rectangle) |
track N | "Great job. I see it. It works." | overlay vanilla-plus-0.0.6-R66-stash1p-tabseam.zip |
Docs rounds inside the window (zero source changes): Rev 80 — the pre-R61 round-by-round record offloaded to HISTORY.md (H29); Rev 81 — the R62 full-SMOKE pass recorded + SHARED-STASH-PLAN.md drafted; Rev 85 — the R64→R66 tab geometry story preserved as a comment block on the anchor constant.
Added
1-page shared stash — [features] shared_stash (track N round 1, R63 + fixes R64–R66)
Owner spec (verbatim, 2026-11-01): "Give us 1 page of retail-shaped shared stash to start. Accessible from Stash Panel. No hotkey. Gold and Items can be deposited. If that works we'll do 10 pages. Then 100." — all four plan design calls resolved in one message.
What it does in game. With shared_stash=1 in d2vp.ini, opening any stash in town shows a "Shared" text tab on the same row as the stash gold label (right of center, above the grid's rightmost columns). White = your personal page, green > Shared = the shared page. Clicking the tab swaps the grid between the two sets; the retail stash grid, buttons and hit-testing are untouched. There is no hotkey — the tab is the only affordance, per spec.
- Items: deposit/withdraw works exactly like the personal stash — drag, shift-click, everything retail.
- Gold: with the shared page active, the stash panel's normal gold deposit/withdraw buttons move shared gold (the display reads the shared amount); tab back and the display returns to your own banked gold.
- Cross-character: everything on the shared page survives Save & Exit and is there for the next character on the same PC. Field-proven end to end: "The stash function behind the art - works. … i put gold and an item in there. switched chars. clicked around, and the item and gold were there." (owner, R65 field run).
How it stays retail-safe (the architecture, zero new packets, zero new item bytes, zero server container code):
- While the shared page is active, the player's retail page-4 stash inventory holds the shared set — real item units materialized from the side file by the retail save-stream loader (the same call the trade-cancel restore uses). Every retail stash flow works unchanged.
- The personal set waits in a fork-owned
'JM'memory snapshot written by the retail per-item encoder. Toggling = snapshot → dematerialize → materialize the other set → push the retail 0x9C/0x9D adds/removes so the client grid redraws. - Shared gold borrows the retail
UNITSTAT_goldbankstat while the page is active; the save flush absorbs whatever the retail buttons moved. I1invariant by construction: the flush sits at the top of the single save dispatcher — before any.d2sis built, the shared set is parked into<save dir>\_d2vp_shared.sss(own 20-byte header: magicVPS1, version 1, pageCount 1, gold u32, additive checksum) and the personal set comes back. The.d2sitself never sees a shared item.- Durability: atomic write (temp →
MoveFileExAreplace), one-generation.bak, checksum-guarded reads — a corrupt file degrades to an empty pool with a log mark, and the file on disk is never touched by a misread (six degrade marks; drill = SMOKE §H item 27). - Scope guards: the server handles the toggle packet only when
[features] shared_stash=1and the game is single-player; other game types (and unpatched retail servers) see an out-of-range button id and reject it harmlessly. Per-game client reset rides the wholesale UI reset.
Config: new documented key [features] shared_stash — 0 (default) = vanilla, 1 = the tab. The template the exe regenerates next to d2exe carries the key with the comment; the 2-second poll means flipping the ini mid-session is picked up without a restart.
Limits of v1 (all deliberate): one page (10/100 pages are a version-2 header extension — the header already carries pageCount), single-player only, and the tab is drawn for the 800×600-class panel set (the 1280×720 check follows the 800×600 pass, per the owner's scope call — the hit-testing math is panel-relative and already verified there).
(nothing else new in this window)
R59's item-ilvl polish is a fix to the R55–R57 feature, listed under Fixed.
Fixed
Save → re-enter crash (issue #19, owner-filed 2026-10-31) — R60 probes, R61 root cause + fix, field-verified same day
Owner symptom (verbatim): "Saving a game and then trying to enter a game again crashes the game" — a second-session defect: the first game entry after boot was fine, the next one died ~47 ms after the first server tick with HALT line=1281 (0x501).
- R60 (instrumentation, zero behavior change): three-candidate static audit (regression window clean;
.d2ssave/load engine byte-symmetric across all ten blocks; the party regroup provably correct on clean data), then shipped probes + guards: roster exit/join state probes (roster: clear lists …/roster state: … [R60]), aSaveMercDatanull-guard so a data-less merc is omitted from the save instead of crashing it, a roll-fail mark naming the merc site, and corpse-flags determinism (01 00 00 00, what real 1.10+ files carry — was uninitialized stack garbage written into every corpse record). - R61 (the catch): the owner's R60 field log killed the stale-roster candidate (exit state provably all-NULL) and pinned the crash. The save was never the trigger — the Vanilla+ Resolution row was. The 720p feature mutates a decomp-era static esc-menu table at runtime (adds a third video mode: count 2→3 with an empty stop path); the retail esc-menu loader re-initializes on every game entry, hit the empty stop path, and took the retail fatal
0x501. Classic decomp-port hazard: "static" tables are live data when a fork writes to them. - The fix: loader guards degrade to marks (an empty stop path keeps the prior DC6 — log:
escmenu: video item 1 stop 2 path empty - prior dc6 kept [R61], exactly once at the second entry) + a sub-count clamp + a draw-site NULL guard. Guards, not a halt. - Field: "R61 Confirmed fixes #19" — the second entry loads and plays; no
halt report:, nod2crash-*.txt. Issue #19 CLOSED; the re-enter check is now a standing per-build regression item (SMOKE §G).
Shared tab invisible (R63 feature, fixed R65 + R66) — the word was drawn, then painted over
After R63–R64 the shared stash worked but the "Shared" word could not be seen anywhere ("i don't see any 'Shared' text tab when I open Stash").
- R65 (anchor): the tab's right-edge anchor had been derived from a wrong panel-width read (
0x1f8— the 2×256 px background tiling); the real 800-class panel content stops at+0x190. Anchor pulled to0x180, inside the panel under every width reading. Decisive side effect: with the tab still invisible, the owner clicked where it should be — and the page swapped, gold and items round-tripped across characters. The entire mechanism (client toggle, 0x4F/0x7D packet, page-4 materialization,.ssswrite/read, cross-character persistence, shared gold) was thereby field-proven blind; the hit test is draw-order-immune. - R66 (the paint-over): the owner's hypothesis — "if anything, it is drawing, and then the art is drawing on top of it" — was exactly right. Tracing the frame: the tab draws inside
DRAW_UI_Inventory()'s stash branch, and ~30 lines later the same function blits the right panel art (Panel::InvCharframes 4–7) atScreenSizeX - gnPanelOffsetX - 0x140— its rectangle starts at panel-relative+0x140(abs x=400 at 800×600) and vertically spans the gold-label row. Both shipped anchors (0x1f8,0x180) right-aligned the word inside that rectangle: drawn, then repainted by the art's opaque pixels. Fix: right edge0x130— 16 px left of the art rect — after auditing everything else the frame paints after the tab site (nothing repaints panel-relative x < 0x140 on that row at any supported width). Hit band re-centered on the word (+0x0c8..+0x130), and the whole R64→R66 geometry story is now a comment block on the constant so the next edit can't re-walk the seam. - Field: "Great job. I see it. It works." — tab visible, page toggle working, on the R66 build.
item-ilvl one-space trim (R59)
"Sometimes there is more than one space between Item Name and (#)" — data-driven, hence "sometimes": the tooltip name line is composed from string-table entries (base name, magic affix words, unique/set/rare names), and several of those entries end with their own trailing space, so the (N) suffix landed after one. Worst case: magic items whose suffix affix word is the last component. Fix (fork TU only, zero decomp edits): ITEMILVL_AppendNameSuffix now strips the caller name's trailing spaces/tabs before appending — the gap before (N) is always exactly the one space the format owns. OFF still never writes the buffer. Field: "Great job. iLvl spacing appears fixed."
Internals
- Upstream sync to Ghidra 1783 (Rev 74–78, three merges:
3d645e71776–1781,19f97911782,d6d4b531783). The regen waves re-typed register parameters, dropped phantom params and normalized switches across the tree; all three absorbed with Vanilla+ intact (markers 0 repo-wide,ghidra_version.cpp= 1783). Two merge casualties caught and fixed immediately: the 1781 resolution droppedD2Game/GAME/SCmd.cpp'sd2_boot.hinclude (restored, Rev 76) and took the regen-header side of theDrawSolidRectExBYTE-vs-DWORD fix, silently overloading it untild2.exefailed to link (definition realigned to BYTE + paired signature tripwires, Rev 77). The whole chain field-run on the first post-1783 build (R62). - Tripwire gate suite: 240 → 283 rows, all green. +6 in R60 (probes/guards), +6 in R61 (escmenu degrade/clamp/retired-halts), +2 sync signature pairs, +25 in R63 (the stash architecture: sites, includes, ini keys,
VPS1magic, marks, atomic write,.bak, goldbank borrow, retail encoder/loader/remove/0x9C, SP guard), +1 per fix round after (R64DWORD dwGot, R65 tab anchor, R66 the must-not-match "out of right-art cover" tripwire). Suite:tools/check-vanilla-plus.sh, manual-only by charter since Rev 58. - Save-path hardening that ships even if you never touch the shared stash (R60, kept): the merc petdata guard and the corpse-flags determinism init make the
.d2swriter defensive and deterministic on data the retail path used to trust or leave uninitialized. - Docs: pre-R61 journal offloaded verbatim to
HISTORY.mdH29 (Rev 80);SHARED-STASH-PLAN.mddrafted and maintained as track N's design record (Rev 81); SMOKE-CHECKLIST gained §G (re-enter regression, items 18–20) and §H (shared stash, items 21–27). - Self-ID:
d2boot.logline 1 readsVanilla+0.0.7-R66for this release (D2Client/Engine/Application.cpp:66).
Known issues (open at the 0.0.7 cut)
- #8 — Necro skeleton spawn tint: R51 spawn-polarity fix is in, field re-verify pending. Symptom: own raised skeletons spawn yellow. One useful datum for the next round: note whether a skeleton ever re-tints (e.g. after a roster-update event) or stays yellow forever — "never re-tints" would falsify the R51 theory. Listed as known-fail in SMOKE (skip, don't burn time).
- The roster-heal corruptor watch stays ARMED.
roster heal:cap marks ind2boot.logmean real roster corruption — the regroup/flatten walks are provably correct on clean data (re-scoped R60). Two marks on the R60 log's fresh session remain unexplained; the crash fix does not depend on them (the regroup spin terminates on a healthy chain), but the watch closes only on a confirmed root cause. If you seeroster heal:in the log, send the log. - Shared stash v1 boundaries (by design, not defects): 1 page / SP-only / 800×600-first (720p tab check owed, then a hardening round: the retail
.d2sround-trip proof — SMOKE §H item 25 — and the checksum degrade drill — item 27 — are still owed on the R66 build).
Upgrade notes
- From 0.0.6 (any round): drop-in. No save conversion, no packet changes, no ini keys removed. New key
[features] shared_stash=0appears in the regeneratedd2vp.initemplate — the game stays vanilla until you opt in. - First boot: line 1 of
d2boot.logmust readVanilla+0.0.7-R66(R66 or lower with a 0.0.6 prefix = wrong exe, don't test further — SMOKE item 0). - Using the shared stash: set
shared_stash=1, save/exit once (or wait for the 2 s ini poll), open a stash in town, click Shared. The pool file_d2vp_shared.sssappears in the save folder on the first tab-back — that file is the shared page; your.d2sfiles are untouched by it and remain fully retail-loadable. - Rollback: set
shared_stash=0(or delete the key) — the tab disappears and the stash is retail again. The pool file is ignored, not deleted. - Smoke test:
SMOKE-CHECKLIST.md— §A/§B for any new build, §G as the #19 regression check, §H items 21–27 for the shared stash. - Source users: apply the 0.0.7 overlay over the Ghidra-1783-synced tree (idempotent over R66), or fast-forward with the git bundle:
git fetch vanilla-plus-1776-1783-R66-0007-release.bundle main:main. Then rebuild on Win11 (tools/build-local-win11.cmd) and verify the self-ID.
Full changelog (Rev 69 → Rev 86)
| Rev | Round | Summary |
|---|---|---|
| 69 | R59 | item-ilvl: exactly one space before (N) — trailing-space trim in ITEMILVL_AppendNameSuffix; gates 239→240 |
| 70 | — | docs: track K field verdict recorded; consolidated release notes R52→R59 |
| 71 | R60 | issue #19 instrumentation: roster probes, merc petdata guard, corpse-flags determinism; gates 240→246 |
| 72 | R61 | issue #19 root-caused + fixed: VP Resolution row vs the static esc-menu table; degrade guards; gates 246→252 |
| 73 | — | docs: track M close record (ISSUE-19-BODY.md), issue #19 CLOSED |
| 74 | sync | upstream Ghidra 1776–1781 absorbed (merge 3d645e7) |
| 75 | sync | upstream Ghidra 1782 absorbed (merge 19f9791) |
| 76 | sync | sync compile-fix: d2_boot.h include restored in D2Game/GAME/SCmd.cpp; gates 253 |
| 77 | sync | sync link-fix: DrawSolidRectEx definition realigned to BYTE + signature tripwires; gates 255 |
| 78 | sync | upstream Ghidra 1783 absorbed (merge d6d4b53); markers 0, stamp 1783, gates 255/255 |
| 79 | R62 | stamp: first post-1783 build field-proven ("Builds. Runs. No obvious regressions."); self-ID R62 |
| 80 | — | docs: handoff trim — pre-R61 journal → HISTORY.md H29 |
| 81 | — | docs: R62 full SMOKE pass recorded; SHARED-STASH-PLAN.md drafted |
| 82 | R63 | shared stash round 1 coded: page-4 materialization, 'JM' snapshot, _d2vp_shared.sss (VPS1, atomic write, .bak, checksum), goldbank borrow, Shared tab (0x4F/0x7D, no hotkey), SP guard; gates 255→280 |
| 83 | R64 | build fix: mingw32 uint32_t* → LPDWORD (DWORD dwGot) in the .sss read; gates 280→281 |
| 84 | R65 | tab anchor 0x1f8→0x180; mechanism field-proven blind (page swap, cross-char gold + item); gates 281→282 |
| 85 | R66 | tab right edge 0x180→0x130 (out of the right-panel art rect), hit band re-centered +0x0c8..+0x130, geometry story commented on the constant; gates 282→283; field: "I see it. It works." |
| 86 | — | this release: 0.0.7 cut — self-ID Vanilla+0.0.7-R66, release notes R59→R66, README/VANILLA-PLUS release rows, SMOKE build lines, track N field status |
Tag: D2LoD-Vanilla-Plus-0.0.7 (cut on the R67 content state). Between tags, work continues as revision rounds (-Rxx); the shipped build self-IDs Vanilla+0.0.7-R67.