From 61c6691555eb0a99714ebbeaf93b0e512f5d6ee4 Mon Sep 17 00:00:00 2001 From: troytt <47798984@qq.com> Date: Mon, 14 Sep 2026 09:18:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=A1=B6=E8=A7=92=E5=A2=99?= =?UTF-8?q?=E7=BC=BA=E5=B7=A6=E5=8D=8A=EF=BC=9ADS1=20=E5=8F=AA=E5=88=97=20?= =?UTF-8?q?type=203=EF=BC=8C=E5=BC=95=E6=93=8E=E4=BC=9A=E8=87=AA=E5=B7=B1?= =?UTF-8?q?=E8=A1=A5=E7=94=BB=20type=204?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现象:墙角(两段墙交汇的上顶点)左边一块渲染不正常——角的左脸缺失,右半的墙基露出来, 接缝处出现台阶/突块。例:第三章「下水道二层」(sewertreasure1)有 5 处。 根因:D2 的顶角美术是两半拼的,DS1 只存「顶角右半」(type 3),左半(type 4)从不列出; 引擎在装配房间瓦片时会自己补—— DRLGROOMTILE_InitWallTileData(D2Common/DRLG/RoomTile.cpp,1.10f 0x6FD889C0): if (nTileType == TILETYPE_WALL_TOP_CORNER_RIGHT) 再以同格的同份 packed tile 信息追加一个 TILETYPE_WALL_TOP_CORNER_LEFT 图块 我们的渲染器只画 DS1 列的引用,所以这一步从未发生。 改动:buildIsoMapScene 的墙循环里,type 3 解析成功后,用同一 style:sequence 精确解析 type 4 伙伴,同格、按各自的 minBlockY 规则绘制,并把它并入碰撞栅格;新增 scene.cornerPairs / scene.missingCornerPairs 供校验使用。verify-tiles 增加两条断言。 实测:35 关共 411 个 type 3,全部有 type 4 伙伴(缺失 0),墙绘制数 9103 → 9514; 「下水道二层」72 → 77 面墙,与 5 个顶角一致。 --- scripts/verify-tiles.ts | 18 ++++++++++++++- src/game/d2map.ts | 49 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/scripts/verify-tiles.ts b/scripts/verify-tiles.ts index 3e77a6a..2b976bf 100644 --- a/scripts/verify-tiles.ts +++ b/scripts/verify-tiles.ts @@ -12,7 +12,10 @@ // 3. `looseRefs == 0`(没有任何引用走类型无关兜底)与 `missingTiles == 0`—— // 这两条合起来保证"画出的类型 == 引用的类型"; // 4. 位置自洽:地面 `y == (cx+cy)*40` 且瓦片 `minBlockY == 0`; -// 墙 `y == (cx+cy)*40 + minBlockY + 80`;屋顶 `y == (cx+cy)*40 - roofHeight`。 +// 墙 `y == (cx+cy)*40 + minBlockY + 80`;屋顶 `y == (cx+cy)*40 - roofHeight`; +// 5. 顶角(DS1 `type 3`)必须连带画出左半(`type 4`)——引擎在 +// `DRLGROOMTILE_InitWallTileData` 里就是这么做的,DS1 自己从不列左半。 +// 漏掉它时墙角会缺一块,看起来像"渲染不正常"。 // // node scripts/verify-tiles.ts [--dir=samples/d2] [--limit=35] // @@ -69,6 +72,9 @@ async function main(): Promise { let looseRefs = 0 let missingTiles = 0 let positionMismatch = 0 + let cornerRefs = 0 + let cornerPairs = 0 + let missingCornerPairs = 0 const worst: [number, string][] = [] for (const row of tables.levels.rows) { @@ -99,6 +105,13 @@ async function main(): Promise { const seed = levelSeed(info.ds1Names[0]!) const scene = buildIsoMapScene(ds1, libs, seed) + for (const cellRef of ds1.cells.flat()) { + for (const wall of cellRef.walls) { + if (!wall.hidden && wall.prop1 !== 0 && wall.type === 3) cornerRefs += 1 + } + } + cornerPairs += scene.cornerPairs + missingCornerPairs += scene.missingCornerPairs looseRefs += scene.looseRefs missingTiles += scene.missingTiles @@ -139,6 +152,9 @@ async function main(): Promise { check('没有任何引用走类型无关兜底', looseRefs === 0, `looseRefs ${String(looseRefs)}`) check('没有解析不到的引用', missingTiles === 0, `missingTiles ${String(missingTiles)}`) check('地面/墙/屋顶的位置公式自洽', positionMismatch === 0, `${String(positionMismatch)} 个不符`) + check('每个顶角右半都画出了左半伙伴', cornerRefs > 0 && cornerPairs === cornerRefs, + `${String(cornerPairs)}/${String(cornerRefs)} 个 type-3 墙带了 type-4 伙伴`) + check('没有解析不到的顶角伙伴', missingCornerPairs === 0, `${String(missingCornerPairs)} 个`) if (worst.length > 0 && worst[0]![0] > 0) console.log(` 最严重:${worst.slice(0, 5).map(([pct, name]) => `${name} ${pct.toFixed(1)}%`).join(',')}`) const passed = checks.filter(entry => entry.ok).length diff --git a/src/game/d2map.ts b/src/game/d2map.ts index a2ee84e..134c137 100644 --- a/src/game/d2map.ts +++ b/src/game/d2map.ts @@ -66,6 +66,19 @@ const FLOOR_TILE_TYPE = 0 * painter order and out of the wall offset rule. */ const ROOF_WALL_TYPE = 15 +/** + * DS1 wall types of the two halves of a "top" (north) corner. + * + * The engine never draws the right half alone: `DRLGROOMTILE_InitWallTileData` + * (`D2Common/DRLG/RoomTile.cpp`) adds a `TILETYPE_WALL_TOP_CORNER_RIGHT` wall and + * then, in the same cell and with the same packed tile information, a second + * `TILETYPE_WALL_TOP_CORNER_LEFT` tile — fetched from the tile cache with the same + * style/sequence. Without that companion the corner is missing its left face, which + * is what a "broken corner block" looks like on screen. + */ +const TOP_CORNER_RIGHT_TYPE = 3 +/** The companion half of {@link TOP_CORNER_RIGHT_TYPE}. */ +const TOP_CORNER_LEFT_TYPE = 4 /** One tile to draw, in screen pixels. */ export interface IsoDraw { /** Index into {@link IsoMapScene.frames}. */ @@ -155,6 +168,15 @@ export interface IsoMapScene { * `npm run verify:tiles` exists to catch. */ readonly looseRefs: number + /** + * Top-corner-right walls that got their left half drawn as well. + * + * The engine always adds the companion tile (`DRLGROOMTILE_InitWallTileData`), so + * this should equal the number of type-3 walls in the level. + */ + readonly cornerPairs: number + /** Top-corner-right walls whose left half could not be resolved (should be 0). */ + readonly missingCornerPairs: number } /** @@ -405,6 +427,8 @@ export function buildIsoMapScene(level: Ds1, libraries: readonly Dt1[], seed = 0 // skipping them would silently drop blocking sub-tiles. let shadowBlocked = 0 let looseRefs = 0 + let cornerPairs = 0 + let missingCornerPairs = 0 const stamp = (cellX: number, cellY: number, tile: Dt1Tile): void => { for (let subY = 0; subY < SUB_TILES_PER_TILE; subY += 1) { for (let subX = 0; subX < SUB_TILES_PER_TILE; subX += 1) { @@ -494,6 +518,29 @@ export function buildIsoMapScene(level: Ds1, libraries: readonly Dt1[], seed = 0 rawWalls.push(draw) } stamp(cellX, cellY, tile) + + // A top-corner-right wall drags its left half along (see + // {@link TOP_CORNER_RIGHT_TYPE}): same cell, same style/sequence, resolved as + // a type-4 tile, drawn with its own block shift like any other wall. The + // engine's DS1s never list the left half themselves, so this is the only way + // the corner gets both faces — 411 of them across the 35 preset levels. + if (tile.type === TOP_CORNER_RIGHT_TYPE) { + const companion = resolveExactType(pool, wall.style, wall.sequence, TOP_CORNER_LEFT_TYPE, cellX, cellY, seed) + if (companion === undefined) { + missingCornerPairs += 1 + noteMissing('corner-left', wall.style, wall.sequence, TOP_CORNER_LEFT_TYPE, cellX, cellY) + } else { + const leftTile = libraries[companion.library]!.tiles[companion.tile]! + rawWalls.push({ + frameIndex: frameFor(companion.library, companion.tile), + x: orthoX + TILE_ANCHOR_X, + y: orthoY + leftTile.minBlockY + WALL_SURFACE_HEIGHT, + cellX, cellY, library: companion.library, tile: companion.tile, + }) + cornerPairs += 1 + stamp(cellX, cellY, leftTile) + } + } } } } @@ -540,6 +587,8 @@ export function buildIsoMapScene(level: Ds1, libraries: readonly Dt1[], seed = 0 clippedTiles, shadowBlockedSubtiles: shadowBlocked, looseRefs, + cornerPairs, + missingCornerPairs, duplicateRefs: pool.duplicates, } }