修复顶角墙缺左半:DS1 只列 type 3,引擎会自己补画 type 4
现象:墙角(两段墙交汇的上顶点)左边一块渲染不正常——角的左脸缺失,右半的墙基露出来,
接缝处出现台阶/突块。例:第三章「下水道二层」(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 个顶角一致。
This commit is contained in:
parent
817dfe5411
commit
1068fce6fa
|
|
@ -12,7 +12,10 @@
|
||||||
// 3. `looseRefs == 0`(没有任何引用走类型无关兜底)与 `missingTiles == 0`——
|
// 3. `looseRefs == 0`(没有任何引用走类型无关兜底)与 `missingTiles == 0`——
|
||||||
// 这两条合起来保证"画出的类型 == 引用的类型";
|
// 这两条合起来保证"画出的类型 == 引用的类型";
|
||||||
// 4. 位置自洽:地面 `y == (cx+cy)*40` 且瓦片 `minBlockY == 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]
|
// node scripts/verify-tiles.ts [--dir=samples/d2] [--limit=35]
|
||||||
//
|
//
|
||||||
|
|
@ -69,6 +72,9 @@ async function main(): Promise<void> {
|
||||||
let looseRefs = 0
|
let looseRefs = 0
|
||||||
let missingTiles = 0
|
let missingTiles = 0
|
||||||
let positionMismatch = 0
|
let positionMismatch = 0
|
||||||
|
let cornerRefs = 0
|
||||||
|
let cornerPairs = 0
|
||||||
|
let missingCornerPairs = 0
|
||||||
const worst: [number, string][] = []
|
const worst: [number, string][] = []
|
||||||
|
|
||||||
for (const row of tables.levels.rows) {
|
for (const row of tables.levels.rows) {
|
||||||
|
|
@ -99,6 +105,13 @@ async function main(): Promise<void> {
|
||||||
|
|
||||||
const seed = levelSeed(info.ds1Names[0]!)
|
const seed = levelSeed(info.ds1Names[0]!)
|
||||||
const scene = buildIsoMapScene(ds1, libs, seed)
|
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
|
looseRefs += scene.looseRefs
|
||||||
missingTiles += scene.missingTiles
|
missingTiles += scene.missingTiles
|
||||||
|
|
||||||
|
|
@ -139,6 +152,9 @@ async function main(): Promise<void> {
|
||||||
check('没有任何引用走类型无关兜底', looseRefs === 0, `looseRefs ${String(looseRefs)}`)
|
check('没有任何引用走类型无关兜底', looseRefs === 0, `looseRefs ${String(looseRefs)}`)
|
||||||
check('没有解析不到的引用', missingTiles === 0, `missingTiles ${String(missingTiles)}`)
|
check('没有解析不到的引用', missingTiles === 0, `missingTiles ${String(missingTiles)}`)
|
||||||
check('地面/墙/屋顶的位置公式自洽', positionMismatch === 0, `${String(positionMismatch)} 个不符`)
|
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(',')}`)
|
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
|
const passed = checks.filter(entry => entry.ok).length
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,19 @@ const FLOOR_TILE_TYPE = 0
|
||||||
* painter order and out of the wall offset rule.
|
* painter order and out of the wall offset rule.
|
||||||
*/
|
*/
|
||||||
const ROOF_WALL_TYPE = 15
|
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. */
|
/** One tile to draw, in screen pixels. */
|
||||||
export interface IsoDraw {
|
export interface IsoDraw {
|
||||||
/** Index into {@link IsoMapScene.frames}. */
|
/** Index into {@link IsoMapScene.frames}. */
|
||||||
|
|
@ -155,6 +168,15 @@ export interface IsoMapScene {
|
||||||
* `npm run verify:tiles` exists to catch.
|
* `npm run verify:tiles` exists to catch.
|
||||||
*/
|
*/
|
||||||
readonly looseRefs: number
|
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.
|
// skipping them would silently drop blocking sub-tiles.
|
||||||
let shadowBlocked = 0
|
let shadowBlocked = 0
|
||||||
let looseRefs = 0
|
let looseRefs = 0
|
||||||
|
let cornerPairs = 0
|
||||||
|
let missingCornerPairs = 0
|
||||||
const stamp = (cellX: number, cellY: number, tile: Dt1Tile): void => {
|
const stamp = (cellX: number, cellY: number, tile: Dt1Tile): void => {
|
||||||
for (let subY = 0; subY < SUB_TILES_PER_TILE; subY += 1) {
|
for (let subY = 0; subY < SUB_TILES_PER_TILE; subY += 1) {
|
||||||
for (let subX = 0; subX < SUB_TILES_PER_TILE; subX += 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)
|
rawWalls.push(draw)
|
||||||
}
|
}
|
||||||
stamp(cellX, cellY, tile)
|
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,
|
clippedTiles,
|
||||||
shadowBlockedSubtiles: shadowBlocked,
|
shadowBlockedSubtiles: shadowBlocked,
|
||||||
looseRefs,
|
looseRefs,
|
||||||
|
cornerPairs,
|
||||||
|
missingCornerPairs,
|
||||||
duplicateRefs: pool.duplicates,
|
duplicateRefs: pool.duplicates,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue