From 540228213d34622e3483aca6e5005d234a207ecd Mon Sep 17 00:00:00 2001 From: Keiichiro Ui Date: Sun, 12 Jul 2026 20:04:40 +0900 Subject: [PATCH] Fail fast in generate-prefab-html.ts on non-ENOENT errors (#230) - Replace catch-all warn/return with rethrow so parser bugs and unknown data shapes fail the build instead of silently dropping prefabs. - Align SleeperVolumeFlags fallback with the 7DTD binary bounds-check (Assembly-CSharp.dll, PrefabVolumes.PrefabSleeperVolumeList::ReadFromProperties): missing entries default to 0. - Document other optional property fallbacks (vanilla omits trailing falses). --- tools/generate-prefab-html.ts | 3 +-- tools/lib/prefab-html.ts | 16 ++++++++++------ tools/test/prefab-html.test.ts | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/tools/generate-prefab-html.ts b/tools/generate-prefab-html.ts index 52ad0ef1..2c2c8521 100644 --- a/tools/generate-prefab-html.ts +++ b/tools/generate-prefab-html.ts @@ -61,8 +61,7 @@ async function buildHtmls(labels: Map) { console.warn("Abort a prefab HTML: ", e.message); return; } - console.warn("Abort a prefab HTML: ", e); - return; + throw e; } if (++successCount % 50 === 0) { console.log("Build HTML files: %d/%d", successCount, xmlFiles.length); diff --git a/tools/lib/prefab-html.ts b/tools/lib/prefab-html.ts index a790726c..ff944257 100644 --- a/tools/lib/prefab-html.ts +++ b/tools/lib/prefab-html.ts @@ -301,7 +301,9 @@ export function buildSleeperVolumes( if (groupIds === undefined) { throw new Error("SleeperVolumeGroupId is not found"); } - // HACK: pad SleeperVolumeGameStageAdjust with empty strings when it is absent or shorter than SleeperVolumeGroup. + // WHY: vanilla prefabs omit trailing falses/empties on optional properties. + // WHY: IsLoot/IsQuestExclude/Flags fallbacks match the 7DTD binary bounds-checks. + // WHY: GameStageAdjust and IsBoss are absent from the binary model. const gameStageAdjusts = parseCsvField(propMap, "SleeperVolumeGameStageAdjust", (s) => s.trim()) ?? Array.from(groups, () => ""); @@ -311,7 +313,6 @@ export function buildSleeperVolumes( (s) => parseInt(s, 10), ); if (flags === undefined) throw new Error("SleeperVolumeFlags is not found"); - // HACK: default SleeperIsBoss/Loot/QuestExclude arrays to all-false when the property is absent or shorter than groups. const parseBool = (s: string) => s === "True"; const isBosses = parseCsvField(propMap, "SleeperIsBossVolume", parseBool) ?? Array.from(groups, () => false); @@ -342,11 +343,14 @@ export function buildSleeperVolumes( groupIds[i], () => `Unexpected state: groupId is not found: index=${String(i)}`, ), + // WHY: trailing empty entries are omitted in vanilla prefabs for optional string arrays. gameStageAdjust: gameStageAdjusts[i] ?? "", - flags: requireNonnull( - flags[i], - () => `Unexpected state: flags is not found: index=${String(i)}`, - ), + // WHY: the 7DTD binary (Assembly-CSharp.dll, PrefabVolumes.PrefabSleeperVolumeList::ReadFromProperties) + // WHY: falls back to 0 when SleeperVolumeFlags is shorter than SleeperVolumeGroup. + flags: flags[i] ?? 0, + // WHY: the 7DTD binary falls back to false when SleeperIsLootVolume and + // WHY: SleeperIsQuestExclude are shorter than SleeperVolumeGroup. + // WHY: isBoss is not part of the binary model; kept here for HTML display. isBoss: isBosses[i] ?? false, isLoot: isLoots[i] ?? false, isQuestExclude: isQuestExcludes[i] ?? false, diff --git a/tools/test/prefab-html.test.ts b/tools/test/prefab-html.test.ts index 85d02263..ed329be0 100644 --- a/tools/test/prefab-html.test.ts +++ b/tools/test/prefab-html.test.ts @@ -71,4 +71,19 @@ describe("buildSleeperVolumes", () => { "SleeperVolumeFlags is not found", ); }); + + it("defaults flags to 0 when the property is shorter than groups", () => { + // WHY: the 7DTD binary also falls back to 0 for missing flags entries. + const props: PrefabProperty[] = [ + { name: "SleeperVolumeGroup", value: "groupA,1,2,groupB,3,4" }, + { name: "SleeperVolumeGroupId", value: "0,1" }, + { name: "SleeperVolumeFlags", value: "1" }, + { name: "SleeperVolumeSize", value: "1,2,3#4,5,6" }, + { name: "SleeperVolumeStart", value: "0,0,0#1,1,1" }, + ]; + const volumes = buildSleeperVolumes(props); + expect(volumes.length).toBe(2); + expect(volumes[0].flags).toBe(1); + expect(volumes[1].flags).toBe(0); + }); });