knosky 0.6.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +149 -93
  2. package/CREDITS.md +14 -14
  3. package/LICENSE.md +76 -76
  4. package/LIMITATIONS.md +33 -23
  5. package/PRIVACY.md +30 -30
  6. package/README.md +170 -117
  7. package/SECURITY.md +78 -46
  8. package/action/post-comment.mjs +94 -89
  9. package/action.yml +62 -62
  10. package/bin/knosky.mjs +279 -105
  11. package/core/CONTRACT.md +70 -70
  12. package/core/append-only-checkpoint.mjs +215 -0
  13. package/core/audit-writer.mjs +317 -0
  14. package/core/benchmark-results.mjs +225 -225
  15. package/core/bundle.mjs +178 -178
  16. package/core/churn.mjs +23 -23
  17. package/core/ci.mjs +268 -268
  18. package/core/comparison.mjs +189 -189
  19. package/core/config.mjs +189 -189
  20. package/core/constants.mjs +13 -13
  21. package/core/contract.mjs +123 -123
  22. package/core/cross-repo.mjs +111 -111
  23. package/core/decision-codes.mjs +92 -0
  24. package/core/destination.mjs +161 -161
  25. package/core/district-classification.mjs +111 -0
  26. package/core/doctor-scorecard.mjs +369 -0
  27. package/core/domain-store.mjs +347 -0
  28. package/core/edges.mjs +43 -43
  29. package/core/escalate.mjs +68 -68
  30. package/core/freshness.mjs +198 -194
  31. package/core/fs-indexer.mjs +218 -218
  32. package/core/key-store.mjs +348 -348
  33. package/core/layout.mjs +46 -46
  34. package/core/ledger.mjs +176 -141
  35. package/core/local-ipc-identity.mjs +500 -0
  36. package/core/lod.mjs +155 -155
  37. package/core/mode-b.mjs +410 -0
  38. package/core/multi-model-benchmark.mjs +405 -405
  39. package/core/net-lockdown.mjs +421 -0
  40. package/core/onboarding.mjs +223 -223
  41. package/core/operator-auth.mjs +317 -0
  42. package/core/overlays.mjs +45 -45
  43. package/core/policy-lattice.mjs +142 -0
  44. package/core/pr-comment.mjs +198 -198
  45. package/core/protocol-spec.mjs +460 -460
  46. package/core/provenance.mjs +320 -0
  47. package/core/retrieve.mjs +63 -63
  48. package/core/route.mjs +304 -304
  49. package/core/schema.mjs +275 -275
  50. package/core/signing-tiers.mjs +1265 -0
  51. package/core/swarm-bench.mjs +106 -0
  52. package/core/swarm-coordinator.mjs +867 -0
  53. package/core/trust-root-rekey.mjs +410 -0
  54. package/mcp/server.mjs +264 -108
  55. package/package.json +56 -46
  56. package/renderer/art/kenney/buildingTiles_sheet.xml +130 -130
  57. package/renderer/art/kenney/cityDetails_sheet.xml +12 -12
  58. package/renderer/art/kenney/landscapeTiles_sheet.xml +129 -129
  59. package/renderer/art/kenney/sheet_allCars.xml +545 -545
  60. package/renderer/build-rich.mjs +43 -43
  61. package/renderer/city.template.html +808 -808
  62. package/ssot/decision-codes.json +133 -0
  63. package/ssot/ladder-l0-l3.md +232 -0
  64. package/ssot/tool-menu.json +130 -0
package/core/lod.mjs CHANGED
@@ -1,155 +1,155 @@
1
- // core/lod.mjs — Level-of-detail, viewport culling, clustering, and streaming
2
- // utilities for large single-repo city graphs (SAT-447).
3
- //
4
- // All functions are pure (no side-effects, no DOM/canvas imports) so they can
5
- // be used both in the Node.js test harness and inlined into the browser renderer.
6
-
7
- // ---------------------------------------------------------------------------
8
- // LOD tier constants
9
- // Each tier drives rendering fidelity in city.template.html.
10
- // FULL — full-detail buildings, cars, trees, props, shadows, connections
11
- // SIMPLE — buildings only (no cars / trees / props / shadows)
12
- // DOT — each building renders as a coloured dot (very-far-out zoom)
13
- // CLUSTER— entire district collapses to a single cluster badge
14
- // ---------------------------------------------------------------------------
15
- export const LOD_FULL = 0; // zoom >= LOD_FULL_THRESHOLD
16
- export const LOD_SIMPLE = 1; // zoom >= LOD_SIMPLE_THRESHOLD
17
- export const LOD_DOT = 2; // zoom >= LOD_DOT_THRESHOLD
18
- export const LOD_CLUSTER = 3; // zoom < LOD_DOT_THRESHOLD
19
-
20
- // Zoom boundary constants (cam.zoom scale, matching renderer defaults)
21
- export const LOD_FULL_THRESHOLD = 0.35; // below → drop cars/trees/props/shadows
22
- export const LOD_SIMPLE_THRESHOLD = 0.12; // below → dots only
23
- export const LOD_DOT_THRESHOLD = 0.06; // below → cluster badges
24
-
25
- /**
26
- * Map a camera zoom value to an LOD tier.
27
- * @param {number} zoom
28
- * @returns {0|1|2|3}
29
- */
30
- export function lodForZoom(zoom) {
31
- if (zoom >= LOD_FULL_THRESHOLD) return LOD_FULL;
32
- if (zoom >= LOD_SIMPLE_THRESHOLD) return LOD_SIMPLE;
33
- if (zoom >= LOD_DOT_THRESHOLD) return LOD_DOT;
34
- return LOD_CLUSTER;
35
- }
36
-
37
- // ---------------------------------------------------------------------------
38
- // Viewport culling
39
- // Works in canvas/screen-space: test an iso-world point against a padded
40
- // screen rectangle. Call once per draw to compute the clip region, then
41
- // call isVisible() per object.
42
- //
43
- // cam = { x, y, zoom } (renderer camera object)
44
- // cw = canvas CSS width
45
- // ch = canvas CSS height
46
- // pad = extra margin in world-units so objects at the edge aren't clipped
47
- // (default: 2 tile-widths, 64px each → 128)
48
- // ---------------------------------------------------------------------------
49
-
50
- /**
51
- * Compute an axis-aligned world-space clip rect for the current camera view.
52
- * Returns { wx0, wy0, wx1, wy1 } in *world* coordinates (pre-camera transform).
53
- * @param {{ x: number, y: number, zoom: number }} cam
54
- * @param {number} cw canvas CSS width
55
- * @param {number} ch canvas CSS height
56
- * @param {number} [pad=128] extra padding around the edges in world units
57
- * @returns {{ wx0: number, wy0: number, wx1: number, wy1: number }}
58
- */
59
- export function viewportClip(cam, cw, ch, pad = 128) {
60
- // Screen corners → world coords: worldX = (screenX - cam.x) / cam.zoom
61
- const wx0 = (0 - cam.x) / cam.zoom - pad;
62
- const wy0 = (0 - cam.y) / cam.zoom - pad;
63
- const wx1 = (cw - cam.x) / cam.zoom + pad;
64
- const wy1 = (ch - cam.y) / cam.zoom + pad;
65
- return { wx0, wy0, wx1, wy1 };
66
- }
67
-
68
- /**
69
- * Return true if the iso-world point (wx, wy) is inside the clip rect.
70
- * @param {number} wx
71
- * @param {number} wy
72
- * @param {{ wx0: number, wy0: number, wx1: number, wy1: number }} clip
73
- * @returns {boolean}
74
- */
75
- export function isVisible(wx, wy, clip) {
76
- return wx >= clip.wx0 && wx <= clip.wx1 && wy >= clip.wy0 && wy <= clip.wy1;
77
- }
78
-
79
- // ---------------------------------------------------------------------------
80
- // Clustering
81
- // Group city nodes by their district and compute a single representative
82
- // "cluster badge" position + count. The badge sits at the
83
- // district centroid in world-space.
84
- // ---------------------------------------------------------------------------
85
-
86
- /**
87
- * Build an array of cluster descriptors from the current NODES array.
88
- * Each cluster has: { districtId, label, color, count, wx, wy }
89
- *
90
- * @param {Array<{ d: string, bx: number, by: number }>} nodes renderer-adapted nodes (with bx/by world pos)
91
- * @param {{ [id: string]: { name: string, color: string } }} dcfg DCFG district config
92
- * @returns {Array<{ districtId: string, label: string, color: string, count: number, wx: number, wy: number }>}
93
- */
94
- export function buildClusters(nodes, dcfg) {
95
- const acc = {};
96
- for (const n of nodes) {
97
- const k = n.d;
98
- if (!acc[k]) acc[k] = { sumX: 0, sumY: 0, count: 0 };
99
- acc[k].sumX += n.bx;
100
- acc[k].sumY += n.by;
101
- acc[k].count++;
102
- }
103
- return Object.keys(acc).map(k => {
104
- const a = acc[k];
105
- const cfg = dcfg[k] || { name: k, color: '#7c83a3' };
106
- return {
107
- districtId: k,
108
- label: cfg.name,
109
- color: cfg.color,
110
- count: a.count,
111
- wx: a.sumX / a.count,
112
- wy: a.sumY / a.count,
113
- };
114
- });
115
- }
116
-
117
- // ---------------------------------------------------------------------------
118
- // Streaming geometry build
119
- // For large repos (node count >= STREAM_THRESHOLD) instead of building the
120
- // entire OBJ array synchronously during ingest, the renderer schedules small
121
- // batches of geometry across animation frames. This function splits a nodes
122
- // array into fixed-size batches.
123
- // ---------------------------------------------------------------------------
124
-
125
- /** Minimum node count before streaming geometry build is preferred. */
126
- export const STREAM_THRESHOLD = 400;
127
-
128
- /** Nodes processed per animation-frame batch. */
129
- export const STREAM_BATCH_SIZE = 80;
130
-
131
- /**
132
- * Split an array into batches of `size`.
133
- * @template T
134
- * @param {T[]} items
135
- * @param {number} [size=STREAM_BATCH_SIZE]
136
- * @returns {T[][]}
137
- */
138
- export function makeBatches(items, size = STREAM_BATCH_SIZE) {
139
- if (!Array.isArray(items)) throw new TypeError('items must be an array');
140
- if (!Number.isInteger(size) || size < 1) throw new RangeError('size must be a positive integer');
141
- const batches = [];
142
- for (let i = 0; i < items.length; i += size) {
143
- batches.push(items.slice(i, i + size));
144
- }
145
- return batches;
146
- }
147
-
148
- /**
149
- * Return true when the node count warrants streaming geometry build.
150
- * @param {number} nodeCount
151
- * @returns {boolean}
152
- */
153
- export function shouldStream(nodeCount) {
154
- return nodeCount >= STREAM_THRESHOLD;
155
- }
1
+ // core/lod.mjs — Level-of-detail, viewport culling, clustering, and streaming
2
+ // utilities for large single-repo city graphs (SAT-447).
3
+ //
4
+ // All functions are pure (no side-effects, no DOM/canvas imports) so they can
5
+ // be used both in the Node.js test harness and inlined into the browser renderer.
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // LOD tier constants
9
+ // Each tier drives rendering fidelity in city.template.html.
10
+ // FULL — full-detail buildings, cars, trees, props, shadows, connections
11
+ // SIMPLE — buildings only (no cars / trees / props / shadows)
12
+ // DOT — each building renders as a coloured dot (very-far-out zoom)
13
+ // CLUSTER— entire district collapses to a single cluster badge
14
+ // ---------------------------------------------------------------------------
15
+ export const LOD_FULL = 0; // zoom >= LOD_FULL_THRESHOLD
16
+ export const LOD_SIMPLE = 1; // zoom >= LOD_SIMPLE_THRESHOLD
17
+ export const LOD_DOT = 2; // zoom >= LOD_DOT_THRESHOLD
18
+ export const LOD_CLUSTER = 3; // zoom < LOD_DOT_THRESHOLD
19
+
20
+ // Zoom boundary constants (cam.zoom scale, matching renderer defaults)
21
+ export const LOD_FULL_THRESHOLD = 0.35; // below → drop cars/trees/props/shadows
22
+ export const LOD_SIMPLE_THRESHOLD = 0.12; // below → dots only
23
+ export const LOD_DOT_THRESHOLD = 0.06; // below → cluster badges
24
+
25
+ /**
26
+ * Map a camera zoom value to an LOD tier.
27
+ * @param {number} zoom
28
+ * @returns {0|1|2|3}
29
+ */
30
+ export function lodForZoom(zoom) {
31
+ if (zoom >= LOD_FULL_THRESHOLD) return LOD_FULL;
32
+ if (zoom >= LOD_SIMPLE_THRESHOLD) return LOD_SIMPLE;
33
+ if (zoom >= LOD_DOT_THRESHOLD) return LOD_DOT;
34
+ return LOD_CLUSTER;
35
+ }
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Viewport culling
39
+ // Works in canvas/screen-space: test an iso-world point against a padded
40
+ // screen rectangle. Call once per draw to compute the clip region, then
41
+ // call isVisible() per object.
42
+ //
43
+ // cam = { x, y, zoom } (renderer camera object)
44
+ // cw = canvas CSS width
45
+ // ch = canvas CSS height
46
+ // pad = extra margin in world-units so objects at the edge aren't clipped
47
+ // (default: 2 tile-widths, 64px each → 128)
48
+ // ---------------------------------------------------------------------------
49
+
50
+ /**
51
+ * Compute an axis-aligned world-space clip rect for the current camera view.
52
+ * Returns { wx0, wy0, wx1, wy1 } in *world* coordinates (pre-camera transform).
53
+ * @param {{ x: number, y: number, zoom: number }} cam
54
+ * @param {number} cw canvas CSS width
55
+ * @param {number} ch canvas CSS height
56
+ * @param {number} [pad=128] extra padding around the edges in world units
57
+ * @returns {{ wx0: number, wy0: number, wx1: number, wy1: number }}
58
+ */
59
+ export function viewportClip(cam, cw, ch, pad = 128) {
60
+ // Screen corners → world coords: worldX = (screenX - cam.x) / cam.zoom
61
+ const wx0 = (0 - cam.x) / cam.zoom - pad;
62
+ const wy0 = (0 - cam.y) / cam.zoom - pad;
63
+ const wx1 = (cw - cam.x) / cam.zoom + pad;
64
+ const wy1 = (ch - cam.y) / cam.zoom + pad;
65
+ return { wx0, wy0, wx1, wy1 };
66
+ }
67
+
68
+ /**
69
+ * Return true if the iso-world point (wx, wy) is inside the clip rect.
70
+ * @param {number} wx
71
+ * @param {number} wy
72
+ * @param {{ wx0: number, wy0: number, wx1: number, wy1: number }} clip
73
+ * @returns {boolean}
74
+ */
75
+ export function isVisible(wx, wy, clip) {
76
+ return wx >= clip.wx0 && wx <= clip.wx1 && wy >= clip.wy0 && wy <= clip.wy1;
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Clustering
81
+ // Group city nodes by their district and compute a single representative
82
+ // "cluster badge" position + count. The badge sits at the
83
+ // district centroid in world-space.
84
+ // ---------------------------------------------------------------------------
85
+
86
+ /**
87
+ * Build an array of cluster descriptors from the current NODES array.
88
+ * Each cluster has: { districtId, label, color, count, wx, wy }
89
+ *
90
+ * @param {Array<{ d: string, bx: number, by: number }>} nodes renderer-adapted nodes (with bx/by world pos)
91
+ * @param {{ [id: string]: { name: string, color: string } }} dcfg DCFG district config
92
+ * @returns {Array<{ districtId: string, label: string, color: string, count: number, wx: number, wy: number }>}
93
+ */
94
+ export function buildClusters(nodes, dcfg) {
95
+ const acc = {};
96
+ for (const n of nodes) {
97
+ const k = n.d;
98
+ if (!acc[k]) acc[k] = { sumX: 0, sumY: 0, count: 0 };
99
+ acc[k].sumX += n.bx;
100
+ acc[k].sumY += n.by;
101
+ acc[k].count++;
102
+ }
103
+ return Object.keys(acc).map(k => {
104
+ const a = acc[k];
105
+ const cfg = dcfg[k] || { name: k, color: '#7c83a3' };
106
+ return {
107
+ districtId: k,
108
+ label: cfg.name,
109
+ color: cfg.color,
110
+ count: a.count,
111
+ wx: a.sumX / a.count,
112
+ wy: a.sumY / a.count,
113
+ };
114
+ });
115
+ }
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // Streaming geometry build
119
+ // For large repos (node count >= STREAM_THRESHOLD) instead of building the
120
+ // entire OBJ array synchronously during ingest, the renderer schedules small
121
+ // batches of geometry across animation frames. This function splits a nodes
122
+ // array into fixed-size batches.
123
+ // ---------------------------------------------------------------------------
124
+
125
+ /** Minimum node count before streaming geometry build is preferred. */
126
+ export const STREAM_THRESHOLD = 400;
127
+
128
+ /** Nodes processed per animation-frame batch. */
129
+ export const STREAM_BATCH_SIZE = 80;
130
+
131
+ /**
132
+ * Split an array into batches of `size`.
133
+ * @template T
134
+ * @param {T[]} items
135
+ * @param {number} [size=STREAM_BATCH_SIZE]
136
+ * @returns {T[][]}
137
+ */
138
+ export function makeBatches(items, size = STREAM_BATCH_SIZE) {
139
+ if (!Array.isArray(items)) throw new TypeError('items must be an array');
140
+ if (!Number.isInteger(size) || size < 1) throw new RangeError('size must be a positive integer');
141
+ const batches = [];
142
+ for (let i = 0; i < items.length; i += size) {
143
+ batches.push(items.slice(i, i + size));
144
+ }
145
+ return batches;
146
+ }
147
+
148
+ /**
149
+ * Return true when the node count warrants streaming geometry build.
150
+ * @param {number} nodeCount
151
+ * @returns {boolean}
152
+ */
153
+ export function shouldStream(nodeCount) {
154
+ return nodeCount >= STREAM_THRESHOLD;
155
+ }