import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { Worker } from 'node:worker_threads'; const HERE = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.resolve(HERE, '..'); function runWorker(seed) { return new Promise((resolve, reject) => { const worker = new Worker(new URL('./helpers-generation-worker-node-wrapper.mjs', import.meta.url), { type: 'module' }); const timer = setTimeout(() => { worker.terminate(); reject(new Error(`worker timeout seed ${seed}`)); }, 180000); worker.on('error', (error) => { clearTimeout(timer); reject(error); }); worker.on('message', async (message) => { if (message?.type !== 'result' || message.id !== seed) return; clearTimeout(timer); worker.removeAllListeners(); await worker.terminate(); if (!message.ok) reject(new Error(message.error || 'generation failed')); else resolve(message.map); }); worker.postMessage({ id: seed, seed, options: { terrainType: 'auto' } }); }); } function pathLength(paths) { let total = 0; for (const p of paths || []) for (let k = 1; k < (p?.length || 0); k++) total += Math.hypot(p[k][0] - p[k - 1][0], p[k][1] - p[k - 1][1]); return total; } function maxVertexGap(paths) { let max = 0; for (const p of paths || []) for (let k = 1; k < (p?.length || 0); k++) max = Math.max(max, Math.hypot(p[k][0] - p[k - 1][0], p[k][1] - p[k - 1][1])); return max; } function pointPathDistance(point, paths) { let best = Infinity; for (const p of paths || []) for (const q of p || []) best = Math.min(best, Math.hypot(point.x - q[0], point.y - q[1])); return best; } function tangent(p, k) { const a = p[Math.max(0, k - 2)], b = p[Math.min(p.length - 1, k + 2)]; const dx = b[0] - a[0], dy = b[1] - a[1], d = Math.hypot(dx, dy) || 1; return [dx / d, dy / d]; } function longestParallelRun(paths, radius) { let worst = 0; for (let a = 0; a < (paths?.length || 0); a++) for (let b = a + 1; b < paths.length; b++) { let run = 0; for (let k = 0; k < paths[a].length; k += 2) { const p = paths[a][k], t = tangent(paths[a], k); let parallel = false; for (let q = 0; q < paths[b].length; q += 2) { const z = paths[b][q], dx = p[0] - z[0], dy = p[1] - z[1], d2 = dx * dx + dy * dy; if (d2 < 0.75 || d2 > radius * radius) continue; const u = tangent(paths[b], q); if (Math.abs(t[0] * u[0] + t[1] * u[1]) >= 0.90) { parallel = true; break; } } run = parallel ? run + 1 : 0; worst = Math.max(worst, run); } } return worst; } function nearMissEndpointCount(map, source, target, maxGap) { let nearMiss = 0, checked = 0; for (const p of source || []) { if (!p?.length) continue; for (const raw of [p[0], p[p.length - 1]]) { const point = { x: raw[0], y: raw[1] }; if (point.x <= 1 || point.y <= 1 || point.x >= map.width - 2 || point.y >= map.height - 2) continue; checked++; let best = Infinity; for (const q of target || []) { if (!q || q === p) continue; best = Math.min(best, pointPathDistance(point, [q])); } if (best > 0.75 && best <= maxGap) nearMiss++; } } return { nearMiss, checked, rate: nearMiss / Math.max(1, checked) }; } function transportTerrainAudit(map, paths) { let seaCells = 0, highElevationCells = 0, extremeSlopeCells = 0, mountainRidgeCells = 0; for (const p of paths || []) for (const raw of p || []) { const x = Math.round(raw[0]), y = Math.round(raw[1]); if (x < 0 || y < 0 || x >= map.width || y >= map.height) { seaCells++; continue; } const i = y * map.width + x; if (map.sea?.[i]) seaCells++; const e = map.elevation?.[i] || 0, s = map.slope?.[i] || 0, r = map.ridgeField?.[i] || 0; if (e >= 0.695) highElevationCells++; if (s >= 0.60) extremeSlopeCells++; if (e >= 0.58 && r >= 0.72) mountainRidgeCells++; } return { seaCells, highElevationCells, extremeSlopeCells, mountainRidgeCells }; } function expresswayDeadEnds(map) { const main = map.expressways || [], external = map.externalExpressways || []; const bad = []; for (let i = 0; i < main.length; i++) { const path = main[i]; if (!path?.length) continue; for (const raw of [path[0], path[path.length - 1]]) { const p = { x: raw[0], y: raw[1] }; if (p.x <= 2 || p.y <= 2 || p.x >= map.width - 3 || p.y >= map.height - 3) continue; const joined = pointPathDistance(p, [...main.filter((_, j) => j !== i), ...external]) <= 3.5; const ic = (map.interchanges || []).some((q) => Math.hypot(q.x - p.x, q.y - p.y) <= 9.0); const city = (map.modernCities || []).some((q) => (q.population || 0) >= 50000 && Math.hypot(q.x - p.x, q.y - p.y) <= 28); const port = (map.ports || []).some((q) => Math.hypot(q.x - p.x, q.y - p.y) <= 18); if (!(joined || (ic && (city || port)))) bad.push({ x: p.x, y: p.y, joined, ic, city, port }); } } return bad; } for (const seed of [1, 2]) { const map = await runWorker(seed); const national = map.nationalRoads || []; const expressway = map.expressways || []; const rail = [...(map.railways || []), ...(map.branchRailways || [])]; const local = map.minorRoads || []; const trunk = [...national, ...expressway, ...rail]; assert(maxVertexGap(trunk) <= Math.SQRT2 + 1e-6, `seed ${seed}: all emitted trunk geometry is raster-contiguous`); const terrain = transportTerrainAudit(map, trunk); assert.equal(terrain.seaCells, 0, `seed ${seed}: no published trunk cell crosses sea`); assert.equal(terrain.highElevationCells, 0, `seed ${seed}: no published trunk cell crosses the hard high-elevation ceiling`); assert.equal(terrain.extremeSlopeCells, 0, `seed ${seed}: no published trunk cell crosses an extreme slope`); assert.equal(terrain.mountainRidgeCells, 0, `seed ${seed}: no published trunk cell traverses a high mountain ridge`); assert(longestParallelRun(national, 3) <= 5, `seed ${seed}: national-road kilometre-scale parallelism is bounded`); assert(longestParallelRun(expressway, 4) <= 3, `seed ${seed}: expressway parallel corridors are collapsed`); const nationalMiss = nearMissEndpointCount(map, national, [...national, ...(map.externalRoads || [])], 4.6); const expressMiss = nearMissEndpointCount(map, expressway, [...expressway, ...(map.externalExpressways || [])], 5.0); const railMiss = nearMissEndpointCount(map, rail, [...rail, ...(map.externalRailways || [])], 4.5); const localMiss = nearMissEndpointCount(map, local, [...local, ...national, ...(map.externalRoads || [])], 3.6); assert(nationalMiss.nearMiss <= 3, `seed ${seed}: national near-miss endpoints are rare (${nationalMiss.nearMiss})`); assert.equal(expressMiss.nearMiss, 0, `seed ${seed}: expressways have no close-but-unjoined endpoints`); assert.equal(railMiss.nearMiss, 0, `seed ${seed}: railways have no close-but-unjoined endpoints`); assert(localMiss.rate <= 0.015, `seed ${seed}: local close-but-unjoined endpoint rate is ${(localMiss.rate * 100).toFixed(2)}%`); const nationalLen = pathLength(national), expressLen = pathLength(expressway), railLen = pathLength(rail); assert(nationalLen > 0 && railLen / nationalLen >= 0.82 && railLen / nationalLen <= 1.02, `seed ${seed}: rail density remains high and slightly below national-road scale (${(railLen / nationalLen).toFixed(3)})`); const icCount = (map.interchanges || []).length; assert(icCount <= Math.ceil(expressLen / 12) + 2, `seed ${seed}: IC count is not over-dense (${icCount} for ${expressLen.toFixed(1)} cells)`); assert(icCount >= Math.max(1, Math.floor(expressLen / 30)), `seed ${seed}: retained expressway still has usable IC coverage`); assert.equal(expresswayDeadEnds(map).length, 0, `seed ${seed}: internal expressway endpoints are connected or terminate at a city/port IC`); const ordinary = [...local, ...national, ...(map.externalRoads || [])]; const villages = map.villages || []; const ruralServed = villages.filter((v) => pointPathDistance(v, ordinary) <= 4).length; assert(!villages.length || ruralServed / villages.length >= 0.80, `seed ${seed}: rural-road village coverage is ${(ruralServed / Math.max(1, villages.length)).toFixed(3)}`); assert(local.length >= 70, `seed ${seed}: countryside retains a substantial organic local-road network (${local.length})`); const post = map.transportDebug?.postAdminTransportFinalization || {}; assert.equal(post.visibleCropMajorCityService?.missing?.length || 0, 0, `seed ${seed}: visible major-city trunk service has no unresolved city`); assert.equal(post.postDedupeMajorCityService?.missing?.length || 0, 0, `seed ${seed}: final major-city national/rail/expressway contract passes`); assert(post.absoluteFinalTerrainInvariant, `seed ${seed}: absolute final terrain invariant executed`); const interchangeAudit = post.absoluteFinalInterchangeRebuild; assert(interchangeAudit, `seed ${seed}: final IC rebuild is audited`); assert(interchangeAudit.added >= icCount, `seed ${seed}: visible ICs are retained from the rebuilt pre-crop IC set`); assert.equal(interchangeAudit.legacyUniformTarget, Math.max(0, Math.round(interchangeAudit.expresswayLength / 19.5)), `seed ${seed}: IC density reference is derived from the audited expressway length`); assert(interchangeAudit.target >= interchangeAudit.added, `seed ${seed}: IC target accounts for every published IC`); } const featureSource = fs.readFileSync(path.join(ROOT, 'src/mapFeatures.js'), 'utf8'); const postSource = fs.readFileSync(path.join(ROOT, 'src/mapPostAdminTransport.js'), 'utf8'); const cropSource = fs.readFileSync(path.join(ROOT, 'src/initialGenerationCrop.js'), 'utf8'); const rendererSource = fs.readFileSync(path.join(ROOT, 'src/renderer.js'), 'utf8'); const appSource = fs.readFileSync(path.join(ROOT, 'src/app.js'), 'utf8'); const cssSource = fs.readFileSync(path.join(ROOT, 'styles/styles.css'), 'utf8'); assert(!postSource.includes('function directPath(') && !postSource.includes('function directLandConnector('), 'no direct trunk fallback exists'); assert(featureSource.includes('const trunkMode = mode === "expressway" || mode === "national" || mode === "rail"'), 'all production trunk route call-sites share one terrain-first policy clamp'); assert(featureSource.includes('heuristicWeight: trunkMode') && featureSource.includes('Math.min(options.heuristicWeight ?? 0.08'), 'late trunk callers cannot restore a dominant Euclidean heuristic'); assert(featureSource.includes('literal endpoint chord') && postSource.includes('geometric chord'), 'subtle near-straight terrain-blind corridors are explicitly audited, not just coordinate jumps'); assert(postSource.includes('if (direct >= 18 && chordHardShare >= 0.08 && straightness > 0.90) return false'), 'final production terrain validator rejects near-chord trunks when the chord crosses hostile terrain'); assert(postSource.includes('runs.maxSeaRun > 0'), 'shared trunk alignment cannot reintroduce a sea crossing'); assert(cropSource.includes('let alreadyConnected = false') && cropSource.includes('passes: 2'), 'visible-crop road welding distinguishes exact junctions from near misses and performs a second bounded pass'); assert(postSource.includes('if (!hit || hit.connected) continue'), 'post-admin topology repair does not keep extending already-connected endpoints'); assert(postSource.includes('if (total < 8)') && postSource.includes('final-terminal-ic'), 'retained short expressway spurs still receive a real terminal IC rather than ending mid-road'); const municipalDraw = rendererSource.indexOf('drawLabels(ctx, municipalFallbackLabels, Infinity, occupiedLabels)'); const prefectureDraw = rendererSource.indexOf('drawLabels(ctx, prefectureLabels, Infinity, occupiedLabels)', municipalDraw + 1); assert(municipalDraw >= 0 && prefectureDraw > municipalDraw, 'prefecture labels render above municipality labels in the general map pass'); assert(rendererSource.includes('drawLabels(ctx, prefectureLabels, Infinity, adminOccupied)'), 'prefecture labels also render last in administrative mode'); assert(rendererSource.includes('land fill and coastline share the exact same binary sea mask'), 'coastline and land fill retain their shared binary sea-mask contract'); assert(cssSource.includes('background:rgba(17,22,31,.84)'), 'tooltip is slightly translucent'); assert(appSource.includes('cursorY + 10') && appSource.includes('maxTop'), 'tooltip follows the cursor to the bottom before clamping'); assert(appSource.includes('const exclusion = 12') && appSource.includes('leftAlt') && appSource.includes('rightAlt'), 'tooltip reserves a cursor exclusion zone and flips horizontally at edges'); console.log('All r11.8 terrain/topology/tooltip regression checks passed.');