- Add src/i18n/lang.ts for URL/query/storage language detection, runtime state, and language switcher URL generation preserving query parameters
- Expand scripts/pack-canonical-tbl.ts and regenerate src/data/canonical-tbl-strings.ts with 3,721 ENG and 3,374 CHI 1.13c .tbl entries (UI, monsters, SuperUniques, levels, skills, items)
- Add completedEn speech strings for Act II Quest 2 and Act IV Quest 2 in src/data/canonical-quest-data.ts
- Add index.html bilingual entry portal, acts-zh.html (CHI), acts-en.html (ENG), and language switcher in acts.html; configure multi-page rollup inputs in vite.config.ts
- Localize all 15 UI/game modules (level-names-zh, character-sheet, control-bar, globes, ground-items, ground-labels, item-bridge, inventory, skill-tree-panel, hotkeys, npc-table, world-panels, monsters, hud-manager, act-scene) for authentic CHI and zero-CJK ENG modes
- Add tests/issue-503-bilingual-parity.test.ts and scripts/verify-issue-503-browser.ts
TAG=agy
CONV=5016e6fd-4af8-421f-ae4b-6d443a0a102a
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