From e817ace5ef4b1391dc9ade2d9ad290e11d781519 Mon Sep 17 00:00:00 2001 From: troytt <47798984@qq.com> Date: Thu, 17 Sep 2026 11:30:47 +0000 Subject: [PATCH] =?UTF-8?q?fix(maze-objects):=20=E5=88=A0=E6=8E=89=20isOpe?= =?UTF-8?q?nFloor=20=E9=87=8C=E6=81=92=E7=9C=9F=E7=9A=84=20`||=20true`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `hasFloor` 的内容检测写成 `(f.prop1 !== 0 || f.style !== 0 || f.sequence !== 0 || true)`,末尾的 `|| true` 让整个条件恒为真:DS1 每个格子的每一层地板都有一条 记录,未绘制处是全零槽位,于是「没有地板美术的空格」也被判定为可放物件的地面。 删除 `|| true` 后,全零地板槽位不再算作地面;新增 6 个回归用例覆盖 painted / 全零 / 仅 style / 仅 sequence / hidden / 多层,以及「整关地板全为空槽 时不生成任何动态物件」。 TAG=agy CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1 --- src/game/maze-objects.ts | 8 ++++-- tests/maze-objects.test.ts | 54 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/game/maze-objects.ts b/src/game/maze-objects.ts index 854f955..8060f7c 100644 --- a/src/game/maze-objects.ts +++ b/src/game/maze-objects.ts @@ -234,8 +234,12 @@ export function isOpenFloor(cells: readonly (readonly Ds1Cell[])[], cx: number, const cell = row[cx] if (!cell) return false - // Must contain floor - const hasFloor = cell.floors.length > 0 && cell.floors.some(f => !f.hidden && (f.prop1 !== 0 || f.style !== 0 || f.sequence !== 0 || true)) + // Must contain floor *art*. A `Ds1Floor` whose `prop1`, `style` and `sequence` + // are all zero is an empty slot, not a tile: DS1 stores one entry per floor + // layer for every cell, so a one-layer map has a zero-filled entry everywhere + // the level does not paint. The `|| true` that used to close this condition + // made the whole content test vacuous, so objects were placed on void cells. + const hasFloor = cell.floors.length > 0 && cell.floors.some(f => !f.hidden && (f.prop1 !== 0 || f.style !== 0 || f.sequence !== 0)) if (!hasFloor) return false // Must not have a solid, visible wall diff --git a/tests/maze-objects.test.ts b/tests/maze-objects.test.ts index 1e259b7..ff44275 100644 --- a/tests/maze-objects.test.ts +++ b/tests/maze-objects.test.ts @@ -2,6 +2,7 @@ import { describe, expect, test } from 'vitest' import type { Ds1, Ds1Cell, Ds1Object } from '../src/formats/ds1.ts' import { populateDungeonObjects, + isOpenFloor, CANONICAL_SHRINES, CHEST_IDS_BY_ACT, RACK_IDS_BY_ACT, @@ -482,3 +483,56 @@ describe('generateMaze end-to-end dynamic object integration', () => { expect(foundStatic).toBe(true) }) }) + +/** + * Regression cover for the `|| true` that used to close `isOpenFloor`'s content + * test (issue #58 item 2). With it, every `Ds1Floor` entry counted as floor art, + * so objects were seeded on cells the level never paints. + */ +describe('isOpenFloor content check', () => { + /** One cell grid with the given floor entries and no walls. */ + function grid(floors: Ds1Cell['floors']): Ds1Cell[][] { + return [[{ walls: [], floors, shadows: [], substitutions: [] }]] + } + + const painted = { prop1: 1, sequence: 0, style: 0, unknown1: 0, unknown2: 0, hidden: false } + const empty = { prop1: 0, sequence: 0, style: 0, unknown1: 0, unknown2: 0, hidden: false } + + test('a painted floor cell is open floor', () => { + expect(isOpenFloor(grid([painted]), 0, 0)).toBe(true) + }) + + test('a cell whose only floor entry is all-zero is not open floor', () => { + expect(isOpenFloor(grid([empty]), 0, 0)).toBe(false) + }) + + test('style or sequence alone still counts as art', () => { + expect(isOpenFloor(grid([{ ...empty, style: 3 }]), 0, 0)).toBe(true) + expect(isOpenFloor(grid([{ ...empty, sequence: 7 }]), 0, 0)).toBe(true) + }) + + test('a hidden painted floor does not rescue an otherwise empty cell', () => { + expect(isOpenFloor(grid([{ ...painted, hidden: true }]), 0, 0)).toBe(false) + }) + + test('a multi-layer cell is open floor when any layer carries art', () => { + expect(isOpenFloor(grid([empty, painted]), 0, 0)).toBe(true) + expect(isOpenFloor(grid([empty, empty]), 0, 0)).toBe(false) + }) + + test('objects are not seeded into a level whose floors are all empty slots', () => { + const { level, rooms } = createSyntheticDungeon() + for (const row of level.cells) { + for (const cell of row) { + ;(cell.floors as Ds1Cell['floors'][number][]).splice( + 0, + cell.floors.length, + { prop1: 0, sequence: 0, style: 0, unknown1: 0, unknown2: 0, hidden: false }, + ) + } + } + const result = populateDungeonObjects(rooms, 0, 0, level, 'Act 1 - Cave', 2, { density: 1.0 }, new Rng(7)) + expect(result.placedObjects.length).toBe(0) + }) +}) +