PSX Research
This commit is contained in:
parent
2f243976b6
commit
8d34c85c22
13 changed files with 1720 additions and 8 deletions
|
|
@ -119,6 +119,132 @@ function chooseBundleForType(bundles, typeWord) {
|
|||
return null;
|
||||
}
|
||||
|
||||
async function loadJsonIfExists(filePath) {
|
||||
try {
|
||||
return JSON.parse(await fs.readFile(filePath, 'utf8'));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function buildRuntimeMap0MaskedBindings(correlation, bundles, tolerance = 0x200) {
|
||||
const byType = new Map();
|
||||
const diagnostics = [];
|
||||
const provisionalBindings = [];
|
||||
|
||||
if (!correlation || correlation.currentMapId !== 0 || !Array.isArray(correlation.combined)) {
|
||||
return { byType, diagnostics };
|
||||
}
|
||||
|
||||
for (const row of correlation.combined) {
|
||||
if (!row || !Number.isInteger(row.typeId) || !Number.isInteger(row.rawRecordCount) || row.rawRecordCount <= 0) {
|
||||
continue;
|
||||
}
|
||||
if ((row.headerKind !== 4 && row.headerKind !== 5) || (row.headerWord3 !== 1 && row.headerWord3 !== 2)) {
|
||||
continue;
|
||||
}
|
||||
if (!Number.isInteger(row.headerWord11) || row.headerWord11 === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const maskedAbsoluteOffset = row.headerWord11 & 0x0fffff;
|
||||
let bestMatch = null;
|
||||
for (const bundle of bundles) {
|
||||
if (bundle.kind !== row.headerKind || bundle.mode !== row.headerWord3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const absoluteOffsetDelta = Math.abs(bundle.absoluteOffset - maskedAbsoluteOffset);
|
||||
if (!bestMatch || absoluteOffsetDelta < bestMatch.absoluteOffsetDelta) {
|
||||
bestMatch = {
|
||||
bundle,
|
||||
absoluteOffsetDelta,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!bestMatch || bestMatch.absoluteOffsetDelta > tolerance) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const binding = {
|
||||
typeId: row.typeId,
|
||||
bundle: bestMatch.bundle,
|
||||
mappingSource: 'runtime-map0-masked-header-offset-proxy',
|
||||
runtimeBinding: {
|
||||
maskedAbsoluteOffset,
|
||||
absoluteOffsetDelta: bestMatch.absoluteOffsetDelta,
|
||||
headerKind: row.headerKind,
|
||||
headerMode: row.headerWord3,
|
||||
headerWord4: row.headerWord4,
|
||||
headerWord8: row.headerWord8,
|
||||
headerWord10: row.headerWord10,
|
||||
visibleCount: row.visibleCount ?? 0,
|
||||
rawRecordCount: row.rawRecordCount,
|
||||
},
|
||||
};
|
||||
|
||||
provisionalBindings.push(binding);
|
||||
}
|
||||
|
||||
const bundleBuckets = new Map();
|
||||
for (const binding of provisionalBindings) {
|
||||
const bucket = bundleBuckets.get(binding.bundle.absoluteOffset) ?? [];
|
||||
bucket.push(binding);
|
||||
bundleBuckets.set(binding.bundle.absoluteOffset, bucket);
|
||||
}
|
||||
|
||||
for (const binding of provisionalBindings) {
|
||||
const bucket = bundleBuckets.get(binding.bundle.absoluteOffset) ?? [];
|
||||
const isCrowdedLargeSingleFrameBundle =
|
||||
bucket.length >= 4 &&
|
||||
binding.bundle.frameCount === 1 &&
|
||||
binding.bundle.width >= 96 &&
|
||||
binding.bundle.height >= 48;
|
||||
|
||||
if (isCrowdedLargeSingleFrameBundle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
byType.set(binding.typeId, binding);
|
||||
diagnostics.push({
|
||||
typeId: binding.typeId,
|
||||
bundleSlot: binding.bundle.slot,
|
||||
bundleAbsoluteOffset: binding.bundle.absoluteOffset,
|
||||
maskedAbsoluteOffset: binding.runtimeBinding.maskedAbsoluteOffset,
|
||||
absoluteOffsetDelta: binding.runtimeBinding.absoluteOffsetDelta,
|
||||
headerKind: binding.runtimeBinding.headerKind,
|
||||
headerMode: binding.runtimeBinding.headerMode,
|
||||
visibleCount: binding.runtimeBinding.visibleCount,
|
||||
rawRecordCount: binding.runtimeBinding.rawRecordCount,
|
||||
crowdedBundleTypeCount: bucket.length,
|
||||
});
|
||||
}
|
||||
|
||||
diagnostics.sort((left, right) => left.typeId - right.typeId);
|
||||
return { byType, diagnostics };
|
||||
}
|
||||
|
||||
function chooseBundleBinding(record, bundles, options = {}) {
|
||||
if (options.bindingMode === 'runtime-map0-masked-proxy') {
|
||||
const runtimeBinding = options.runtimeMap0Bindings?.get(record.typeWord) ?? null;
|
||||
if (runtimeBinding?.bundle) {
|
||||
return runtimeBinding;
|
||||
}
|
||||
}
|
||||
|
||||
const rawTypeBundle = chooseBundleForType(bundles, record.typeWord);
|
||||
if (!rawTypeBundle) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
bundle: rawTypeBundle,
|
||||
mappingSource: 'raw-typeword-bundle-slot-diagnostic',
|
||||
runtimeBinding: null,
|
||||
};
|
||||
}
|
||||
|
||||
function describeMapScope(recordSet) {
|
||||
if (recordSet.source === 'combined') {
|
||||
return 'layered object-projection probe from both loader-sized section-0 constructor-placement and root-dispatch records in post_audio_section_00';
|
||||
|
|
@ -738,9 +864,9 @@ async function buildSceneItems(region04, records, bundles, options = {}) {
|
|||
continue;
|
||||
}
|
||||
|
||||
const rawTypeBundle = chooseBundleForType(bundles, record.typeWord);
|
||||
const bundle = rawTypeBundle;
|
||||
if (!bundle) {
|
||||
const binding = chooseBundleBinding(record, bundles, options);
|
||||
const bundle = binding?.bundle ?? null;
|
||||
if (!bundle || !binding) {
|
||||
continue;
|
||||
}
|
||||
if (nonMapFacingBundleOffsets.has(bundle.absoluteOffset)) {
|
||||
|
|
@ -788,9 +914,13 @@ async function buildSceneItems(region04, records, bundles, options = {}) {
|
|||
defaultPaletteIndex: bundle.defaultPaletteIndex ?? null,
|
||||
resolvedPaletteIndex: bundle.resolvedPaletteIndex ?? null,
|
||||
paletteFormula: bundle.paletteFormula ?? null,
|
||||
mappingSource: 'raw-typeword-bundle-slot-diagnostic',
|
||||
mappingSource: binding.mappingSource,
|
||||
templateTypeId: null,
|
||||
donorTypeId: null,
|
||||
runtimeBindingMaskedAbsoluteOffset: binding.runtimeBinding?.maskedAbsoluteOffset ?? null,
|
||||
runtimeBindingOffsetDelta: binding.runtimeBinding?.absoluteOffsetDelta ?? null,
|
||||
runtimeBindingVisibleCount: binding.runtimeBinding?.visibleCount ?? null,
|
||||
runtimeBindingRawRecordCount: binding.runtimeBinding?.rawRecordCount ?? null,
|
||||
rawWords: record.rawWords ?? record.words,
|
||||
flipped: (record.laneWord & 0x0002) !== 0,
|
||||
width: sprite.width,
|
||||
|
|
@ -903,10 +1033,17 @@ export async function exportMap(options) {
|
|||
{ mode1RuntimePalette }
|
||||
);
|
||||
|
||||
const runtimeMap0Correlation = options.bindingMode === 'runtime-map0-masked-proxy'
|
||||
? await loadJsonIfExists(path.join(cacheBaseRoot, 'runtime-map0-correlation.json'))
|
||||
: null;
|
||||
const runtimeMap0BindingResult = buildRuntimeMap0MaskedBindings(runtimeMap0Correlation, bundles);
|
||||
|
||||
const { items: sceneItems, skippedRecords } = await buildSceneItems(region04, recordSet.records, bundles, {
|
||||
paletteSets,
|
||||
bindingMode: options.bindingMode,
|
||||
mode1RuntimePalette,
|
||||
mode1PaletteBank,
|
||||
runtimeMap0Bindings: runtimeMap0BindingResult.byType,
|
||||
});
|
||||
const bindingDiversity = summarizeBindingDiversity(sceneItems);
|
||||
const authoredLayerSummary = summarizeAuthoredLayers(recordSet.records);
|
||||
|
|
@ -949,7 +1086,11 @@ export async function exportMap(options) {
|
|||
bundleCount: bundles.length,
|
||||
bundleSource: bundles[0]?.bundleSource ?? 'none',
|
||||
gpuRamDumpPath: options.gpuRamDumpPath ?? null,
|
||||
artBindingSource: 'raw-typeword-bundle-slot-diagnostic',
|
||||
artBindingSource: options.bindingMode === 'runtime-map0-masked-proxy'
|
||||
? 'runtime-map0-masked-header-offset-proxy-with-raw-fallback'
|
||||
: 'raw-typeword-bundle-slot-diagnostic',
|
||||
runtimeMap0BindingTypeCount: runtimeMap0BindingResult.diagnostics.length,
|
||||
runtimeMap0BindingTypes: runtimeMap0BindingResult.diagnostics,
|
||||
activeHeaderOverrideCandidateCount: activeHeaderOverrideCandidates.length,
|
||||
bestActiveHeaderOverrideCandidate: activeHeaderOverrideCandidates[0]
|
||||
? {
|
||||
|
|
@ -975,7 +1116,9 @@ export async function exportMap(options) {
|
|||
'The root-dispatch lane is now rendered as a second authored layer, but runtime-driven control mutations and dynamic effect spawns are still out of scope.',
|
||||
'Known non-map-facing portrait/talk root types `0x0042` and `0x0049`, plus the known portrait bundle `0x000D84F4`, are excluded from probe rendering.',
|
||||
'Viewer-derived sidecars, donor mappings, and cached scene references are intentionally disabled in this standalone exporter.',
|
||||
'Current bundle selection is still diagnostic-only until the late DAT_800758d8 art bank is parsed directly.',
|
||||
options.bindingMode === 'runtime-map0-masked-proxy'
|
||||
? 'Current bundle selection uses an experimental map-0 runtime masked-offset proxy where live headerWord11 low bits land near a scanned raw bundle; all non-matching types still fall back to the raw slot diagnostic.'
|
||||
: 'Current bundle selection is still diagnostic-only until the late DAT_800758d8 art bank is parsed directly.',
|
||||
'No floor or tile layer is decoded directly yet; post_audio_region_02 and the decompressed level-state lane remain unresolved.',
|
||||
]),
|
||||
'Palette routing remains partly heuristic when authored token and default bank evidence are both absent.',
|
||||
|
|
@ -1023,6 +1166,10 @@ export async function exportMap(options) {
|
|||
mappingSource: item.mappingSource,
|
||||
templateTypeId: item.templateTypeId,
|
||||
donorTypeId: item.donorTypeId,
|
||||
runtimeBindingMaskedAbsoluteOffset: item.runtimeBindingMaskedAbsoluteOffset,
|
||||
runtimeBindingOffsetDelta: item.runtimeBindingOffsetDelta,
|
||||
runtimeBindingVisibleCount: item.runtimeBindingVisibleCount,
|
||||
runtimeBindingRawRecordCount: item.runtimeBindingRawRecordCount,
|
||||
token06HighByte: item.paletteDiagnostics?.token06HighByte ?? null,
|
||||
token0cHighByte: item.paletteDiagnostics?.token0cHighByte ?? null,
|
||||
expectedPaletteToken: item.paletteDiagnostics?.expectedPaletteToken ?? null,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue