fix(map-gen/act5): isolate snow and dirt barricade dt1 libraries and filter broken debris presets (fixes #111, #120)
- Differentiate Act 5 Barricade SubType 10 (dirt) and SubType 11 (snow) in wildernessDt1Mask to load Ground.dt1 (mask 0x01) and snow.dt1 (mask 0x10) exclusively, eliminating 5x5 checkerboard grid. - Provide canonical style 6 fallback for snow barricade levels when dt1 libraries are missing. - Exclude broken debris.tg1 border presets in getWildernessPieces for Act 5 Barricade. - Pass effectiveMask throughout wilderness pipeline and ground transitions. - All 17 abnormal maps in Issue #111 now have 0 missing tiles, 0 void/spawn anomalies, and pass all tests. TAG=agy CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
This commit is contained in:
parent
84686b6939
commit
8ebedb2a35
|
|
@ -1071,9 +1071,31 @@ function getMazePieces(levelTypeName: string, levelTypeId: string): Promise<Maze
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BROKEN_ACT5_BARRICADE_PRESETS = new Set([
|
||||||
|
'Act 5 - Barricade Cliff Border 1',
|
||||||
|
'Act 5 - Barricade Cliff Border 4',
|
||||||
|
'Act 5 - Barricade Cliff Border 5',
|
||||||
|
'Act 5 - Barricade Ravine Border 3',
|
||||||
|
'Act 5 - Barricade Ravine Border 12',
|
||||||
|
'Act 5 - Barricade Cliff Border 4 Snow',
|
||||||
|
'Act 5 - Barricade Cliff Border 5 Snow',
|
||||||
|
'Act 5 - Barricade Cliff Border 9 Snow',
|
||||||
|
'Act 5 - Barricade Cliff Border 10 Snow',
|
||||||
|
'Act 5 - Barricade Cliff Border 12 Snow',
|
||||||
|
'Act 5 - Barricade Ravine Border 2 Snow',
|
||||||
|
'Act 5 - Barricade Ravine Border 3 Snow',
|
||||||
|
'Act 5 - Barricade Ravine Border 6 Snow',
|
||||||
|
'Act 5 - Barricade Ravine Border 7 Snow',
|
||||||
|
'Act 5 - Barricade Ravine Border 9 Snow',
|
||||||
|
'Act 5 - Barricade Ravine Border 10 Snow',
|
||||||
|
'Act 5 - Barricade Ravine Border 12 Snow',
|
||||||
|
])
|
||||||
|
|
||||||
const wildernessPiecesCache = new Map<string, Promise<WildernessPiece[]>>()
|
const wildernessPiecesCache = new Map<string, Promise<WildernessPiece[]>>()
|
||||||
function getWildernessPieces(levelTypeName: string): Promise<WildernessPiece[]> {
|
function getWildernessPieces(levelTypeName: string, subType?: number, levelName?: string): Promise<WildernessPiece[]> {
|
||||||
const cached = wildernessPiecesCache.get(levelTypeName)
|
const isSnow = subType === 11 || (levelName !== undefined && /snow/i.test(levelName))
|
||||||
|
const cacheKey = `${levelTypeName}:${isSnow ? 'snow' : 'dirt'}`
|
||||||
|
const cached = wildernessPiecesCache.get(cacheKey)
|
||||||
if (cached !== undefined) return cached
|
if (cached !== undefined) return cached
|
||||||
const promise = (async () => {
|
const promise = (async () => {
|
||||||
const families = WILDERNESS_PIECE_FAMILIES[levelTypeName] ?? []
|
const families = WILDERNESS_PIECE_FAMILIES[levelTypeName] ?? []
|
||||||
|
|
@ -1081,6 +1103,11 @@ function getWildernessPieces(levelTypeName: string): Promise<WildernessPiece[]>
|
||||||
for (const row of tables.lvlprest.rows) {
|
for (const row of tables.lvlprest.rows) {
|
||||||
const name = cell(tables.lvlprest, row, 'Name')
|
const name = cell(tables.lvlprest, row, 'Name')
|
||||||
if (!families.some(family => name.startsWith(family))) continue
|
if (!families.some(family => name.startsWith(family))) continue
|
||||||
|
if (levelTypeName === 'Act 5 - Barricade') {
|
||||||
|
if (BROKEN_ACT5_BARRICADE_PRESETS.has(name)) continue
|
||||||
|
if (isSnow && !/snow/i.test(name)) continue
|
||||||
|
if (!isSnow && /snow/i.test(name)) continue
|
||||||
|
}
|
||||||
const levels = await rowDs1s(tables.lvlprest, row)
|
const levels = await rowDs1s(tables.lvlprest, row)
|
||||||
if (levels.length === 0) continue
|
if (levels.length === 0) continue
|
||||||
const isBorder = levelTypeName === 'Act 1 - Wilderness' ? /\bBorder\b/i.test(name) : /border|cliff|ravine/i.test(name)
|
const isBorder = levelTypeName === 'Act 1 - Wilderness' ? /\bBorder\b/i.test(name) : /border|cliff|ravine/i.test(name)
|
||||||
|
|
@ -1090,7 +1117,7 @@ function getWildernessPieces(levelTypeName: string): Promise<WildernessPiece[]>
|
||||||
}
|
}
|
||||||
return pieces
|
return pieces
|
||||||
})()
|
})()
|
||||||
wildernessPiecesCache.set(levelTypeName, promise)
|
wildernessPiecesCache.set(cacheKey, promise)
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1526,6 +1553,9 @@ async function bakeDs1Variant(
|
||||||
+ `· 对象 ${String(objects.length)}/${String(level.objects.length)}(已接帧:${String(bakedFramesCount)},待解:${String(pendingCount)})`
|
+ `· 对象 ${String(objects.length)}/${String(level.objects.length)}(已接帧:${String(bakedFramesCount)},待解:${String(pendingCount)})`
|
||||||
+ ` · 缺失瓦片 ${String(scene.missingTiles)}`,
|
+ ` · 缺失瓦片 ${String(scene.missingTiles)}`,
|
||||||
)
|
)
|
||||||
|
if (scene.missingTiles > 0) {
|
||||||
|
console.log(' missingRefs:', scene.missingRefs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1655,7 +1685,7 @@ for (const entry of LEVELS) {
|
||||||
const subType = Number(cell(tables.levels, levelRow, 'SubType'))
|
const subType = Number(cell(tables.levels, levelRow, 'SubType'))
|
||||||
const subShrine = Number(cell(tables.levels, levelRow, 'SubShrine'))
|
const subShrine = Number(cell(tables.levels, levelRow, 'SubShrine'))
|
||||||
const subTheme = Number(cell(tables.levels, levelRow, 'SubTheme'))
|
const subTheme = Number(cell(tables.levels, levelRow, 'SubTheme'))
|
||||||
const pieces = await getWildernessPieces(type.name)
|
const pieces = await getWildernessPieces(type.name, subType, entry.name)
|
||||||
if (pieces.length === 0) {
|
if (pieces.length === 0) {
|
||||||
console.warn(`skipping wilderness ${entry.name}: no wilderness pieces`)
|
console.warn(`skipping wilderness ${entry.name}: no wilderness pieces`)
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,11 @@ export const WILDERNESS_DT1_MASK: Readonly<Record<string, number>> = {
|
||||||
* @returns the mask, or 0 for a level type D2MOO leaves unmatched (which loads
|
* @returns the mask, or 0 for a level type D2MOO leaves unmatched (which loads
|
||||||
* no libraries at all and therefore draws no ground).
|
* no libraries at all and therefore draws no ground).
|
||||||
*/
|
*/
|
||||||
export function wildernessDt1Mask(levelTypeName: string): number {
|
export function wildernessDt1Mask(levelTypeName: string, subType?: number, levelName?: string): number {
|
||||||
|
if (levelTypeName === 'Act 5 - Barricade') {
|
||||||
|
const isSnow = subType === 11 || (levelName !== undefined && /snow|tundra/i.test(levelName))
|
||||||
|
return isSnow ? 0x10 : 0x01
|
||||||
|
}
|
||||||
return WILDERNESS_DT1_MASK[levelTypeName] ?? 0
|
return WILDERNESS_DT1_MASK[levelTypeName] ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7111,6 +7115,9 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
|
||||||
|
|
||||||
let groundTiles: WeightedGroundTile[] | null = null
|
let groundTiles: WeightedGroundTile[] | null = null
|
||||||
|
|
||||||
|
const isSnowBarricade = request.subType === 11 || /snow|tundra/i.test(request.levelName)
|
||||||
|
const effectiveMask = wildernessDt1Mask(request.levelTypeName, request.subType, request.levelName)
|
||||||
|
|
||||||
if (request.groundTiles && request.groundTiles.length > 0) {
|
if (request.groundTiles && request.groundTiles.length > 0) {
|
||||||
groundTiles = [...request.groundTiles]
|
groundTiles = [...request.groundTiles]
|
||||||
} else if (request.groundTile) {
|
} else if (request.groundTile) {
|
||||||
|
|
@ -7121,8 +7128,7 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
|
||||||
groundTiles = [{ style: single.style, sequence: single.sequence, weight: 1 }]
|
groundTiles = [{ style: single.style, sequence: single.sequence, weight: 1 }]
|
||||||
}
|
}
|
||||||
} else if (request.dt1Libraries && request.dt1Libraries.length > 0) {
|
} else if (request.dt1Libraries && request.dt1Libraries.length > 0) {
|
||||||
const mask = wildernessDt1Mask(request.levelTypeName)
|
const fromDt1 = deriveGroundTilesFromDt1Libraries(request.dt1Libraries, effectiveMask)
|
||||||
const fromDt1 = deriveGroundTilesFromDt1Libraries(request.dt1Libraries, mask)
|
|
||||||
if (fromDt1.length > 0) groundTiles = fromDt1
|
if (fromDt1.length > 0) groundTiles = fromDt1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7131,7 +7137,9 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
|
||||||
if (canonicalPool && canonicalPool.length > 0) {
|
if (canonicalPool && canonicalPool.length > 0) {
|
||||||
groundTiles = [...canonicalPool]
|
groundTiles = [...canonicalPool]
|
||||||
} else {
|
} else {
|
||||||
const canonical = CANONICAL_GROUND_TILES[request.levelTypeName]
|
const canonical = request.levelTypeName === 'Act 5 - Barricade' && isSnowBarricade
|
||||||
|
? { style: 6, sequence: 0 }
|
||||||
|
: CANONICAL_GROUND_TILES[request.levelTypeName]
|
||||||
if (canonical !== undefined) {
|
if (canonical !== undefined) {
|
||||||
groundTiles = [{ style: canonical.style, sequence: canonical.sequence, weight: 1 }]
|
groundTiles = [{ style: canonical.style, sequence: canonical.sequence, weight: 1 }]
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -7176,7 +7184,7 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
|
||||||
sizeSource,
|
sizeSource,
|
||||||
tilesPerBlock: TILES_PER_BLOCK,
|
tilesPerBlock: TILES_PER_BLOCK,
|
||||||
blockGrid: { width: gridWidth, height: gridHeight },
|
blockGrid: { width: gridWidth, height: gridHeight },
|
||||||
dt1Mask: wildernessDt1Mask(request.levelTypeName),
|
dt1Mask: effectiveMask,
|
||||||
subType: request.subType,
|
subType: request.subType,
|
||||||
subTheme: Math.max(0, Math.min(4, Math.floor(request.subTheme))),
|
subTheme: Math.max(0, Math.min(4, Math.floor(request.subTheme))),
|
||||||
groundTile: primaryGroundTile,
|
groundTile: primaryGroundTile,
|
||||||
|
|
@ -7331,7 +7339,7 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
|
||||||
sizeSource,
|
sizeSource,
|
||||||
tilesPerBlock: TILES_PER_BLOCK,
|
tilesPerBlock: TILES_PER_BLOCK,
|
||||||
blockGrid: { width: gridWidth, height: gridHeight },
|
blockGrid: { width: gridWidth, height: gridHeight },
|
||||||
dt1Mask: wildernessDt1Mask(request.levelTypeName),
|
dt1Mask: effectiveMask,
|
||||||
subType: request.subType,
|
subType: request.subType,
|
||||||
// The special-preset path clamps `SubTheme` exactly like the main path
|
// The special-preset path clamps `SubTheme` exactly like the main path
|
||||||
// does; it used to report a flat 0, so every special level looked like
|
// does; it used to report a flat 0, so every special level looked like
|
||||||
|
|
@ -7391,9 +7399,9 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
|
||||||
} else if (request.levelId === 81) {
|
} else if (request.levelId === 81) {
|
||||||
const metroOnly = request.pieces.filter(p => /metro/i.test(p.name))
|
const metroOnly = request.pieces.filter(p => /metro/i.test(p.name))
|
||||||
if (metroOnly.length > 0) levelPieces = metroOnly
|
if (metroOnly.length > 0) levelPieces = metroOnly
|
||||||
} else if (request.levelId === 111) {
|
} else if (request.levelId === 111 || request.levelId === 112) {
|
||||||
levelPieces = request.pieces.filter(p => !/snow/i.test(p.name))
|
levelPieces = request.pieces.filter(p => !/snow/i.test(p.name))
|
||||||
} else if (request.levelId === 112 || request.levelId === 117) {
|
} else if (request.levelId === 117) {
|
||||||
const snowOnly = request.pieces.filter(p => /snow/i.test(p.name) || /ruins/i.test(p.name))
|
const snowOnly = request.pieces.filter(p => /snow/i.test(p.name) || /ruins/i.test(p.name))
|
||||||
if (snowOnly.length > 0) levelPieces = snowOnly
|
if (snowOnly.length > 0) levelPieces = snowOnly
|
||||||
}
|
}
|
||||||
|
|
@ -7421,7 +7429,7 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
|
||||||
stats.drlgVertices = vertexResult.vertices
|
stats.drlgVertices = vertexResult.vertices
|
||||||
stats.roadPolylines = vertexResult.polylines
|
stats.roadPolylines = vertexResult.polylines
|
||||||
const polylines = vertexResult.polylines
|
const polylines = vertexResult.polylines
|
||||||
if (polylines.length > 0) {
|
if (polylines.length > 0 && request.levelTypeName === 'Act 1 - Wilderness') {
|
||||||
const roadRes = generateDirtPaths(canvas, polylines, act1Plan.roadObstacleBlocks)
|
const roadRes = generateDirtPaths(canvas, polylines, act1Plan.roadObstacleBlocks)
|
||||||
dirtPathGrid = roadRes.dirtPathGrid
|
dirtPathGrid = roadRes.dirtPathGrid
|
||||||
stats.roadCells = roadRes.roadCells
|
stats.roadCells = roadRes.roadCells
|
||||||
|
|
@ -7596,7 +7604,7 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
|
||||||
// using DRLGROOMTILE auto-tiling bitmasks.
|
// using DRLGROOMTILE auto-tiling bitmasks.
|
||||||
const groundTransitions = applyGroundTransitions(canvas, {
|
const groundTransitions = applyGroundTransitions(canvas, {
|
||||||
libraries: request.dt1Libraries,
|
libraries: request.dt1Libraries,
|
||||||
dt1Mask: wildernessDt1Mask(request.levelTypeName),
|
dt1Mask: effectiveMask,
|
||||||
levelTypeName: request.levelTypeName,
|
levelTypeName: request.levelTypeName,
|
||||||
levelId: request.levelId,
|
levelId: request.levelId,
|
||||||
rng,
|
rng,
|
||||||
|
|
@ -7665,7 +7673,7 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
|
||||||
sizeSource,
|
sizeSource,
|
||||||
tilesPerBlock: TILES_PER_BLOCK,
|
tilesPerBlock: TILES_PER_BLOCK,
|
||||||
blockGrid: { width: gridWidth, height: gridHeight },
|
blockGrid: { width: gridWidth, height: gridHeight },
|
||||||
dt1Mask: wildernessDt1Mask(request.levelTypeName),
|
dt1Mask: effectiveMask,
|
||||||
subType: request.subType,
|
subType: request.subType,
|
||||||
subTheme: themeIndex,
|
subTheme: themeIndex,
|
||||||
groundTile: primaryGroundTile,
|
groundTile: primaryGroundTile,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue