@buley/hexgrid-3d 3.4.0 → 3.5.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.
- package/dist/adapters/DashAdapter.d.ts.map +1 -1
- package/dist/adapters/DashAdapter.js +4 -3
- package/dist/algorithms/FluidEngineFactory.d.ts.map +1 -1
- package/dist/algorithms/FluidEngineFactory.js +6 -5
- package/dist/components/HexGrid.d.ts.map +1 -1
- package/dist/components/HexGrid.js +4 -5
- package/dist/lib/logger.d.ts +2 -8
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +2 -2
- package/dist/wasm/HexGridWasmWrapper.d.ts.map +1 -1
- package/dist/wasm/HexGridWasmWrapper.js +3 -2
- package/dist/webgpu/WebGPUContext.d.ts.map +1 -1
- package/dist/webgpu/WebGPUContext.js +6 -5
- package/dist/webnn/WebNNContext.d.ts.map +1 -1
- package/dist/webnn/WebNNContext.js +6 -5
- package/dist/workers/hexgrid-worker.worker.js +78 -74
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/DashAdapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"DashAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/DashAdapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAM;gBAEN,YAAY,EAAE,GAAG;IAI7B;;;;;OAKG;IACH,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG;CA6BtD"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* This adapter subscribes to a Dash "LiveQuery" which returns pointers to shared memory (SharedArrayBuffer)
|
|
5
5
|
* or Float32Arrays. It then syncs this data directly to the HexGridWasm instance.
|
|
6
6
|
*/
|
|
7
|
+
import { logger } from '../lib/logger';
|
|
7
8
|
export class DashAdapter {
|
|
8
9
|
constructor(dashInstance) {
|
|
9
10
|
this.dash = dashInstance;
|
|
@@ -15,11 +16,11 @@ export class DashAdapter {
|
|
|
15
16
|
* @param gridInstance The WASM instance of the HexGrid
|
|
16
17
|
*/
|
|
17
18
|
bindSemanticSearch(query, particleSystem) {
|
|
18
|
-
|
|
19
|
+
logger.log('[DashAdapter] Binding semantic search:', query);
|
|
19
20
|
// Hypothetical Zero-Copy API from Dash 2.0
|
|
20
21
|
if (this.dash.liveQueryPtr) {
|
|
21
22
|
this.dash.liveQueryPtr(`SELECT embedding FROM dash_vec_idx WHERE embedding MATCH '${query}'`).subscribe((handle) => {
|
|
22
|
-
|
|
23
|
+
logger.log(`[DashAdapter] Received ${handle.size} bytes from Dash.`);
|
|
23
24
|
// Assume the handle.buffer contains [pos, color, scale] interleaved or tightly packed
|
|
24
25
|
// For this MVP, we treat it as just positions
|
|
25
26
|
const floatView = new Float32Array(handle.buffer);
|
|
@@ -36,7 +37,7 @@ export class DashAdapter {
|
|
|
36
37
|
});
|
|
37
38
|
}
|
|
38
39
|
else {
|
|
39
|
-
|
|
40
|
+
logger.warn('[DashAdapter] Dash instance does not support Zero-Copy liveQueryPtr yet.');
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FluidEngineFactory.d.ts","sourceRoot":"","sources":["../../src/algorithms/FluidEngineFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"FluidEngineFactory.d.ts","sourceRoot":"","sources":["../../src/algorithms/FluidEngineFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAG9D,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,oBAAoB,GAAG,oBAAoB,CAAC;AAEvF;;;;;;GAMG;AACH,qBAAa,kBAAkB;WAChB,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC;CA6BjE"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StableFluids3D } from './FluidSimulation3D';
|
|
2
2
|
import { FluidSimulationWebNN } from './FluidSimulationWebNN';
|
|
3
3
|
import { FluidSimulation3DGPU } from './FluidSimulation3DGPU';
|
|
4
|
+
import { logger } from '../lib/logger';
|
|
4
5
|
/**
|
|
5
6
|
* Factory to select the best available Fluid Engine.
|
|
6
7
|
* Priority:
|
|
@@ -15,27 +16,27 @@ export class FluidEngineFactory {
|
|
|
15
16
|
const webnn = new FluidSimulationWebNN(config);
|
|
16
17
|
const webnnSupported = await webnn.initialize();
|
|
17
18
|
if (webnnSupported) {
|
|
18
|
-
|
|
19
|
+
logger.log("Fluid Engine: Using WebNN (NPU)");
|
|
19
20
|
return webnn;
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
catch (e) {
|
|
23
|
-
|
|
24
|
+
logger.warn("Fluid Engine: WebNN init failed", e);
|
|
24
25
|
}
|
|
25
26
|
// 2. Try WebGPU
|
|
26
27
|
try {
|
|
27
28
|
const webgpu = new FluidSimulation3DGPU(config);
|
|
28
29
|
const webgpuSupported = await webgpu.initialize();
|
|
29
30
|
if (webgpuSupported) {
|
|
30
|
-
|
|
31
|
+
logger.log("Fluid Engine: Using WebGPU");
|
|
31
32
|
return webgpu;
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
catch (e) {
|
|
35
|
-
|
|
36
|
+
logger.warn("Fluid Engine: WebGPU init failed", e);
|
|
36
37
|
}
|
|
37
38
|
// 3. Fallback to CPU
|
|
38
|
-
|
|
39
|
+
logger.log("Fluid Engine: using CPU Fallback");
|
|
39
40
|
return new StableFluids3D(config);
|
|
40
41
|
}
|
|
41
42
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HexGrid.d.ts","sourceRoot":"","sources":["../../src/components/HexGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAYhF,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAI/C,YAAY,EAAE,KAAK,EAAE,CAAA;AAWrB,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IAEvC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;IACrB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;IAGhB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;IACzC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAEnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAA;IAC9C,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,KAAK,IAAI,CAAA;IAC5G,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,0BAA0B,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACpD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC1C,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAoLD,eAAO,MAAM,OAAO,GAAI,CAAC,GAAG,OAAO,EAAE,iMAalC,YAAY,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"HexGrid.d.ts","sourceRoot":"","sources":["../../src/components/HexGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAYhF,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAI/C,YAAY,EAAE,KAAK,EAAE,CAAA;AAWrB,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IAEvC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;IACrB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAA;IAGhB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;IACzC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAEnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAA;IAC9C,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,KAAK,IAAI,CAAA;IAC5G,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,0BAA0B,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACpD,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,KAAK,CAAA;IACZ,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC1C,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAoLD,eAAO,MAAM,OAAO,GAAI,CAAC,GAAG,OAAO,EAAE,iMAalC,YAAY,CAAC,CAAC,CAAC,4CAwtIjB,CAAA;AAk7DD,eAAe,OAAO,CAAC"}
|
|
@@ -283,10 +283,9 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
283
283
|
// gridScale >1 reduces effective hex radius (more hexes), <1 increases radius (fewer hexes)
|
|
284
284
|
gridScale: 1,
|
|
285
285
|
// tileSize: base hex radius in pixels (editable)
|
|
286
|
-
|
|
287
|
-
tileSize: 12,
|
|
286
|
+
tileSize: 8,
|
|
288
287
|
// hexSpacing: multiplier for hex size (1.0 = perfect touching, <1.0 = gaps, >1.0 = overlap)
|
|
289
|
-
hexSpacing:
|
|
288
|
+
hexSpacing: 0.95,
|
|
290
289
|
// sphericalDensity: multiplier for spherical grid density (1.0 = default, >1.0 = more hexes)
|
|
291
290
|
sphericalDensity: 1.4,
|
|
292
291
|
// curvature controls (degrees) - start with a visually pleasing curvature
|
|
@@ -304,8 +303,8 @@ export const HexGrid = ({ items, photos: photosProp, onItemClick, onHexClick, sp
|
|
|
304
303
|
polePower: 0.9,
|
|
305
304
|
// Render both sides option: when true, draw an antipodal copy so images show on inside/outside
|
|
306
305
|
renderBothSides: false,
|
|
307
|
-
// Worker debug logs (
|
|
308
|
-
debugLogs:
|
|
306
|
+
// Worker debug logs (disabled by default for performance)
|
|
307
|
+
debugLogs: false,
|
|
309
308
|
// Cluster tiling defaults: preserve aspect, center anchor, no global alignment,
|
|
310
309
|
// zero uv inset for perfect alignment (seam blending handled separately), no jitter by default
|
|
311
310
|
clusterPreserveAspect: true,
|
package/dist/lib/logger.d.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
export declare const logger: {
|
|
2
2
|
debug: (..._args: unknown[]) => void;
|
|
3
|
-
log:
|
|
4
|
-
|
|
5
|
-
(...data: any[]): void;
|
|
6
|
-
};
|
|
7
|
-
info: {
|
|
8
|
-
(...data: any[]): void;
|
|
9
|
-
(...data: any[]): void;
|
|
10
|
-
};
|
|
3
|
+
log: (..._args: unknown[]) => void;
|
|
4
|
+
info: (..._args: unknown[]) => void;
|
|
11
5
|
warn: {
|
|
12
6
|
(...data: any[]): void;
|
|
13
7
|
(...data: any[]): void;
|
package/dist/lib/logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,MAAM;sBANK,OAAO,EAAE
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,MAAM;sBANK,OAAO,EAAE;oBAAT,OAAO,EAAE;qBAAT,OAAO,EAAE;;;;;;;;;CAYhC,CAAA"}
|
package/dist/lib/logger.js
CHANGED
|
@@ -2,8 +2,8 @@ const noop = (..._args) => { };
|
|
|
2
2
|
const isDev = typeof process !== 'undefined' && process?.env?.NODE_ENV !== 'production';
|
|
3
3
|
export const logger = {
|
|
4
4
|
debug: isDev ? console.debug.bind(console) : noop,
|
|
5
|
-
log: console.log.bind(console),
|
|
6
|
-
info: console.info.bind(console),
|
|
5
|
+
log: isDev ? console.log.bind(console) : noop,
|
|
6
|
+
info: isDev ? console.info.bind(console) : noop,
|
|
7
7
|
warn: console.warn.bind(console),
|
|
8
8
|
error: console.error.bind(console),
|
|
9
9
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HexGridWasmWrapper.d.ts","sourceRoot":"","sources":["../../src/wasm/HexGridWasmWrapper.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"HexGridWasmWrapper.d.ts","sourceRoot":"","sources":["../../src/wasm/HexGridWasmWrapper.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module wasm/HexGridWasmWrapper
|
|
8
8
|
*/
|
|
9
|
+
import { logger } from '../lib/logger';
|
|
9
10
|
/**
|
|
10
11
|
* Wrapper class that provides TypeScript interface to WASM
|
|
11
12
|
* with automatic fallback
|
|
@@ -36,11 +37,11 @@ export class HexGridWasmWrapper {
|
|
|
36
37
|
const wasmModule = await import('../rust/pkg/hexgrid_wasm');
|
|
37
38
|
await wasmModule.default();
|
|
38
39
|
this.module = wasmModule;
|
|
39
|
-
|
|
40
|
+
logger.log('[HexGrid] WASM module loaded successfully');
|
|
40
41
|
return this.module;
|
|
41
42
|
}
|
|
42
43
|
catch (error) {
|
|
43
|
-
|
|
44
|
+
logger.warn('[HexGrid] WASM module not available, using fallback:', error);
|
|
44
45
|
this.loadFailed = true;
|
|
45
46
|
return null;
|
|
46
47
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebGPUContext.d.ts","sourceRoot":"","sources":["../../src/webgpu/WebGPUContext.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"WebGPUContext.d.ts","sourceRoot":"","sources":["../../src/webgpu/WebGPUContext.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAgB;IACvC,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,WAAW,CAAkB;IAErC,OAAO;IAEP,MAAM,CAAC,WAAW,IAAI,aAAa;IAOnC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAoCpC,SAAS,IAAI,SAAS,GAAG,IAAI;IAI7B,UAAU,IAAI,UAAU,GAAG,IAAI;IAI/B,WAAW,IAAI,OAAO;CAGvB"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* WebGPU Context Manager
|
|
3
3
|
* Handles the creation and management of the WebGPU Device and Adapter.
|
|
4
4
|
*/
|
|
5
|
+
import { logger } from '../lib/logger';
|
|
5
6
|
export class WebGPUContext {
|
|
6
7
|
constructor() {
|
|
7
8
|
this.adapter = null;
|
|
@@ -19,7 +20,7 @@ export class WebGPUContext {
|
|
|
19
20
|
*/
|
|
20
21
|
async initialize() {
|
|
21
22
|
if (typeof navigator === 'undefined' || !navigator.gpu) {
|
|
22
|
-
|
|
23
|
+
logger.warn('WebGPU is not supported in this environment.');
|
|
23
24
|
this.isSupported = false;
|
|
24
25
|
return false;
|
|
25
26
|
}
|
|
@@ -28,22 +29,22 @@ export class WebGPUContext {
|
|
|
28
29
|
powerPreference: 'high-performance'
|
|
29
30
|
});
|
|
30
31
|
if (!this.adapter) {
|
|
31
|
-
|
|
32
|
+
logger.warn('No WebGPU adapter found.');
|
|
32
33
|
this.isSupported = false;
|
|
33
34
|
return false;
|
|
34
35
|
}
|
|
35
36
|
this.device = await this.adapter.requestDevice();
|
|
36
37
|
this.device.lost.then((info) => {
|
|
37
|
-
|
|
38
|
+
logger.error(`WebGPU device lost: ${info.message}`);
|
|
38
39
|
this.device = null;
|
|
39
40
|
this.isSupported = false;
|
|
40
41
|
});
|
|
41
42
|
this.isSupported = true;
|
|
42
|
-
|
|
43
|
+
logger.log('WebGPU initialized successfully.');
|
|
43
44
|
return true;
|
|
44
45
|
}
|
|
45
46
|
catch (e) {
|
|
46
|
-
|
|
47
|
+
logger.error('Failed to initialize WebGPU:', e);
|
|
47
48
|
this.isSupported = false;
|
|
48
49
|
return false;
|
|
49
50
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebNNContext.d.ts","sourceRoot":"","sources":["../../src/webnn/WebNNContext.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"WebNNContext.d.ts","sourceRoot":"","sources":["../../src/webnn/WebNNContext.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAEpD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAe;IACtC,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,WAAW,CAAkB;IAErC,OAAO;IAEP,MAAM,CAAC,WAAW,IAAI,YAAY;IAOlC;;OAEG;IACG,UAAU,CAAC,UAAU,GAAE,eAAuB,GAAG,OAAO,CAAC,OAAO,CAAC;IAuCvE,UAAU,IAAI,SAAS,GAAG,IAAI;IAI9B,aAAa,IAAI,eAAe;IAIhC,WAAW,IAAI,OAAO;CAGvB;AAGD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,SAAS;QACjB,EAAE,EAAE;YACF,aAAa,CAAC,OAAO,CAAC,EAAE;gBAAE,UAAU,CAAC,EAAE,MAAM,CAAA;aAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;SACtE,CAAC;KACH;IAED,UAAU,SAAS;QAEjB,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;KACtI;IAED,UAAU,OAAO;KAEhB;IAED,UAAU,eAAe;KAExB;CACF"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Handles the creation and management of the WebNN MLContext.
|
|
4
4
|
* Prioritizes NPU -> GPU -> CPU.
|
|
5
5
|
*/
|
|
6
|
+
import { logger } from '../lib/logger';
|
|
6
7
|
export class WebNNContext {
|
|
7
8
|
constructor() {
|
|
8
9
|
this.context = null;
|
|
@@ -20,7 +21,7 @@ export class WebNNContext {
|
|
|
20
21
|
*/
|
|
21
22
|
async initialize(preference = 'npu') {
|
|
22
23
|
if (typeof navigator === 'undefined' || !navigator.ml) {
|
|
23
|
-
|
|
24
|
+
logger.warn('WebNN is not supported in this environment.');
|
|
24
25
|
this.isSupported = false;
|
|
25
26
|
return false;
|
|
26
27
|
}
|
|
@@ -29,11 +30,11 @@ export class WebNNContext {
|
|
|
29
30
|
this.context = await navigator.ml.createContext({ deviceType: preference });
|
|
30
31
|
this.deviceType = preference;
|
|
31
32
|
this.isSupported = true;
|
|
32
|
-
|
|
33
|
+
logger.log(`WebNN initialized successfully on ${preference}`);
|
|
33
34
|
return true;
|
|
34
35
|
}
|
|
35
36
|
catch (e) {
|
|
36
|
-
|
|
37
|
+
logger.warn(`Failed to initialize WebNN on ${preference}, trying fallback chain...`, e);
|
|
37
38
|
// Fallback chain: NPU -> GPU -> CPU
|
|
38
39
|
const chain = ['npu', 'gpu', 'cpu'];
|
|
39
40
|
const startIndex = chain.indexOf(preference) + 1;
|
|
@@ -43,11 +44,11 @@ export class WebNNContext {
|
|
|
43
44
|
this.context = await navigator.ml.createContext({ deviceType: fallback });
|
|
44
45
|
this.deviceType = fallback;
|
|
45
46
|
this.isSupported = true;
|
|
46
|
-
|
|
47
|
+
logger.log(`WebNN initialized successfully on fallback ${fallback}`);
|
|
47
48
|
return true;
|
|
48
49
|
}
|
|
49
50
|
catch (err) {
|
|
50
|
-
|
|
51
|
+
logger.warn(`Failed to initialize WebNN on fallback ${fallback}`, err);
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
}
|
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
// - defensive guards and error reporting via postMessage({type:'error', ...})
|
|
8
8
|
import { getGridBounds as _getGridBounds, distanceBetween as _distanceBetween, calculateUvBoundsFromGridPosition as _calculateUvBoundsFromGridPosition, calculateContiguity as _calculateContiguity, calculatePhotoContiguity as _calculatePhotoContiguity, } from './hexgrid-math';
|
|
9
9
|
const WORKER_ID = Math.random().toString(36).substring(7);
|
|
10
|
-
|
|
10
|
+
/** Guarded log – only emits when workerDebug.debugLogs is true. */
|
|
11
|
+
function debugLog(...args) {
|
|
12
|
+
if (workerDebug.debugLogs)
|
|
13
|
+
console.log(...args);
|
|
14
|
+
}
|
|
11
15
|
const workerDebug = {
|
|
12
16
|
cohesionBoost: 6.0, // BOOSTED: strongly favor growth near cluster centroids to build larger regions
|
|
13
17
|
enableMerges: true, // ENABLED: merge small fragments into nearby larger clusters
|
|
@@ -152,7 +156,7 @@ function calculateUvBoundsFromGridPosition(gridCol, gridRow, tilesX, tilesY) {
|
|
|
152
156
|
function findConnectedComponents(indices, positions, hexRadius) {
|
|
153
157
|
// Immediate synchronous check - if this doesn't log, the function isn't being called or is blocked
|
|
154
158
|
const startMarker = performance.now();
|
|
155
|
-
|
|
159
|
+
debugLog('[findConnectedComponents] FUNCTION ENTERED - indices.length=', indices.length, 'positions.length=', positions.length, 'hexRadius=', hexRadius, 'marker=', startMarker);
|
|
156
160
|
// Validate inputs immediately
|
|
157
161
|
if (!indices || !Array.isArray(indices)) {
|
|
158
162
|
console.error('[findConnectedComponents] Invalid indices:', indices);
|
|
@@ -166,13 +170,13 @@ function findConnectedComponents(indices, positions, hexRadius) {
|
|
|
166
170
|
console.error('[findConnectedComponents] Invalid hexRadius:', hexRadius);
|
|
167
171
|
return [];
|
|
168
172
|
}
|
|
169
|
-
|
|
173
|
+
debugLog('[findConnectedComponents] About to enter try block');
|
|
170
174
|
// Add immediate log after try block entry to confirm execution reaches here
|
|
171
175
|
let tryBlockEntered = false;
|
|
172
176
|
try {
|
|
173
177
|
tryBlockEntered = true;
|
|
174
|
-
|
|
175
|
-
|
|
178
|
+
debugLog('[findConnectedComponents] ✅ TRY BLOCK ENTERED - marker=', performance.now() - startMarker, 'ms');
|
|
179
|
+
debugLog('[findConnectedComponents] Inside try block - Starting with', indices.length, 'indices');
|
|
176
180
|
const set = new Set(indices);
|
|
177
181
|
const visited = new Set();
|
|
178
182
|
const comps = [];
|
|
@@ -181,7 +185,7 @@ function findConnectedComponents(indices, positions, hexRadius) {
|
|
|
181
185
|
if (visited.has(start))
|
|
182
186
|
continue;
|
|
183
187
|
componentCount++;
|
|
184
|
-
|
|
188
|
+
debugLog('[findConnectedComponents] Starting component', componentCount, 'from index', start);
|
|
185
189
|
const q = [start];
|
|
186
190
|
visited.add(start);
|
|
187
191
|
const comp = [];
|
|
@@ -194,7 +198,7 @@ function findConnectedComponents(indices, positions, hexRadius) {
|
|
|
194
198
|
break;
|
|
195
199
|
}
|
|
196
200
|
if (iterations % 100 === 0) {
|
|
197
|
-
|
|
201
|
+
debugLog('[findConnectedComponents] Component', componentCount, 'iteration', iterations, 'queue length', q.length);
|
|
198
202
|
}
|
|
199
203
|
const cur = q.shift();
|
|
200
204
|
if (cur === undefined || cur === null) {
|
|
@@ -224,12 +228,12 @@ function findConnectedComponents(indices, positions, hexRadius) {
|
|
|
224
228
|
continue;
|
|
225
229
|
}
|
|
226
230
|
}
|
|
227
|
-
|
|
231
|
+
debugLog('[findConnectedComponents] Component', componentCount, 'complete:', comp.length, 'nodes,', iterations, 'iterations');
|
|
228
232
|
comps.push(comp);
|
|
229
233
|
}
|
|
230
|
-
|
|
234
|
+
debugLog('[findConnectedComponents] Complete:', comps.length, 'components found');
|
|
231
235
|
const elapsed = performance.now() - startMarker;
|
|
232
|
-
|
|
236
|
+
debugLog('[findConnectedComponents] ✅ RETURNING - elapsed=', elapsed, 'ms, components=', comps.length);
|
|
233
237
|
return comps;
|
|
234
238
|
}
|
|
235
239
|
catch (e) {
|
|
@@ -250,7 +254,7 @@ function findConnectedComponents(indices, positions, hexRadius) {
|
|
|
250
254
|
}
|
|
251
255
|
function calculatePhotoCentroids(infections, positions, hexRadius) {
|
|
252
256
|
try {
|
|
253
|
-
|
|
257
|
+
debugLog('[calculatePhotoCentroids] Starting with', infections.size, 'infections');
|
|
254
258
|
const byPhoto = new Map();
|
|
255
259
|
for (const [idx, inf] of infections) {
|
|
256
260
|
if (!inf || !inf.photo)
|
|
@@ -259,14 +263,14 @@ function calculatePhotoCentroids(infections, positions, hexRadius) {
|
|
|
259
263
|
arr.push(idx);
|
|
260
264
|
byPhoto.set(inf.photo.id, arr);
|
|
261
265
|
}
|
|
262
|
-
|
|
266
|
+
debugLog('[calculatePhotoCentroids] Grouped into', byPhoto.size, 'photos');
|
|
263
267
|
const centroids = new Map();
|
|
264
268
|
let photoNum = 0;
|
|
265
269
|
for (const [photoId, inds] of byPhoto) {
|
|
266
270
|
photoNum++;
|
|
267
|
-
|
|
271
|
+
debugLog('[calculatePhotoCentroids] Processing photo', photoNum, '/', byPhoto.size, 'photoId=', photoId, 'indices=', inds.length);
|
|
268
272
|
try {
|
|
269
|
-
|
|
273
|
+
debugLog('[calculatePhotoCentroids] About to call findConnectedComponents with', inds.length, 'indices');
|
|
270
274
|
const callStartTime = performance.now();
|
|
271
275
|
let comps;
|
|
272
276
|
try {
|
|
@@ -282,7 +286,7 @@ function calculatePhotoCentroids(infections, positions, hexRadius) {
|
|
|
282
286
|
else {
|
|
283
287
|
comps = findConnectedComponents(inds, positions, hexRadius);
|
|
284
288
|
const callElapsed = performance.now() - callStartTime;
|
|
285
|
-
|
|
289
|
+
debugLog('[calculatePhotoCentroids] findConnectedComponents RETURNED with', comps.length, 'components after', callElapsed, 'ms');
|
|
286
290
|
}
|
|
287
291
|
}
|
|
288
292
|
catch (e) {
|
|
@@ -291,8 +295,8 @@ function calculatePhotoCentroids(infections, positions, hexRadius) {
|
|
|
291
295
|
// Return empty components on error to allow evolution to continue
|
|
292
296
|
comps = [];
|
|
293
297
|
}
|
|
294
|
-
|
|
295
|
-
|
|
298
|
+
debugLog('[calculatePhotoCentroids] findConnectedComponents returned', comps.length, 'components');
|
|
299
|
+
debugLog('[calculatePhotoCentroids] Found', comps.length, 'components for photo', photoId);
|
|
296
300
|
const cs = [];
|
|
297
301
|
for (const comp of comps) {
|
|
298
302
|
let sx = 0, sy = 0;
|
|
@@ -313,7 +317,7 @@ function calculatePhotoCentroids(infections, positions, hexRadius) {
|
|
|
313
317
|
centroids.set(photoId, []);
|
|
314
318
|
}
|
|
315
319
|
}
|
|
316
|
-
|
|
320
|
+
debugLog('[calculatePhotoCentroids] Completed, returning', centroids.size, 'photo centroids');
|
|
317
321
|
return centroids;
|
|
318
322
|
}
|
|
319
323
|
catch (e) {
|
|
@@ -330,7 +334,7 @@ function calculateContiguity(indices, positions, hexRadius) {
|
|
|
330
334
|
function assignClusterGridPositions(infections, positions, hexRadius) {
|
|
331
335
|
const debugCenters = [];
|
|
332
336
|
try {
|
|
333
|
-
|
|
337
|
+
debugLog('[assignClusterGridPositions] Starting with', infections.size, 'infections');
|
|
334
338
|
// Group infections by photo
|
|
335
339
|
const byPhoto = new Map();
|
|
336
340
|
for (const [idx, inf] of infections) {
|
|
@@ -340,7 +344,7 @@ function assignClusterGridPositions(infections, positions, hexRadius) {
|
|
|
340
344
|
arr.push(idx);
|
|
341
345
|
byPhoto.set(inf.photo.id, arr);
|
|
342
346
|
}
|
|
343
|
-
|
|
347
|
+
debugLog('[assignClusterGridPositions] Processing', byPhoto.size, 'unique photos');
|
|
344
348
|
// Cluster size analytics
|
|
345
349
|
let totalClusters = 0;
|
|
346
350
|
let clusterSizes = [];
|
|
@@ -353,7 +357,7 @@ function assignClusterGridPositions(infections, positions, hexRadius) {
|
|
|
353
357
|
if (comp && comp.length > 0)
|
|
354
358
|
clusterSizes.push(comp.length);
|
|
355
359
|
}
|
|
356
|
-
|
|
360
|
+
debugLog('[assignClusterGridPositions] Photo', photoId.substring(0, 8), 'has', components.length, 'clusters, sizes:', components.map((c) => c.length).join(','));
|
|
357
361
|
// Process each cluster separately
|
|
358
362
|
let clusterIndex = 0;
|
|
359
363
|
for (const cluster of components) {
|
|
@@ -432,7 +436,7 @@ function assignClusterGridPositions(infections, positions, hexRadius) {
|
|
|
432
436
|
tilesY++;
|
|
433
437
|
}
|
|
434
438
|
}
|
|
435
|
-
|
|
439
|
+
debugLog('[assignClusterGridPositions][hex-lattice] cluster', photoId.substring(0, 8), 'size', cluster.length, 'latticeCols', latticeCols, 'latticeRows', latticeRows, 'tilesX', tilesX, 'tilesY', tilesY);
|
|
436
440
|
// Build optional serpentine ordering for assignment uniqueness (not strictly needed since lattice mapping is direct)
|
|
437
441
|
const serpentine = workerDebug.clusterScanMode === 'serpentine';
|
|
438
442
|
// Assign each infection a gridPosition derived from lattice coordinates compressed into tile grid domain.
|
|
@@ -586,7 +590,7 @@ function assignClusterGridPositions(infections, positions, hexRadius) {
|
|
|
586
590
|
}
|
|
587
591
|
const clusterWidth = Math.max(0, maxX - minX);
|
|
588
592
|
const clusterHeight = Math.max(0, maxY - minY);
|
|
589
|
-
|
|
593
|
+
debugLog('[assignClusterGridPositions] Cluster bounds:', {
|
|
590
594
|
photoId: photoId.substring(0, 8),
|
|
591
595
|
clusterIndex,
|
|
592
596
|
hexCount: cluster.length,
|
|
@@ -601,7 +605,7 @@ function assignClusterGridPositions(infections, positions, hexRadius) {
|
|
|
601
605
|
// This ensures the tile grid matches the spatial layout of the cluster
|
|
602
606
|
const clusterAspect = clusterHeight > 0 ? clusterWidth / clusterHeight : 1.0;
|
|
603
607
|
const targetTileCount = 16; // Target ~16 tiles total for good image distribution
|
|
604
|
-
|
|
608
|
+
debugLog('[assignClusterGridPositions] Cluster aspect:', clusterAspect.toFixed(3), '(width/height)');
|
|
605
609
|
let tilesX;
|
|
606
610
|
let tilesY;
|
|
607
611
|
if (cluster.length === 1) {
|
|
@@ -659,13 +663,13 @@ function assignClusterGridPositions(infections, positions, hexRadius) {
|
|
|
659
663
|
newTilesY = Math.max(1, Math.min(16, newTilesY));
|
|
660
664
|
tilesX = newTilesX;
|
|
661
665
|
tilesY = newTilesY;
|
|
662
|
-
|
|
666
|
+
debugLog('[assignClusterGridPositions] Expanded tile grid to', tilesX, 'x', tilesY, '=', tilesX * tilesY, 'tiles');
|
|
663
667
|
}
|
|
664
668
|
}
|
|
665
669
|
catch (e) {
|
|
666
670
|
// if anything goes wrong, keep original tilesX/tilesY
|
|
667
671
|
}
|
|
668
|
-
|
|
672
|
+
debugLog('[assignClusterGridPositions] Final tile dimensions:', tilesX, 'x', tilesY, '=', tilesX * tilesY, 'tiles for', cluster.length, 'hexes');
|
|
669
673
|
// Single-hex or degenerate clusters: assign a deterministic tile so single hexes don't all use [0,0]
|
|
670
674
|
if (cluster.length === 1 ||
|
|
671
675
|
clusterWidth < 1e-6 ||
|
|
@@ -859,7 +863,7 @@ function assignClusterGridPositions(infections, positions, hexRadius) {
|
|
|
859
863
|
const y = normMinY + v * normHeight;
|
|
860
864
|
return [x, y];
|
|
861
865
|
};
|
|
862
|
-
|
|
866
|
+
debugLog('[assignClusterGridPositions] Normalized bounds for tiling:', {
|
|
863
867
|
normMinX: normMinX.toFixed(2),
|
|
864
868
|
normMinY: normMinY.toFixed(2),
|
|
865
869
|
normWidth: normWidth.toFixed(2),
|
|
@@ -923,8 +927,8 @@ function assignClusterGridPositions(infections, positions, hexRadius) {
|
|
|
923
927
|
});
|
|
924
928
|
}
|
|
925
929
|
}
|
|
926
|
-
|
|
927
|
-
|
|
930
|
+
debugLog('[assignClusterGridPositions] Spatially assigned', cluster.length, 'hexes to nearest tile centers');
|
|
931
|
+
debugLog('[assignClusterGridPositions] Sample assignments:', assignmentSamples
|
|
928
932
|
.map((s) => `node#${s.nodeId} at (${s.nodeX.toFixed(1)},${s.nodeY.toFixed(1)}) → tile[${s.tileCol},${s.tileRow}] center(${s.centerX.toFixed(1)},${s.centerY.toFixed(1)}) dist=${s.dist.toFixed(1)}`)
|
|
929
933
|
.join('\n '));
|
|
930
934
|
// Optional: Neighborhood-aware refinement to reduce visual seams
|
|
@@ -996,7 +1000,7 @@ function assignClusterGridPositions(infections, positions, hexRadius) {
|
|
|
996
1000
|
}
|
|
997
1001
|
if (adjustments === 0)
|
|
998
1002
|
break; // Converged
|
|
999
|
-
|
|
1003
|
+
debugLog('[assignClusterGridPositions] Neighbor-aware refinement iteration', iter + 1, ':', adjustments, 'adjustments');
|
|
1000
1004
|
}
|
|
1001
1005
|
}
|
|
1002
1006
|
// Finally write assignments back into infections with UV bounds/inset
|
|
@@ -1028,7 +1032,7 @@ function assignClusterGridPositions(infections, positions, hexRadius) {
|
|
|
1028
1032
|
tilesY,
|
|
1029
1033
|
});
|
|
1030
1034
|
}
|
|
1031
|
-
|
|
1035
|
+
debugLog('[assignClusterGridPositions] Assigned grid positions to', cluster.length, 'hexes in cluster (BFS)');
|
|
1032
1036
|
}
|
|
1033
1037
|
catch (e) {
|
|
1034
1038
|
console.error('[assignClusterGridPositions] BFS assignment failed, falling back to quantization', e);
|
|
@@ -1044,9 +1048,9 @@ function assignClusterGridPositions(infections, positions, hexRadius) {
|
|
|
1044
1048
|
const medianSize = clusterSizes[Math.floor(clusterSizes.length / 2)];
|
|
1045
1049
|
const maxSize = clusterSizes[0];
|
|
1046
1050
|
const smallClusters = clusterSizes.filter((s) => s <= 3).length;
|
|
1047
|
-
|
|
1051
|
+
debugLog('[assignClusterGridPositions] CLUSTER STATS: total=', totalClusters, 'avg=', avgSize.toFixed(1), 'median=', medianSize, 'max=', maxSize, 'small(≤3)=', smallClusters, '/', totalClusters, '(', ((100 * smallClusters) / totalClusters).toFixed(0), '%)');
|
|
1048
1052
|
}
|
|
1049
|
-
|
|
1053
|
+
debugLog('[assignClusterGridPositions] Complete');
|
|
1050
1054
|
}
|
|
1051
1055
|
catch (e) {
|
|
1052
1056
|
console.error('[assignClusterGridPositions] Error:', e);
|
|
@@ -1057,7 +1061,7 @@ function postOptimizationMerge(infections, positions, hexRadius, debug = false)
|
|
|
1057
1061
|
try {
|
|
1058
1062
|
if (!workerDebug || !workerDebug.enableMerges) {
|
|
1059
1063
|
if (debug && workerDebug.mergeLogs)
|
|
1060
|
-
|
|
1064
|
+
debugLog('[merge] disabled');
|
|
1061
1065
|
return;
|
|
1062
1066
|
}
|
|
1063
1067
|
const threshold = typeof workerDebug.mergeSmallComponentsThreshold === 'number'
|
|
@@ -1132,7 +1136,7 @@ function postOptimizationMerge(infections, positions, hexRadius, debug = false)
|
|
|
1132
1136
|
}
|
|
1133
1137
|
merges++;
|
|
1134
1138
|
if (debug && workerDebug.mergeLogs)
|
|
1135
|
-
|
|
1139
|
+
debugLog(`[merge] moved ${s.length} -> ${recipientId}`);
|
|
1136
1140
|
}
|
|
1137
1141
|
}
|
|
1138
1142
|
}
|
|
@@ -1187,36 +1191,36 @@ function normalizePrevState(prevState) {
|
|
|
1187
1191
|
}
|
|
1188
1192
|
function evolveInfectionSystem(prevState, positions, photos, hexRadius, currentTime, debug = false) {
|
|
1189
1193
|
try {
|
|
1190
|
-
|
|
1194
|
+
debugLog('[evolve] Step 1: Validating positions...');
|
|
1191
1195
|
if (!positions || positions.length === 0) {
|
|
1192
1196
|
safePostError(new Error('positions required for evolve'));
|
|
1193
1197
|
return null;
|
|
1194
1198
|
}
|
|
1195
|
-
|
|
1199
|
+
debugLog('[evolve] Step 2: Normalizing state...');
|
|
1196
1200
|
const normalized = normalizePrevState(prevState);
|
|
1197
1201
|
const infectionsMap = normalized.infections;
|
|
1198
1202
|
const availableSet = new Set(Array.isArray(normalized.availableIndices)
|
|
1199
1203
|
? normalized.availableIndices
|
|
1200
1204
|
: []);
|
|
1201
|
-
|
|
1205
|
+
debugLog('[evolve] Step 3: Cleaning infections...');
|
|
1202
1206
|
for (const [idx, inf] of infectionsMap) {
|
|
1203
1207
|
if (!inf || !inf.photo) {
|
|
1204
1208
|
infectionsMap.delete(idx);
|
|
1205
1209
|
availableSet.add(idx);
|
|
1206
1210
|
}
|
|
1207
1211
|
}
|
|
1208
|
-
|
|
1212
|
+
debugLog('[evolve] Step 4: Calculating centroids...');
|
|
1209
1213
|
const centroids = calculatePhotoCentroids(infectionsMap, positions, hexRadius);
|
|
1210
|
-
|
|
1214
|
+
debugLog('[evolve] Step 5: Creating new state copies...');
|
|
1211
1215
|
const newInfections = new Map(infectionsMap);
|
|
1212
1216
|
const newAvailable = new Set(availableSet);
|
|
1213
1217
|
const generation = prevState && typeof prevState.generation === 'number'
|
|
1214
1218
|
? prevState.generation + 1
|
|
1215
1219
|
: 0;
|
|
1216
|
-
|
|
1220
|
+
debugLog('[evolve] Step 6: Growth step - processing', infectionsMap.size, 'infections...');
|
|
1217
1221
|
// Skip growth step if we have no infections or no photos
|
|
1218
1222
|
if (infectionsMap.size === 0 || photos.length === 0) {
|
|
1219
|
-
|
|
1223
|
+
debugLog('[evolve] Skipping growth - no infections or no photos');
|
|
1220
1224
|
}
|
|
1221
1225
|
else {
|
|
1222
1226
|
// Cell death step: allow fully surrounded cells to die and respawn for optimization
|
|
@@ -1351,7 +1355,7 @@ function evolveInfectionSystem(prevState, positions, photos, hexRadius, currentT
|
|
|
1351
1355
|
}
|
|
1352
1356
|
}
|
|
1353
1357
|
if (deathCount > 0 || mutationCount > 0 || invaderExpulsions > 0) {
|
|
1354
|
-
|
|
1358
|
+
debugLog('[evolve] Cell death: removed', deathCount, 'cells (', invaderExpulsions, 'invaders expelled), mutated', mutationCount, 'cells');
|
|
1355
1359
|
}
|
|
1356
1360
|
}
|
|
1357
1361
|
// Growth step: prefer neighbors that increase contiguity and are closer to centroids
|
|
@@ -1359,7 +1363,7 @@ function evolveInfectionSystem(prevState, positions, photos, hexRadius, currentT
|
|
|
1359
1363
|
for (const [idx, inf] of infectionsMap) {
|
|
1360
1364
|
growthIterations++;
|
|
1361
1365
|
if (growthIterations % 10 === 0)
|
|
1362
|
-
|
|
1366
|
+
debugLog('[evolve] Growth iteration', growthIterations, '/', infectionsMap.size);
|
|
1363
1367
|
const neighbors = getNeighborsCached(idx, positions, hexRadius);
|
|
1364
1368
|
for (const n of neighbors) {
|
|
1365
1369
|
if (!newAvailable.has(n))
|
|
@@ -1440,7 +1444,7 @@ function evolveInfectionSystem(prevState, positions, photos, hexRadius, currentT
|
|
|
1440
1444
|
}
|
|
1441
1445
|
}
|
|
1442
1446
|
}
|
|
1443
|
-
|
|
1447
|
+
debugLog('[evolve] Step 6.5: Entropy decay - applying decay to dominant successful photos...');
|
|
1444
1448
|
// Entropy decay: successful/dominant photos decay over time to allow new dominance to emerge
|
|
1445
1449
|
if (workerDebug.enableEntropyDecay && newInfections.size > 0) {
|
|
1446
1450
|
// Calculate current territory shares
|
|
@@ -1524,14 +1528,14 @@ function evolveInfectionSystem(prevState, positions, photos, hexRadius, currentT
|
|
|
1524
1528
|
entropyDecayCount++;
|
|
1525
1529
|
}
|
|
1526
1530
|
if (entropyDecayCount > 0) {
|
|
1527
|
-
|
|
1531
|
+
debugLog('[evolve] Entropy decay: removed', entropyDecayCount, 'cells from dominant successful photos');
|
|
1528
1532
|
}
|
|
1529
1533
|
}
|
|
1530
1534
|
}
|
|
1531
|
-
|
|
1535
|
+
debugLog('[evolve] Step 7: Deterministic fill - processing', newAvailable.size, 'available positions...');
|
|
1532
1536
|
// Skip deterministic fill if we have no photos or no existing infections to base decisions on
|
|
1533
1537
|
if (photos.length === 0 || newInfections.size === 0) {
|
|
1534
|
-
|
|
1538
|
+
debugLog('[evolve] Skipping deterministic fill - no photos or no infections');
|
|
1535
1539
|
}
|
|
1536
1540
|
else {
|
|
1537
1541
|
// Deterministic fill for holes with >=2 same-photo neighbors
|
|
@@ -1539,7 +1543,7 @@ function evolveInfectionSystem(prevState, positions, photos, hexRadius, currentT
|
|
|
1539
1543
|
for (const a of Array.from(newAvailable)) {
|
|
1540
1544
|
fillIterations++;
|
|
1541
1545
|
if (fillIterations % 50 === 0)
|
|
1542
|
-
|
|
1546
|
+
debugLog('[evolve] Fill iteration', fillIterations, '/', newAvailable.size);
|
|
1543
1547
|
const neighbors = getNeighborsCached(a, positions, hexRadius);
|
|
1544
1548
|
const counts = new Map();
|
|
1545
1549
|
for (const n of neighbors) {
|
|
@@ -1578,13 +1582,13 @@ function evolveInfectionSystem(prevState, positions, photos, hexRadius, currentT
|
|
|
1578
1582
|
}
|
|
1579
1583
|
}
|
|
1580
1584
|
}
|
|
1581
|
-
|
|
1585
|
+
debugLog('[evolve] Step 8: Optimization merge pass...');
|
|
1582
1586
|
// Conservative merge pass (opt-in)
|
|
1583
1587
|
postOptimizationMerge(newInfections, positions, hexRadius, !!workerDebug.mergeLogs);
|
|
1584
|
-
|
|
1588
|
+
debugLog('[evolve] Step 9: Assigning cluster-aware grid positions...');
|
|
1585
1589
|
// Make clusters self-aware by assigning grid positions based on spatial layout
|
|
1586
1590
|
const tileCenters = assignClusterGridPositions(newInfections, positions, hexRadius);
|
|
1587
|
-
|
|
1591
|
+
debugLog('[evolve] Step 10: Returning result - generation', generation, 'infections', newInfections.size);
|
|
1588
1592
|
return {
|
|
1589
1593
|
infections: newInfections,
|
|
1590
1594
|
availableIndices: Array.from(newAvailable),
|
|
@@ -1637,7 +1641,7 @@ self.onmessage = function (ev) {
|
|
|
1637
1641
|
if (!positions || !Array.isArray(positions))
|
|
1638
1642
|
return;
|
|
1639
1643
|
const hexRadius = typeof payload.hexRadius === 'number' ? payload.hexRadius : 24;
|
|
1640
|
-
|
|
1644
|
+
debugLog('[hexgrid-worker] Pre-building neighbor cache for', positions.length, 'positions...');
|
|
1641
1645
|
const startTime = Date.now();
|
|
1642
1646
|
// Build ALL neighbor relationships in one O(n²) pass instead of n×O(n) passes
|
|
1643
1647
|
try {
|
|
@@ -1667,11 +1671,11 @@ self.onmessage = function (ev) {
|
|
|
1667
1671
|
}
|
|
1668
1672
|
// Log progress every 100 positions
|
|
1669
1673
|
if ((i + 1) % 100 === 0) {
|
|
1670
|
-
|
|
1674
|
+
debugLog('[hexgrid-worker] Processed', i + 1, '/', positions.length, 'positions');
|
|
1671
1675
|
}
|
|
1672
1676
|
}
|
|
1673
1677
|
const elapsed = Date.now() - startTime;
|
|
1674
|
-
|
|
1678
|
+
debugLog('[hexgrid-worker] ✅ Neighbor cache built in', elapsed, 'ms - ready for evolution!');
|
|
1675
1679
|
// Mark cache as ready
|
|
1676
1680
|
cache.cacheReady = true;
|
|
1677
1681
|
// Notify main thread that cache is ready
|
|
@@ -1694,7 +1698,7 @@ self.onmessage = function (ev) {
|
|
|
1694
1698
|
if (type === 'evolve') {
|
|
1695
1699
|
// Check if neighbor cache is ready before processing evolve
|
|
1696
1700
|
if (!cache.cacheReady) {
|
|
1697
|
-
|
|
1701
|
+
debugLog('[hexgrid-worker] ⏸️ Evolve message received but cache not ready yet - deferring...');
|
|
1698
1702
|
// Defer this evolve message by re-posting it after a short delay
|
|
1699
1703
|
setTimeout(() => {
|
|
1700
1704
|
try {
|
|
@@ -1714,7 +1718,7 @@ self.onmessage = function (ev) {
|
|
|
1714
1718
|
// Diagnostic: log that an evolve was received and the available payload keys (only when debugLogs enabled)
|
|
1715
1719
|
try {
|
|
1716
1720
|
if (workerDebug && workerDebug.debugLogs) {
|
|
1717
|
-
|
|
1721
|
+
debugLog('[hexgrid-worker] evolve received, payload keys=', Object.keys(payload || {}), 'workerDebug.evolutionIntervalMs=', workerDebug.evolutionIntervalMs, 'workerDebug.evolveIntervalMs=', workerDebug.evolveIntervalMs);
|
|
1718
1722
|
}
|
|
1719
1723
|
}
|
|
1720
1724
|
catch (e) { }
|
|
@@ -1724,12 +1728,12 @@ self.onmessage = function (ev) {
|
|
|
1724
1728
|
: typeof workerDebug.evolveIntervalMs === 'number'
|
|
1725
1729
|
? workerDebug.evolveIntervalMs
|
|
1726
1730
|
: 60000;
|
|
1727
|
-
|
|
1731
|
+
debugLog('[hexgrid-worker] Throttle check: interval=', interval, 'lastEvolutionAt=', lastEvolutionAt, 'now=', now, 'diff=', now - lastEvolutionAt, 'willThrottle=', now - lastEvolutionAt < interval);
|
|
1728
1732
|
// Throttle: if we're within the interval, notify (debug) and skip processing
|
|
1729
1733
|
const reason = payload.reason || (raw && raw.reason);
|
|
1730
1734
|
const bypassThrottle = reason === 'photos-init' || reason === 'reset';
|
|
1731
1735
|
// Clear, high-signal log for build verification: reports whether the current evolve will bypass the worker throttle
|
|
1732
|
-
|
|
1736
|
+
debugLog('[hexgrid-worker] THROTTLE DECISION', {
|
|
1733
1737
|
interval,
|
|
1734
1738
|
lastEvolutionAt,
|
|
1735
1739
|
now,
|
|
@@ -1740,7 +1744,7 @@ self.onmessage = function (ev) {
|
|
|
1740
1744
|
});
|
|
1741
1745
|
// Throttle: if we're within the interval and not bypassed, notify (debug) and skip processing
|
|
1742
1746
|
if (!bypassThrottle && now - lastEvolutionAt < interval) {
|
|
1743
|
-
|
|
1747
|
+
debugLog('[hexgrid-worker] ⛔ THROTTLED - skipping evolution processing');
|
|
1744
1748
|
if (workerDebug && workerDebug.debugLogs) {
|
|
1745
1749
|
try {
|
|
1746
1750
|
self.postMessage({
|
|
@@ -1759,7 +1763,7 @@ self.onmessage = function (ev) {
|
|
|
1759
1763
|
}
|
|
1760
1764
|
// Mark processed time and send ack for an evolve we will process
|
|
1761
1765
|
lastEvolutionAt = now;
|
|
1762
|
-
|
|
1766
|
+
debugLog('[hexgrid-worker] ✅ PROCESSING evolution - lastEvolutionAt updated to', now);
|
|
1763
1767
|
try {
|
|
1764
1768
|
if (workerDebug && workerDebug.debugLogs) {
|
|
1765
1769
|
try {
|
|
@@ -1800,12 +1804,12 @@ self.onmessage = function (ev) {
|
|
|
1800
1804
|
Boolean(payload.isSpherical) !== cache.isSpherical) {
|
|
1801
1805
|
invalidateCaches(Boolean(payload.isSpherical));
|
|
1802
1806
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1807
|
+
debugLog('[hexgrid-worker] 🔧 About to call evolveInfectionSystem');
|
|
1808
|
+
debugLog('[hexgrid-worker] - state generation:', state?.generation);
|
|
1809
|
+
debugLog('[hexgrid-worker] - state infections:', state?.infections?.length || state?.infections?.size || 0);
|
|
1810
|
+
debugLog('[hexgrid-worker] - positions:', positions?.length || 0);
|
|
1811
|
+
debugLog('[hexgrid-worker] - photos:', photos?.length || 0);
|
|
1812
|
+
debugLog('[hexgrid-worker] - hexRadius:', hexRadius);
|
|
1809
1813
|
let res;
|
|
1810
1814
|
let timeoutId;
|
|
1811
1815
|
let timedOut = false;
|
|
@@ -1822,12 +1826,12 @@ self.onmessage = function (ev) {
|
|
|
1822
1826
|
catch (e) { }
|
|
1823
1827
|
}, 10000);
|
|
1824
1828
|
try {
|
|
1825
|
-
|
|
1829
|
+
debugLog('[hexgrid-worker] 🚀 Calling evolveInfectionSystem NOW...');
|
|
1826
1830
|
const startTime = Date.now();
|
|
1827
1831
|
res = evolveInfectionSystem(state, positions, photos, hexRadius, now, !!workerDebug.debugLogs);
|
|
1828
1832
|
const elapsed = Date.now() - startTime;
|
|
1829
1833
|
clearTimeout(timeoutId);
|
|
1830
|
-
|
|
1834
|
+
debugLog('[hexgrid-worker] ✅ evolveInfectionSystem RETURNED successfully in', elapsed, 'ms');
|
|
1831
1835
|
}
|
|
1832
1836
|
catch (err) {
|
|
1833
1837
|
clearTimeout(timeoutId);
|
|
@@ -1840,10 +1844,10 @@ self.onmessage = function (ev) {
|
|
|
1840
1844
|
console.error('[hexgrid-worker] ⏱️ Function eventually returned but after timeout was triggered');
|
|
1841
1845
|
}
|
|
1842
1846
|
if (!res) {
|
|
1843
|
-
|
|
1847
|
+
debugLog('[hexgrid-worker] ❌ evolveInfectionSystem returned null!');
|
|
1844
1848
|
return;
|
|
1845
1849
|
}
|
|
1846
|
-
|
|
1850
|
+
debugLog('[hexgrid-worker] ✅ Evolution complete! New generation=', res.generation, 'infections=', res.infections.size);
|
|
1847
1851
|
try {
|
|
1848
1852
|
const payload = {
|
|
1849
1853
|
infections: Array.from(res.infections.entries()),
|
|
@@ -1853,7 +1857,7 @@ self.onmessage = function (ev) {
|
|
|
1853
1857
|
};
|
|
1854
1858
|
if (res.tileCenters && res.tileCenters.length > 0) {
|
|
1855
1859
|
payload.tileCenters = res.tileCenters;
|
|
1856
|
-
|
|
1860
|
+
debugLog('[hexgrid-worker] Including', res.tileCenters.length, 'tile center sets in evolved message');
|
|
1857
1861
|
}
|
|
1858
1862
|
self.postMessage({ type: 'evolved', data: payload });
|
|
1859
1863
|
// Record posted generation/infection count so later auto-triggers can avoid regressing
|
|
@@ -1866,7 +1870,7 @@ self.onmessage = function (ev) {
|
|
|
1866
1870
|
catch (e) {
|
|
1867
1871
|
console.error('[hexgrid-worker] ❌ Failed to post evolved message:', e);
|
|
1868
1872
|
}
|
|
1869
|
-
|
|
1873
|
+
debugLog('[hexgrid-worker] 📤 Posted evolved message back to main thread');
|
|
1870
1874
|
// Emit a completion marker so the client can confirm the evolve finished end-to-end
|
|
1871
1875
|
try {
|
|
1872
1876
|
if (workerDebug && workerDebug.debugLogs) {
|
|
@@ -2011,4 +2015,4 @@ function invalidateCaches(isSpherical) {
|
|
|
2011
2015
|
if (typeof isSpherical === 'boolean')
|
|
2012
2016
|
cache.isSpherical = isSpherical;
|
|
2013
2017
|
}
|
|
2014
|
-
|
|
2018
|
+
debugLog('[hexgrid-worker] ready');
|