psx map improvement
This commit is contained in:
parent
328a8ba30f
commit
9fe261610f
14 changed files with 859 additions and 483 deletions
|
|
@ -9,8 +9,9 @@ import {
|
|||
extractPaletteSets,
|
||||
} from './bundles.js';
|
||||
import { decodeBundleFrame, encodePng, scanSpriteBundles } from './bundles.js';
|
||||
import { buildOverrideBundleBindings, parseOverrideBank } from './override-bank.js';
|
||||
import { renderMap } from './render.js';
|
||||
import { parseLsetWdl, parseRegion00Records, parseRegion01Records, summarizeRegion02 } from './wdl.js';
|
||||
import { parseCtorPlacementsBlock, parseDispatchRootsBlock, parseLoaderLayout, parseLsetWdl, parseRegion00Records, parseRegion01Records, summarizeRegion02 } from './wdl.js';
|
||||
|
||||
const DEFAULT_BACKGROUND = { red: 18, green: 18, blue: 18, alpha: 255 };
|
||||
|
||||
|
|
@ -85,17 +86,38 @@ function chooseRecordSet(wdl, mapSource) {
|
|||
? parseRegion01Records(region01Source)
|
||||
: { source: 'region01', recordStartOffset: 0, records: [] };
|
||||
|
||||
// Loader-faithful paths: pull ctorPlacements and dispatchRoots directly from
|
||||
// the packSubranges in the 14-u32 loader header. When present these override
|
||||
// the heuristic region scans because they match the executable's dispatch
|
||||
// iterators 1:1.
|
||||
const packSubranges = wdl.loaderLayout?.packSubranges ?? [];
|
||||
const ctorBlock = packSubranges.find((block) => block.name === 'ctorPlacements');
|
||||
const dispatchBlock = packSubranges.find((block) => block.name === 'dispatchRoots');
|
||||
const ctorPlacementsLoader = ctorBlock
|
||||
? parseCtorPlacementsBlock(ctorBlock)
|
||||
: { source: 'ctorPlacements', records: [] };
|
||||
const dispatchRootsLoader = dispatchBlock
|
||||
? parseDispatchRootsBlock(dispatchBlock)
|
||||
: { source: 'dispatchRoots', records: [] };
|
||||
|
||||
const constructorRecordSet = ctorPlacementsLoader.records.length > 0
|
||||
? ctorPlacementsLoader
|
||||
: region01Records;
|
||||
const rootRecordSet = dispatchRootsLoader.records.length > 0
|
||||
? dispatchRootsLoader
|
||||
: region00Records;
|
||||
|
||||
if (mapSource === 'region00' || mapSource === 'roots') {
|
||||
return region00Records;
|
||||
return rootRecordSet;
|
||||
}
|
||||
if (mapSource === 'region01' || mapSource === 'constructors') {
|
||||
return region01Records;
|
||||
return constructorRecordSet;
|
||||
}
|
||||
|
||||
if (mapSource === 'combined' || mapSource === 'layered' || mapSource === 'auto') {
|
||||
const records = [
|
||||
...region01Records.records.map((record) => ({ ...record, authoredLayer: 'constructors' })),
|
||||
...region00Records.records.map((record) => ({ ...record, authoredLayer: 'roots' })),
|
||||
...constructorRecordSet.records.map((record) => ({ ...record, authoredLayer: record.authoredLayer ?? 'constructors' })),
|
||||
...rootRecordSet.records.map((record) => ({ ...record, authoredLayer: record.authoredLayer ?? 'roots' })),
|
||||
];
|
||||
|
||||
return {
|
||||
|
|
@ -103,13 +125,25 @@ function chooseRecordSet(wdl, mapSource) {
|
|||
recordStartOffset: 0,
|
||||
records,
|
||||
layers: {
|
||||
constructors: region01Records.records.length,
|
||||
roots: region00Records.records.length,
|
||||
constructors: constructorRecordSet.records.length,
|
||||
roots: rootRecordSet.records.length,
|
||||
},
|
||||
loaderSources: {
|
||||
ctorPlacements: {
|
||||
used: ctorPlacementsLoader.records.length > 0,
|
||||
count: ctorPlacementsLoader.records.length,
|
||||
reportedCount: ctorPlacementsLoader.reportedCount ?? null,
|
||||
},
|
||||
dispatchRoots: {
|
||||
used: dispatchRootsLoader.records.length > 0,
|
||||
count: dispatchRootsLoader.records.length,
|
||||
reportedCount: dispatchRootsLoader.reportedCount ?? null,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return region01Records.records.length >= 8 ? region01Records : region00Records;
|
||||
return constructorRecordSet.records.length >= 8 ? constructorRecordSet : rootRecordSet;
|
||||
}
|
||||
|
||||
function chooseBundleForType(bundles, typeWord) {
|
||||
|
|
@ -127,6 +161,146 @@ async function loadJsonIfExists(filePath) {
|
|||
}
|
||||
}
|
||||
|
||||
async function loadOverrideBankBindingsForBuffer(buffer, layout, variant, paletteSets, options = {}) {
|
||||
if (!layout) {
|
||||
return {
|
||||
bundles: [],
|
||||
byType: new Map(),
|
||||
overrideBank: null,
|
||||
artInstallBank: null,
|
||||
failedEntries: [],
|
||||
};
|
||||
}
|
||||
const overrideBlock = layout.blocksByName.get('override');
|
||||
const artInstallBlock = layout.blocksByName.get('artInstall');
|
||||
|
||||
const result = {
|
||||
bundles: [],
|
||||
byType: new Map(),
|
||||
overrideBank: null,
|
||||
artInstallBank: null,
|
||||
failedEntries: [],
|
||||
};
|
||||
|
||||
// Parse art-install first: it provides the initial per-type drawable bindings
|
||||
// for both kind-4 and kind-5 resources. The later override pass then
|
||||
// replaces some slots with raw-header pointers.
|
||||
if (artInstallBlock && artInstallBlock.buffer) {
|
||||
const bank = parseOverrideBank(artInstallBlock, {
|
||||
variant,
|
||||
payloadBase: 0x2718,
|
||||
});
|
||||
result.artInstallBank = bank;
|
||||
if (bank.valid) {
|
||||
const { bundles: raw, failedEntries } = buildOverrideBundleBindings(bank, { buffer });
|
||||
const tagged = raw.map((bundle) => ({
|
||||
...bundle,
|
||||
bundleSource: variant === 'spec_a' ? 'art-install-spec-a' : 'art-install-lset',
|
||||
}));
|
||||
const resolved = resolveBundlePalettes(tagged, paletteSets, {
|
||||
mode1RuntimePalette: options.mode1RuntimePalette,
|
||||
});
|
||||
for (const bundle of resolved) {
|
||||
if (!bundle.sourceBuffer && Number.isInteger(bundle.absoluteOffset)) {
|
||||
bundle.sourceBuffer = buffer.subarray(bundle.absoluteOffset);
|
||||
}
|
||||
result.byType.set(bundle.typeId, bundle);
|
||||
result.bundles.push(bundle);
|
||||
}
|
||||
result.failedEntries.push(...failedEntries);
|
||||
}
|
||||
}
|
||||
|
||||
if (overrideBlock && overrideBlock.buffer) {
|
||||
const bank = parseOverrideBank(overrideBlock, { variant, payloadBase: 8 });
|
||||
result.overrideBank = bank;
|
||||
if (bank.valid) {
|
||||
const { bundles: raw, failedEntries } = buildOverrideBundleBindings(bank, { buffer });
|
||||
const tagged = raw.map((bundle) => ({
|
||||
...bundle,
|
||||
bundleSource: variant === 'spec_a' ? 'override-bank-spec-a' : 'override-bank-lset',
|
||||
}));
|
||||
const resolved = resolveBundlePalettes(tagged, paletteSets, {
|
||||
mode1RuntimePalette: options.mode1RuntimePalette,
|
||||
});
|
||||
for (const bundle of resolved) {
|
||||
if (!bundle.sourceBuffer && Number.isInteger(bundle.absoluteOffset)) {
|
||||
bundle.sourceBuffer = buffer.subarray(bundle.absoluteOffset);
|
||||
}
|
||||
// Late override wins over earlier art-install for the same type,
|
||||
// matching the loader's bank-slot overwrite behaviour.
|
||||
result.byType.set(bundle.typeId, bundle);
|
||||
result.bundles.push(bundle);
|
||||
}
|
||||
result.failedEntries.push(...failedEntries);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function loadSpecAOverrideBank(wdlPath, discRoot, paletteSets, options = {}) {
|
||||
const candidatePaths = [];
|
||||
if (discRoot) {
|
||||
candidatePaths.push(path.join(discRoot, 'SPEC_A.WDL'));
|
||||
}
|
||||
const wdlDir = path.dirname(wdlPath);
|
||||
candidatePaths.push(path.resolve(wdlDir, '..', 'SPEC_A.WDL'));
|
||||
candidatePaths.push(path.resolve(wdlDir, 'SPEC_A.WDL'));
|
||||
|
||||
for (const candidate of candidatePaths) {
|
||||
try {
|
||||
const buffer = await fs.readFile(candidate);
|
||||
const layout = parseLoaderLayout(buffer, { variant: 'spec_a' });
|
||||
const result = await loadOverrideBankBindingsForBuffer(
|
||||
buffer,
|
||||
layout,
|
||||
'spec_a',
|
||||
paletteSets,
|
||||
options,
|
||||
);
|
||||
result.sourcePath = candidate;
|
||||
return result;
|
||||
} catch {
|
||||
// try next candidate
|
||||
}
|
||||
}
|
||||
|
||||
return { bundles: [], byType: new Map(), overrideBank: null, failedEntries: [], sourcePath: null };
|
||||
}
|
||||
|
||||
async function loadOverrideBankBindings(lsetWdl, wdlPath, discRoot, paletteSets, options = {}) {
|
||||
// SPEC_A.WDL provides the base (bundle A) override table; the map-local
|
||||
// LSET*.WDL provides the override B table. The loader runs SPEC_A first
|
||||
// then LSET, and for matching typeIds LSET wins because it overwrites the
|
||||
// bank slot. We merge in the same order here.
|
||||
const specAResult = await loadSpecAOverrideBank(wdlPath, discRoot, paletteSets, options);
|
||||
const lsetLayout = lsetWdl.loaderLayout;
|
||||
const lsetResult = await loadOverrideBankBindingsForBuffer(
|
||||
lsetWdl.buffer,
|
||||
lsetLayout,
|
||||
'lset',
|
||||
paletteSets,
|
||||
options,
|
||||
);
|
||||
|
||||
const bundles = [...specAResult.bundles, ...lsetResult.bundles];
|
||||
const byType = new Map();
|
||||
for (const bundle of specAResult.bundles) {
|
||||
byType.set(bundle.typeId, bundle);
|
||||
}
|
||||
// LSET wins over SPEC_A for identical typeIds (matches loader behaviour).
|
||||
for (const bundle of lsetResult.bundles) {
|
||||
byType.set(bundle.typeId, bundle);
|
||||
}
|
||||
return {
|
||||
bundles,
|
||||
byType,
|
||||
specA: specAResult,
|
||||
lset: lsetResult,
|
||||
};
|
||||
}
|
||||
|
||||
function buildRuntimeMap0MaskedBindings(correlation, bundles, tolerance = 0x200) {
|
||||
const byType = new Map();
|
||||
const diagnostics = [];
|
||||
|
|
@ -226,6 +400,23 @@ function buildRuntimeMap0MaskedBindings(correlation, bundles, tolerance = 0x200)
|
|||
}
|
||||
|
||||
function chooseBundleBinding(record, bundles, options = {}) {
|
||||
// Primary path: per-type override-bank binding. The late header-only override
|
||||
// block of each WDL pass (SPEC_A.WDL followed by the map-local LSET*.WDL)
|
||||
// writes raw 0x58-byte active-header pointers into
|
||||
// `psx_type_art_active_header_bank[type]`. Constructors and the render
|
||||
// submitters read that bank at runtime, so an override-bank bundle IS the
|
||||
// drawable resource bound to that type for this level.
|
||||
const overrideBundle = options.overrideBundlesByType?.get(record.typeWord) ?? null;
|
||||
if (overrideBundle) {
|
||||
return {
|
||||
bundle: overrideBundle,
|
||||
mappingSource: overrideBundle.overrideVariant === 'spec_a'
|
||||
? 'override-bank-spec-a'
|
||||
: 'override-bank-lset',
|
||||
runtimeBinding: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (options.bindingMode === 'runtime-map0-masked-proxy') {
|
||||
const runtimeBinding = options.runtimeMap0Bindings?.get(record.typeWord) ?? null;
|
||||
if (runtimeBinding?.bundle) {
|
||||
|
|
@ -233,6 +424,13 @@ function chooseBundleBinding(record, bundles, options = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
if (options.bindingMode === 'override-bank') {
|
||||
// Strict mode: refuse to fall back to the diagnostic slot rule. Types with
|
||||
// no override binding get dropped from the scene so the render reflects
|
||||
// only executable-backed art.
|
||||
return null;
|
||||
}
|
||||
|
||||
const rawTypeBundle = chooseBundleForType(bundles, record.typeWord);
|
||||
if (!rawTypeBundle) {
|
||||
return null;
|
||||
|
|
@ -1033,6 +1231,20 @@ export async function exportMap(options) {
|
|||
{ mode1RuntimePalette }
|
||||
);
|
||||
|
||||
// Override-bank bindings: per-type drawable-header pointers installed by the
|
||||
// late override pass in wdl_resource_bundle_load_by_index. This is the same
|
||||
// runtime state constructors and render submitters read when resolving art,
|
||||
// so it gives executable-backed typeWord -> bundle truth for the bundle A
|
||||
// (SPEC_A.WDL) and bundle B (map-local LSET WDL) passes combined.
|
||||
const overrideBankResult = await loadOverrideBankBindings(
|
||||
wdl,
|
||||
options.wdlPath,
|
||||
options.discRoot ?? null,
|
||||
paletteSets,
|
||||
{ mode1RuntimePalette },
|
||||
);
|
||||
bundles = [...bundles, ...overrideBankResult.bundles];
|
||||
|
||||
const runtimeMap0Correlation = options.bindingMode === 'runtime-map0-masked-proxy'
|
||||
? await loadJsonIfExists(path.join(cacheBaseRoot, 'runtime-map0-correlation.json'))
|
||||
: null;
|
||||
|
|
@ -1044,6 +1256,7 @@ export async function exportMap(options) {
|
|||
mode1RuntimePalette,
|
||||
mode1PaletteBank,
|
||||
runtimeMap0Bindings: runtimeMap0BindingResult.byType,
|
||||
overrideBundlesByType: overrideBankResult.byType,
|
||||
});
|
||||
const bindingDiversity = summarizeBindingDiversity(sceneItems);
|
||||
const authoredLayerSummary = summarizeAuthoredLayers(recordSet.records);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue