From 8bb4e7a8e607dcfc8018ead1a7d0f3de6ed7dafd Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Fri, 2 Oct 2026 15:59:38 +0000 Subject: [PATCH] circularLayout: static-import forkRoute + register gate after merge Switch forkRoute/placePinsInState to static imports from their sibling library modules (now on development) and delete the vendored forkRoute fallback; opts may still override for a Worker. Re-register check:circular-layout (script + gates:pre + description) after the merge took development's package.json. --- library/packet/circularLayout.js | 63 ++++++-------------------------- package.json | 4 +- 2 files changed, 14 insertions(+), 53 deletions(-) diff --git a/library/packet/circularLayout.js b/library/packet/circularLayout.js index 16dcc5b10..6740824eb 100644 --- a/library/packet/circularLayout.js +++ b/library/packet/circularLayout.js @@ -34,10 +34,12 @@ * fmt:{ nameMaxPt, photos, rangeBadge, classic4, fork, sideLanes, pageWidthIn, * w, h }, seamYs:[y...], placePinsInState, forkRoute, feats:[...] } * - * placePinsInState and forkRoute are INJECTED so this module stays import-light and - * Worker-portable; callers in this repo pass the library implementations. A default - * import is attempted lazily for Node callers (see resolveDeps). + * placePinsInState and forkRoute come from their sibling library modules (static + * imports below); a caller may still override either via opts for a Worker that + * injects its own. Both are pure ESM, so the static import stays Worker-portable. */ +import { placePinsInState as libPlacePinsInState } from './pins-in-state.js'; +import { forkRoute as libForkRoute } from './fork-leaders.js'; // ── text advance (lifted verbatim from poster-render.js: CHADV × WFUDGE) ─────── // Worker-safe: a static char-advance table, no canvas. Callers may override via @@ -176,55 +178,12 @@ function statesToPx(feats, proj) { } // ── dependency resolution (injected; lazy Node fallback) ─────────────────────── -async function resolveDeps(opts) { - let placePinsInState = opts && opts.placePinsInState; - let forkRoute = opts && opts.forkRoute; - if (!placePinsInState) ({ placePinsInState } = await import('./pins-in-state.js')); - if (!forkRoute) { - // fork-leaders.js may not be on this branch yet; try, else vendor the fork math. - try { ({ forkRoute } = await import('./fork-leaders.js')); } - catch { forkRoute = vendoredForkRoute; } - } - return { placePinsInState, forkRoute }; -} - -// Vendored fallback copy of forkRoute (the shared fork-leaders reference), used only if -// ./fork-leaders.js is not importable in this tree. The library version supersedes -// it the moment that file lands on the branch. -function vendoredForkRoute({ lane, a, b, dirOut, mode = 'row', forkFrac = 0.5, obstacles = [], r = 0 }) { - const STUB = 7; - const ff = forkFrac < 0 ? 0 : forkFrac > 1 ? 1 : forkFrac; - const midX = (a.x + b.x) / 2; - const budget = Math.max(0, (midX - lane.x) * dirOut); - const stemLen = mode === 'trunk' ? Math.max(STUB, ff * budget) : STUB; - const forkPt = [lane.x + dirOut * stemLen, lane.y]; - const stem = [[lane.x, lane.y], forkPt]; - const octiLeg = (from, pin) => { - const [fx, fy] = from; const dx = pin.x - fx, dy = pin.y - fy; - const diag = Math.min(Math.abs(dx), Math.abs(dy)); - const sxs = Math.sign(dx) || dirOut, sys = Math.sign(dy) || 0; - const corner = [fx + sxs * diag, fy + sys * diag]; - const pts = [[fx, fy], corner]; - if (Math.abs(corner[0] - pin.x) > 1e-6 || Math.abs(corner[1] - pin.y) > 1e-6) pts.push([pin.x, pin.y]); - return pts; +function resolveDeps(opts) { + // The library implementations by default; a Worker may inject its own via opts. + return { + placePinsInState: (opts && opts.placePinsInState) || libPlacePinsInState, + forkRoute: (opts && opts.forkRoute) || libForkRoute, }; - const legA = octiLeg(forkPt, a), legB = octiLeg(forkPt, b); - const polyA = [...stem, ...legA.slice(1)], polyB = [...stem, ...legB.slice(1)]; - const toS = (pts) => { const o = []; for (let i = 0; i < pts.length - 1; i++) o.push([pts[i], pts[i + 1]]); return o; }; - const seen = new Set(), segs = []; - for (const s of [...toS(stem), ...toS(legA), ...toS(legB)]) { - const k = `${s[0]}|${s[1]}`, k2 = `${s[1]}|${s[0]}`; - if (seen.has(k) || seen.has(k2)) continue; seen.add(k); segs.push(s); - } - const throughPins = []; - if (obstacles.length && r > 0) { - const own = new Set([a.id, b.id]); - for (const ob of obstacles) { - if (own.has(ob.id)) continue; - if (polyNearPoint(polyA, ob.x, ob.y, r) || polyNearPoint(polyB, ob.x, ob.y, r)) throughPins.push(ob.id); - } - } - return { stem, legA, legB, polys: [polyA, polyB], segs, octilinear: isOctilinear(polyA) && isOctilinear(polyB), throughPins }; } // ── clustering (id-stable; CL_R on the given coordinate pair) ────────────────── @@ -256,7 +215,7 @@ export async function layoutCircular({ pins, states, box, mode, opts = {} }) { if (!['sideLanes', 'fourSides', 'pairedRows'].includes(mode)) throw new TypeError(`unknown mode: ${mode}`); const measure = opts.measure || textW; const fmt = opts.fmt || {}; - const { placePinsInState, forkRoute } = await resolveDeps(opts); + const { placePinsInState, forkRoute } = resolveDeps(opts); if (mode === 'fourSides') return fourSides({ pins, box, opts, fmt, measure, placePinsInState }); if (mode === 'pairedRows') return pairedRows({ pins, box, opts, fmt, measure, placePinsInState, forkRoute }); return sideLanes({ pins, box, opts, fmt, measure }); diff --git a/package.json b/package.json index a40cd4adf..0f98d0c67 100644 --- a/package.json +++ b/package.json @@ -225,6 +225,7 @@ "check:selector-fields:self-test": "node internal/check-selector-fields.mjs --self-test", "check:packet-strings-parity": "node internal/check-packet-strings-parity.mjs", "check:packet-variants": "node internal/check-packet-variants.mjs", + "check:circular-layout": "node library/packet/scripts/check-circular-layout.mjs", "check:fork-leaders": "node library/packet/scripts/check-fork-leaders.mjs", "check:pins-in-state": "node library/packet/scripts/check-pins-in-state.mjs", "check:label-key-coverage": "node internal/check-label-key-coverage.mjs", @@ -356,7 +357,7 @@ "fix:sri": "node internal/fix-sri.mjs", "gallery": "node internal/generate-gallery.mjs", "gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:script-flags check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:auto-engine check:engine-contract check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:no-silent-engine:self-test check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:region-paint-single-source check:poster-print-fallback check:posters-exportable check:country-names check:reduced-motion check:no-dead-library check:universal-props check:readme-size check:tree-relative check:leaf-hints check:snippet-engine check:snippet-engine:self-test check:embed-base check:link-coverage check:no-public-binds check:shared-reached check:tree-weight check:no-fleet-names:self-test check:pin-hint-floor:self-test check:poster-sizes:self-test check:shell-header:self-test check:preview-flag-dev-only:self-test check:poster-type-registry:self-test check:host-floor:self-test", - "gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:pins-in-state check:fork-leaders check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-internal-role-nouns check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:side-lanes check:side-lanes:self-test check:poster-section check:poster-section:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams check:poster-sections check:poster-sections:self-test check:poster-design-catalogue check:poster-design-catalogue:self-test check:wall-photo-weight check:no-fleet-names check:pin-hint-floor check:poster-sizes check:upg-count check:shell-header check:pg-name-gate check:preview-flag-dev-only check:poster-type-registry check:host-floor", + "gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:pins-in-state check:fork-leaders check:circular-layout check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-internal-role-nouns check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:side-lanes check:side-lanes:self-test check:poster-section check:poster-section:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams check:poster-sections check:poster-sections:self-test check:poster-design-catalogue check:poster-design-catalogue:self-test check:wall-photo-weight check:no-fleet-names check:pin-hint-floor check:poster-sizes check:upg-count check:shell-header check:pg-name-gate check:preview-flag-dev-only check:poster-type-registry check:host-floor", "gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs", "internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs", "library-index": "node internal/generate-library-index.mjs", @@ -586,6 +587,7 @@ "check:packet-variants": "the poster wall's Prayer List prints the PACKET (not the columnar list), and every variant it offers exists in packet-render's VARIANTS. A variant may drop fixed pages but never people: lightweight keeps every group. By the UPG-100 owner; mutation-tested. Headless; in gates:pre.", "check:pins-in-state": "library/packet/pins-in-state.js places whole-India poster pins so no two overlap AND every pin stays inside its own ADM1 state polygon, at the 60x96in sheet scale (2500x4000px, PIN_R 8). A deterministic repulsion relaxation (imitating poster-render.js, read not called) with the map-rect clamp replaced by a per-state multipolygon clamp: a pin pushed off its land is pulled to the nearest point just inside, holes and multi-part states (islands/coasts) honoured. Runs on the real India 855-pin data when present, else a synthetic 60x96 fixture (floor 200 pins, no vacuous pass); asserts 0 pins outside their state and 0 overlaps that are not NAMED in `infeasible` (a too-small state is reported, never silently stacked), plus a hole/island arm and a poison arm. Node only, headless, deterministic.", "check:fork-leaders": "library/packet/fork-leaders.js routes ONE paired-row leader as an octilinear fork: a stem forks at a 45° chamfer into two short H/V legs, one per pin. Modes row and trunk. Returns polylines + a deduped segs list (one polyline id per fork for crossings). Reports octilinear + throughPins. Pure, px-only, deterministic. The gate asserts both modes octilinear, legs land on pins, 0 crossings on a 220-row set (floor 200), through-pin hit+miss, determinism, and a poison. Node only, headless.", + "check:circular-layout": "library/packet/circularLayout.js is the poster circular-logic (ring order + leader routing + numbered four-sides + paired-row forks) as one pure Worker-safe module: numbers in, geometry out (positions/polylines/stats), no SVG. Ported from poster-render.js + the classic4 four-sides reference. Modes sideLanes (today L/R), fourSides (design 1: numbered, no leaders, 0 crossings), pairedRows (design 2: two leaders/row, 0 crossings). forkRoute + placePinsInState are static imports (opts may override for a Worker). Gate asserts on a frozen 855-pin 60x96 fixture: fourSides ring-ordered + 0 dupes + 0 pins-outside-state (floor 800) + side sequence L