map/src/mapPatchWorker.js

1759 lines
80 KiB
JavaScript
Raw Normal View History

2026-08-10 13:59:33 +09:00
import { capturePatchTransactionSnapshot, generatePatch, generatePatchAsync, restorePatchTransactionSnapshot } from "./mapPatch.js";
2026-08-08 17:41:30 +09:00
import { collectTransferableBuffers } from "./transferUtils.js";
2026-08-10 13:59:33 +09:00
import { applyCommittedWorldDelta, hashCommittedWorld } from "./committedWorldDelta.js";
2026-08-08 17:41:30 +09:00
2026-08-10 13:59:33 +09:00
export { applyCommittedWorldDelta as applyCommittedMirrorDelta, hashCommittedWorld } from "./committedWorldDelta.js";
let persistentCommittedMirror = null;
let persistentCommittedRevision = -1;
let pendingMirrorBootstrap = null;
const pendingApplyDeltas = new Map();
const rawCandidateWorkerSlots = [];
const rawCandidateWorkerTerminations = [];
let rawCandidateTaskSerial = 0;
function normalizeWorkerTermination(value) {
return Promise.resolve(value).catch(() => undefined);
}
function destroyRawCandidateWorkerSlot(index, reason = "Raw patch candidate worker was terminated.") {
const slot = rawCandidateWorkerSlots[index];
if (!slot) return rawCandidateWorkerTerminations[index] || Promise.resolve();
rawCandidateWorkerSlots[index] = null;
const active = slot.active;
slot.active = null;
if (active) {
const error = new Error(reason);
error.code = "raw-candidate-worker-terminated";
active.reject(error);
}
let termination;
2026-08-08 17:41:30 +09:00
try {
2026-08-10 13:59:33 +09:00
termination = normalizeWorkerTermination(slot.worker?.terminate?.());
} catch {
termination = Promise.resolve();
}
const trackedTermination = termination.finally(() => {
if (rawCandidateWorkerTerminations[index] === trackedTermination) rawCandidateWorkerTerminations[index] = null;
});
rawCandidateWorkerTerminations[index] = trackedTermination;
return trackedTermination;
}
async function ensureRawCandidateWorkerSlot(index) {
if (rawCandidateWorkerTerminations[index]) await rawCandidateWorkerTerminations[index];
const existing = rawCandidateWorkerSlots[index];
if (existing?.worker) return existing;
if (typeof Worker !== "function") {
const error = new Error("Nested module workers are not available in this environment.");
error.code = "raw-candidate-worker-unavailable";
throw error;
}
const worker = new Worker(new URL("./patchCandidateWorker.js", import.meta.url), { type: "module" });
const slot = { worker, active: null, index };
worker.onmessage = (event) => {
const message = event.data || {};
const active = slot.active;
if (!active || Number(message.id) !== active.id) return;
if (message.type === "raw-patch-candidate-progress") {
active.onProgress?.(active.request, message.progress || {});
return;
}
if (message.type !== "raw-patch-candidate-result") return;
slot.active = null;
if (!message.ok) {
const error = new Error(message.error || "Raw patch candidate generation failed.");
error.code = message.code || "raw-patch-candidate-error";
error.stack = message.stack || error.stack;
active.reject(error);
return;
}
active.resolve(message.candidate);
};
worker.onerror = (event) => {
const active = slot.active;
slot.active = null;
const error = new Error(event?.message || "Raw patch candidate worker failed.");
error.code = "raw-candidate-worker-error";
if (active) active.reject(error);
// Do not reuse a worker that raised an uncaught error. The next task in
// this lane waits for termination before constructing a replacement.
destroyRawCandidateWorkerSlot(index, error.message);
};
rawCandidateWorkerSlots[index] = slot;
return slot;
}
async function runRawCandidateTask(slotIndex, request, onProgress) {
const slot = await ensureRawCandidateWorkerSlot(slotIndex);
if (slot.active) {
const error = new Error(`Raw candidate worker slot ${slotIndex} is unexpectedly busy.`);
error.code = "raw-candidate-worker-busy";
throw error;
}
const id = ++rawCandidateTaskSerial;
return new Promise((resolve, reject) => {
slot.active = { id, request, onProgress, resolve, reject };
try {
slot.worker.postMessage({
type: "generate-raw-patch-candidate",
id,
taskId: request.taskId || `raw-candidate-${id}`,
seed: request.seed >>> 0,
mapOptions: request.mapOptions || {},
});
} catch (error) {
slot.active = null;
reject(error);
}
});
}
async function precomputeRawCandidateBatch(requests, onProgress) {
if (!Array.isArray(requests) || !requests.length) return [];
if (requests.length > 2) throw new Error(`Raw candidate batch exceeds the two-worker limit (${requests.length}).`);
try {
return await Promise.all(requests.map((request, index) => runRawCandidateTask(index, request, onProgress)));
} catch (error) {
// A failed child may have left its module worker in an unknown state. Reset
// the bounded pool before the coordinator falls back to serial generation.
await Promise.all(rawCandidateWorkerSlots.map((slot, index) => slot?.active ? destroyRawCandidateWorkerSlot(index) : null));
throw error;
}
}
// Continuously feed at most two production helpers instead of waiting for
// fixed pairs to finish. Merge order remains strictly deterministic in
// mapPatch.js. Callers choose a bounded sliding window; production uses one
// resident result per lane so transferred candidate graphs cannot accumulate.
export function scheduleRawCandidateSequence(requests, onProgress, options = {}) {
const list = Array.isArray(requests) ? requests : [];
const count = list.length;
const laneCount = Math.max(1, Math.min(2, Math.floor(options.parallelism || 2), Math.max(1, count)));
const windowSize = Math.max(laneCount, Math.min(count || laneCount, Math.floor(options.windowSize || (laneCount + 1))));
const recycleWorkers = options.recycleWorkers === true;
// Keep each helper for a small bounded run. Reusing it forever lets
// generateMap's module-local/cache state accumulate; recycling after every
// tile creates excessive isolate churn on max-range selections. Three
// complete candidates per lane is the bounded middle ground.
const recycleEvery = recycleWorkers
? Math.max(1, Math.min(4, Math.floor(options.recycleEvery || 3)))
: Infinity;
const controls = list.map(() => {
let resolve;
let reject;
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
// The coordinator can stop after an earlier failure. Register a rejection
// observer now so later cancelled items cannot become unhandled rejections.
promise.catch(() => undefined);
return { promise, resolve, reject, settled: false };
});
const released = new Uint8Array(count);
let releasedPrefix = 0;
let nextToSchedule = 0;
let cancelled = false;
let cancelError = null;
const gateWaiters = new Set();
const wake = () => {
for (const resolve of gateWaiters) resolve();
gateWaiters.clear();
};
const waitForGate = () => new Promise((resolve) => gateWaiters.add(resolve));
const allowedEnd = () => Math.min(count, releasedPrefix + windowSize);
const takeNext = () => {
if (cancelled || nextToSchedule >= count) return -1;
if (nextToSchedule >= allowedEnd()) return -2;
return nextToSchedule++;
};
const failAll = (error) => {
if (cancelled) return;
cancelled = true;
cancelError = error instanceof Error ? error : new Error(String(error || "Raw candidate sequence cancelled."));
for (const control of controls) {
if (!control?.settled) {
control.settled = true;
const reject = control.reject;
// Promise capability functions keep their Promise reachable. Clear
// both callbacks as soon as the public Promise has settled so a
// transferred raw candidate cannot remain retained through scheduler
// bookkeeping after the coordinator drops publicPromises[index].
control.resolve = null;
control.reject = null;
reject?.(cancelError);
}
}
for (let index = 0; index < laneCount; index++) {
if (rawCandidateWorkerSlots[index]?.active) destroyRawCandidateWorkerSlot(index, cancelError.message);
}
wake();
};
const lane = async (slotIndex) => {
let tasksSinceRecycle = 0;
while (!cancelled) {
const index = takeNext();
if (index === -1) return;
if (index === -2) {
await waitForGate();
continue;
}
try {
let candidate = await runRawCandidateTask(slotIndex, list[index], onProgress);
if (cancelled) return;
const control = controls[index];
if (!control.settled) {
control.settled = true;
const resolve = control.resolve;
control.resolve = null;
control.reject = null;
resolve?.(candidate);
}
// The Promise result owns the transferred graph now. Do not keep an
// additional lane-local reference while waiting for the next window.
candidate = null;
// Reclaim helper-local generator state before it can grow without
// paying module-worker startup/teardown cost on every single tile.
tasksSinceRecycle++;
if (recycleWorkers && tasksSinceRecycle >= recycleEvery) {
await destroyRawCandidateWorkerSlot(slotIndex, `Raw candidate helper recycled after ${tasksSinceRecycle} completed tiles.`);
tasksSinceRecycle = 0;
}
} catch (error) {
failAll(error);
return;
}
}
};
// Keep the fulfilled Promise graph only in the public array. Once the
// coordinator nulls an entry after merge, scheduler bookkeeping must not keep
// the transferred candidate reachable for the rest of the sequence.
const publicPromises = controls.map((control) => control.promise);
for (const control of controls) control.promise = null;
const lanes = count ? Array.from({ length: laneCount }, (_, index) => lane(index)) : [];
const done = Promise.allSettled(lanes).then(() => undefined);
return {
promises: publicPromises,
release(index) {
const i = Math.floor(Number(index));
if (i < 0 || i >= count || released[i]) return;
released[i] = 1;
while (releasedPrefix < count && released[releasedPrefix]) releasedPrefix++;
wake();
},
cancel(reason = "Raw candidate sequence cancelled.") {
const error = reason instanceof Error ? reason : new Error(String(reason));
if (!error.code) error.code = "raw-candidate-sequence-cancelled";
failAll(error);
return done;
},
done,
async dispose(reason = "Raw candidate sequence disposed.") {
await done;
await Promise.allSettled(Array.from({ length: laneCount }, (_, index) =>
rawCandidateWorkerSlots[index] ? destroyRawCandidateWorkerSlot(index, reason) : rawCandidateWorkerTerminations[index]
).filter(Boolean));
},
get cancelled() { return cancelled; },
get error() { return cancelError; },
laneCount,
windowSize,
};
}
export async function shutdownRawCandidateWorkers() {
const pending = [];
for (let index = 0; index < rawCandidateWorkerSlots.length; index++) {
if (rawCandidateWorkerSlots[index]) pending.push(destroyRawCandidateWorkerSlot(index, "Raw candidate operation completed."));
else if (rawCandidateWorkerTerminations[index]) pending.push(rawCandidateWorkerTerminations[index]);
}
if (pending.length) await Promise.allSettled(pending);
}
function isMirrorSyncMessage(type) {
return typeof type === "string" && type.startsWith("patch-mirror-sync-");
}
function safeMirrorKey(key, label = "key") {
const normalized = String(key ?? "");
if (!normalized || normalized === "__proto__" || normalized === "prototype" || normalized === "constructor") {
throw new Error(`Invalid mirror ${label}: ${normalized || "<empty>"}.`);
}
return normalized;
}
function defineMirrorEntry(target, key, value) {
Object.defineProperty(target, key, {
value,
enumerable: true,
configurable: true,
writable: true,
});
}
function sameMirrorKeys(actual, expected) {
const actualKeys = Object.keys(actual || {});
if (!Array.isArray(expected) || actualKeys.length !== expected.length) return false;
const actualSet = new Set(actualKeys);
return expected.every((key) => actualSet.has(key));
}
function validateCompletedMirrorBootstrap(bootstrap) {
if (!bootstrap?.world || !bootstrap?.manifest) throw new Error("Mirror bootstrap is incomplete.");
const { world, manifest } = bootstrap;
if (!sameMirrorKeys(world.fields, manifest.fieldKeys)) throw new Error("Mirror field manifest is incomplete.");
if (!sameMirrorKeys(world.sourceMap, manifest.sourceKeys)) throw new Error("Mirror sourceMap manifest is incomplete.");
const rootActual = Object.keys(world).filter((key) => key !== "fields" && key !== "sourceMap" && key !== "generatedMask");
const rootExpected = Array.isArray(manifest.rootKeys) ? manifest.rootKeys : [];
if (rootActual.length !== rootExpected.length || !rootExpected.every((key) => rootActual.includes(key))) {
throw new Error("Mirror root manifest is incomplete.");
}
if (!!manifest.hasGeneratedMask !== !!world.generatedMask) throw new Error("Mirror generatedMask manifest is incomplete.");
const width = Number(world.width);
const height = Number(world.height);
const area = width * height;
if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0 || !Number.isSafeInteger(area) || area <= 0) {
throw new Error("Mirror dimensions are invalid.");
}
for (const key of manifest.fieldKeys || []) {
const field = world.fields[key];
if (!ArrayBuffer.isView(field) || field.length !== area) {
throw new Error(`Mirror field ${key} has invalid storage (${field?.length ?? "missing"} != ${area}).`);
}
}
if (manifest.hasGeneratedMask && (!ArrayBuffer.isView(world.generatedMask) || world.generatedMask.length !== area)) {
throw new Error(`Mirror generatedMask has invalid storage (${world.generatedMask?.length ?? "missing"} != ${area}).`);
}
for (const [key, childKeys] of Object.entries(manifest.expandedSourceObjects || {})) {
if (!world.sourceMap[key] || !sameMirrorKeys(world.sourceMap[key], childKeys)) {
throw new Error(`Mirror sourceMap.${key} manifest is incomplete.`);
}
}
}
function handleMirrorSyncMessage(incoming) {
if (!isMirrorSyncMessage(incoming?.type)) return false;
const type = incoming.type;
const syncId = String(incoming.syncId || "");
const sequence = Number(incoming.sequence || 0);
const acknowledge = (ok, error = null, extra = {}) => {
self.postMessage({
id: incoming.id,
type: "patch-mirror-sync-ack",
syncId,
sequence,
stage: type,
ok,
error,
...extra,
2026-08-08 17:41:30 +09:00
});
2026-08-10 13:59:33 +09:00
};
try {
if (!syncId || !Number.isSafeInteger(sequence) || sequence <= 0) throw new Error("Invalid mirror synchronization envelope.");
if (type === "patch-mirror-sync-start") {
const manifest = incoming.manifest || {};
if (!Array.isArray(manifest.rootKeys) || !Array.isArray(manifest.fieldKeys) || !Array.isArray(manifest.sourceKeys)) {
throw new Error("Mirror synchronization manifest is invalid.");
}
pendingMirrorBootstrap = {
id: incoming.id,
syncId,
committedRevision: Number(incoming.committedRevision ?? -1),
lastSequence: sequence,
manifest,
world: { fields: {}, sourceMap: {} },
};
acknowledge(true);
return true;
}
const bootstrap = pendingMirrorBootstrap;
if (!bootstrap || bootstrap.syncId !== syncId || bootstrap.id !== incoming.id) throw new Error("Mirror synchronization session is unavailable or stale.");
if (sequence <= bootstrap.lastSequence) throw new Error(`Mirror synchronization sequence did not advance (${sequence} <= ${bootstrap.lastSequence}).`);
bootstrap.lastSequence = sequence;
if (type === "patch-mirror-sync-root") {
const key = safeMirrorKey(incoming.key, "root key");
if (!bootstrap.manifest.rootKeys.includes(key)) throw new Error(`Unexpected mirror root key ${key}.`);
defineMirrorEntry(bootstrap.world, key, incoming.value);
} else if (type === "patch-mirror-sync-field") {
const key = safeMirrorKey(incoming.key, "field key");
if (!bootstrap.manifest.fieldKeys.includes(key)) throw new Error(`Unexpected mirror field ${key}.`);
defineMirrorEntry(bootstrap.world.fields, key, incoming.value);
} else if (type === "patch-mirror-sync-generated-mask") {
if (!bootstrap.manifest.hasGeneratedMask) throw new Error("Unexpected mirror generatedMask.");
bootstrap.world.generatedMask = incoming.value;
} else if (type === "patch-mirror-sync-source") {
const key = safeMirrorKey(incoming.key, "source key");
if (!bootstrap.manifest.sourceKeys.includes(key)) throw new Error(`Unexpected mirror source key ${key}.`);
if (Object.prototype.hasOwnProperty.call(bootstrap.manifest.expandedSourceObjects || {}, key)) {
throw new Error(`Mirror source key ${key} must be synchronized by child entries.`);
}
defineMirrorEntry(bootstrap.world.sourceMap, key, incoming.value);
} else if (type === "patch-mirror-sync-source-object-start") {
const key = safeMirrorKey(incoming.key, "source object key");
if (!Object.prototype.hasOwnProperty.call(bootstrap.manifest.expandedSourceObjects || {}, key)) {
throw new Error(`Unexpected expanded mirror source key ${key}.`);
}
defineMirrorEntry(bootstrap.world.sourceMap, key, {});
} else if (type === "patch-mirror-sync-source-object-entry") {
const key = safeMirrorKey(incoming.key, "source object key");
const childKey = safeMirrorKey(incoming.childKey, "source child key");
const expectedChildren = bootstrap.manifest.expandedSourceObjects?.[key];
if (!Array.isArray(expectedChildren) || !expectedChildren.includes(childKey) || !bootstrap.world.sourceMap[key]) {
throw new Error(`Unexpected mirror source child ${key}.${childKey}.`);
}
defineMirrorEntry(bootstrap.world.sourceMap[key], childKey, incoming.value);
} else if (type === "patch-mirror-sync-finish") {
validateCompletedMirrorBootstrap(bootstrap);
persistentCommittedMirror = bootstrap.world;
persistentCommittedRevision = bootstrap.committedRevision;
pendingMirrorBootstrap = null;
pendingApplyDeltas.clear();
acknowledge(true, null, { mirrorCommittedRevision: persistentCommittedRevision });
return true;
} else if (type === "patch-mirror-sync-abort") {
pendingMirrorBootstrap = null;
acknowledge(true);
return true;
} else {
throw new Error(`Unknown mirror synchronization stage ${type}.`);
}
acknowledge(true);
2026-08-08 17:41:30 +09:00
} catch (error) {
2026-08-10 13:59:33 +09:00
if (type === "patch-mirror-sync-start" || pendingMirrorBootstrap?.syncId === syncId) pendingMirrorBootstrap = null;
acknowledge(false, error?.message || String(error));
}
return true;
}
function valuesEqual(a, b) {
return a === b || (Number.isNaN(a) && Number.isNaN(b));
}
function exactStructuredEqual(a, b) {
if (valuesEqual(a, b)) return true;
if (a == null || b == null || typeof a !== "object" || typeof b !== "object") return false;
if (ArrayBuffer.isView(a) || ArrayBuffer.isView(b)) {
if (!ArrayBuffer.isView(a) || !ArrayBuffer.isView(b)
|| a.constructor !== b.constructor || a.length !== b.length) return false;
for (let index = 0; index < a.length; index++) if (!valuesEqual(a[index], b[index])) return false;
return true;
}
if (Array.isArray(a) || Array.isArray(b)) {
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
for (let index = 0; index < a.length; index++) if (!exactStructuredEqual(a[index], b[index])) return false;
// Generated paths use one enumerable array annotation. Compare it directly
// instead of allocating Object.keys arrays for every coordinate tuple.
return a.patchGenerated === b.patchGenerated;
}
if (a instanceof Date || b instanceof Date) return a instanceof Date && b instanceof Date && a.getTime() === b.getTime();
if (a instanceof Map || b instanceof Map) {
if (!(a instanceof Map) || !(b instanceof Map) || a.size !== b.size) return false;
for (const [key, value] of a) if (!b.has(key) || !exactStructuredEqual(value, b.get(key))) return false;
return true;
}
if (a instanceof Set || b instanceof Set) {
if (!(a instanceof Set) || !(b instanceof Set) || a.size !== b.size) return false;
for (const value of a) if (!b.has(value)) return false;
return true;
}
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
if (aKeys.length !== bKeys.length) return false;
for (let index = 0; index < aKeys.length; index++) {
const key = aKeys[index];
if (key !== bKeys[index] || !exactStructuredEqual(a[key], b[key])) return false;
}
return true;
}
function isDensePlainArray(value) {
if (!Array.isArray(value)) return false;
const keys = Object.keys(value);
if (keys.length !== value.length) return false;
for (let index = 0; index < keys.length; index++) if (keys[index] !== String(index)) return false;
return true;
}
function buildExactArraySplice(base, next, { cloneValues = true } = {}) {
if (!isDensePlainArray(base) || !isDensePlainArray(next)) return null;
const commonLimit = Math.min(base.length, next.length);
let start = 0;
while (start < commonLimit && exactStructuredEqual(base[start], next[start])) start++;
if (start === base.length && start === next.length) return { unchanged: true };
let suffix = 0;
while (suffix < commonLimit - start
&& exactStructuredEqual(base[base.length - 1 - suffix], next[next.length - 1 - suffix])) suffix++;
const items = next.slice(start, next.length - suffix);
return {
start,
deleteCount: base.length - start - suffix,
items: cloneValues ? structuredClone(items) : items,
};
}
function buildExactObjectDelta(base = {}, next = {}, { cloneValues = true } = {}) {
const set = {};
const arraySplices = {};
for (const [key, value] of Object.entries(next || {})) {
if (!Object.prototype.hasOwnProperty.call(base || {}, key)) {
set[key] = cloneValues ? structuredClone(value) : value;
continue;
}
if (isDensePlainArray(base[key]) && isDensePlainArray(value)) {
const splice = buildExactArraySplice(base[key], value, { cloneValues });
if (splice && !splice.unchanged) arraySplices[key] = splice;
continue;
}
if (!exactStructuredEqual(base[key], value)) set[key] = cloneValues ? structuredClone(value) : value;
}
const removed = Object.keys(base || {}).filter((key) => !Object.prototype.hasOwnProperty.call(next || {}, key));
return { set, arraySplices, removed };
}
function buildTypedRowDelta(base, next, width) {
if (!ArrayBuffer.isView(next)) return null;
if (!ArrayBuffer.isView(base) || base.constructor?.name !== next.constructor?.name || base.length !== next.length) {
return { constructorName: next.constructor.name, length: next.length, replace: new next.constructor(next) };
}
const rows = [];
const rowWidth = Math.max(1, Number(width || next.length));
for (let rowStart = 0; rowStart < next.length; rowStart += rowWidth) {
const rowEnd = Math.min(next.length, rowStart + rowWidth);
let first = -1;
let last = -1;
for (let index = rowStart; index < rowEnd; index++) {
if (valuesEqual(base[index], next[index])) continue;
if (first < 0) first = index;
last = index;
}
if (first >= 0) rows.push({ start: first, values: new next.constructor(next.subarray(first, last + 1)) });
2026-08-08 17:41:30 +09:00
}
2026-08-10 13:59:33 +09:00
return rows.length ? { constructorName: next.constructor.name, length: next.length, rows } : null;
}
export function buildCommittedMirrorDelta(baseWorld, nextWorld) {
if (!baseWorld || !nextWorld) throw new Error("Cannot build a committed mirror delta without both worlds.");
const fieldNames = new Set([...Object.keys(baseWorld.fields || {}), ...Object.keys(nextWorld.fields || {})]);
const fields = {};
for (const name of fieldNames) {
const next = nextWorld.fields?.[name];
if (!next) {
fields[name] = { remove: true };
continue;
}
const delta = buildTypedRowDelta(baseWorld.fields?.[name], next, nextWorld.width);
if (delta) fields[name] = delta;
}
const generatedMask = buildTypedRowDelta(baseWorld.generatedMask, nextWorld.generatedMask, nextWorld.width);
const baseMeta = {};
const nextMeta = {};
for (const [key, value] of Object.entries(baseWorld)) {
if (key !== "fields" && key !== "generatedMask" && key !== "sourceMap") baseMeta[key] = value;
}
for (const [key, value] of Object.entries(nextWorld)) {
if (key !== "fields" && key !== "generatedMask" && key !== "sourceMap") nextMeta[key] = value;
}
const exactDeltas = structuredClone({
sourceMapDelta: buildExactObjectDelta(baseWorld.sourceMap || {}, nextWorld.sourceMap || {}, { cloneValues: false }),
metaDelta: buildExactObjectDelta(baseMeta, nextMeta, { cloneValues: false }),
});
return {
width: nextWorld.width,
height: nextWorld.height,
fields,
generatedMask,
sourceMapDelta: exactDeltas.sourceMapDelta,
metaDelta: exactDeltas.metaDelta,
};
}
export function buildMainThreadTransferDelta(delta) {
if (!delta) return null;
// Raster row buffers are transferred to the main thread and therefore need
// a Worker-side copy so the retained Apply-ACK delta is not detached. Plain
// metadata is already cloned once by postMessage; cloning that graph here as
// well only doubled paths, points, histories and diagnostics at peak memory.
const raster = structuredClone({
fields: delta.fields || {},
generatedMask: delta.generatedMask || null,
});
return {
...delta,
fields: raster.fields,
generatedMask: raster.generatedMask,
};
}
function markPreviewChange(changeTracker, worldIndex, category = 0) {
if (!changeTracker?.mask) return;
const width = changeTracker.worldWidth;
const x = worldIndex % width;
const y = Math.floor(worldIndex / width);
const rect = changeTracker.rect;
if (x < rect.x0 || y < rect.y0 || x >= rect.x1 || y >= rect.y1) return;
const localIndex = (y - rect.y0) * changeTracker.width + (x - rect.x0);
changeTracker.mask[localIndex] |= 1 | category;
}
function markAllPreviewChanges(changeTracker, category = 0) {
if (!changeTracker?.mask) return;
const flags = 1 | category;
changeTracker.mask.fill(flags);
}
function buildTypedRowDeltaFromTransaction(snapshot, name, next, changeTracker = null) {
if (!ArrayBuffer.isView(next)) return null;
const baseRef = snapshot?.fieldRefs?.get(name);
if (!ArrayBuffer.isView(baseRef) || baseRef.constructor?.name !== next.constructor?.name || baseRef.length !== next.length) {
const category = PREVIEW_TERRAIN_FIELDS.has(name) ? 2 : PREVIEW_ADMIN_FIELDS.has(name) ? 4 : 0;
markAllPreviewChanges(changeTracker, category);
return { constructorName: next.constructor.name, length: next.length, replace: new next.constructor(next) };
}
const localEntry = snapshot?.fields?.get(name) || null;
const rect = snapshot?.fieldRect || null;
const width = Math.max(1, Number(snapshot?.width || next.length));
const rows = [];
const scanLocalRect = localEntry && rect;
const startY = scanLocalRect ? Math.max(0, rect.y0) : 0;
const endY = scanLocalRect ? Math.min(snapshot.height, rect.y1) : Math.ceil(next.length / width);
for (let y = startY; y < endY; y++) {
const rowStart = y * width;
const rowEnd = Math.min(next.length, rowStart + width);
const scanStart = scanLocalRect ? Math.max(rowStart, rowStart + rect.x0) : rowStart;
const scanEnd = scanLocalRect ? Math.min(rowEnd, rowStart + rect.x1) : rowEnd;
let first = -1;
let last = -1;
for (let index = scanStart; index < scanEnd; index++) {
const x = index - rowStart;
let oldValue = baseRef[index];
if (localEntry && rect && x >= rect.x0 && x < rect.x1 && y >= rect.y0 && y < rect.y1) {
oldValue = localEntry.data[(y - rect.y0) * snapshot.fieldWidth + (x - rect.x0)];
}
if (valuesEqual(oldValue, next[index])) continue;
const category = PREVIEW_TERRAIN_FIELDS.has(name) ? 2 : PREVIEW_ADMIN_FIELDS.has(name) ? 4 : 0;
markPreviewChange(changeTracker, index, category);
if (first < 0) first = index;
last = index;
}
if (first >= 0) rows.push({ start: first, values: new next.constructor(next.subarray(first, last + 1)) });
}
return rows.length ? { constructorName: next.constructor.name, length: next.length, rows } : null;
}
export function buildCommittedMirrorDeltaFromTransaction(snapshot, nextWorld) {
if (!snapshot || snapshot.lightweight || !nextWorld) throw new Error("A full patch transaction snapshot is required.");
const fieldNames = new Set([...(snapshot.fieldNames || []), ...Object.keys(nextWorld.fields || {})]);
const fields = {};
const snapshotRect = snapshot.fieldRect || { x0: 0, y0: 0, x1: nextWorld.width, y1: nextWorld.height };
const changeRect = {
x0: Math.max(0, Math.floor(snapshotRect.x0 || 0)),
y0: Math.max(0, Math.floor(snapshotRect.y0 || 0)),
x1: Math.min(nextWorld.width, Math.ceil(snapshotRect.x1 || 0)),
y1: Math.min(nextWorld.height, Math.ceil(snapshotRect.y1 || 0)),
};
const changeTracker = {
rect: changeRect,
width: Math.max(0, changeRect.x1 - changeRect.x0),
worldWidth: nextWorld.width,
mask: null,
};
changeTracker.mask = new Uint8Array(changeTracker.width * Math.max(0, changeRect.y1 - changeRect.y0));
for (const name of fieldNames) {
const next = nextWorld.fields?.[name];
if (!next) {
if (snapshot.fieldNames?.has(name)) {
fields[name] = { remove: true };
const category = PREVIEW_TERRAIN_FIELDS.has(name) ? 2 : PREVIEW_ADMIN_FIELDS.has(name) ? 4 : 0;
markAllPreviewChanges(changeTracker, category);
}
continue;
}
const delta = buildTypedRowDeltaFromTransaction(snapshot, name, next, changeTracker);
if (delta) fields[name] = delta;
}
const baseMeta = {};
const nextMeta = {};
for (const [key, value] of Object.entries(nextWorld)) {
if (key !== "fields" && key !== "generatedMask" && key !== "sourceMap") {
baseMeta[key] = value;
nextMeta[key] = value;
}
}
Object.assign(baseMeta, {
width: snapshot.width,
height: snapshot.height,
originX: snapshot.originX,
originY: snapshot.originY,
sourceWidth: snapshot.sourceWidth,
sourceHeight: snapshot.sourceHeight,
generatedRects: snapshot.generatedRects,
lastPatchResult: snapshot.lastPatchResult,
patchGenerationSerial: snapshot.patchGenerationSerial,
seaLevel: snapshot.seaLevel,
});
const rawSourceMapDelta = buildExactObjectDelta(snapshot.sourceMap || {}, nextWorld.sourceMap || {}, { cloneValues: false });
const rawMetaDelta = buildExactObjectDelta(baseMeta, nextMeta, { cloneValues: false });
// Transaction restore replaces world/sourceMap roots; it does not mutate the
// accepted arrays and diagnostic objects referenced by these deltas. Adopt
// that completed graph directly. Cloning it here duplicated all changed
// metadata immediately before rollback and the later postMessage clone.
const exactDeltas = { sourceMapDelta: rawSourceMapDelta, metaDelta: rawMetaDelta };
let changedCells = 0;
let terrainChangedCells = 0;
let adminChangedCells = 0;
for (const flags of changeTracker.mask) {
if (flags & 1) changedCells++;
if (flags & 2) terrainChangedCells++;
if (flags & 4) adminChangedCells++;
}
const changedSourceKeys = new Set([
...Object.keys(rawSourceMapDelta.set || {}),
...Object.keys(rawSourceMapDelta.arraySplices || {}),
...(rawSourceMapDelta.removed || []),
]);
let featureLayersChanged = 0;
for (const key of PREVIEW_FEATURE_KEYS) if (changedSourceKeys.has(key)) featureLayersChanged++;
return {
width: nextWorld.width,
height: nextWorld.height,
fields,
generatedMask: buildTypedRowDelta(
snapshot.generatedMask || snapshot.generatedMaskRef,
nextWorld.generatedMask,
nextWorld.width
),
sourceMapDelta: exactDeltas.sourceMapDelta,
metaDelta: exactDeltas.metaDelta,
previewDelta: {
changedCells,
terrainChangedCells,
adminChangedCells,
featureLayersChanged,
identical: changedCells === 0 && featureLayersChanged === 0,
},
};
}
function nowMs() {
return typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
}
function compactQuality(quality) {
if (!quality || typeof quality !== "object") return null;
return {
hardPass: quality.hardPass !== false,
score: Number(quality.score || 0),
selectedVariant: Number.isFinite(quality.selectedVariant) ? quality.selectedVariant >>> 0 : null,
terrainType: quality.terrainType || quality.terrain?.terrainType || null,
terrain: quality.terrain ? {
hardPass: quality.terrain.hardPass !== false,
landRatio: Number(quality.terrain.landRatio || 0),
developableRatio: Number(quality.terrain.developableRatio || 0),
largestComponentRatio: Number(quality.terrain.largestComponentRatio || 0),
} : null,
human: quality.human ? {
hardPass: quality.human.hardPass !== false,
settlementCount: Number(quality.human.settlementCount || 0),
labelCount: Number(quality.human.labelCount || 0),
roadPaths: Number(quality.human.roadPaths || 0),
railPaths: Number(quality.human.railPaths || 0),
} : null,
finalMerge: quality.finalMerge ? {
hardPass: quality.finalMerge.hardPass !== false,
ownedCells: Number(quality.finalMerge.ownedCells || 0),
ownedLandCells: Number(quality.finalMerge.ownedLandCells || 0),
ownedLandRatio: Number(quality.finalMerge.ownedLandRatio || 0),
landFloor: Number(quality.finalMerge.landFloor || 0),
settlementCount: Number(quality.finalMerge.settlementCount || 0),
minFinalSettlements: Number(quality.finalMerge.minFinalSettlements || 0),
preMergeSettlementCount: Number.isFinite(quality.finalMerge.preMergeSettlementCount)
? Number(quality.finalMerge.preMergeSettlementCount) : null,
candidateMinimumSettlements: Number(quality.finalMerge.candidateMinimumSettlements || 0),
labelCount: Number(quality.finalMerge.labelCount || 0),
minFinalLabels: Number(quality.finalMerge.minFinalLabels || 0),
preMergeLabelCount: Number.isFinite(quality.finalMerge.preMergeLabelCount)
? Number(quality.finalMerge.preMergeLabelCount) : null,
candidateMinimumLabels: Number(quality.finalMerge.candidateMinimumLabels || 0),
adminCenters: Number(quality.finalMerge.counts?.adminCenters || 0),
minFinalAdminCenters: Number(quality.finalMerge.minFinalAdminCenters || 0),
transportRequired: quality.finalMerge.transportRequired === true,
roadPaths: Number(quality.finalMerge.roadPaths || 0),
railPaths: Number(quality.finalMerge.railPaths || 0),
rectangularCoastHardPass: quality.finalMerge.rectangularCoastHardPass !== false,
rectangularCoastRun: Number(quality.finalMerge.rectangularCoastCut?.maxAxisAlignedRun || 0),
rectangularCoastLongestRun: quality.finalMerge.rectangularCoastCut?.longestRun
? { ...quality.finalMerge.rectangularCoastCut.longestRun }
: null,
rectangularCoastCutScope: quality.finalMerge.rectangularCoastCutScope || null,
score: Number(quality.finalMerge.score || 0),
} : null,
};
}
function compactSeam(diagnostics) {
if (!diagnostics || typeof diagnostics !== "object") return null;
return {
hardPass: diagnostics.hardPass !== false,
status: diagnostics.status || null,
gateReasons: [...(diagnostics.gateReasons || [])],
roadPortalsBroken: Number(diagnostics.roadPortalsBroken || 0),
railPortalsBroken: Number(diagnostics.railPortalsBroken || 0),
prefectureSeamBreakEdges: Number(diagnostics.prefectureSeamBreakEdges || 0),
adminSeamBreakEdges: Number(diagnostics.adminSeamBreakEdges || 0),
landToSeaCells: Number(diagnostics.landToSeaCells || 0),
transportLandToSeaConflicts: Number(diagnostics.transportLandToSeaConflicts || 0),
duplicateBoundaryPairs: Number(diagnostics.duplicateBoundaryPairs || 0),
expansionFootprintEscapedCells: Number(diagnostics.expansionFootprintEscapedCells || 0),
maxEstablishedFrontierElevationJump: Number(diagnostics.maxEstablishedFrontierElevationJump || 0),
humanTerrainReconciliation: diagnostics.humanTerrainReconciliation ? {
checked: Number(diagnostics.humanTerrainReconciliation.checked || 0),
alreadyValid: Number(diagnostics.humanTerrainReconciliation.alreadyValid || 0),
relocated: Number(diagnostics.humanTerrainReconciliation.relocated || 0),
dropped: Number(diagnostics.humanTerrainReconciliation.dropped || 0),
stagedOutsideOwnership: Number(diagnostics.humanTerrainReconciliation.stagedOutsideOwnership || 0),
byLayer: diagnostics.humanTerrainReconciliation.byLayer
? Object.fromEntries(Object.entries(diagnostics.humanTerrainReconciliation.byLayer).map(([key, value]) => [key, {
checked: Number(value?.checked || 0),
alreadyValid: Number(value?.alreadyValid || 0),
relocated: Number(value?.relocated || 0),
dropped: Number(value?.dropped || 0),
stagedOutsideOwnership: Number(value?.stagedOutsideOwnership || 0),
}]))
: {},
} : null,
};
}
function isInvariantFailure(result) {
if (result?.tileResult && isInvariantFailure(result.tileResult)) return true;
const code = String(result?.code || "");
const reasons = result?.seamDiagnostics?.gateReasons || [];
return code === "patch-candidate-coverage-incomplete"
|| code === "patch-generated-footprint-write-escape"
|| code === "patch-invariant-breach"
|| reasons.includes("generated-footprint-write-escape")
|| Number(result?.candidateUnmappedActiveCells || 0) > 0;
}
function isContentRejection(result) {
const code = String(result?.code || "");
if (isInvariantFailure(result)) return false;
if ((code === "patch-large-tile-failed" || code === "patch-large-regeneration-tile-failed") && result?.tileResult) {
return isContentRejection(result.tileResult);
}
return code === "patch-quality-gate-failed"
|| code === "patch-seam-gate-failed"
|| code === "patch-large-final-seam-failed";
}
function normalizeCandidateResult(result) {
if (!result?.ok || result?.seamDiagnostics?.hardPass !== false) return result;
return {
...result,
ok: false,
code: "patch-seam-gate-failed",
reason: `Candidate failed final seam continuity (${(result.seamDiagnostics.gateReasons || []).join(", ") || "unknown seam failure"}).`,
rolledBack: true,
};
}
function summarizeAttempt(result, candidate, candidateOrdinal, wallMs, status, executionAttempt = 1) {
return {
candidateId: candidate.candidateId || `${candidate.variant >>> 0}:${candidate.seed >>> 0}`,
candidateOrdinal,
executionAttempt: Math.max(1, Number(executionAttempt || 1)),
variant: candidate.variant >>> 0,
seed: candidate.seed >>> 0,
status,
ok: result?.ok === true,
code: result?.code || null,
reason: result?.reason || null,
patchMode: result?.patchMode || null,
tileCount: Number(result?.tileCount || 0),
wallMs: Math.round(wallMs * 10) / 10,
candidateQuality: compactQuality(result?.candidateQuality),
seamDiagnostics: compactSeam(result?.seamDiagnostics),
patchTimings: (result?.patchTimings || []).map((entry) => ({
key: entry.key || null,
label: entry.label || entry.key || "Stage",
ms: Number(entry.ms || 0),
})),
};
}
const PREVIEW_TERRAIN_FIELDS = new Set(["elevation", "slope", "sea", "landMask", "plain", "landuse", "populationDensity"]);
const PREVIEW_ADMIN_FIELDS = new Set(["adminId", "municipalityId", "prefectureRegionId", "prefectureMask", "humanRegionMask"]);
const PREVIEW_FEATURE_KEYS = [
"villages", "markets", "castles", "ports", "modernCities", "satelliteCities", "stations",
"interchanges", "industrialZones", "logisticsParks", "newTowns", "adminCenters", "externalGateways",
"prefectureRegions", "premodernRoads", "minorRoads", "nationalRoads", "ringRoads", "externalRoads",
"railways", "branchRailways", "ringRailways", "externalRailways", "expressways", "externalExpressways",
"mainRivers", "tributaryRivers", "smallStreams", "prefectureBorder", "regionalPrefectureBorders", "adminBorders",
];
function computePreviewDelta(baseWorld, previewWorld, rectLike, onProgress = () => {}) {
const rect = rectLike?.transportReachRect || rectLike?.repairRect || rectLike?.writeRect || rectLike || null;
if (!baseWorld || !previewWorld || !rect) return null;
const x0 = Math.max(0, Math.floor(rect.x0 || 0));
const y0 = Math.max(0, Math.floor(rect.y0 || 0));
const x1 = Math.min(Math.max(baseWorld.width || 0, previewWorld.width || 0), Math.ceil(rect.x1 || 0));
const y1 = Math.min(Math.max(baseWorld.height || 0, previewWorld.height || 0), Math.ceil(rect.y1 || 0));
const fieldNames = new Set([...Object.keys(baseWorld.fields || {}), ...Object.keys(previewWorld.fields || {})]);
let changedCells = 0;
let terrainChangedCells = 0;
let adminChangedCells = 0;
const totalRows = Math.max(0, y1 - y0) + PREVIEW_FEATURE_KEYS.length;
for (let y = y0; y < y1; y++) {
for (let x = x0; x < x1; x++) {
const bi = x >= 0 && y >= 0 && x < baseWorld.width && y < baseWorld.height ? y * baseWorld.width + x : -1;
const pi = x >= 0 && y >= 0 && x < previewWorld.width && y < previewWorld.height ? y * previewWorld.width + x : -1;
if (bi < 0 || pi < 0) continue;
let terrainChanged = false;
let adminChanged = false;
let cellChanged = false;
for (const name of fieldNames) {
const a = baseWorld.fields?.[name]?.[bi];
const b = previewWorld.fields?.[name]?.[pi];
if (a === b || (Number.isNaN(a) && Number.isNaN(b))) continue;
cellChanged = true;
if (PREVIEW_TERRAIN_FIELDS.has(name)) terrainChanged = true;
if (PREVIEW_ADMIN_FIELDS.has(name)) adminChanged = true;
}
if (cellChanged) changedCells++;
if (terrainChanged) terrainChangedCells++;
if (adminChanged) adminChangedCells++;
}
if ((y - y0) % 16 === 15 || y + 1 === y1) onProgress(y - y0 + 1, totalRows, "raster");
}
let featureLayersChanged = 0;
for (let index = 0; index < PREVIEW_FEATURE_KEYS.length; index++) {
const key = PREVIEW_FEATURE_KEYS[index];
if (!exactStructuredEqual(baseWorld.sourceMap?.[key] || [], previewWorld.sourceMap?.[key] || [])) featureLayersChanged++;
onProgress(Math.max(0, y1 - y0) + index + 1, totalRows, "features");
}
return {
changedCells,
terrainChangedCells,
adminChangedCells,
featureLayersChanged,
identical: changedCells === 0 && featureLayersChanged === 0,
};
}
export function runPatchCandidateSearch(message, dependencies = {}) {
const { id, world, rect, options, search } = message || {};
const cloneWorld = dependencies.cloneWorld || ((value) => structuredClone(value));
const generateCandidate = dependencies.generateCandidate || generatePatch;
const clock = dependencies.now || nowMs;
const publishProgress = dependencies.onProgress || (() => {});
const transactional = dependencies.transactional === true;
const candidatePlan = Array.isArray(search?.candidatePlan) && search.candidatePlan.length
? search.candidatePlan
: [{ variant: options?.variant || 0, seed: options?.seed || world?.seed || 0 }];
const candidateCount = Math.max(candidatePlan.length, Number(search?.totalCandidateCount || candidatePlan.length));
const searchId = search?.searchId || `patch-${id}`;
const workerEpoch = Number(search?.workerEpoch || 0);
const executionAttempt = Math.max(1, Number(search?.executionAttempt || 1));
let eventSeq = 0;
let phaseOrdinal = 0;
let currentPhase = null;
const workUnits = new Map();
const emitProgress = (progress = {}, candidateOrdinal = 0) => {
const phase = String(progress.phase || progress.key || "patch");
if (phase !== currentPhase) {
currentPhase = phase;
phaseOrdinal++;
}
const hasCompleted = progress.completed != null;
const hasTotal = progress.total != null;
const completed = Number(progress.completed);
const total = Number(progress.total);
const boundedWork = hasCompleted || hasTotal;
const explicitWorkUnitId = progress.workUnitId != null && String(progress.workUnitId).length > 0;
const workUnitId = String(explicitWorkUnitId ? progress.workUnitId : (progress.key || phase));
if (boundedWork) {
if (!hasCompleted || !hasTotal || !Number.isFinite(completed) || !Number.isFinite(total)) {
const error = new Error(`Incomplete bounded progress for ${phase}/${workUnitId}: ${String(progress.completed)}/${String(progress.total)}`);
error.code = "worker-progress-invariant";
throw error;
}
if (!explicitWorkUnitId) {
const error = new Error(`Bounded progress is missing an explicit workUnitId for ${phase}/${workUnitId}.`);
error.code = "worker-progress-invariant";
throw error;
}
if (completed < 0 || total < 0 || completed > total) {
const error = new Error(`Invalid progress bounds for ${phase}/${workUnitId}: ${completed}/${total}`);
error.code = "worker-progress-invariant";
throw error;
}
// workUnitId is globally unique within one candidate-search operation.
// Do not reset its monotonicity merely because a display phase changed in
// between heartbeats; that would hide real restarts/runaway producers.
const unitKey = workUnitId;
const previous = workUnits.get(unitKey);
if (previous && (total !== previous.total || completed < previous.completed)) {
const error = new Error(`Non-monotonic progress for ${phase}/${workUnitId}: ${completed}/${total} after ${previous.completed}/${previous.total}`);
error.code = "worker-progress-invariant";
throw error;
}
const workerNow = nowMs();
const startedAtWorker = previous?.startedAtWorker ?? workerNow;
const lastAdvancedAtWorker = !previous || completed > previous.completed
? workerNow
: (previous.lastAdvancedAtWorker ?? startedAtWorker);
workUnits.set(unitKey, { completed, total, phase, startedAtWorker, lastAdvancedAtWorker });
}
eventSeq++;
publishProgress({
id,
type: "progress",
progress: {
...progress,
searchId,
operationId: search?.operationId || searchId,
candidateOrdinal,
candidateCount,
executionAttempt,
workerEpoch,
committedRevision: Number(search?.committedRevision || 0),
eventSeq,
counter: eventSeq,
phase,
phaseOrdinal,
workUnitId,
boundedWork,
startedAtWorker: boundedWork ? workUnits.get(workUnitId)?.startedAtWorker : undefined,
lastAdvancedAtWorker: boundedWork ? workUnits.get(workUnitId)?.lastAdvancedAtWorker : undefined,
cooperative: progress.nonCooperative !== true,
},
});
};
try {
const attempts = [];
let terminalResult = null;
let successWorld = null;
let successDelta = null;
let successTargetHash = null;
emitProgress({ status: "start", key: "search", phase: "search", workUnitId: "candidate-search", label: `Searching ${candidateCount} complete candidate${candidateCount === 1 ? "" : "s"}`, completed: 0, total: candidateCount });
for (let index = 0; index < candidatePlan.length; index++) {
const candidate = candidatePlan[index];
const candidateOrdinal = Math.max(1, Number(candidate.candidateOrdinal || index + 1));
emitProgress({
status: "start",
key: transactional ? "candidate-transaction" : "candidate-clone",
phase: transactional ? "candidate-transaction" : "candidate-clone",
label: transactional
? `Candidate ${candidateOrdinal}/${candidateCount}: capturing exact rollback state`
: `Candidate ${candidateOrdinal}/${candidateCount}: preparing immutable baseline`,
variant: candidate.variant >>> 0,
workUnitId: "candidate-search",
// candidateOrdinal is global across the full search, whereas `index` is
// local to this Worker execution. Recovery can restart with candidates
// 2..N after candidate 1 was already rejected, so using `index` here
// made the shared search work unit move backward (2/3 -> 1/3).
completed: Math.max(0, candidateOrdinal - 1),
total: candidateCount,
nonCooperative: true,
}, candidateOrdinal);
let candidateWorld;
let transaction = null;
try {
if (transactional) {
transaction = capturePatchTransactionSnapshot(world, {
lightweight: false,
isolateSourceMap: true,
copyGeneratedMask: String(search?.resolvedPatchMode || "").toLowerCase() !== "regeneration",
});
candidateWorld = world;
} else {
candidateWorld = cloneWorld(world);
}
} catch (error) {
const failed = {
ok: false,
code: transactional ? "candidate-transaction-failed" : "candidate-clone-failed",
reason: error?.message || String(error),
};
attempts.push(summarizeAttempt(failed, candidate, candidateOrdinal, 0, "infrastructure-error", executionAttempt));
terminalResult = {
...failed,
searchAttempts: attempts,
searchStatus: "infrastructure-error",
nextVariant: candidate.variant >>> 0,
};
break;
}
const startedAt = clock();
let result;
try {
result = generateCandidate(candidateWorld, rect, {
...(options || {}),
seed: candidate.seed >>> 0,
variant: candidate.variant >>> 0,
maxQualityRetries: 0,
// The production Worker mutates its committed mirror transactionally
// and restores it after extracting the accepted delta. Direct callers
// retain the immutable-clone reference path for independent tests.
_workerOwnedPreview: !transactional,
_externalTransactionSnapshot: transaction,
onProgress: (progress) => {
// Producer workUnitIds are invocation-local. The same complete
// pipeline is executed again when a content-rejected candidate
// advances to the next Variant, so namespace every explicit inner
// unit by candidate ordinal before the search-wide monotonicity
// validator sees it. Do not invent an ID for a bounded producer
// that omitted one; emitProgress must still reject that protocol
// violation instead of masking it.
const rawWorkUnitId = progress?.workUnitId != null && String(progress.workUnitId).length > 0
? String(progress.workUnitId)
: null;
emitProgress({
...progress,
workUnitId: rawWorkUnitId ? `candidate-${candidateOrdinal}/${rawWorkUnitId}` : progress?.workUnitId,
variant: candidate.variant >>> 0,
seed: candidate.seed >>> 0,
}, candidateOrdinal);
},
});
result = normalizeCandidateResult(result);
} catch (error) {
if (transaction) restorePatchTransactionSnapshot(world, transaction);
if (error?.code === "worker-progress-invariant") throw error;
const wallMs = clock() - startedAt;
const failed = {
ok: false,
code: "candidate-execution-error",
reason: error?.message || String(error),
stack: error?.stack || "",
};
attempts.push(summarizeAttempt(failed, candidate, candidateOrdinal, wallMs, "execution-error", executionAttempt));
terminalResult = { ...failed, searchAttempts: attempts, searchStatus: "execution-error", nextVariant: candidate.variant >>> 0 };
break;
}
const wallMs = clock() - startedAt;
if (result?.ok) {
if (transaction) {
try {
successDelta = buildCommittedMirrorDeltaFromTransaction(transaction, candidateWorld);
successTargetHash = hashCommittedWorld(candidateWorld);
} catch (error) {
restorePatchTransactionSnapshot(world, transaction);
const failed = {
ok: false,
code: "candidate-delta-build-failed",
reason: error?.message || String(error),
};
attempts.push(summarizeAttempt(failed, candidate, candidateOrdinal, wallMs, "infrastructure-error", executionAttempt));
terminalResult = { ...failed, searchAttempts: attempts, searchStatus: "infrastructure-error", nextVariant: candidate.variant >>> 0 };
break;
}
restorePatchTransactionSnapshot(world, transaction);
}
attempts.push(summarizeAttempt(result, candidate, candidateOrdinal, wallMs, "success", executionAttempt));
result.searchAttempts = attempts;
result.searchStatus = "succeeded";
result.candidateOrdinal = candidateOrdinal;
result.candidateCount = candidateCount;
result.actualVariant = candidate.variant >>> 0;
result.actualSeed = candidate.seed >>> 0;
result.nextVariant = ((candidate.variant >>> 0) + 1) >>> 0;
if (successDelta?.previewDelta) result.previewDelta = { ...successDelta.previewDelta };
terminalResult = result;
successWorld = transactional ? null : candidateWorld;
emitProgress({ status: "done", key: "search", phase: "search", workUnitId: "candidate-search", label: `Candidate ${candidateOrdinal}/${candidateCount} accepted`, variant: candidate.variant >>> 0, completed: candidateOrdinal, total: candidateCount }, candidateOrdinal);
break;
}
const invariant = isInvariantFailure(result);
const contentRejected = isContentRejection(result);
const status = invariant ? "invariant-breach" : contentRejected ? "rejected" : "failed";
const attemptSummary = summarizeAttempt(result, candidate, candidateOrdinal, wallMs, status, executionAttempt);
attempts.push(attemptSummary);
if (transaction) restorePatchTransactionSnapshot(world, transaction);
emitProgress({
status: contentRejected ? "rejected" : "error",
key: contentRejected ? "candidate-rejected" : "candidate-failed",
phase: "candidate-result",
label: contentRejected
? `Candidate ${candidateOrdinal}/${candidateCount} rejected; searching the next complete candidate`
: `Candidate ${candidateOrdinal}/${candidateCount} stopped: ${result?.reason || result?.code || "failure"}`,
variant: candidate.variant >>> 0,
workUnitId: "candidate-search",
completed: candidateOrdinal,
total: candidateCount,
code: result?.code || null,
attemptSummary,
}, candidateOrdinal);
if (!contentRejected) {
terminalResult = {
...(result || { ok: false }),
searchAttempts: attempts,
searchStatus: invariant ? "invariant-breach" : "failed",
nextVariant: candidate.variant >>> 0,
};
break;
}
// The rejected full world is no longer observable. Drop the last strong
// references before cloning the next Variant so the browser may reclaim
// its field buffers instead of retaining multiple complete candidates.
candidateWorld = null;
transaction = null;
result = null;
}
if (!terminalResult) {
const last = candidatePlan[candidatePlan.length - 1];
terminalResult = {
ok: false,
code: "patch-search-exhausted",
reason: `All ${candidateCount} complete production candidates were rejected.`,
searchStatus: "exhausted",
searchAttempts: attempts,
nextVariant: (((last?.variant || 0) >>> 0) + 1) >>> 0,
candidateCount,
};
emitProgress({ status: "done", key: "search-exhausted", phase: "search", workUnitId: "candidate-search", label: terminalResult.reason, completed: candidateCount, total: candidateCount });
}
if (successWorld && terminalResult?.ok) {
emitProgress({
status: "start",
key: "preview-delta",
phase: "preview-delta",
label: "Auditing preview changes",
}, terminalResult.candidateOrdinal || 0);
terminalResult.previewDelta = computePreviewDelta(world, successWorld, terminalResult.rects || rect, (completed, total, part) => {
emitProgress({
status: "step",
key: `preview-delta:${part}`,
phase: "preview-delta",
label: part === "raster" ? "Auditing preview raster changes" : "Auditing preview feature changes",
workUnitId: "preview-delta-audit",
completed,
total,
}, terminalResult.candidateOrdinal || 0);
});
emitProgress({
status: "done",
key: "preview-delta",
phase: "preview-delta",
label: "Preview change audit complete",
}, terminalResult.candidateOrdinal || 0);
}
// Return only the accepted candidate world. Rejected candidate clones and
// the immutable worker baseline remain worker-local and become collectible.
return {
id,
ok: true,
world: successWorld,
transactionDelta: successDelta,
targetHash: successTargetHash,
result: terminalResult,
searchId,
workerEpoch,
eventSeq,
};
} catch (error) {
return { id, ok: false, code: error?.code || "candidate-search-error", error: error?.message || String(error), stack: error?.stack || "", searchId, workerEpoch, eventSeq };
}
}
export async function runPatchCandidateSearchAsync(message, dependencies = {}) {
const { id, world, rect, options, search } = message || {};
const cloneWorld = dependencies.cloneWorld || ((value) => structuredClone(value));
const generateCandidate = dependencies.generateCandidate || generatePatchAsync;
const clock = dependencies.now || nowMs;
const publishProgress = dependencies.onProgress || (() => {});
const transactional = dependencies.transactional === true;
const candidatePlan = Array.isArray(search?.candidatePlan) && search.candidatePlan.length
? search.candidatePlan
: [{ variant: options?.variant || 0, seed: options?.seed || world?.seed || 0 }];
const candidateCount = Math.max(candidatePlan.length, Number(search?.totalCandidateCount || candidatePlan.length));
const searchId = search?.searchId || `patch-${id}`;
const workerEpoch = Number(search?.workerEpoch || 0);
const executionAttempt = Math.max(1, Number(search?.executionAttempt || 1));
let eventSeq = 0;
let phaseOrdinal = 0;
let currentPhase = null;
const workUnits = new Map();
const emitProgress = (progress = {}, candidateOrdinal = 0) => {
const phase = String(progress.phase || progress.key || "patch");
if (phase !== currentPhase) {
currentPhase = phase;
phaseOrdinal++;
}
const hasCompleted = progress.completed != null;
const hasTotal = progress.total != null;
const completed = Number(progress.completed);
const total = Number(progress.total);
const boundedWork = hasCompleted || hasTotal;
const explicitWorkUnitId = progress.workUnitId != null && String(progress.workUnitId).length > 0;
const workUnitId = String(explicitWorkUnitId ? progress.workUnitId : (progress.key || phase));
if (boundedWork) {
if (!hasCompleted || !hasTotal || !Number.isFinite(completed) || !Number.isFinite(total)) {
const error = new Error(`Incomplete bounded progress for ${phase}/${workUnitId}: ${String(progress.completed)}/${String(progress.total)}`);
error.code = "worker-progress-invariant";
throw error;
}
if (!explicitWorkUnitId) {
const error = new Error(`Bounded progress is missing an explicit workUnitId for ${phase}/${workUnitId}.`);
error.code = "worker-progress-invariant";
throw error;
}
if (completed < 0 || total < 0 || completed > total) {
const error = new Error(`Invalid progress bounds for ${phase}/${workUnitId}: ${completed}/${total}`);
error.code = "worker-progress-invariant";
throw error;
}
// workUnitId is globally unique within one candidate-search operation.
// Do not reset its monotonicity merely because a display phase changed in
// between heartbeats; that would hide real restarts/runaway producers.
const unitKey = workUnitId;
const previous = workUnits.get(unitKey);
if (previous && (total !== previous.total || completed < previous.completed)) {
const error = new Error(`Non-monotonic progress for ${phase}/${workUnitId}: ${completed}/${total} after ${previous.completed}/${previous.total}`);
error.code = "worker-progress-invariant";
throw error;
}
const workerNow = nowMs();
const startedAtWorker = previous?.startedAtWorker ?? workerNow;
const lastAdvancedAtWorker = !previous || completed > previous.completed
? workerNow
: (previous.lastAdvancedAtWorker ?? startedAtWorker);
workUnits.set(unitKey, { completed, total, phase, startedAtWorker, lastAdvancedAtWorker });
}
eventSeq++;
publishProgress({
id,
type: "progress",
progress: {
...progress,
searchId,
operationId: search?.operationId || searchId,
candidateOrdinal,
candidateCount,
executionAttempt,
workerEpoch,
committedRevision: Number(search?.committedRevision || 0),
eventSeq,
counter: eventSeq,
phase,
phaseOrdinal,
workUnitId,
boundedWork,
startedAtWorker: boundedWork ? workUnits.get(workUnitId)?.startedAtWorker : undefined,
lastAdvancedAtWorker: boundedWork ? workUnits.get(workUnitId)?.lastAdvancedAtWorker : undefined,
cooperative: progress.nonCooperative !== true,
},
});
};
try {
const attempts = [];
let terminalResult = null;
let successWorld = null;
let successDelta = null;
let successTargetHash = null;
emitProgress({ status: "start", key: "search", phase: "search", workUnitId: "candidate-search", label: `Searching ${candidateCount} complete candidate${candidateCount === 1 ? "" : "s"}`, completed: 0, total: candidateCount });
for (let index = 0; index < candidatePlan.length; index++) {
const candidate = candidatePlan[index];
const candidateOrdinal = Math.max(1, Number(candidate.candidateOrdinal || index + 1));
emitProgress({
status: "start",
key: transactional ? "candidate-transaction" : "candidate-clone",
phase: transactional ? "candidate-transaction" : "candidate-clone",
label: transactional
? `Candidate ${candidateOrdinal}/${candidateCount}: capturing exact rollback state`
: `Candidate ${candidateOrdinal}/${candidateCount}: preparing immutable baseline`,
variant: candidate.variant >>> 0,
workUnitId: "candidate-search",
// candidateOrdinal is global across the full search, whereas `index` is
// local to this Worker execution. Recovery can restart with candidates
// 2..N after candidate 1 was already rejected, so using `index` here
// made the shared search work unit move backward (2/3 -> 1/3).
completed: Math.max(0, candidateOrdinal - 1),
total: candidateCount,
nonCooperative: true,
}, candidateOrdinal);
let candidateWorld;
let transaction = null;
try {
if (transactional) {
transaction = capturePatchTransactionSnapshot(world, {
lightweight: false,
isolateSourceMap: true,
copyGeneratedMask: String(search?.resolvedPatchMode || "").toLowerCase() !== "regeneration",
});
candidateWorld = world;
} else {
candidateWorld = cloneWorld(world);
}
} catch (error) {
const failed = {
ok: false,
code: transactional ? "candidate-transaction-failed" : "candidate-clone-failed",
reason: error?.message || String(error),
};
attempts.push(summarizeAttempt(failed, candidate, candidateOrdinal, 0, "infrastructure-error", executionAttempt));
terminalResult = {
...failed,
searchAttempts: attempts,
searchStatus: "infrastructure-error",
nextVariant: candidate.variant >>> 0,
};
break;
}
const startedAt = clock();
let result;
try {
result = await generateCandidate(candidateWorld, rect, {
...(options || {}),
seed: candidate.seed >>> 0,
variant: candidate.variant >>> 0,
maxQualityRetries: 0,
// The production Worker mutates its committed mirror transactionally
// and restores it after extracting the accepted delta. Direct callers
// retain the immutable-clone reference path for independent tests.
_workerOwnedPreview: !transactional,
_externalTransactionSnapshot: transaction,
_precomputeRawCandidateBatch: dependencies.precomputeRawCandidateBatch,
_precomputeRawCandidateSequence: dependencies.precomputeRawCandidateSequence,
_rawCandidateParallelism: Math.max(1, Math.min(2, Number(dependencies.rawCandidateParallelism || 2))),
onProgress: (progress) => {
// Producer workUnitIds are invocation-local. The same complete
// pipeline is executed again when a content-rejected candidate
// advances to the next Variant, so namespace every explicit inner
// unit by candidate ordinal before the search-wide monotonicity
// validator sees it. Do not invent an ID for a bounded producer
// that omitted one; emitProgress must still reject that protocol
// violation instead of masking it.
const rawWorkUnitId = progress?.workUnitId != null && String(progress.workUnitId).length > 0
? String(progress.workUnitId)
: null;
emitProgress({
...progress,
workUnitId: rawWorkUnitId ? `candidate-${candidateOrdinal}/${rawWorkUnitId}` : progress?.workUnitId,
variant: candidate.variant >>> 0,
seed: candidate.seed >>> 0,
}, candidateOrdinal);
},
});
result = normalizeCandidateResult(result);
} catch (error) {
if (transaction) restorePatchTransactionSnapshot(world, transaction);
if (error?.code === "worker-progress-invariant") throw error;
const wallMs = clock() - startedAt;
const failed = {
ok: false,
code: "candidate-execution-error",
reason: error?.message || String(error),
stack: error?.stack || "",
};
attempts.push(summarizeAttempt(failed, candidate, candidateOrdinal, wallMs, "execution-error", executionAttempt));
terminalResult = { ...failed, searchAttempts: attempts, searchStatus: "execution-error", nextVariant: candidate.variant >>> 0 };
break;
}
const wallMs = clock() - startedAt;
if (result?.ok) {
if (transaction) {
try {
successDelta = buildCommittedMirrorDeltaFromTransaction(transaction, candidateWorld);
successTargetHash = hashCommittedWorld(candidateWorld);
} catch (error) {
restorePatchTransactionSnapshot(world, transaction);
const failed = {
ok: false,
code: "candidate-delta-build-failed",
reason: error?.message || String(error),
};
attempts.push(summarizeAttempt(failed, candidate, candidateOrdinal, wallMs, "infrastructure-error", executionAttempt));
terminalResult = { ...failed, searchAttempts: attempts, searchStatus: "infrastructure-error", nextVariant: candidate.variant >>> 0 };
break;
}
restorePatchTransactionSnapshot(world, transaction);
}
attempts.push(summarizeAttempt(result, candidate, candidateOrdinal, wallMs, "success", executionAttempt));
result.searchAttempts = attempts;
result.searchStatus = "succeeded";
result.candidateOrdinal = candidateOrdinal;
result.candidateCount = candidateCount;
result.actualVariant = candidate.variant >>> 0;
result.actualSeed = candidate.seed >>> 0;
result.nextVariant = ((candidate.variant >>> 0) + 1) >>> 0;
if (successDelta?.previewDelta) result.previewDelta = { ...successDelta.previewDelta };
terminalResult = result;
successWorld = transactional ? null : candidateWorld;
emitProgress({ status: "done", key: "search", phase: "search", workUnitId: "candidate-search", label: `Candidate ${candidateOrdinal}/${candidateCount} accepted`, variant: candidate.variant >>> 0, completed: candidateOrdinal, total: candidateCount }, candidateOrdinal);
break;
}
const invariant = isInvariantFailure(result);
const contentRejected = isContentRejection(result);
const status = invariant ? "invariant-breach" : contentRejected ? "rejected" : "failed";
const attemptSummary = summarizeAttempt(result, candidate, candidateOrdinal, wallMs, status, executionAttempt);
attempts.push(attemptSummary);
if (transaction) restorePatchTransactionSnapshot(world, transaction);
emitProgress({
status: contentRejected ? "rejected" : "error",
key: contentRejected ? "candidate-rejected" : "candidate-failed",
phase: "candidate-result",
label: contentRejected
? `Candidate ${candidateOrdinal}/${candidateCount} rejected; searching the next complete candidate`
: `Candidate ${candidateOrdinal}/${candidateCount} stopped: ${result?.reason || result?.code || "failure"}`,
variant: candidate.variant >>> 0,
workUnitId: "candidate-search",
completed: candidateOrdinal,
total: candidateCount,
code: result?.code || null,
attemptSummary,
}, candidateOrdinal);
if (!contentRejected) {
terminalResult = {
...(result || { ok: false }),
searchAttempts: attempts,
searchStatus: invariant ? "invariant-breach" : "failed",
nextVariant: candidate.variant >>> 0,
};
break;
}
// The rejected full world is no longer observable. Drop the last strong
// references before cloning the next Variant so the browser may reclaim
// its field buffers instead of retaining multiple complete candidates.
candidateWorld = null;
transaction = null;
result = null;
}
if (!terminalResult) {
const last = candidatePlan[candidatePlan.length - 1];
terminalResult = {
ok: false,
code: "patch-search-exhausted",
reason: `All ${candidateCount} complete production candidates were rejected.`,
searchStatus: "exhausted",
searchAttempts: attempts,
nextVariant: (((last?.variant || 0) >>> 0) + 1) >>> 0,
candidateCount,
};
emitProgress({ status: "done", key: "search-exhausted", phase: "search", workUnitId: "candidate-search", label: terminalResult.reason, completed: candidateCount, total: candidateCount });
}
if (successWorld && terminalResult?.ok) {
emitProgress({
status: "start",
key: "preview-delta",
phase: "preview-delta",
label: "Auditing preview changes",
}, terminalResult.candidateOrdinal || 0);
terminalResult.previewDelta = computePreviewDelta(world, successWorld, terminalResult.rects || rect, (completed, total, part) => {
emitProgress({
status: "step",
key: `preview-delta:${part}`,
phase: "preview-delta",
label: part === "raster" ? "Auditing preview raster changes" : "Auditing preview feature changes",
workUnitId: "preview-delta-audit",
completed,
total,
}, terminalResult.candidateOrdinal || 0);
});
emitProgress({
status: "done",
key: "preview-delta",
phase: "preview-delta",
label: "Preview change audit complete",
}, terminalResult.candidateOrdinal || 0);
}
// Return only the accepted candidate world. Rejected candidate clones and
// the immutable worker baseline remain worker-local and become collectible.
return {
id,
ok: true,
world: successWorld,
transactionDelta: successDelta,
targetHash: successTargetHash,
result: terminalResult,
searchId,
workerEpoch,
eventSeq,
};
} catch (error) {
return { id, ok: false, code: error?.code || "candidate-search-error", error: error?.message || String(error), stack: error?.stack || "", searchId, workerEpoch, eventSeq };
}
}
if (typeof self !== "undefined") {
self.onmessage = async (event) => {
const incoming = event.data || {};
if (handleMirrorSyncMessage(incoming)) return;
if (incoming.type === "patch-apply-discard") {
if (incoming.applyToken) pendingApplyDeltas.delete(incoming.applyToken);
return;
}
if (incoming.type === "patch-apply-ack") {
const pending = pendingApplyDeltas.get(incoming.applyToken);
let ok = false;
let error = null;
try {
if (!pending) throw new Error("Pending patch delta is unavailable.");
if (persistentCommittedRevision !== Number(incoming.baseCommittedRevision)) {
throw new Error(`Committed mirror revision conflict (${persistentCommittedRevision} != ${incoming.baseCommittedRevision}).`);
}
applyCommittedWorldDelta(persistentCommittedMirror, pending.delta, { consumeMetadata: true });
const mirrorHash = hashCommittedWorld(persistentCommittedMirror);
if (mirrorHash !== pending.targetHash) {
throw new Error(`Committed mirror hash mismatch (${mirrorHash} != ${pending.targetHash}).`);
}
persistentCommittedRevision = Number(incoming.committedRevision);
pendingApplyDeltas.delete(incoming.applyToken);
ok = true;
} catch (applyError) {
error = applyError?.message || String(applyError);
persistentCommittedMirror = null;
persistentCommittedRevision = -1;
pendingApplyDeltas.clear();
}
self.postMessage({
type: "patch-apply-ack-result",
ackId: incoming.ackId,
applyToken: incoming.applyToken,
ok,
error,
mirrorHash: ok ? pending?.targetHash || null : null,
mirrorCommittedRevision: persistentCommittedRevision,
});
return;
}
const requestedRevision = Number(incoming.search?.committedRevision ?? -1);
if (incoming.world) {
persistentCommittedMirror = incoming.world;
persistentCommittedRevision = requestedRevision;
pendingMirrorBootstrap = null;
pendingApplyDeltas.clear();
} else if (!incoming.search?.reuseCommittedMirror || !persistentCommittedMirror || persistentCommittedRevision !== requestedRevision) {
self.postMessage({
id: incoming.id,
ok: false,
error: "Committed worker mirror is unavailable or stale.",
code: "worker-mirror-stale",
searchId: incoming.search?.searchId || null,
workerEpoch: Number(incoming.search?.workerEpoch || 0),
});
return;
}
const payload = await runPatchCandidateSearchAsync({ ...incoming, world: persistentCommittedMirror }, {
onProgress: (message) => self.postMessage(message),
transactional: true,
precomputeRawCandidateBatch,
precomputeRawCandidateSequence: scheduleRawCandidateSequence,
rawCandidateParallelism: 2,
});
// Helper workers are scoped to one candidate-search operation. Releasing
// them here prevents a completed/rejected large search from retaining two
// full generator heaps while the coordinator waits for Apply/Alternative.
await shutdownRawCandidateWorkers();
payload.mirrorCommittedRevision = persistentCommittedRevision;
if (payload.ok && payload.result?.ok && (payload.transactionDelta || payload.world)) {
payload.eventSeq = Number(payload.eventSeq || 0) + 1;
self.postMessage({
id: incoming.id,
type: "progress",
progress: {
searchId: payload.searchId,
operationId: incoming.search?.operationId || payload.searchId,
candidateOrdinal: Number(payload.result?.candidateOrdinal || 0),
candidateCount: Number(payload.result?.candidateCount || incoming.search?.totalCandidateCount || 0),
executionAttempt: Math.max(1, Number(incoming.search?.executionAttempt || 1)),
workerEpoch: payload.workerEpoch,
committedRevision: requestedRevision,
eventSeq: payload.eventSeq,
counter: payload.eventSeq,
phase: "mirror-delta-build",
phaseOrdinal: Number.MAX_SAFE_INTEGER - 1,
workUnitId: "mirror-delta-build",
boundedWork: false,
cooperative: false,
nonCooperative: true,
status: "start",
key: "mirror-delta-build",
label: "Building transactional Apply delta",
},
});
const applyToken = `${payload.searchId}:${payload.result.candidateOrdinal || 0}:${payload.result.actualVariant ?? payload.result.variant ?? 0}:${requestedRevision}`;
try {
const delta = payload.transactionDelta || buildCommittedMirrorDelta(persistentCommittedMirror, payload.world);
const targetHash = payload.targetHash || hashCommittedWorld(payload.world);
const mainThreadDelta = buildMainThreadTransferDelta(delta);
pendingApplyDeltas.clear();
pendingApplyDeltas.set(applyToken, { delta, baseCommittedRevision: requestedRevision, targetHash });
// The main thread needs one transferable copy to materialize the
// preview. Keep the original delta Worker-local for the later Apply ACK.
payload.worldDelta = mainThreadDelta;
payload.transactionDelta = null;
payload.targetHash = null;
payload.world = null;
payload.result.applyToken = applyToken;
payload.result.acceptedWorldHash = targetHash;
} catch (deltaError) {
pendingApplyDeltas.delete(applyToken);
payload.transactionDelta = null;
payload.targetHash = null;
payload.world = null;
payload.result = {
...payload.result,
ok: false,
code: "candidate-delta-build-failed",
reason: deltaError?.message || String(deltaError),
searchStatus: "infrastructure-error",
applyToken: null,
applyDeltaError: deltaError?.message || String(deltaError),
};
}
}
if (payload.ok) {
payload.eventSeq = Number(payload.eventSeq || 0) + 1;
self.postMessage({
id: incoming.id,
type: "progress",
progress: {
searchId: payload.searchId,
operationId: incoming.search?.operationId || payload.searchId,
candidateOrdinal: Number(payload.result?.candidateOrdinal || 0),
candidateCount: Number(payload.result?.candidateCount || incoming.search?.totalCandidateCount || 0),
executionAttempt: Math.max(1, Number(incoming.search?.executionAttempt || 1)),
workerEpoch: payload.workerEpoch,
committedRevision: requestedRevision,
eventSeq: payload.eventSeq,
counter: payload.eventSeq,
phase: "result-serialization",
phaseOrdinal: Number.MAX_SAFE_INTEGER,
workUnitId: "result-serialization",
boundedWork: false,
cooperative: false,
nonCooperative: true,
status: "start",
key: "result-serialization",
label: "Serializing accepted patch result",
},
});
}
// Accepted transactional results keep all large typed payloads under the
// world delta. Traversing the complete diagnostic/result graph here caused
// a long no-progress pause at the final stage without finding additional
// buffers. Legacy full-world payloads still transfer their world buffers.
// Only the copied raster delta buffers are transferable. Metadata in
// worldDelta deliberately shares the Worker-retained ACK graph until
// postMessage clones it; transferring a nested typed metadata buffer would
// detach the retained delta and make Apply fail.
const transferRoot = payload.worldDelta
? { fields: payload.worldDelta.fields, generatedMask: payload.worldDelta.generatedMask }
: (payload.world || null);
const transfer = payload.ok && transferRoot ? Array.from(collectTransferableBuffers(transferRoot)) : [];
self.postMessage(payload, transfer);
};
}