q
This commit is contained in:
parent
810ad6f5cb
commit
de7c6c32bd
123 changed files with 13201 additions and 6213 deletions
139
src/app.js
139
src/app.js
|
|
@ -87,7 +87,6 @@ const showLabelsInput = document.getElementById("showLabels");
|
|||
const showSeamDiagnosticsInput = document.getElementById("showSeamDiagnostics");
|
||||
const modeGrid = document.getElementById("modeGrid");
|
||||
const mainLegendGrid = document.getElementById("mainLegendGrid");
|
||||
const floatingLegendGrid = document.getElementById("floatingLegendGrid");
|
||||
const statsEl = document.getElementById("stats");
|
||||
const advancedGenerationStatsEl = document.getElementById("advancedGenerationStats");
|
||||
const advancedGenerationHistoryEl = document.getElementById("advancedGenerationHistory");
|
||||
|
|
@ -128,8 +127,9 @@ let patchGeometryPreviewCache = null;
|
|||
let patchWorkerEpoch = 0;
|
||||
let patchWorkerConstructorCount = 0;
|
||||
let patchSearchSeries = { contextId: null, consumedCandidateIds: new Set(), nextVariant: null };
|
||||
const PATCH_SEARCH_DEFAULT_LIMIT = 3;
|
||||
const PATCH_SEARCH_LARGE_LIMIT = 2;
|
||||
const PATCH_SEARCH_BATCH_SIZE = 3;
|
||||
const PATCH_SEARCH_DEFAULT_LIMIT = 12;
|
||||
const PATCH_SEARCH_LARGE_LIMIT = 12;
|
||||
const PATCH_WORKER_STALL_MS = 120_000;
|
||||
const PATCH_NON_COOPERATIVE_DEADLINE_MS = 300_000;
|
||||
let generationWorker = null;
|
||||
|
|
@ -718,6 +718,14 @@ function commitPendingPatch({ redrawAfter = true } = {}) {
|
|||
const acceptedPatch = state.pendingPatch;
|
||||
const applyWorker = acceptedPatch.worker && acceptedPatch.result?.applyToken ? patchWorker : null;
|
||||
state.world = acceptedPatch.world;
|
||||
// Seam diagnostics are a preview aid. Once the patch is applied, disable the
|
||||
// magenta/red dashed overlay in both the committed source data and UI state so
|
||||
// it cannot remain stuck on the map after the selection overlay disappears.
|
||||
if (state.world?.sourceMap?.patchSeamDiagnostics) {
|
||||
state.world.sourceMap.patchSeamDiagnostics.enabled = false;
|
||||
}
|
||||
state.showSeamDiagnostics = false;
|
||||
if (showSeamDiagnosticsInput) showSeamDiagnosticsInput.checked = false;
|
||||
const committedRevision = advanceCommittedRevision({ preservePatchWorker: !!applyWorker });
|
||||
state.map = state.world.sourceMap || state.map;
|
||||
state.lastPatchResult = state.pendingPatch.result || state.world.lastPatchResult || state.lastPatchResult;
|
||||
|
|
@ -1236,7 +1244,7 @@ function seamDiagnosticRows() {
|
|||
{ label: "Candidate mode", value: d.patchGenerationMode || "-", sub: modeSub },
|
||||
{ label: "Candidate window", value: `${Number(d.candidateWidth || 0)}×${Number(d.candidateHeight || 0)}`, sub: `${formatPercent(Number(d.candidateAreaRatio || 0) * 100)} of full candidate area` },
|
||||
...(d.qualityPolicyVersion ? [
|
||||
{ label: "Expansion quality gate", value: d.qualityHardPass ? "PASS" : "BEST AVAILABLE", sub: `${d.qualityPolicyVersion} · score ${Number(d.qualityScore || 0).toFixed(3)} · selected variant ${Number(d.qualitySelectedVariant || 0)}` },
|
||||
{ label: "Expansion quality gate", value: d.qualityHardPass ? "PASS" : "FAIL", sub: `${d.qualityPolicyVersion} · score ${Number(d.qualityScore || 0).toFixed(3)} · selected variant ${Number(d.qualitySelectedVariant || 0)}` },
|
||||
{ label: "Candidate land quality", value: formatPercent(Number(d.qualityLandRatio || 0) * 100), sub: `${d.qualityTerrainType || "-"} · ${formatPercent(Number(d.qualityDevelopableRatio || 0) * 100)} developable · ${formatPercent(Number(d.qualityLargestComponentRatio || 0) * 100)} largest component` },
|
||||
{ label: "Candidate place density", value: Number(d.qualityLabelCount || 0).toLocaleString(), sub: `${Number(d.qualitySettlementCount || 0)} settlements · ${Number(d.qualityLabelDensityPer1000 || 0).toFixed(2)} labels / 1000 land cells` },
|
||||
{ label: "Merged patch quality", value: d.qualityFinalHardPass ? "PASS" : "WARNING", sub: `${formatPercent(Number(d.qualityFinalOwnedLandRatio || 0) * 100)} land in owned interior · ${Number(d.qualityFinalLabelCount || 0)} labels / ${Number(d.qualityFinalSettlementCount || 0)} settlements` },
|
||||
|
|
@ -2036,7 +2044,6 @@ function renderLegendGrid(container, rows) {
|
|||
function renderLegend() {
|
||||
const rows = legendRowsForMode(state.mode);
|
||||
renderLegendGrid(mainLegendGrid, rows);
|
||||
renderLegendGrid(floatingLegendGrid, rows.slice(0, 5));
|
||||
}
|
||||
|
||||
function buildHoverEntities(map) {
|
||||
|
|
@ -2246,13 +2253,49 @@ function updateTooltip(event) {
|
|||
if (entity?.population) lines.splice(1, 0, `Population: ${entity.population.toLocaleString()}`);
|
||||
tooltipEl.innerHTML = lines.join("<br>");
|
||||
const margin = 8;
|
||||
const offset = 14;
|
||||
const maxLeft = Math.max(margin, rect.width - tooltipEl.offsetWidth - margin);
|
||||
const maxTop = Math.max(margin, rect.height - tooltipEl.offsetHeight - margin);
|
||||
const desiredLeft = event.clientX - rect.left + offset;
|
||||
const desiredTop = event.clientY - rect.top + offset;
|
||||
tooltipEl.style.left = `${Math.min(Math.max(margin, desiredLeft), maxLeft)}px`;
|
||||
tooltipEl.style.top = `${Math.min(Math.max(margin, desiredTop), maxTop)}px`;
|
||||
const gap = 18;
|
||||
const cursorX = event.clientX - rect.left;
|
||||
const cursorY = event.clientY - rect.top;
|
||||
const tooltipWidth = tooltipEl.offsetWidth;
|
||||
const tooltipHeight = tooltipEl.offsetHeight;
|
||||
const maxLeft = Math.max(margin, rect.width - tooltipWidth - margin);
|
||||
const maxTop = Math.max(margin, rect.height - tooltipHeight - margin);
|
||||
|
||||
// Keep following the cursor all the way to the bottom. Near the lower edge,
|
||||
// prefer moving to the cursor's left/right while pinning the tooltip to the
|
||||
// bottom margin; the old vertical flip made it appear to stop moving well
|
||||
// before the cursor reached the bottom of the map.
|
||||
const roomRight = rect.width - cursorX - gap;
|
||||
const roomLeft = cursorX - gap;
|
||||
const canRight = roomRight >= tooltipWidth;
|
||||
const canLeft = roomLeft >= tooltipWidth;
|
||||
let left = canRight ? cursorX + gap
|
||||
: canLeft ? cursorX - gap - tooltipWidth
|
||||
: Math.min(Math.max(margin, cursorX + gap), maxLeft);
|
||||
let top = Math.min(Math.max(margin, cursorY + 10), maxTop);
|
||||
left = Math.min(Math.max(margin, left), maxLeft);
|
||||
|
||||
// Cursor exclusion is a hard invariant. If horizontal separation is not
|
||||
// available (very narrow viewport), then and only then move vertically.
|
||||
const exclusion = 12;
|
||||
const overlapsCursor = () => cursorX >= left - exclusion && cursorX <= left + tooltipWidth + exclusion
|
||||
&& cursorY >= top - exclusion && cursorY <= top + tooltipHeight + exclusion;
|
||||
if (overlapsCursor()) {
|
||||
const leftAlt = cursorX - gap - tooltipWidth;
|
||||
const rightAlt = cursorX + gap;
|
||||
if (leftAlt >= margin) left = leftAlt;
|
||||
else if (rightAlt + tooltipWidth <= rect.width - margin) left = rightAlt;
|
||||
}
|
||||
if (overlapsCursor()) {
|
||||
const above = cursorY - gap - tooltipHeight;
|
||||
const below = cursorY + gap;
|
||||
if (above >= margin) top = above;
|
||||
else if (below + tooltipHeight <= rect.height - margin) top = below;
|
||||
}
|
||||
left = Math.min(Math.max(margin, left), maxLeft);
|
||||
top = Math.min(Math.max(margin, top), maxTop);
|
||||
tooltipEl.style.left = `${left}px`;
|
||||
tooltipEl.style.top = `${top}px`;
|
||||
tooltipEl.classList.add("visible");
|
||||
}
|
||||
|
||||
|
|
@ -2443,7 +2486,7 @@ function patchSearchContextId(world, rect, terrainType, requestedPatchMode, reso
|
|||
world?.height || 0,
|
||||
world?.originX || 0,
|
||||
world?.originY || 0,
|
||||
"production-search-v2|single-explicit-production-candidate-v2",
|
||||
"quality-batched-production-search-v4|initial-quality-oracle-transport-parity-v3",
|
||||
].join("|");
|
||||
}
|
||||
|
||||
|
|
@ -2489,7 +2532,7 @@ function buildPatchCandidatePlan(world, rect, terrainType, requestedPatchMode, r
|
|||
function consumePatchSearchAttempts(contextId, attempts = []) {
|
||||
if (patchSearchSeries.contextId !== contextId) return;
|
||||
for (const attempt of attempts) {
|
||||
if (attempt?.status !== "rejected" && attempt?.status !== "success") continue;
|
||||
if (attempt?.status !== "rejected" && attempt?.status !== "evaluated" && attempt?.status !== "success") continue;
|
||||
const candidateId = `${contextId}|${normalizePatchVariant(attempt.variant)}|${normalizePatchVariant(attempt.seed)}`;
|
||||
patchSearchSeries.consumedCandidateIds.add(candidateId);
|
||||
}
|
||||
|
|
@ -3059,6 +3102,8 @@ function runPatchInWorker(world, rect, options, operation = null) {
|
|||
totalCandidateCount: operation.candidateLimit || operation.candidatePlan.length,
|
||||
reuseCommittedMirror,
|
||||
resolvedPatchMode: operation.resolvedPatchMode,
|
||||
selectBestCandidate: operation.selectBestCandidate === true,
|
||||
draftSelection: operation.draftSelection === true,
|
||||
candidatePlan: operation.candidatePlan,
|
||||
} : null,
|
||||
});
|
||||
|
|
@ -3199,6 +3244,41 @@ async function generatePatchPreviewWorld(baseWorld, rect, options, operation) {
|
|||
operation.executionAttempt = infrastructureRetries + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// r9 quality search is batched: only three drafts are evaluated at a time.
|
||||
// A batch that contains no publishable, fully finalized candidate advances
|
||||
// to the next three variants without ever exposing a draft or a quality-
|
||||
// rejected production result. Structural/invariant failures remain terminal.
|
||||
const contentBatchExhausted = execution.result?.searchStatus === "exhausted"
|
||||
&& ["patch-search-exhausted", "patch-draft-search-exhausted"].includes(String(execution.result?.code || ""));
|
||||
const allCandidatePlan = Array.isArray(operation.allCandidatePlan) ? operation.allCandidatePlan : operation.candidatePlan;
|
||||
const nextCandidateIndex = Math.max(0, Number(operation.nextCandidateIndex || 0));
|
||||
if (contentBatchExhausted && nextCandidateIndex < allCandidatePlan.length) {
|
||||
operation.completedAttemptSummaries ||= [];
|
||||
for (const attempt of execution.result?.searchAttempts || []) {
|
||||
const identity = `${attempt?.candidateId || `${attempt?.variant}:${attempt?.seed}`}|${attempt?.status || "unknown"}`;
|
||||
if (!operation.completedAttemptSummaries.some((entry) => `${entry?.candidateId || `${entry?.variant}:${entry?.seed}`}|${entry?.status || "unknown"}` === identity)) {
|
||||
operation.completedAttemptSummaries.push({ ...attempt });
|
||||
}
|
||||
}
|
||||
const batchSize = Math.max(1, Number(operation.candidateBatchSize || PATCH_SEARCH_BATCH_SIZE));
|
||||
const batchEnd = Math.min(allCandidatePlan.length, nextCandidateIndex + batchSize);
|
||||
operation.candidatePlan = allCandidatePlan.slice(nextCandidateIndex, batchEnd);
|
||||
operation.nextCandidateIndex = batchEnd;
|
||||
operation.candidateBatchOrdinal = Math.max(1, Number(operation.candidateBatchOrdinal || 1)) + 1;
|
||||
operation.executionAttempt = Math.max(1, Number(operation.executionAttempt || 1)) + 1;
|
||||
if (progressStageEl) {
|
||||
const firstOrdinal = operation.candidatePlan[0]?.candidateOrdinal || (nextCandidateIndex + 1);
|
||||
const lastOrdinal = operation.candidatePlan[operation.candidatePlan.length - 1]?.candidateOrdinal || batchEnd;
|
||||
progressStageEl.textContent = `Quality batch ${operation.candidateBatchOrdinal}: candidates ${firstOrdinal}-${lastOrdinal}/${allCandidatePlan.length}; previous batch did not meet the production quality floor...`;
|
||||
}
|
||||
recordDiagnosticLog("info", "Patch quality search continuing", `No publishable candidate in quality batch ${operation.candidateBatchOrdinal - 1}; evaluating the next batch.`, {
|
||||
completedCandidates: nextCandidateIndex,
|
||||
remainingCandidates: allCandidatePlan.length - nextCandidateIndex,
|
||||
nextVariants: operation.candidatePlan.map((candidate) => candidate.variant >>> 0),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
job = execution;
|
||||
} catch (error) {
|
||||
executionRecord.endedAt = performance.now();
|
||||
|
|
@ -3311,8 +3391,10 @@ async function generateSelectedPatch(kind = "Patch preview") {
|
|||
patchMode,
|
||||
resolvedPatchMode,
|
||||
candidateWindowSignature,
|
||||
generatorPolicyVersion: "production-search-v2",
|
||||
qualityPolicyVersion: "single-explicit-production-candidate-v2",
|
||||
generatorPolicyVersion: "quality-batched-production-search-v4",
|
||||
qualityPolicyVersion: "initial-quality-oracle-transport-parity-v3",
|
||||
selectBestCandidate: true,
|
||||
draftSelection: true,
|
||||
requestedVariant: variant,
|
||||
requestedSeed: seed,
|
||||
currentVariant: variant,
|
||||
|
|
@ -3322,7 +3404,11 @@ async function generateSelectedPatch(kind = "Patch preview") {
|
|||
completedAttemptSummaries: [],
|
||||
executions: [],
|
||||
candidateLimit: searchPlan.candidateLimit,
|
||||
candidatePlan: searchPlan.plan,
|
||||
candidateBatchSize: PATCH_SEARCH_BATCH_SIZE,
|
||||
allCandidatePlan: searchPlan.plan,
|
||||
candidatePlan: searchPlan.plan.slice(0, PATCH_SEARCH_BATCH_SIZE),
|
||||
nextCandidateIndex: Math.min(PATCH_SEARCH_BATCH_SIZE, searchPlan.plan.length),
|
||||
candidateBatchOrdinal: 1,
|
||||
estimatedTileCount: searchPlan.estimatedTileCount,
|
||||
includeSeamVisualization: state.showSeamDiagnostics,
|
||||
selectionRect: validation.rect,
|
||||
|
|
@ -3333,7 +3419,7 @@ async function generateSelectedPatch(kind = "Patch preview") {
|
|||
state.patchBusyVariant = variant;
|
||||
state.patchStatusMessage = "";
|
||||
updatePatchControls();
|
||||
setProgressVisible(true, `Searching up to ${searchPlan.plan.length} complete candidate${searchPlan.plan.length === 1 ? "" : "s"} from variant ${variant}...`);
|
||||
setProgressVisible(true, `Generating quality batch 1 (${operation.candidatePlan.length} lightweight drafts, up to ${searchPlan.plan.length} candidates); only fully finalized candidates can be previewed...`);
|
||||
await nextFrame();
|
||||
try {
|
||||
if (!isPatchOperationCurrent(operation)) return;
|
||||
|
|
@ -3342,12 +3428,10 @@ async function generateSelectedPatch(kind = "Patch preview") {
|
|||
patchMode,
|
||||
seed,
|
||||
variant,
|
||||
// Expansion previews use the same complete production pipeline as initial
|
||||
// generation. A quality rejection is shown for this exact variant; only
|
||||
// the explicit Alternative action requests another complete candidate.
|
||||
// Three lightweight drafts are ranked first. Only the top draft normally
|
||||
// runs administration, final transport, merge repair, seam audit, delta,
|
||||
// and hash construction. Structural/invariant failures remain fatal.
|
||||
maxQualityRetries: 0,
|
||||
// One attempt still runs the complete production pipeline. Additional
|
||||
// complete candidates are generated only if the strict final gate fails.
|
||||
qualityTerrainAttempts: 1,
|
||||
acceptBestAvailableQuality: false,
|
||||
includeSeamVisualization: operation.includeSeamVisualization,
|
||||
|
|
@ -3423,20 +3507,17 @@ async function generateSelectedPatch(kind = "Patch preview") {
|
|||
inputDispatchMs: job.dispatchMs, inputMirrorReused: job.mirrorReused, estimatedTileCount: operation.estimatedTileCount,
|
||||
});
|
||||
state.patchStatusMessage = !previewDelta.identical
|
||||
? `Candidate ${result.candidateOrdinal || 1}/${result.candidateCount || searchPlan.plan.length}, variant ${actualVariant}, is displayed (render revision ${job.world.renderRevision}); ${previewDelta.changedCells.toLocaleString()} cells and ${previewDelta.featureLayersChanged.toLocaleString()} feature layers differ from the committed map.`
|
||||
? `Highest-quality candidate ${result.candidateOrdinal || 1}/${result.candidateCount || searchPlan.plan.length}, variant ${actualVariant}, is displayed (render revision ${job.world.renderRevision}); ${previewDelta.changedCells.toLocaleString()} cells and ${previewDelta.featureLayersChanged.toLocaleString()} feature layers differ from the committed map.`
|
||||
: `Variant ${actualVariant} completed but is identical to the committed map in the audited change scope.`;
|
||||
updatePatchControls();
|
||||
const modeText = `${result.patchMode || patchMode} / ${result.patchGenerationMode}`;
|
||||
const quality = result.candidateQuality;
|
||||
const qualityText = quality
|
||||
? ` / quality ${quality.hardPass ? "PASS" : "best available"} ${Number(quality.score || 0).toFixed(3)} / selected terrain variant ${Number(quality.selectedVariant || 0)}`
|
||||
? ` / quality ${quality.hardPass ? "PASS" : "FAIL"} ${Number(quality.score || 0).toFixed(3)} / selected terrain variant ${Number(quality.selectedVariant || 0)}`
|
||||
: "";
|
||||
const retryText = job.qualityWorkerRetryCount ? ` / quality retries ${job.qualityWorkerRetryCount}` : "";
|
||||
const searchText = ` / candidate ${result.candidateOrdinal || 1}/${result.candidateCount || searchPlan.plan.length} / complete attempts ${searchAttempts.length || 1}`;
|
||||
const searchText = ` / best candidate ${result.candidateOrdinal || 1}/${result.candidateCount || searchPlan.plan.length} / evaluated ${searchAttempts.length || 1}`;
|
||||
finishProgress(`Preview variant ${actualVariant} displayed${job.worker ? " from worker" : ""}: ${previewDelta.changedCells.toLocaleString()} changed cells + ${previewDelta.featureLayersChanged.toLocaleString()} changed feature layers / end-to-end ${formatMs(performance.now() - patchStartedAt)} / dispatch ${formatMs(job.dispatchMs || 0)} / render ${formatMs(renderMs)}${searchText}${retryText}${qualityText} / ${modeText}.`, result.patchTimings || [], 1400);
|
||||
if (result.candidateQuality && !result.candidateQuality.hardPass) {
|
||||
recordDiagnosticLog("warning", "Expansion quality floor not fully met", `Selected the best available production candidate (score ${Number(result.candidateQuality.score || 0).toFixed(3)}).`, { terrainType, variant: actualVariant, requestedVariant: variant, candidateQuality: result.candidateQuality });
|
||||
}
|
||||
} catch (error) {
|
||||
if (error?.name === "AbortError") {
|
||||
if (requestId === patchRequestSeq) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue