fix(wilderness): 修复空地面图层擦除底图与补全第一幕专属预设景观 (refs #15)
1. 根因修复:在 stampDs1 中采用非破坏性图层合并,避免 DS1 空地面(prop1 === 0)覆盖擦除底图导致黑暗森林等关卡出现大片黑色虚空深渊(恢复 100% 满地面覆盖); 2. 补全第一幕(Level 2, 3, 4, 5, 6, 7, 17, 39)特殊预设配置(SPECIAL_PRESETS_BY_LEVEL),对齐 D2MOO DRLGOUTWILD_SpawnSpecialPresets,恢复冰冷之原毕须博什营地、艾尼弗斯之树、石阵、农舍等专属景观; 3. 扩充 WILDERNESS_PIECE_FAMILIES 并增加针对 Level 3 与 Level 5 的回归断言护栏; 4. 全量 70 个迷宫关卡 + 31 个野外关卡通过 verify:generators 自动化测试(785/785 项断言通过)。
This commit is contained in:
parent
4cc49a01aa
commit
36d5624b14
|
|
@ -524,6 +524,15 @@ const WILDERNESS_PIECE_FAMILIES: Readonly<Record<string, readonly string[]>> = {
|
|||
'Act 1 - Pond',
|
||||
'Act 1 - Swamp Fill',
|
||||
'Act 1 - Stone Fill',
|
||||
'Act 1 - Cottages',
|
||||
'Act 1 - Fallen Camp',
|
||||
'Act 1 - Camp',
|
||||
'Act 1 - Cairn Stones',
|
||||
'Act 1 - Inifus',
|
||||
'Act 1 - Tower',
|
||||
'Act 1 - Ruin',
|
||||
'Act 1 - Tree Fill',
|
||||
'Act 1 - Graveyard',
|
||||
],
|
||||
'Act 2 - Desert': ['Act 2 - Desert'],
|
||||
'Act 3 - Jungle': ['Act 3 - Jungle'],
|
||||
|
|
|
|||
|
|
@ -404,6 +404,15 @@ const WILDERNESS_PIECE_FAMILIES: Readonly<Record<string, readonly string[]>> = {
|
|||
'Act 1 - Pond',
|
||||
'Act 1 - Swamp Fill',
|
||||
'Act 1 - Stone Fill',
|
||||
'Act 1 - Cottages',
|
||||
'Act 1 - Fallen Camp',
|
||||
'Act 1 - Camp',
|
||||
'Act 1 - Cairn Stones',
|
||||
'Act 1 - Inifus',
|
||||
'Act 1 - Tower',
|
||||
'Act 1 - Ruin',
|
||||
'Act 1 - Tree Fill',
|
||||
'Act 1 - Graveyard',
|
||||
],
|
||||
'Act 2 - Desert': ['Act 2 - Desert'],
|
||||
'Act 3 - Jungle': ['Act 3 - Jungle'],
|
||||
|
|
@ -674,6 +683,17 @@ for (const { id, name } of wildLevels) {
|
|||
check(first.level.width === expectedBlocksX * AREA_CELLS && first.level.height === expectedBlocksY * AREA_CELLS, label, 'map extent does not match whole blocks of the declared size')
|
||||
check(missingShare <= MAX_MISSING_SHARE, label, `${(missingShare * 100).toFixed(2)}% of tile references are missing`)
|
||||
check(fill.share >= MIN_REACHABLE_SHARE, label, `only ${(fill.share * 100).toFixed(1)}% of walkable areas are reachable (${String(fill.reached)}/${String(fill.total)})`)
|
||||
check(Number(first.stats.groundCells) === first.level.width * first.level.height, label, `ground floor coverage is incomplete: ${String(first.stats.groundCells)}/${String(first.level.width * first.level.height)}`)
|
||||
if (id === 3) {
|
||||
check(scene.walls.length > 500, label, `Cold Plains walls (${String(scene.walls.length)}) indicates missing interior preset structures`)
|
||||
const substitutions = (first.stats.substitutions ?? []) as readonly { readonly name: string }[]
|
||||
check(substitutions.some(s => s.name.includes('Bishibosh')), label, 'Cold Plains missing Bishibosh preset')
|
||||
}
|
||||
if (id === 5) {
|
||||
check(scene.walls.length > 500, label, `Dark Wood walls (${String(scene.walls.length)}) indicates missing interior preset structures`)
|
||||
const substitutions = (first.stats.substitutions ?? []) as readonly { readonly name: string }[]
|
||||
check(substitutions.some(s => s.name.includes('Inifus')), label, 'Dark Wood missing Inifus preset')
|
||||
}
|
||||
if (id === 39) {
|
||||
check(scene.walls.length > 700, label, `Moo Moo Farm walls (${String(scene.walls.length)}) indicates missing interior corral/preset structures`)
|
||||
const substitutions = (first.stats.substitutions ?? []) as readonly { readonly name: string }[]
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@
|
|||
* so this port takes the ground tile from the level's own border pieces instead
|
||||
* — see {@link WildernessRequest.groundTile}.
|
||||
*/
|
||||
import type { Ds1, Ds1Cell, Ds1Floor, Ds1Object, Ds1Wall } from '../formats/ds1.ts'
|
||||
import type { Ds1, Ds1Cell, Ds1Floor, Ds1Object, Ds1Wall, Ds1Substitution } from '../formats/ds1.ts'
|
||||
import { Rng } from './rng.ts'
|
||||
import { SUB_TILES_PER_TILE } from './map.ts'
|
||||
|
||||
|
|
@ -354,7 +354,61 @@ function stampDs1(canvas: Canvas, source: Ds1, originX: number, originY: number)
|
|||
if (targetX < 0 || targetX >= canvas.width) continue
|
||||
const cell = sourceRow[x]
|
||||
if (cell === undefined) continue
|
||||
targetRow[targetX] = cell
|
||||
const targetCell = targetRow[targetX]!
|
||||
|
||||
// Floors: copy any non-empty floor layer from source. If source cell has no
|
||||
// non-empty floors, target's ground floor is preserved intact.
|
||||
for (let i = 0; i < cell.floors.length; i += 1) {
|
||||
const floor = cell.floors[i]
|
||||
if (floor !== undefined && !floor.hidden && floor.prop1 !== 0) {
|
||||
const targetFloors = targetCell.floors as Ds1Floor[]
|
||||
if (i < targetFloors.length) {
|
||||
targetFloors[i] = floor
|
||||
} else {
|
||||
targetFloors.push(floor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Walls: copy any non-empty wall layer from source.
|
||||
for (let i = 0; i < cell.walls.length; i += 1) {
|
||||
const wall = cell.walls[i]
|
||||
if (wall !== undefined && !wall.hidden && wall.prop1 !== 0) {
|
||||
const targetWalls = targetCell.walls as Ds1Wall[]
|
||||
if (i < targetWalls.length) {
|
||||
targetWalls[i] = wall
|
||||
} else {
|
||||
targetWalls.push(wall)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shadows: copy any non-empty shadow layer from source.
|
||||
for (let i = 0; i < cell.shadows.length; i += 1) {
|
||||
const shadow = cell.shadows[i]
|
||||
if (shadow !== undefined && !shadow.hidden && shadow.prop1 !== 0) {
|
||||
const targetShadows = targetCell.shadows as Ds1Floor[]
|
||||
if (i < targetShadows.length) {
|
||||
targetShadows[i] = shadow
|
||||
} else {
|
||||
targetShadows.push(shadow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Substitutions: copy non-zero substitution layer values.
|
||||
for (let i = 0; i < cell.substitutions.length; i += 1) {
|
||||
const sub = cell.substitutions[i]
|
||||
if (sub !== undefined && sub.value !== 0) {
|
||||
const targetSubs = targetCell.substitutions as Ds1Substitution[]
|
||||
if (i < targetSubs.length) {
|
||||
targetSubs[i] = sub
|
||||
} else {
|
||||
targetSubs.push(sub)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
written += 1
|
||||
}
|
||||
}
|
||||
|
|
@ -767,6 +821,56 @@ function applySubstitutions(
|
|||
* fills.
|
||||
*/
|
||||
export const SPECIAL_PRESETS_BY_LEVEL: Readonly<Record<number, readonly string[]>> = {
|
||||
2: [
|
||||
'Act 1 - Pond',
|
||||
'Act 1 - Cottages 1',
|
||||
'Act 1 - Stone Fill 1',
|
||||
'Act 1 - Stone Fill 2',
|
||||
],
|
||||
3: [
|
||||
'Act 1 - Cottages 2',
|
||||
'Act 1 - Cottages 3',
|
||||
'Act 1 - Fallen Camp Bishibosh',
|
||||
'Act 1 - Stone Fill 1',
|
||||
'Act 1 - Stone Fill 2',
|
||||
],
|
||||
4: [
|
||||
'Act 1 - Cairn Stones',
|
||||
'Act 1 - Camp',
|
||||
'Act 1 - Tower Tome',
|
||||
'Act 1 - Cottages 1',
|
||||
'Act 1 - Cottages 3',
|
||||
'Act 1 - Fallen Camp 1',
|
||||
'Act 1 - Corral Fill',
|
||||
],
|
||||
5: [
|
||||
'Act 1 - Inifus',
|
||||
'Act 1 - Ruin',
|
||||
'Act 1 - Tree Fill',
|
||||
'Act 1 - Cottages 2',
|
||||
'Act 1 - Cottages 3',
|
||||
'Act 1 - Fallen Camp 2',
|
||||
'Act 1 - Stone Fill 1',
|
||||
'Act 1 - Stone Fill 2',
|
||||
],
|
||||
6: [
|
||||
'Act 1 - Tower 1',
|
||||
'Act 1 - Swamp Fill 1',
|
||||
'Act 1 - Swamp Fill 2',
|
||||
'Act 1 - Cottages 1',
|
||||
'Act 1 - Cottages 3',
|
||||
'Act 1 - Fallen Camp 1',
|
||||
'Act 1 - Stone Fill 1',
|
||||
'Act 1 - Stone Fill 2',
|
||||
],
|
||||
7: [
|
||||
'Act 1 - Cottages 2',
|
||||
'Act 1 - Fallen Camp 2',
|
||||
'Act 1 - Corral Fill',
|
||||
],
|
||||
17: [
|
||||
'Act 1 - Graveyard',
|
||||
],
|
||||
39: [
|
||||
'Act 1 - Bivouac',
|
||||
'Act 1 - Pond',
|
||||
|
|
@ -1069,6 +1173,17 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
|
|||
}
|
||||
}
|
||||
|
||||
let finalGroundCells = 0
|
||||
for (let y = 0; y < canvas.height; y += 1) {
|
||||
const row = canvas.cells[y]
|
||||
if (row === undefined) continue
|
||||
for (let x = 0; x < canvas.width; x += 1) {
|
||||
const c = row[x]
|
||||
if (c !== undefined && c.floors.some(f => !f.hidden && f.prop1 !== 0)) finalGroundCells += 1
|
||||
}
|
||||
}
|
||||
stats.groundCells = finalGroundCells
|
||||
|
||||
if (!Number.isFinite(canvas.width * canvas.height) || canvas.width * canvas.height > (1 << 22)) {
|
||||
throw new Error(`${where}: synthesized map is ${String(canvas.width)}x${String(canvas.height)} cells, outside the supported bound`)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue