The eight Act I outdoor levels (Blood Moor 2, Cold Plains 3, Stony Field
4, Dark Wood 5, Black Marsh 6, Tamoe Highland 7, Burial Grounds 17, Moo
Moo Farm 39) are now generated by the function-by-function port of
D2MOO's D2Common DRLG in src/game/drlg, bit-exact against a native D2MOO
oracle. The legacy Act I generator (src/game/wilderness/*, the Blood Moor
branch and the Act I paths of wilderness.ts) is deleted; there is no
fallback. Acts 2-5 are unchanged.
Port (completes the kernel committed earlier):
- Tile-map stage: DRLGROOMTILE_AddRoomMapTiles with its tile-data,
linked-tile and warp helpers, DRLGPRESET_AddPresetRoomMapTiles,
DRLGOUTROOM_InitializeDrlgOutdoorRoom (drlg-outroom.ts), the
DrlgDrlgLogic.cpp parts they reach (drlg-logic.ts), D2CMP shim updates.
- drlg-dump.ts: runAct1() driver API; schema 3 dumps with per-room tile
lists. The port is now type-checked (tsconfig no longer excludes it).
Integration:
- drlg-map.ts (replaces drlg/adapter.ts): turns a generated level into
the bake's DS1-shaped canvas with exact (style, sequence, type) per
tile and per-cell DT1 masks, objects, seams from the DRLG level links,
warps from their UNIT_TILE preset units; blank cells stay blocked.
- pack-act-assets: one D2 game seed per (act, variant), a DRLG bake job
and index.json actLayouts; scripts/lib/drlg-mpq-source.ts feeds the
port from the MPQs with the oracle's file list.
- act-variants.ts: WorldVariants picks one generated copy per act (and
the Rogue Encampment DS1 that copy fixed); act-scene.ts and
verify-world-walk both use it. act-scene no longer paints grass into
the blank cells of DRLG scenes.
- world-graph: remove the invented Stony Field <-> Dark Wood seam
(gAct1WildernessDrlgLink / gAct1MonasteryDrlgLink keep them apart;
they meet only through the Underground Passage); world-placement
anchors the second group at the Monastery Gate (3000, 1000).
- level-links: findBorderOpening takes an optional edge range.
- d2map: dt1MaskAllows extracted, behaviour unchanged.
Tools and tests:
- tools/d2moo-oracle: tile-map stage and schema 3; the 4 fixtures are
regenerated (scripts/drlg-fixtures.ts).
- scripts/render-drlg-compare.ts (both dumps through the same adapter
and renderer, fails unless the XOR is empty) and
scripts/audit-levels-browser.ts (headless audit of every baked map).
- tests/drlg-act1-oracle.test.ts (all stages, all 4 fixtures) and
tests/drlg-act1-invariants.test.ts; tests and stress scripts of the
deleted generator are retired or now run on Acts 2-5 only.
- verify-world-walk: start in the town the runtime opens and floor the
spawn sub-tile like the movement collision does.
Verification:
- drlg-diff --levels 2-7,17,39 --seeds 32: 32/32 identical at every
stage; --trace-check 64/64, --isolated 32/32, difficulty 1 and 2 16/16.
- render-drlg-compare, 3 bake copies x 8 levels: 24/24 XOR empty.
- Full re-bake: 365 maps, 0 missing tiles. Browser audit: 355/365 maps,
133/136 level ids clean (112/124/129: Act 5 minions without art, as
before).
- verify:world-graph 37583 assertions; verify:world-walk 29/29;
verify:entity-packs, typecheck clean. vitest 6369 passed; the 6
failures (5 ground-item name formatting, 123-var3 waypoint) and the
verify:packs / verify:generators failures also occur at bad6806.
Test pins updated, measured on the new bake: SceneEntrance 172 -> 166
and world-graph edges 286 -> 284 (the removed seam), seamless pairs
27 -> 26 and placement pairs 19 -> 18; full-spectrum floors / walls /
PNGs 1008898 / 237542 / 1390 (the old pins matched no bake of the
current code).
TAG=agy
CONV=a4f9163c-a011-4161-b169-7f361b1ed57e
Commit 2e73d7b changed the Hell Bovine art token from CW to EC
(MonStats.txt hellbovine, Code EC) but never added EC to the act 1 list
of ACT_MONSTER_SPECS, so pack-entity-assets baked no ec-hth atlas and the
Moo Moo Farm's (level 39) Hell Bovines had no art.
- ACT_MONSTER_SPECS[1]: add { token: "EC", weapon: "hth" }.
- Re-baked entities: 70 monster atlases (was 69). entities/index.json is
now sha256 675757cf...; the new ec-hth entry is its only change.
- Tests: pin 70 entities, 66 unique specs and the new index hash.
TAG=agy
CONV=a4f9163c-a011-4161-b169-7f361b1ed57e
Merge pull request 'fix(ui): align Private Stash header and gold text inside TradeStash.dc6 recessed boxes (#493)' (#494) from fix/stash-text-align into main
- Resolve exact Diablo II v1.13c DS1 Type-2 Object #33 (Objects.txt ID 59) Town Portal sub-tiles across all 9 town presets (Acts 1-5) in src/game/portal.ts
- Implement TownPortalSlot.mouthInLevel(levelId) and enterFrom(levelId) per ObjMode.cpp:3245 (wilderness -> town keeps portal open; town -> wilderness closes portal)
- Add 20-charge quantity tracking for Tome of Town Portal (tbk) and Tome of Identify (ibk), right-click casting for tbk/tsc in inventory, and scroll-to-tome refill
- Bake authentic TP DCC sprite atlases (tptrlitophth, tphdlitophth, tptrlitonhth, tphdlitonhth) into public/overlays/town_portal_*
- Render Town Portal entity with OP/ON DCC animation, dynamic blue light (Light=19, RGB 64/110/255), overhead destination label, and proximity walk-to-interact in src/scene/act-scene.ts
- Add unit and parity tests in tests/town-portal.test.ts
Closes#394
1. Sub-Tile Scatter Spacing (D2Common.dll COLLISION_GetFreeCoordinatesImpl 0x6FD9D140):
- Unify GameEngine.triggerMonsterDrop, GameEngine.dropItem, GameEngine.dropGold,
and net-scene monster drops on findIsometricDropPosition (16x8 sub-tile diamond
lattice, 16px orthogonal / 8px vertical spacing) instead of 80x40px macro cells.
- Derive GroundItemEntity (cellX, cellY) from final scattered (finalX, finalY)
via worldToCell rather than overwriting with stale origin cell coordinates.
2. Monster vs. Inventory Drop Metadata & Bilingual Name Parity:
- Add formatBilingualGroundItemName in src/game/ground-items.ts using
translateItemName / translateMagicName / translateRareName so raw monster-dropped
Item objects receive identical '中文名 (English Name)' nameZh as UiInventoryItem.
- Detect runes via isRuneCode in resolveItemQualityString so monster-dropped runes
render with #ff9c18 orange labels matching inventory-dropped runes.
- Preserve flippyFile, dropSound, and dropSfxFrame in itemToUiInventoryItem, fix
socket count resolution so unsocketed items do not inherit base.gemsockets, and
add embedded drop table fallback in resolveGroundItemMetadata.
3. Anti-Stacking for Sequential Player Inventory/Body Drops:
- Simplify act-scene onDropItemToGround / onDropGoldToGround to delegate directly
to engine.dropItem / engine.dropGold at (player.x, player.y) so every dropped
item checks engine.groundItems.all and scatters to a unique sub-tile slot.
Fixes#479