fix(assets): track fallback warps in stats.notes and set assertion cap in verify-packs
- Track fallback warps in buildSceneLinks with level and destination names, recording to stats.notes in scene.json - Expose notes and unresolved in WildernessResult.stats and SceneLinks.notes - Add MAX_EXPECTED_FALLBACK_WARPS assertion (<= 80) and level breakdown logging in verify-packs.ts to prevent silent proliferation of invented staircases TAG=agy CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
This commit is contained in:
parent
fea677b0b4
commit
0b08b4e125
|
|
@ -541,6 +541,7 @@ function buildSceneLinks(
|
|||
const outEntrances: SceneEntrance[] = []
|
||||
const outWarps: SceneWarp[] = []
|
||||
const unplaced: { toLevelId: number; reason: string }[] = []
|
||||
const notes: string[] = []
|
||||
const placedSeams = new Set<number>()
|
||||
const placedWarps = new Set<number>()
|
||||
|
||||
|
|
@ -751,6 +752,9 @@ function buildSceneLinks(
|
|||
unplaced.push({ toLevelId: edge.to, reason: 'no walkable ground for a fallback warp' })
|
||||
return
|
||||
}
|
||||
const fromName = worldGraph.levels.get(levelId)?.name ?? `level ${String(levelId)}`
|
||||
const toName = worldGraph.levels.get(edge.to)?.name ?? `level ${String(edge.to)}`
|
||||
notes.push(`fallback warp placed for edge ${fromName} -> ${toName}`)
|
||||
outWarps.push(makeWarp(
|
||||
edge,
|
||||
directionTo(edge.to),
|
||||
|
|
@ -797,7 +801,7 @@ function buildSceneLinks(
|
|||
}
|
||||
}
|
||||
|
||||
return { entrances: outEntrances, warps: outWarps, waypoints, unplacedEdges: unplaced }
|
||||
return { entrances: outEntrances, warps: outWarps, waypoints, unplacedEdges: unplaced, notes }
|
||||
}
|
||||
|
||||
const allNames = await archives.listFiles()
|
||||
|
|
@ -1157,6 +1161,7 @@ async function bakeDs1Variant(
|
|||
readonly mazeWarps: readonly MazeWarp[]
|
||||
readonly mazeWaypoints?: readonly MazeWaypoint[]
|
||||
readonly landmarks?: readonly { readonly id: string; readonly tileX: number; readonly tileY: number }[]
|
||||
readonly notes?: readonly string[] | undefined
|
||||
} = { entrances: [], mazeWarps: [] },
|
||||
): Promise<void> {
|
||||
const scene: IsoMapScene = buildIsoMapScene(level, libraries, seed)
|
||||
|
|
@ -1445,6 +1450,10 @@ async function bakeDs1Variant(
|
|||
// Edges the world graph has that this copy of the level has nowhere to
|
||||
// put. Reported rather than dropped: each one is a hole in the world.
|
||||
unplacedEdges: sceneLinks.unplacedEdges,
|
||||
notes: [
|
||||
...(generatorLinks.notes ?? []),
|
||||
...(sceneLinks.notes ?? []),
|
||||
],
|
||||
},
|
||||
}
|
||||
const sceneBytes = new TextEncoder().encode(JSON.stringify(sceneJson))
|
||||
|
|
@ -1649,6 +1658,7 @@ for (const entry of LEVELS) {
|
|||
...(Array.isArray(result.stats.landmarkEntities)
|
||||
? { landmarks: result.stats.landmarkEntities as readonly { readonly id: string; readonly tileX: number; readonly tileY: number }[] }
|
||||
: {}),
|
||||
...(Array.isArray(result.stats.notes) ? { notes: result.stats.notes } : {}),
|
||||
},
|
||||
)
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -295,7 +295,10 @@ interface PackedLinks {
|
|||
readonly source?: 'tile' | 'room' | 'fallback'
|
||||
}[]
|
||||
readonly waypoints?: readonly { readonly waypointId: number }[]
|
||||
readonly stats?: { readonly unplacedEdges?: readonly { readonly toLevelId: number; readonly reason: string }[] }
|
||||
readonly stats?: {
|
||||
readonly unplacedEdges?: readonly { readonly toLevelId: number; readonly reason: string }[]
|
||||
readonly notes?: readonly string[]
|
||||
}
|
||||
}
|
||||
|
||||
const graph = JSON.parse(await readFile(join(packDir, 'world-graph.json'), 'utf8')) as PackedWorldGraph
|
||||
|
|
@ -385,13 +388,24 @@ for (const key of orphanEdges.slice(0, 20)) {
|
|||
}
|
||||
check(orphanEdges.length === 0, `world: no graph edge left without an opening (${String(orphanEdges.length)} orphans)`)
|
||||
|
||||
// Not an assertion: an approximate staircase is traversable, just not authentic.
|
||||
// The number is printed every run so it cannot creep up unnoticed, and it only
|
||||
// falls when a level type's DRLG staircase pass gets transcribed.
|
||||
// Cap fallback staircases at the known baseline (80 across 15 levels) so invented
|
||||
// positions cannot silently proliferate without being accounted for.
|
||||
const MAX_EXPECTED_FALLBACK_WARPS = 80
|
||||
check(
|
||||
warpSources.fallback <= MAX_EXPECTED_FALLBACK_WARPS,
|
||||
`world: fallback warps (${String(warpSources.fallback)}) within expected threshold (<= ${String(MAX_EXPECTED_FALLBACK_WARPS)})`,
|
||||
)
|
||||
|
||||
console.log(
|
||||
`\n楼梯来源:美术瓦片 ${String(warpSources.tile)},迷宫楼梯间 ${String(warpSources.room)},`
|
||||
+ `兜底 ${String(warpSources.fallback)}(涉及 ${String(approximateLevels.size)} 个关卡)`,
|
||||
)
|
||||
if (approximateLevels.size > 0) {
|
||||
const breakdown = [...approximateLevels]
|
||||
.map(id => `${String(id)} (${graphLevelName.get(id) ?? 'unknown'})`)
|
||||
.join(', ')
|
||||
console.log(` 兜底关卡列表: ${breakdown}`)
|
||||
}
|
||||
|
||||
console.log(`\n${String(checks - failures)}/${String(checks)} 项断言通过(逐像素比对了 ${(comparedPixels / 1048576).toFixed(1)} MB 的索引数据)`)
|
||||
if (failures > 0) process.exit(1)
|
||||
|
|
|
|||
|
|
@ -180,6 +180,8 @@ export interface SceneLinks {
|
|||
* hole in the world, and the verifier reports on this list.
|
||||
*/
|
||||
readonly unplacedEdges: readonly { readonly toLevelId: number; readonly reason: string }[]
|
||||
/** Diagnostic notes and warnings from link resolution (e.g. fallback warps placed). */
|
||||
readonly notes?: readonly string[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -325,6 +325,8 @@ export interface WildernessResult {
|
|||
/** What the generator did, for reporting and tests. */
|
||||
readonly stats: Record<string, unknown> & {
|
||||
readonly landmarkEntities?: LandmarkEntity[]
|
||||
readonly notes?: string[]
|
||||
readonly unresolved?: string[]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue