39 lines
2.2 KiB
JavaScript
39 lines
2.2 KiB
JavaScript
/** Read-only evidence projection. Removed/refreshed auras end the observed window. */
|
|
function summarizeActorDisarmEvidence(events, auraSamples, playerId, partyIds = []) {
|
|
const actorIds = new Set([playerId, ...partyIds]);
|
|
const windows = [], open = new Map();
|
|
const keyFor = (aura) => JSON.stringify([aura.instanceId, aura.sourceId, aura.targetId, aura.appliedAt]);
|
|
const samples = [...auraSamples].sort((a, b) => a.observedAt - b.observedAt);
|
|
for (const sample of samples) {
|
|
const auras = (sample.auras ?? []).filter((aura) => aura.definitionId === 'enemy-source-spell-6713'
|
|
&& actorIds.has(aura.targetId) && Number.isFinite(aura.appliedAt));
|
|
const keys = new Set(auras.map(keyFor));
|
|
for (const [key, window] of open) {
|
|
if (keys.has(key)) continue;
|
|
const replacement = auras.find((aura) => aura.instanceId === window.instanceId && aura.targetId === window.targetId);
|
|
const removalAt = Number.isFinite(replacement?.appliedAt) ? replacement.appliedAt : sample.observedAt;
|
|
window.observedEndAt = Math.min(window.expiresAt ?? Number.POSITIVE_INFINITY, removalAt);
|
|
window.endEvidence = replacement ? 'refresh' : 'committed-removal';
|
|
open.delete(key);
|
|
}
|
|
for (const aura of auras) {
|
|
const key = keyFor(aura);
|
|
if (open.has(key)) continue;
|
|
const window = { ...aura, observedEndAt: aura.expiresAt ?? null, endEvidence: 'source-expiry' };
|
|
windows.push(window);
|
|
open.set(key, window);
|
|
}
|
|
}
|
|
const actorHandEvents = events.filter((event) => event.kind === 'damage'
|
|
&& actorIds.has(event.sourceActorId) && event.attackHand != null);
|
|
const disarmWindows = windows.map((window) => ({ ...window,
|
|
handEvents: actorHandEvents.filter((event) => event.sourceActorId === window.targetId
|
|
&& event.occurredAt >= window.appliedAt
|
|
&& window.observedEndAt !== null && event.occurredAt < window.observedEndAt),
|
|
}));
|
|
return { actorHandEvents, disarmWindows,
|
|
coverage: 'Hand-tagged player/party damage results matched to committed source Disarm windows; removal/refresh ends a window. No event proves an unsampled cast gate, damage formula, or missing hand.' };
|
|
}
|
|
|
|
module.exports = { summarizeActorDisarmEvidence };
|