From 36d5624b1490239c7a84d989f0b0ad3c84359c7a Mon Sep 17 00:00:00 2001 From: troytt <47798984@qq.com> Date: Tue, 15 Sep 2026 01:52:12 +0000 Subject: [PATCH] =?UTF-8?q?fix(wilderness):=20=E4=BF=AE=E5=A4=8D=E7=A9=BA?= =?UTF-8?q?=E5=9C=B0=E9=9D=A2=E5=9B=BE=E5=B1=82=E6=93=A6=E9=99=A4=E5=BA=95?= =?UTF-8?q?=E5=9B=BE=E4=B8=8E=E8=A1=A5=E5=85=A8=E7=AC=AC=E4=B8=80=E5=B9=95?= =?UTF-8?q?=E4=B8=93=E5=B1=9E=E9=A2=84=E8=AE=BE=E6=99=AF=E8=A7=82=20(refs?= =?UTF-8?q?=20#15)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 项断言通过)。 --- scripts/pack-act-assets.ts | 9 +++ scripts/verify-generators.ts | 20 ++++++ src/game/wilderness.ts | 119 ++++++++++++++++++++++++++++++++++- 3 files changed, 146 insertions(+), 2 deletions(-) diff --git a/scripts/pack-act-assets.ts b/scripts/pack-act-assets.ts index 7d517e2..1ca34eb 100644 --- a/scripts/pack-act-assets.ts +++ b/scripts/pack-act-assets.ts @@ -524,6 +524,15 @@ const WILDERNESS_PIECE_FAMILIES: Readonly> = { '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'], diff --git a/scripts/verify-generators.ts b/scripts/verify-generators.ts index 39d4b78..68556cb 100644 --- a/scripts/verify-generators.ts +++ b/scripts/verify-generators.ts @@ -404,6 +404,15 @@ const WILDERNESS_PIECE_FAMILIES: Readonly> = { '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 }[] diff --git a/src/game/wilderness.ts b/src/game/wilderness.ts index 493227a..01093d3 100644 --- a/src/game/wilderness.ts +++ b/src/game/wilderness.ts @@ -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> = { + 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`) }