392 lines
32 KiB
JavaScript
392 lines
32 KiB
JavaScript
const { spawnSync } = require('node:child_process');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const projectRoot = path.resolve(__dirname, '..');
|
|
const sourceWrapperPath = path.join(__dirname, 'playtest-stockade-stances-blocking.cjs');
|
|
const sourceWrapper = fs.readFileSync(sourceWrapperPath, 'utf8');
|
|
const runtimeWrapperPath = path.join(__dirname, `.playtest-stockade-spell-blocking-${process.pid}.cjs`);
|
|
|
|
function replaceExactlyOnce(source, marker, replacement, label) {
|
|
const count = source.split(marker).length - 1;
|
|
if (count !== 1) throw new Error(`${label} must occur exactly once; found ${count}.`);
|
|
return source.replace(marker, replacement);
|
|
}
|
|
|
|
function transformSpellCampaignHarness(source) {
|
|
const replaceOnce = (marker, replacement, label) => {
|
|
const count = source.split(marker).length - 1;
|
|
if (count !== 1) throw new Error(`${label} must occur exactly once in the campaign source; found ${count}.`);
|
|
source = source.replace(marker, replacement);
|
|
};
|
|
const replaceEvery = (marker, replacement, label) => {
|
|
const count = source.split(marker).length - 1;
|
|
if (!count) throw new Error(`${label} marker was not found in the campaign source.`);
|
|
source = source.replaceAll(marker, replacement);
|
|
};
|
|
|
|
replaceOnce(
|
|
`async function runCampaignBossRoute(page, route, entryState, profile) {
|
|
const guidance = readNavigationGuidance(route.id);`,
|
|
`async function runCampaignBossRoute(page, route, entryState, profile) {
|
|
if (!entryState.dungeonRun?.id && !entryState.dungeonRun?.biteSizedSelection) {
|
|
const selectionDeadline = Date.now() + 8_000;
|
|
while (Date.now() < selectionDeadline
|
|
&& !entryState.dungeonRun?.id && !entryState.dungeonRun?.biteSizedSelection) {
|
|
await page.waitForTimeout(250);
|
|
entryState = await readLiveState(page, true);
|
|
}
|
|
}
|
|
const fullStockadeGuidance = readNavigationGuidance(FULL_STOCKADE_DUNGEON_ID);
|
|
const sourceBossObjectives = fullStockadeGuidance.objectives;
|
|
const observedBossesAtEntry = entryState.mobs.filter((mob) => mob.boss && !mob.dead
|
|
&& sourceBossObjectives.some((objective) => objective.name === mob.name));
|
|
if (observedBossesAtEntry.length > 1) {
|
|
throw new Error(\`Bite-Sized Stockade exposed multiple source bosses at entry: \${JSON.stringify(observedBossesAtEntry.map((mob) => ({ id: mob.id, name: mob.name, serverEntry: mob.serverEntry })))}.\`);
|
|
}
|
|
const selectedRunBoss = entryState.dungeonRun?.biteSizedSelection?.boss ?? null;
|
|
const runId = entryState.dungeonRun?.id ?? null;
|
|
const observedSelectedBoss = observedBossesAtEntry[0] ?? null;
|
|
const runIdSelectedObjective = runId ? (() => {
|
|
let hash = 2_166_136_261;
|
|
for (let index = 0; index < runId.length; index += 1) {
|
|
hash ^= runId.charCodeAt(index);
|
|
hash = Math.imul(hash, 16_777_619);
|
|
}
|
|
return sourceBossObjectives[(hash >>> 0) % sourceBossObjectives.length];
|
|
})() : null;
|
|
const selectedSourceObjective = selectedRunBoss
|
|
? sourceBossObjectives.find((objective) => objective.name === selectedRunBoss.name
|
|
&& objective.id === selectedRunBoss.objectiveId)
|
|
: runIdSelectedObjective ?? sourceBossObjectives.find((objective) => objective.name === observedSelectedBoss?.name);
|
|
if (!selectedSourceObjective) {
|
|
throw new Error(\`Bite-Sized Stockade selected boss was not present in run selection or a live entry mob: selection=\${JSON.stringify(selectedRunBoss)}, observed=\${JSON.stringify(observedBossesAtEntry.map((mob) => ({ id: mob.id, name: mob.name, serverEntry: mob.serverEntry })))}.\`);
|
|
}
|
|
if (observedSelectedBoss && observedSelectedBoss.name !== selectedSourceObjective.name) {
|
|
throw new Error('The live entry boss name disagrees with the seeded dungeon-run selection.');
|
|
}
|
|
const cellblockAnchor = sourceBossObjectives.find((objective) => objective.name === 'Targorr the Dread');
|
|
if (!selectedSourceObjective || !cellblockAnchor) {
|
|
throw new Error('The observed seeded boss or reviewed central-cell navigation anchor is missing from the source guide.');
|
|
}
|
|
const expectedBiteSizedBossRoom = [-159.582, -25.6062, 1.2531];
|
|
if (!Array.isArray(cellblockAnchor.position)
|
|
|| cellblockAnchor.position.some((coordinate, index) => coordinate !== expectedBiteSizedBossRoom[index])) {
|
|
throw new Error(\`Targorr guide anchor \${JSON.stringify(cellblockAnchor.position)} does not exactly match the Bite-Sized boss room \${JSON.stringify(expectedBiteSizedBossRoom)}.\`);
|
|
}
|
|
const biteWaypoints = fullStockadeGuidance.waypoints.slice(0, cellblockAnchor.waypointIndex + 1);
|
|
const biteDistance = biteWaypoints.slice(1).reduce((sum, waypoint, index) =>
|
|
sum + distance3(biteWaypoints[index], waypoint), 0);
|
|
const selectedObjective = { ...selectedSourceObjective,
|
|
position: cellblockAnchor.position, waypointIndex: cellblockAnchor.waypointIndex };
|
|
const selectedBossIdentity = {
|
|
name: selectedSourceObjective.name,
|
|
sourceSpawnId: selectedSourceObjective.id,
|
|
entityId: selectedRunBoss?.entityId ?? null,
|
|
sourceDungeonId: selectedRunBoss?.sourceDungeonId ?? null,
|
|
runId,
|
|
liveActorId: observedSelectedBoss?.id ?? null,
|
|
serverEntry: observedSelectedBoss?.serverEntry ?? null,
|
|
selectionEvidence: selectedRunBoss
|
|
? 'read-only biteSizedSelection.boss from normal dungeon-run progress'
|
|
: runIdSelectedObjective
|
|
? 'source seededBossIndex FNV-1a selection reconstructed from read-only dungeon-run id'
|
|
: 'the one live source boss mob observed at normal Bite-Sized Stockade entry',
|
|
};
|
|
const guidance = {
|
|
...fullStockadeGuidance,
|
|
dungeonId: route.id,
|
|
objective: selectedObjective.name,
|
|
objectiveId: selectedObjective.id,
|
|
objectivePosition: selectedObjective.position,
|
|
objectives: [selectedObjective],
|
|
waypoints: biteWaypoints,
|
|
waypointCount: biteWaypoints.length,
|
|
expectedNavigationDistance: Number(biteDistance.toFixed(2)),
|
|
};`,
|
|
'dynamic seeded one-boss navigation guide',
|
|
);
|
|
replaceOnce(
|
|
' objective: { name: guidance.objective, position: guidance.objectivePosition },',
|
|
' objective: { name: guidance.objective, position: guidance.objectivePosition },\n selectedBossIdentity,',
|
|
'selected boss identity in campaign result',
|
|
);
|
|
replaceOnce(
|
|
' dungeonRun: dungeonRunProgress ? {\n dungeonId: dungeonRunProgress.dungeonId,',
|
|
' dungeonRun: dungeonRunProgress ? {\n id: dungeonRunProgress.id ?? null,\n biteSizedSelection: dungeonRunProgress.biteSizedSelection ?? null,\n dungeonId: dungeonRunProgress.dungeonId,',
|
|
'read-only seeded boss run selection',
|
|
);
|
|
replaceOnce(
|
|
' const playerToBoss = liveBoss ? distance3(current.player.position, liveBoss.position) : bossDistance;',
|
|
' if (liveBoss) {\n selectedBossIdentity.liveActorId = liveBoss.id;\n selectedBossIdentity.serverEntry = liveBoss.serverEntry ?? selectedBossIdentity.serverEntry;\n }\n const playerToBoss = liveBoss ? distance3(current.player.position, liveBoss.position) : bossDistance;',
|
|
'late live seeded boss identity confirmation',
|
|
);
|
|
replaceOnce(
|
|
' result.pageErrors = [];\n result.consoleErrors = [];\n result.frozenBuildAssetUrls = moduleUrls.map((url) => new URL(url, page.url()).href);',
|
|
' const selectedBossMobObservation = [...entryState.mobs, ...current.mobs,\n ...result.samples.flatMap((sample) => sample.mobs ?? [])]\n .find((mob) => mob.name === selectedBossIdentity.name && mob.id);\n const selectedBossDeathObservation = [...observedDeadMobs].find(([, name]) => name === selectedBossIdentity.name);\n if (selectedBossMobObservation) {\n selectedBossIdentity.liveActorId = selectedBossMobObservation.id;\n selectedBossIdentity.serverEntry = selectedBossMobObservation.serverEntry ?? selectedBossIdentity.serverEntry;\n } else if (selectedBossDeathObservation) selectedBossIdentity.liveActorId = selectedBossDeathObservation[0];\n result.pageErrors = [];\n result.consoleErrors = [];\n result.frozenBuildAssetUrls = moduleUrls.map((url) => new URL(url, page.url()).href);',
|
|
'final selected live boss actor identity',
|
|
);
|
|
|
|
replaceOnce(
|
|
` const initialEventKeys = new Set(entryState.events.map((event) => [event.occurredAt, event.sourceActorId,
|
|
event.targetActorId, event.abilityId, event.kind, event.effectiveAmount].join('|')));
|
|
const seenEventKeys = initialEventKeys;`,
|
|
` const eventKey = (event) => event.id != null ? \`event:\${event.id}\` : JSON.stringify(event);
|
|
const initialEventKeys = new Set(entryState.events.map(eventKey));
|
|
const seenEventKeys = initialEventKeys;`,
|
|
'stable source event identity key',
|
|
);
|
|
replaceEvery(
|
|
` const key = [event.occurredAt, event.sourceActorId, event.targetActorId,
|
|
event.abilityId, event.kind, event.effectiveAmount].join('|');`,
|
|
' const key = eventKey(event);',
|
|
'stable sampled-event deduplication key',
|
|
);
|
|
replaceOnce(
|
|
" if (lastCombatResult?.outcome === 'block') {\n const latestDamageEvent = [...snapshot.events].reverse().find((event) => event.kind === 'damage');\n const matchedLatestBlockEvent = latestDamageEvent?.outcome === 'block'\n && Number(latestDamageEvent.rawAmount) === Number(lastCombatResult.rawDamage)\n ? latestDamageEvent : null;",
|
|
" if (lastCombatResult?.blocked === true || lastCombatResult?.outcome === 'block') {\n const latestDamageEvent = [...snapshot.events].reverse().find((event) => event.kind === 'damage'\n && (event.blocked === true || Number(event.blockedAmount) > 0 || event.outcome === 'block'));\n const matchedLatestBlockEvent = latestDamageEvent\n && Number(latestDamageEvent.rawAmount) === Number(lastCombatResult.rawDamage)\n ? latestDamageEvent : null;",
|
|
'crit-with-block last result sampling',
|
|
);
|
|
|
|
replaceOnce(
|
|
' school: attack.school ?? null, castTimeMs: attack.castTimeMs ?? 0,',
|
|
' school: attack.school ?? null, wowSpellDefense: attack.wowSpellDefense ?? null,\n effects: attack.effects ?? [], castTimeMs: attack.castTimeMs ?? 0,',
|
|
'source NPC spell metadata projection',
|
|
);
|
|
replaceOnce(
|
|
' playerAtEntry: entryState.player, partyAtEntry: entryState.party,',
|
|
' playerAtEntry: entryState.player, partyAtEntry: entryState.party,\n sourceSpellAbilityMetadata: (entryState.abilities ?? [])\n .filter((ability) => ability.wowSpellDefense || ability.ranks?.some((rank) => rank.wowSpellDefense))\n .map((ability) => ({ ...ability, ranks: (ability.ranks ?? []).filter((rank) => rank.wowSpellDefense) })),',
|
|
'read-only ability source metadata',
|
|
);
|
|
|
|
return source;
|
|
}
|
|
|
|
function summarizeSpellBlockingEvidence(route) {
|
|
const events = route.observedCombatEvents ?? [];
|
|
const partyActorIds = new Set(['__player__', ...(route.partyAtEntry ?? []).map((actor) => actor.id)]);
|
|
for (const sample of route.samples ?? []) {
|
|
for (const member of sample.party ?? []) partyActorIds.add(member.id);
|
|
}
|
|
const hostileActorIds = new Set((route.samples ?? []).flatMap((sample) => (sample.mobs ?? [])
|
|
.map((mob) => mob.id).filter(Boolean)));
|
|
const resultByEventId = new Map((route.observedBlockResultObservations ?? [])
|
|
.filter((observation) => observation.event?.id != null)
|
|
.map((observation) => [observation.event.id, observation]));
|
|
const blockedDamageEvents = events.filter((event) => event.kind === 'damage'
|
|
&& (event.blocked === true || Number(event.blockedAmount) > 0 || event.outcome === 'block'))
|
|
.map((event) => {
|
|
const result = resultByEventId.get(event.id)?.combatResult ?? null;
|
|
return {
|
|
...event,
|
|
blockDirection: hostileActorIds.has(event.sourceActorId) && partyActorIds.has(event.targetActorId)
|
|
? 'hostile-attack-blocked-by-party'
|
|
: partyActorIds.has(event.sourceActorId) && hostileActorIds.has(event.targetActorId)
|
|
? 'party-attack-blocked-by-hostile' : 'actor-team-unresolved',
|
|
blocked: event.blocked ?? result?.blocked ?? (event.outcome === 'block'),
|
|
blockedAmount: event.blockedAmount ?? result?.blockedAmount ?? null,
|
|
sampledCombatResult: result,
|
|
};
|
|
});
|
|
const mobSamples = (route.samples ?? []).flatMap((sample) => (sample.mobs ?? []).map((mob) => ({
|
|
observedAt: sample.at ?? sample.now ?? null,
|
|
id: mob.id ?? null,
|
|
name: mob.name ?? null,
|
|
serverEntry: mob.serverEntry ?? null,
|
|
attacks: mob.attacks ?? [],
|
|
auras: mob.auras ?? [],
|
|
combatAuraTelemetry: mob.combatAuraTelemetry ?? [],
|
|
})));
|
|
return {
|
|
definitions: {
|
|
blockedDamageEvents: 'Read-only StructuredCombatEvent values retained verbatim from sampled combatEvents snapshots when blocked is true, blockedAmount is positive, or outcome is block; crit outcomes remain crit.',
|
|
eventSampling: 'The source snapshots take the latest 200 events from the store ring; polling plus ID deduplication can miss events that age out between snapshots.',
|
|
blockDirection: 'Party and hostile sides are determined only from sampled party/mob actor IDs; unresolved IDs retain an explicit unresolved direction.',
|
|
blockedAmount: 'Prefers the event field; otherwise uses a sampled lastCombatResult only when correlated by event id.',
|
|
npcSources: 'Mob attack definitions, source aura lists, and combatStore aura telemetry copied from read-only campaign snapshots.',
|
|
abilityMetadata: 'Source spell metadata is recorded only when the entry ability or rank supplied wowSpellDefense.',
|
|
negativeEvidence: 'No observed blocked event is not evidence that a spell cannot be blocked; this route samples ordinary live UI snapshots.',
|
|
},
|
|
counts: {
|
|
rawCombatEvents: events.length,
|
|
blockedDamageEvents: blockedDamageEvents.length,
|
|
blockedDamageEventsWithAmount: blockedDamageEvents.filter((event) => event.blockedAmount !== null).length,
|
|
critAndBlockedDamageEvents: blockedDamageEvents.filter((event) => event.outcome === 'crit'
|
|
&& (event.blocked === true || Number(event.blockedAmount) > 0)).length,
|
|
hostileAttacksBlockedByParty: blockedDamageEvents.filter((event) => event.blockDirection === 'hostile-attack-blocked-by-party').length,
|
|
partyAttacksBlockedByHostile: blockedDamageEvents.filter((event) => event.blockDirection === 'party-attack-blocked-by-hostile').length,
|
|
actorTeamUnresolvedBlockEvents: blockedDamageEvents.filter((event) => event.blockDirection === 'actor-team-unresolved').length,
|
|
sourceNpcSamples: mobSamples.length,
|
|
sourceNpcAuraSamples: mobSamples.filter((mob) => mob.auras.length || mob.combatAuraTelemetry.length).length,
|
|
sourceSpellAbilityMetadata: route.sourceSpellAbilityMetadata?.length ?? 0,
|
|
},
|
|
selectedBossIdentity: route.selectedBossIdentity ?? null,
|
|
blockedDamageEvents,
|
|
sourceNpcSamples: mobSamples,
|
|
sourceSpellAbilityMetadata: route.sourceSpellAbilityMetadata ?? [],
|
|
rawCombatEvents: events,
|
|
};
|
|
}
|
|
|
|
function makeBiteSizedWrapper() {
|
|
let source = sourceWrapper;
|
|
source = replaceExactlyOnce(source,
|
|
'const {\n FULL_STOCKADE_DUNGEON_ID,',
|
|
'const {\n DEFAULT_TEST_DUNGEON_ID,\n DEFAULT_TEST_DUNGEON_TITLE,\n FULL_STOCKADE_DUNGEON_ID,',
|
|
'bite-sized defaults import');
|
|
source = source.replaceAll('stockade-stances-blocking', 'stockade-spell-blocking');
|
|
source = replaceExactlyOnce(source,
|
|
'function main() {\n const instrumentedHarness = createInstrumentedHarness();',
|
|
`${transformSpellCampaignHarness.toString()}\n\n${summarizeSpellBlockingEvidence.toString()}\n\nfunction main() {\n const instrumentedHarness = transformSpellCampaignHarness(createInstrumentedHarness());`,
|
|
'spell-specific campaign transformation');
|
|
source = replaceExactlyOnce(source,
|
|
" if (!process.argv.includes('--full-stockade')) {\n throw new Error('This source-specific full-campaign harness requires explicit --full-stockade opt-in.');\n }",
|
|
" if (!process.argv.includes('--bite-sized-stockade')) {\n throw new Error('This source-specific Bite-Sized Stockade harness requires explicit --bite-sized-stockade opt-in.');\n }",
|
|
'explicit Bite-Sized route opt-in');
|
|
source = replaceExactlyOnce(source,
|
|
' dungeon: { id: FULL_STOCKADE_DUNGEON_ID, title: FULL_STOCKADE_DUNGEON_TITLE },',
|
|
" dungeon: { id: DEFAULT_TEST_DUNGEON_ID, title: DEFAULT_TEST_DUNGEON_TITLE },\n routeScope: 'Bite-Sized Cellblock Rush; one seeded source boss, ordinary UI traversal, 900-second hard bound',",
|
|
'Bite-Sized route record');
|
|
source = replaceExactlyOnce(source,
|
|
' entryAndPlay: \'normal full Stockade picker and Discipline healer-role entry; campaign harness ordinary WASD, target selection, attack/support healing/party commands; no Boss Testing, teleport, collision bypass, enemy/damage/aura/health/cast seeding, or combat store writes\',',
|
|
" entryAndPlay: 'normal Bite-Sized Stockade picker and Discipline healer-role entry; ordinary WASD, target selection, attack/support healing/party commands; no Boss Testing, teleport, collision bypass, enemy/damage/aura/health/cast seeding, or combat store writes',",
|
|
'normal Bite-Sized entry description');
|
|
source = replaceExactlyOnce(source,
|
|
" instrumentation: ['active enemy-source aura applications for spells 7164/7165/3419 with owner, target, apply/expiry, and stack fields',\n 'raw observed combat events with block outcomes and actor direction',\n 'read-only lastCombatResult block amount snapshots correlated to matching damage events',\n 'read-only live-state progress.json snapshots at 30-second intervals'],\n requestedMaximumCampaignSeconds: 1800,",
|
|
" instrumentation: ['sampled read-only StructuredCombatEvent ring snapshots (latest 200), retaining blocked and blockedAmount fields; crit-plus-block stays visible',\n 'read-only source NPC attacks, auras, and combatAuraTelemetry',\n 'source spell metadata only where wowSpellDefense is present on an entry ability/rank',\n 'actual seeded boss identity plus one-objective native clear result',\n 'complete content manifest JSON before and after and hash-addressed objects actually served'],\n requestedMaximumCampaignSeconds: 900,",
|
|
'spell run instrumentation record');
|
|
source = replaceExactlyOnce(source,
|
|
" const contentManifestBefore = collectContentManifestSnapshot();",
|
|
" const contentManifestBefore = collectContentManifestSnapshot();",
|
|
'baseline content manifest location');
|
|
source = replaceExactlyOnce(source,
|
|
" const child = spawnSync(process.execPath, [runtimeHarnessPath,\n '--campaign-boss-route', `--route=${FULL_STOCKADE_DUNGEON_ID}`, '--max-campaign-seconds=1800'], {",
|
|
" const child = spawnSync(process.execPath, [runtimeHarnessPath,\n '--campaign-boss-route', `--route=${DEFAULT_TEST_DUNGEON_ID}`, '--max-campaign-seconds=900'], {",
|
|
'bounded Bite-Sized campaign route');
|
|
source = replaceExactlyOnce(source,
|
|
' timeout: 2_100_000,',
|
|
' timeout: 900_000,',
|
|
'900-second child hard bound');
|
|
source = replaceExactlyOnce(source,
|
|
' ? path.join(campaignDirectory, `${FULL_STOCKADE_DUNGEON_ID}-campaign-boss-route.json`) : null;',
|
|
' ? path.join(campaignDirectory, `${DEFAULT_TEST_DUNGEON_ID}-campaign-boss-route.json`) : null;',
|
|
'Bite-Sized route result path');
|
|
source = replaceExactlyOnce(source,
|
|
' ? path.join(campaignDirectory, `${FULL_STOCKADE_DUNGEON_ID}-route.json`) : null;',
|
|
' ? path.join(campaignDirectory, `${DEFAULT_TEST_DUNGEON_ID}-route.json`) : null;',
|
|
'Bite-Sized summary result path');
|
|
source = replaceExactlyOnce(source,
|
|
" const evidence = route ? summarizeStanceBlockingEvidence(route) : null;\n if (evidence) writeJson(path.join(outputDirectory, 'stances-blocking-evidence.json'), evidence);",
|
|
" const evidence = route ? summarizeSpellBlockingEvidence(route) : null;\n if (route && evidence?.selectedBossIdentity) {\n const selectedBossSample = evidence.sourceNpcSamples.find((mob) => mob.name === evidence.selectedBossIdentity.name && mob.id);\n if (selectedBossSample) {\n const observedIdentity = { ...route.selectedBossIdentity, liveActorId: selectedBossSample.id,\n serverEntry: selectedBossSample.serverEntry ?? null };\n route.selectedBossIdentity = observedIdentity;\n evidence.selectedBossIdentity = observedIdentity;\n writeJson(routePath, route);\n }\n }\n if (evidence) writeJson(path.join(outputDirectory, 'spell-blocking-evidence.json'), evidence);",
|
|
'spell-blocking evidence output');
|
|
source = replaceExactlyOnce(source,
|
|
' runRecord.stanceBlockingEvidenceCounts = evidence?.counts ?? null;',
|
|
' runRecord.spellBlockingEvidenceCounts = evidence?.counts ?? null;',
|
|
'spell-blocking evidence count field');
|
|
source = replaceExactlyOnce(source,
|
|
"specializationId: 'priest:discipline', role: 'healer', level: 30,",
|
|
"specializationId: 'priest:discipline', role: 'healer', level: 30,\n preEntryLevelPreparation: 'UI-created profile advanced through isolated shell.saveActiveProgression(30, 0, talentRanks) before dungeon entry; no combat-state writes during play',",
|
|
'explicit isolated pre-entry progression setup');
|
|
source = replaceExactlyOnce(source,
|
|
" if (route && !evidence?.counts.stanceAuraApplications) {\n runRecord.statusNotes.push('No live application of Defensive Stance 7164 or Battle Stance 7165 was captured; aura outcomes are not observed in this run.');\n }\n if (route && !evidence?.counts.improvedBlockingAuraApplications) {\n runRecord.statusNotes.push('No live Improved Blocking 3419 application was captured; aura outcomes are not observed in this run.');\n }\n if (route && !evidence?.counts.rawBlockEvents) {\n runRecord.statusNotes.push('No damage event with outcome=block was captured; intermittent live sampling limits negative conclusions.');\n } else if (route && evidence?.counts.matchedBlockedAmountEvents < evidence?.counts.rawBlockEvents) {\n runRecord.statusNotes.push(`${evidence.counts.rawBlockEvents - evidence.counts.matchedBlockedAmountEvents} block outcome event(s) had no matching sampled lastCombatResult amount; the outcome is retained without inferring a blocked amount.`);\n }\n if (route && evidence?.counts.actorTeamUnresolvedBlockEvents) {\n runRecord.statusNotes.push(`${evidence.counts.actorTeamUnresolvedBlockEvents} block event(s) could not be assigned to party-versus-hostile direction from sampled actor IDs.`);\n }",
|
|
" if (route && !evidence?.counts.blockedDamageEvents) {\n runRecord.statusNotes.push('No block-indicated damage event was captured in sampled combat-event snapshots; this does not establish that a spell cannot be blocked.');\n } else if (route && evidence?.counts.blockedDamageEventsWithAmount < evidence?.counts.blockedDamageEvents) {\n runRecord.statusNotes.push(`${evidence.counts.blockedDamageEvents - evidence.counts.blockedDamageEventsWithAmount} block-indicated event(s) had no correlated numeric blocked amount; missing amounts remain unknown.`);\n }\n if (route && evidence?.counts.actorTeamUnresolvedBlockEvents) {\n runRecord.statusNotes.push(`${evidence.counts.actorTeamUnresolvedBlockEvents} block-indicated event(s) could not be assigned to party-versus-hostile direction from sampled actor IDs.`);\n }",
|
|
'spell-appropriate sampled block status notes');
|
|
source = replaceExactlyOnce(source,
|
|
' const fiveRequiredBossesVerified = expectedBossIds.length === 5\n && expectedBossIds.every((bossId) => killedBossIds.has(bossId));',
|
|
" const selectedBossIdentity = route?.selectedBossIdentity ?? null;\n const supportedSeededBossNames = new Set(['Targorr the Dread', 'Kam Deepfury', 'Hamhock', 'Bazil Thredd', 'Dextren Ward']);\n const oneSelectedBossVerified = expectedBossIds.length === 1\n && killedBossIds.has(expectedBossIds[0])\n && selectedBossIdentity?.sourceSpawnId === expectedBossIds[0]\n && supportedSeededBossNames.has(selectedBossIdentity?.name);",
|
|
'single selected boss result verification');
|
|
source = replaceExactlyOnce(source,
|
|
' && fiveRequiredBossesVerified);',
|
|
' && oneSelectedBossVerified);',
|
|
'single boss native clear assertion');
|
|
source = replaceExactlyOnce(source,
|
|
' runRecord.requiredBossIds = expectedBossIds;\n runRecord.killedBossIds = [...killedBossIds];',
|
|
' runRecord.requiredBossIds = expectedBossIds;\n runRecord.killedBossIds = [...killedBossIds];\n runRecord.selectedBossIdentity = selectedBossIdentity;',
|
|
'actual boss identity in run record');
|
|
source = replaceExactlyOnce(source,
|
|
' runRecord.contentSourceResponseCounts = contentSourceEvidence.counts;',
|
|
' runRecord.contentSourceResponseCounts = contentSourceEvidence.counts;\n runRecord.contentObjectsActuallyLoaded = contentSourceEvidence.counts.successfulManifestResponses > 0\n && contentSourceEvidence.counts.verifiedHashAddressedObjects > 0\n && contentSourceEvidence.counts.objectIntegrityFailures === 0;\n runRecord.contentObjectsActuallyLoadedRequirement = \'At least one successful manifest response and one served hash-addressed content object whose bytes match its address; zero integrity failures.\';',
|
|
'loaded hash-addressed content verification');
|
|
source = replaceExactlyOnce(source,
|
|
" if (!runRecord.fullClearVerified && runRecord.executionStatus === 'complete' && route) {\n runRecord.statusNotes.push('Campaign process exited successfully, but results UI plus all five required boss IDs were not verified; this remains a partial live attempt.');\n }",
|
|
" if (!runRecord.fullClearVerified && runRecord.executionStatus === 'complete' && route) {\n runRecord.statusNotes.push('Campaign process exited successfully, but the native result UI plus the one seeded boss ID were not verified; this remains a partial live attempt.');\n }\n if (!runRecord.contentObjectsActuallyLoaded && runRecord.executionStatus === 'complete') {\n runRecord.statusNotes.push('The run did not prove that hash-addressed content objects were served from the recorded manifest; content provenance remains unverified.');\n }",
|
|
'single boss and content-provenance notes');
|
|
source = replaceExactlyOnce(source,
|
|
" runRecord.status = runRecord.executionStatus === 'complete' && route\n ? runRecord.fullClearVerified ? 'complete' : 'partial-full-clear-unverified' : 'failed';",
|
|
" runRecord.status = runRecord.executionStatus === 'complete' && route\n ? runRecord.fullClearVerified && runRecord.contentObjectsActuallyLoaded ? 'complete'\n : 'partial-spell-blocking-run-unverified' : 'failed';",
|
|
'final route status requires content provenance');
|
|
source = source.replaceAll('Stockade stance/blocking QA', 'Stockade spell-blocking QA');
|
|
source = source.replaceAll('Prepared scoped Stockade stance/blocking harness', 'Prepared scoped Bite-Sized Stockade spell-blocking harness');
|
|
source = source.replaceAll('all five required boss IDs', 'the one seeded boss ID');
|
|
source = source.replaceAll('contentManifestSha256: contentManifestBefore.manifestSha256,',
|
|
'contentManifestSha256: contentManifestBefore.manifestSha256,\n contentManifestJsonIncluded: true,');
|
|
source = replaceExactlyOnce(source,
|
|
' manifestVersion: manifest.version ?? null,',
|
|
' manifest,\n manifestVersion: manifest.version ?? null,',
|
|
'full source content manifest JSON');
|
|
source = replaceExactlyOnce(source,
|
|
'function collectLoadedContentObjectEvidence(responses) {',
|
|
'function collectLoadedContentObjectEvidence(responses, manifest) {',
|
|
'manifest-backed content object evidence');
|
|
source = replaceExactlyOnce(source,
|
|
" return uniqueObjects.map((response) => {\n const relativeObjectPath = decodeURIComponent(response.pathname.slice('/content/'.length));",
|
|
" return uniqueObjects.map((response) => {\n const manifestReferences = Object.entries(manifest?.files ?? {})\n .filter(([, entry]) => [entry.url, entry.download?.url].includes(response.pathname))\n .map(([logicalPath, entry]) => ({ logicalPath, sha256: entry.download?.sha256 ?? entry.sha256 ?? null }));\n const manifestReferencedLogicalPaths = manifestReferences.map((entry) => entry.logicalPath);\n const relativeObjectPath = decodeURIComponent(response.pathname.slice('/content/'.length));",
|
|
'content object manifest membership');
|
|
source = replaceExactlyOnce(source,
|
|
'return { ...response, localObjectPath: null, localBytes: null, localSha256: null,\n objectIntegrity:',
|
|
'return { ...response, manifestReferencedLogicalPaths, manifestReferenceCount: manifestReferences.length,\n localObjectPath: null, localBytes: null, localSha256: null,\n objectIntegrity:',
|
|
'unsafe content object manifest telemetry');
|
|
source = replaceExactlyOnce(source,
|
|
'return { ...response, localObjectPath: path.relative(projectRoot, absoluteObjectPath),\n localBytes: null, localSha256: null, objectIntegrity:',
|
|
'return { ...response, manifestReferencedLogicalPaths, manifestReferenceCount: manifestReferences.length,\n localObjectPath: path.relative(projectRoot, absoluteObjectPath),\n localBytes: null, localSha256: null, objectIntegrity:',
|
|
'missing content object manifest telemetry');
|
|
source = replaceExactlyOnce(source,
|
|
' return {\n ...response,\n localObjectPath: path.relative(projectRoot, absoluteObjectPath),',
|
|
' return {\n ...response,\n manifestReferencedLogicalPaths,\n manifestReferenceCount: manifestReferences.length,\n localObjectPath: path.relative(projectRoot, absoluteObjectPath),',
|
|
'verified content object manifest telemetry');
|
|
source = replaceExactlyOnce(source,
|
|
' const loadedContentObjects = collectLoadedContentObjectEvidence(contentSourceResponses);',
|
|
' const loadedContentObjects = collectLoadedContentObjectEvidence(contentSourceResponses, contentManifestBefore.manifest);',
|
|
'captured-manifest object verification');
|
|
source = replaceExactlyOnce(source,
|
|
" verifiedHashAddressedObjects: loadedContentObjects.filter((object) => object.objectIntegrity === 'served-file-matches-hash-address').length,",
|
|
" verifiedHashAddressedObjects: loadedContentObjects.filter((object) => object.objectIntegrity === 'served-file-matches-hash-address').length,\n verifiedManifestReferencedHashAddressedObjects: loadedContentObjects.filter((object) => object.objectIntegrity === 'served-file-matches-hash-address'\n && object.manifestReferenceCount > 0).length,",
|
|
'manifest reference match count');
|
|
source = replaceExactlyOnce(source,
|
|
' && contentSourceEvidence.counts.verifiedHashAddressedObjects > 0\n && contentSourceEvidence.counts.objectIntegrityFailures === 0;',
|
|
' && contentSourceEvidence.counts.verifiedHashAddressedObjects > 0\n && contentSourceEvidence.counts.verifiedManifestReferencedHashAddressedObjects > 0\n && contentSourceEvidence.counts.objectIntegrityFailures === 0;',
|
|
'manifest-backed object success gate');
|
|
return source;
|
|
}
|
|
|
|
function main() {
|
|
const wrapper = makeBiteSizedWrapper();
|
|
fs.writeFileSync(runtimeWrapperPath, wrapper, 'utf8');
|
|
try {
|
|
const syntax = spawnSync(process.execPath, ['--check', runtimeWrapperPath], {
|
|
cwd: projectRoot,
|
|
encoding: 'utf8',
|
|
windowsHide: true,
|
|
});
|
|
if (syntax.status !== 0) throw new Error(`Generated Stockade wrapper syntax check failed: ${syntax.stderr || syntax.error}`);
|
|
const args = process.argv.slice(2);
|
|
const run = spawnSync(process.execPath, [runtimeWrapperPath, ...args], {
|
|
cwd: projectRoot,
|
|
stdio: 'inherit',
|
|
windowsHide: true,
|
|
});
|
|
if (run.error) throw run.error;
|
|
process.exitCode = run.status ?? 1;
|
|
} finally {
|
|
fs.rmSync(runtimeWrapperPath, { force: true });
|
|
}
|
|
}
|
|
|
|
try {
|
|
main();
|
|
} catch (error) {
|
|
fs.rmSync(runtimeWrapperPath, { force: true });
|
|
console.error(error.stack ?? error.message);
|
|
process.exitCode = 1;
|
|
}
|