Files
healer-man/scripts/playtest-stockade-weapon-utilities.cjs
T
2026-10-06 22:25:34 -04:00

587 lines
48 KiB
JavaScript

const { spawnSync } = require('node:child_process');
const fs = require('node:fs');
const path = require('node:path');
const { summarizeActorDisarmEvidence } = require('./actor-disarm-evidence.cjs');
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-weapon-utilities-${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 transformWeaponCampaignHarness(source) {
const { createWeaponEvidenceRecorder } = require('./weapon-evidence-recorder.cjs');
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(
' id: mob.id,\n name: mob.name,',
' id: mob.id,\n name: mob.name,\n serverEntry: mob.serverEntry ?? null,',
'read-only source NPC identity projection',
);
replaceOnce(
'window.nativeRouteKeyLog = [];',
`window.nativeRouteKeyLog = [];
window.nativeRouteCreateWeaponEvidenceRecorder = (${createWeaponEvidenceRecorder.toString()});`,
'read-only weapon evidence recorder factory installation',
);
replaceOnce(
' activeCast: mob.activeCast, attacks: mob.attacks ?? [], auras: mob.auras ?? [],\n combatAuraTelemetry: mob.combatAuraTelemetry ?? [], abilityCastCounts: mob.abilityCastCounts ?? {},\n statuses: mob.statuses ?? [], position: mob.position })),\n stanceBlockingAuraTelemetry: current.stanceBlockingAuraTelemetry ?? [] });',
' activeCast: mob.activeCast, serverEntry: mob.serverEntry ?? null,\n attacks: mob.attacks ?? [], auras: mob.auras ?? [],\n combatAuraTelemetry: mob.combatAuraTelemetry ?? [], abilityCastCounts: mob.abilityCastCounts ?? {},\n dualWieldEnabled: mob.dualWieldEnabled ?? null,\n nextEnemyOffhandAttackAt: mob.nextEnemyOffhandAttackAt ?? null,\n pendingExtraAttacks: mob.pendingExtraAttacks ?? null,\n lastDamagedActorId: mob.lastDamagedActorId ?? null,\n statuses: mob.statuses ?? [], position: mob.position })),\n stanceBlockingAuraTelemetry: current.stanceBlockingAuraTelemetry ?? [],\n sourceUtilityAuraTelemetry: current.sourceUtilityAuraTelemetry ?? [] });',
'weapon and global aura telemetry retained in route samples',
);
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;`,
` await page.evaluate(async () => {
const { combat } = await window.nativeRouteStores();
if (!combat || typeof window.nativeRouteCreateWeaponEvidenceRecorder !== 'function') {
throw new Error('Read-only weapon evidence recorder could not resolve the active combat store.');
}
if (window.nativeRouteWeaponEvidenceRecorder) {
throw new Error('A weapon evidence recorder is already active for this route.');
}
window.nativeRouteWeaponEvidenceRecorder = window.nativeRouteCreateWeaponEvidenceRecorder(combat, { capacity: 50_000 });
});
try {
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(
" events: combatState.combatEvents.slice(-200),",
" events: combatState.combatEvents.slice(-200).map((event) => ({ ...event, attackHand: event.attackHand ?? null })),",
'attack hand on retained source event snapshots',
);
replaceOnce(
" stanceBlockingAuraTelemetry: (combatState.auras ?? []).filter((aura) => /^enemy-source-spell-(?:7164|7165|3419)$/.test(aura.definition?.id ?? '')).map((aura) => ({\n instanceId: aura.instanceId ?? null, definitionId: aura.definition?.id ?? null,\n definitionName: aura.definition?.name ?? null, sourceId: aura.sourceId ?? null,\n targetId: aura.targetId ?? null, appliedAt: aura.appliedAt ?? null,\n expiresAt: aura.expiresAt ?? null, stacks: aura.stacks ?? null,\n })),",
" stanceBlockingAuraTelemetry: (combatState.auras ?? []).filter((aura) => /^enemy-source-spell-(?:7164|7165|3419)$/.test(aura.definition?.id ?? '')).map((aura) => ({\n instanceId: aura.instanceId ?? null, definitionId: aura.definition?.id ?? null,\n definitionName: aura.definition?.name ?? null, sourceId: aura.sourceId ?? null,\n targetId: aura.targetId ?? null, appliedAt: aura.appliedAt ?? null,\n expiresAt: aura.expiresAt ?? null, stacks: aura.stacks ?? null,\n })),\n sourceUtilityAuraTelemetry: (combatState.auras ?? []).filter((aura) => /^enemy-source-spell-(?:6742|6713|17230)$/.test(aura.definition?.id ?? '')).map((aura) => ({\n instanceId: aura.instanceId ?? null, definitionId: aura.definition?.id ?? null,\n definitionName: aura.definition?.name ?? null, sourceId: aura.sourceId ?? null,\n targetId: aura.targetId ?? null, appliedAt: aura.appliedAt ?? null,\n expiresAt: aura.expiresAt ?? null, stacks: aura.stacks ?? null,\n dispelCategory: aura.definition?.dispelCategory ?? null,\n })),",
'global Bloodlust, Disarm, and Infected Wound aura telemetry',
);
replaceOnce(
"if (observedLosRejection && recoveryTarget && lastLosRecallAt === 0",
"if (false && observedLosRejection && recoveryTarget && lastLosRecallAt === 0",
'disable party recall after healing line-of-sight rejection',
);
replaceOnce(
"if (observedLosRejection && recoveryTarget?.health > 0 && lastLosRecallAt === 0",
"if (false && observedLosRejection && recoveryTarget?.health > 0 && lastLosRecallAt === 0",
'disable party recall after campaign-route line-of-sight rejection',
);
replaceOnce(
` fs.writeFileSync(path.join(outputDirectory, \`\${route.id}-campaign-boss-route.json\`), \`\${JSON.stringify(result, null, 2)}\\n\`);
return result;
}`,
` } finally {
try {
const recorder = await page.evaluate(() => {
const active = window.nativeRouteWeaponEvidenceRecorder;
window.nativeRouteWeaponEvidenceRecorder = null;
return active?.stop() ?? null;
});
result.weaponEvidenceRecorder = recorder ? {
status: 'installed',
...recorder,
capacityLimit: 50_000,
capacityScope: 'Events, changed mob samples, and aura signature samples each have this independent retention limit.',
} : { status: 'not-installed' };
} catch (error) {
result.weaponEvidenceRecorder = { status: 'stop-failed', error: error.stack ?? error.message };
}
fs.writeFileSync(path.join(outputDirectory, \`\${route.id}-campaign-boss-route.json\`), \`\${JSON.stringify(result, null, 2)}\\n\`);
}
return result;
}`,
'stop and serialize route weapon evidence recorder',
);
return source;
}
function summarizeWeaponUtilitiesEvidence(route) {
const recorder = route.weaponEvidenceRecorder ?? {};
const eventByKey = new Map();
for (const event of [...(route.observedCombatEvents ?? []), ...(recorder.events ?? [])]) {
const key = event.id != null ? `id:${event.id}` : `json:${JSON.stringify(event)}`;
if (!eventByKey.has(key)) eventByKey.set(key, { ...event });
}
const events = [...eventByKey.values()];
const partyIds = [...new Set([...(route.partyAtEntry ?? []), ...(route.partyAtEndpoint ?? []),
...(route.samples ?? []).flatMap((sample) => sample.party ?? [])].map((member) => member.id).filter(Boolean))];
const actorDisarmEvidence = summarizeActorDisarmEvidence(events, recorder.auraSamples ?? [], '__player__', partyIds);
const routeMobSamples = (route.samples ?? []).flatMap((sample) => (sample.mobs ?? []).map((mob) => ({
observedAt: sample.at ?? sample.now ?? null,
captureSource: 'route-snapshot',
id: mob.id ?? null,
name: mob.name ?? null,
serverEntry: mob.serverEntry ?? null,
attacks: mob.attacks ?? [],
auras: mob.auras ?? [],
combatAuraTelemetry: mob.combatAuraTelemetry ?? [],
dualWieldEnabled: mob.dualWieldEnabled ?? null,
nextEnemyOffhandAttackAt: mob.nextEnemyOffhandAttackAt ?? null,
pendingExtraAttacks: mob.pendingExtraAttacks ?? null,
lastDamagedActorId: mob.lastDamagedActorId ?? null,
})));
const recorderMobSamples = (recorder.mobSamples ?? []).map((mob) => ({
...mob,
captureSource: 'store-subscription',
attacks: [], auras: [], combatAuraTelemetry: [],
}));
const mobSamples = [...routeMobSamples, ...recorderMobSamples];
const routeAuraSamples = (route.samples ?? []).flatMap((sample) =>
(sample.sourceUtilityAuraTelemetry ?? []).map((aura) => ({
observedAt: sample.at ?? sample.now ?? null,
captureSource: 'route-snapshot',
...aura,
})));
const recorderAuraSamples = (recorder.auraSamples ?? []).flatMap((sample) =>
(sample.auras ?? []).map((aura) => ({
observedAt: sample.observedAt ?? null,
captureSource: 'store-subscription',
...aura,
})));
const sourceUtilityAuraSamples = [...routeAuraSamples, ...recorderAuraSamples];
const weaponAttackEvents = events.filter((event) => event.attackHand != null);
const sourceNpcIds = new Set(mobSamples.map((mob) => mob.id).filter(Boolean));
const sourceNpcWeaponAttackEvents = weaponAttackEvents.filter((event) => sourceNpcIds.has(event.sourceActorId));
const countBy = (entries, keyFor) => {
const counts = new Map();
for (const entry of entries) {
const key = keyFor(entry);
const serializedKey = JSON.stringify(key);
const current = counts.get(serializedKey) ?? { ...key, eventCount: 0 };
current.eventCount += 1;
counts.set(serializedKey, current);
}
return [...counts.values()].sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
};
const sourceNpcAbilityEvents = events.filter((event) => sourceNpcIds.has(event.sourceActorId)
&& (event.abilityId != null || event.spellId != null || event.extraAttackSourceSpellId != null));
const sourceNpcSpellEventCounts = countBy(sourceNpcAbilityEvents,
(event) => ({ sourceActorId: event.sourceActorId ?? null,
abilityId: event.abilityId ?? null, spellId: event.spellId ?? null,
extraAttackSourceSpellId: event.extraAttackSourceSpellId ?? null }));
const handEventCounts = countBy(weaponAttackEvents,
(event) => ({ attackHand: event.attackHand ?? 'unknown' }));
const handSpellEventCounts = countBy(weaponAttackEvents,
(event) => ({ attackHand: event.attackHand ?? 'unknown',
abilityId: event.abilityId ?? null, spellId: event.spellId ?? null,
extraAttackSourceSpellId: event.extraAttackSourceSpellId ?? null }));
const sourceNpcHandSpellEventCounts = countBy(sourceNpcWeaponAttackEvents,
(event) => ({ attackHand: event.attackHand ?? 'unknown',
abilityId: event.abilityId ?? null, spellId: event.spellId ?? null,
extraAttackSourceSpellId: event.extraAttackSourceSpellId ?? null }));
const mobsWithWeaponState = mobSamples.filter((mob) => mob.dualWieldEnabled !== null
|| mob.nextEnemyOffhandAttackAt !== null || mob.pendingExtraAttacks !== null
|| mob.lastDamagedActorId !== null);
const pendingExtraAttackSamples = mobSamples.filter((mob) => Array.isArray(mob.pendingExtraAttacks)
&& mob.pendingExtraAttacks.length > 0);
const recorderCoverageIssues = [];
if (recorder.status !== 'installed') recorderCoverageIssues.push('recorder-not-installed');
if (!Array.isArray(recorder.events) || !Array.isArray(recorder.gaps)
|| !Array.isArray(recorder.mobSamples) || !Array.isArray(recorder.auraSamples)
|| !Array.isArray(recorder.errors)) recorderCoverageIssues.push('recorder-data-missing');
if ((recorder.gaps ?? []).length) recorderCoverageIssues.push('event-id-gaps');
if ((recorder.errors ?? []).length) recorderCoverageIssues.push('sampling-errors');
if ((recorder.discardedEvents ?? 0) > 0 || (recorder.discardedStates ?? 0) > 0) {
recorderCoverageIssues.push('retention-overflow');
}
const recorderCoverageComplete = recorderCoverageIssues.length === 0;
return {
definitions: {
eventSampling: 'A read-only combat-store subscription retains post-baseline event IDs between route checkpoints, with ID gaps and capacity overflow reported. Route snapshots supplement the subscription and are deduplicated by event ID; pre-start events remain excluded.',
mobStateSampling: 'Weapon state combines ordinary route snapshots with changed-state samples from a read-only combat-store subscription. Null means the running build did not expose that field at that sample; it is not evidence of no weapon state.',
attackHand: 'Attack definitions and retained combat events preserve attackHand only when the runtime emitted it; no hand is inferred from a spell or attack name.',
pendingExtraAttacks: 'Pending entries are copied from the sampled mob state; transient queues may appear and resolve between snapshots.',
sourceUtilities: 'Source NPC attacks, mob auras, and combatStore aura telemetry are retained from route snapshots; pinned global source aura snapshots also include subscription changes.',
sourceUtilityAuras: 'Snapshots retain pinned source aura IDs 6742, 6713, 17230, 3419 and shout parent/child IDs 5246, 19134, 20511, 29544, including player and party targets when present. Aura state is observed at store commits; an unobserved aura cannot be ruled out.',
recorderLimits: 'The recorder retains at most capacityLimit events, changed mob samples, and aura signature samples independently. discardedEvents/discardedStates report overflow, gaps report missing event IDs, and recorder errors are capped at 10.',
negativeEvidence: 'No observed offhand or extra-attack event is not proof the mechanic cannot occur; subscription coverage is limited to committed store notifications and retained capacity.',
},
counts: {
rawCombatEvents: events.length,
routeSnapshotCombatEvents: (route.observedCombatEvents ?? []).length,
recorderCombatEvents: (recorder.events ?? []).length,
recorderEventGaps: (recorder.gaps ?? []).length,
recorderDiscardedEvents: recorder.discardedEvents ?? 0,
recorderDiscardedStates: recorder.discardedStates ?? 0,
recorderErrorCount: (recorder.errors ?? []).length,
recorderCapacityLimit: recorder.capacityLimit ?? null,
recorderCoverageComplete,
recorderCoverageIssueCount: recorderCoverageIssues.length,
recorderCoverageIssues,
sourceNpcSamples: mobSamples.length,
sourceNpcAuraSamples: mobSamples.filter((mob) => mob.auras.length || mob.combatAuraTelemetry.length).length,
sourceUtilityAuraSnapshots: sourceUtilityAuraSamples.length,
sourceUtilityAuraInstancesObserved: new Set(sourceUtilityAuraSamples.map((aura) => aura.instanceId).filter(Boolean)).size,
bloodlustAuraSnapshots: sourceUtilityAuraSamples.filter((aura) => aura.definitionId === 'enemy-source-spell-6742').length,
disarmAuraSnapshots: sourceUtilityAuraSamples.filter((aura) => aura.definitionId === 'enemy-source-spell-6713').length,
infectedWoundAuraSnapshots: sourceUtilityAuraSamples.filter((aura) => aura.definitionId === 'enemy-source-spell-17230').length,
blockAuraSnapshots: sourceUtilityAuraSamples.filter((aura) => aura.definitionId === 'enemy-source-spell-3419').length,
shoutAuraSnapshots: sourceUtilityAuraSamples.filter((aura) => /^enemy-control:source-spell-(?:5246|19134|20511|29544)$/.test(aura.definitionId)).length,
sourceUtilityAuraTargets: [...new Set(sourceUtilityAuraSamples.map((aura) => aura.targetId).filter(Boolean))],
sourceNpcSamplesWithWeaponState: mobsWithWeaponState.length,
samplesWithDualWieldEnabled: mobSamples.filter((mob) => mob.dualWieldEnabled === true).length,
samplesWithOffhandTimerField: mobSamples.filter((mob) => mob.nextEnemyOffhandAttackAt !== null).length,
samplesWithOffhandSchedule: mobSamples.filter((mob) => Number.isFinite(Number(mob.nextEnemyOffhandAttackAt))
&& Number(mob.nextEnemyOffhandAttackAt) > 0).length,
samplesWithZeroOffhandTimer: mobSamples.filter((mob) => mob.nextEnemyOffhandAttackAt === 0).length,
samplesWithPendingExtraAttacks: pendingExtraAttackSamples.length,
pendingExtraAttackEntries: pendingExtraAttackSamples.reduce((sum, mob) => sum + mob.pendingExtraAttacks.length, 0),
samplesWithLastDamagedActor: mobSamples.filter((mob) => mob.lastDamagedActorId !== null).length,
sourceAttackDefinitionsWithHand: mobSamples.flatMap((mob) => mob.attacks)
.filter((attack) => attack.attackHand != null).length,
combatEventsWithAttackHand: weaponAttackEvents.length,
sourceNpcCombatEventsWithAttackHand: sourceNpcWeaponAttackEvents.length,
sourceNpcAbilityEvents: sourceNpcAbilityEvents.length,
playerPartyHandEvents: actorDisarmEvidence.actorHandEvents.length,
observedActorDisarmWindows: actorDisarmEvidence.disarmWindows.length,
actorHandEventsDuringDisarm: actorDisarmEvidence.disarmWindows.reduce((sum, window) => sum + window.handEvents.length, 0),
},
selectedBossIdentity: route.selectedBossIdentity ?? null,
actorDisarmEvidence,
weaponAttackEvents,
handEventCounts,
handSpellEventCounts,
sourceNpcWeaponAttackEvents,
sourceNpcHandSpellEventCounts,
sourceNpcSpellEventCounts,
pendingExtraAttackSamples,
sourceUtilityAuraSamples,
sourceNpcSamples: mobSamples,
rawCombatEvents: events,
recorderCoverage: {
baselineEventId: recorder.baselineEventId ?? null,
lastEventId: recorder.lastEventId ?? null,
eventGaps: recorder.gaps ?? [],
discardedEvents: recorder.discardedEvents ?? 0,
discardedStates: recorder.discardedStates ?? 0,
errors: recorder.errors ?? [],
coverageStatus: recorderCoverageComplete ? 'complete' : 'incomplete',
coverageIssues: recorderCoverageIssues,
eventCoverage: recorder.eventCoverage ?? null,
stateCoverage: recorder.stateCoverage ?? null,
capacityLimit: recorder.capacityLimit ?? null,
},
};
}
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-weapon-utilities');
source = replaceExactlyOnce(source,
"const contentSourceDirectory = path.join(projectRoot, 'content');",
"const requestedContentSourceDirectory = process.env.HEALER_MAN_STOCKADE_CONTENT_SOURCE?.trim()\n ? path.resolve(process.env.HEALER_MAN_STOCKADE_CONTENT_SOURCE.trim()) : path.join(projectRoot, 'content');\nconst contentSourceDirectory = requestedContentSourceDirectory;",
'optional selected content source directory');
source = replaceExactlyOnce(source,
"contentDir: path.resolve('content')",
"contentDir: process.env.HEALER_MAN_STOCKADE_CONTENT_SOURCE?.trim()\\n ? path.resolve(process.env.HEALER_MAN_STOCKADE_CONTENT_SOURCE.trim()) : path.resolve('content')",
'content override passed to playtest server');
source = replaceExactlyOnce(source,
"path.resolve(runtime.contentDir) !== path.resolve('content')",
"path.resolve(runtime.contentDir) !== (process.env.HEALER_MAN_STOCKADE_CONTENT_SOURCE?.trim()\\n ? path.resolve(process.env.HEALER_MAN_STOCKADE_CONTENT_SOURCE.trim()) : path.resolve('content'))",
'playtest server content override assertion');
source = replaceExactlyOnce(source,
"env: { ...process.env, HEALER_MAN_STOCKADE_DIST: requestedDistRealPath,\n HEALER_MAN_STOCKADE_PROGRESS_PATH: path.join(outputDirectory, 'progress.json') },",
"env: { ...process.env, HEALER_MAN_STOCKADE_DIST: requestedDistRealPath,\n HEALER_MAN_STOCKADE_CONTENT_SOURCE: requestedContentRealPath,\n HEALER_MAN_STOCKADE_PROGRESS_PATH: path.join(outputDirectory, 'progress.json') },",
'same content override passed to campaign subprocess');
source = replaceExactlyOnce(source,
" const requestedDistRealPath = fs.realpathSync(requestedBuildDirectory);",
" const requestedDistRealPath = fs.realpathSync(requestedBuildDirectory);\n if (!process.env.HEALER_MAN_STOCKADE_CONTENT_SOURCE?.trim()) {\n throw new Error('Live mode requires HEALER_MAN_STOCKADE_CONTENT_SOURCE to point to the isolated frozen content copy.');\n }\n if (!fs.existsSync(requestedContentSourceDirectory) || !fs.statSync(requestedContentSourceDirectory).isDirectory()) {\n throw new Error(`Task-specific frozen content source is missing: ${requestedContentSourceDirectory}`);\n }\n const requestedContentRealPath = fs.realpathSync(requestedContentSourceDirectory);\n const projectContentRealPath = fs.realpathSync(path.join(projectRoot, 'content'));\n if (requestedContentRealPath === projectContentRealPath) {\n throw new Error('Live mode requires the isolated frozen content source, not the mutable project content directory.');\n }",
'isolated frozen content preflight');
source = replaceExactlyOnce(source,
" school: attack.school ?? null, castTimeMs: attack.castTimeMs ?? 0,",
" school: attack.school ?? null, attackKind: attack.attackKind ?? null,\n attackHand: attack.attackHand ?? null, castTimeMs: attack.castTimeMs ?? 0,",
'source NPC weapon attack hand projection');
source = replaceExactlyOnce(source,
" schoolLockedUntil: mob.schoolLockedUntil ?? {},`, 'Mob telemetry projection');",
" dualWieldEnabled: mob.dualWieldEnabled ?? null,\n nextEnemyOffhandAttackAt: mob.nextEnemyOffhandAttackAt ?? null,\n pendingExtraAttacks: Array.isArray(mob.pendingExtraAttacks)\n ? mob.pendingExtraAttacks.map((entry) => ({ id: entry.id ?? null, sourceSpellId: entry.sourceSpellId ?? null,\n targetActorId: entry.targetActorId ?? null, queuedAt: entry.queuedAt ?? null, remaining: entry.remaining ?? null }))\n : null,\n lastDamagedActorId: mob.lastDamagedActorId ?? null,\n schoolLockedUntil: mob.schoolLockedUntil ?? {},`, 'Mob telemetry projection');",
'read-only NPC weapon-state projection');
source = replaceExactlyOnce(source,
'function main() {\n const instrumentedHarness = createInstrumentedHarness();',
`${transformWeaponCampaignHarness.toString()}\n\n${summarizeActorDisarmEvidence.toString()}\n\n${summarizeWeaponUtilitiesEvidence.toString()}\n\nfunction main() {\n const instrumentedHarness = transformWeaponCampaignHarness(createInstrumentedHarness());`,
'weapon-utility 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 combatEvents ring snapshots, capped at the latest 200 events per read',\n 'read-only combat-store subscription retaining post-baseline event IDs between route checkpoints with gap and overflow reporting',\n 'source NPC weapon-state changes: dualWieldEnabled, nextEnemyOffhandAttackAt, pendingExtraAttacks, and lastDamagedActorId when exposed',\n 'attackHand and spell IDs counted only when emitted by source attack definitions or combat events; no hand is inferred',\n 'read-only source NPC attacks, auras, and combatAuraTelemetry',\n 'source utility aura changes for spell IDs 6742, 6713, and 17230, including player or party targets when present',\n 'actual seeded boss identity plus one-objective native clear result',\n 'complete selected content manifest JSON before and after and hash-addressed objects actually served'],\n requestedMaximumCampaignSeconds: 900,",
'weapon utility 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 ? summarizeWeaponUtilitiesEvidence(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, 'weapon-utilities-evidence.json'), evidence);",
'weapon-utilities evidence output');
source = replaceExactlyOnce(source,
' runRecord.stanceBlockingEvidenceCounts = evidence?.counts ?? null;',
' runRecord.weaponUtilitiesEvidenceCounts = evidence?.counts ?? null;',
'weapon-utilities 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.recorderEventGaps) {\n runRecord.statusNotes.push(`${evidence.counts.recorderEventGaps} combat-event ID gap(s) were reported by the recorder; event coverage is incomplete for those IDs.`);\n }\n if (route && (evidence?.counts.recorderDiscardedEvents || evidence?.counts.recorderDiscardedStates)) {\n runRecord.statusNotes.push(`Weapon evidence recorder overflow discarded ${evidence.counts.recorderDiscardedEvents} event(s) and ${evidence.counts.recorderDiscardedStates} state sample(s); retained arrays are capped at ${evidence.counts.recorderCapacityLimit} entries each.`);\n }\n if (route && evidence?.counts.recorderErrorCount) {\n runRecord.statusNotes.push(`The read-only weapon evidence recorder reported ${evidence.counts.recorderErrorCount} sampling error(s).`);\n }\n if (route && !evidence?.counts.samplesWithDualWieldEnabled) {\n runRecord.statusNotes.push('No sampled NPC state reported dualWieldEnabled=true; ordinary snapshots cannot establish that no offhand swing occurred between reads.');\n }\n if (route && !evidence?.counts.samplesWithPendingExtraAttacks) {\n runRecord.statusNotes.push('No pending extra-attack queue was present in sampled NPC states; short-lived queues may resolve between reads.');\n }\n if (route && !evidence?.counts.combatEventsWithAttackHand) {\n runRecord.statusNotes.push('No combat event carried attackHand in sampled snapshots; the route does not infer main-hand/offhand identity from names.');\n }",
'weapon utility sampled 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,
' readOnlyContentSourceDirectory: contentSourceDirectory,',
' readOnlyContentSourceDirectory: requestedContentRealPath,',
'frozen content source recorded by real path');
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-weapon-utilities-run-unverified' : 'failed';",
'final route status requires content provenance');
source = source.replaceAll('Stockade stance/blocking QA', 'Stockade weapon-utilities QA');
source = source.replaceAll('Prepared scoped Stockade stance/blocking harness', 'Prepared scoped Bite-Sized Stockade weapon-utilities 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;
}