hypercube-compute 2.0.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.
@@ -0,0 +1,119 @@
1
+ import { TriadeMasterBuffer } from './TriadeMasterBuffer';
2
+ import { TriadeCubeV2 } from './TriadeCubeV2';
3
+ import type { ITriadeEngine } from '../engines/ITriadeEngine';
4
+
5
+ /**
6
+ * TriadeGrid gère un assemblage N x M de TriadeCubes adjacents.
7
+ * Il assure la communication "Boundary Exchange" (Ghost Cells) entre les cubes
8
+ * à la fin de chaque étape de calcul pour unifier la simulation.
9
+ */
10
+ export class TriadeGrid {
11
+ public cubes: (TriadeCubeV2 | null)[][] = [];
12
+ public readonly cols: number;
13
+ public readonly rows: number;
14
+ public readonly cubeSize: number;
15
+ public isPeriodic: boolean;
16
+
17
+ constructor(
18
+ cols: number,
19
+ rows: number,
20
+ cubeSize: number,
21
+ masterBuffer: TriadeMasterBuffer,
22
+ engineFactory: () => ITriadeEngine,
23
+ numFaces: number = 6,
24
+ isPeriodic: boolean = true
25
+ ) {
26
+ this.cols = cols;
27
+ this.rows = rows;
28
+ this.cubeSize = cubeSize;
29
+ this.isPeriodic = isPeriodic;
30
+
31
+ // Allocation de la grille de cubes
32
+ for (let y = 0; y < rows; y++) {
33
+ this.cubes[y] = [];
34
+ for (let x = 0; x < cols; x++) {
35
+ const cube = new TriadeCubeV2(cubeSize, masterBuffer, numFaces);
36
+ cube.setEngine(engineFactory());
37
+ this.cubes[y][x] = cube;
38
+ }
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Calcule une étape complète de la grille.
44
+ * 1. Exécute "compute()" sur chaque cube
45
+ * 2. Synchronise les bords (Boundary Exchange) sur les faces demandées
46
+ */
47
+ compute(facesToSynchronize: number | number[] = 0) {
48
+ // 1. Calcul (Intra-Cube)
49
+ for (let y = 0; y < this.rows; y++) {
50
+ for (let x = 0; x < this.cols; x++) {
51
+ this.cubes[y][x]?.compute();
52
+ }
53
+ }
54
+
55
+ // 2. Synchronisation des bords O(1) Data Copy
56
+ const faces = Array.isArray(facesToSynchronize) ? facesToSynchronize : [facesToSynchronize];
57
+ for (const f of faces) {
58
+ this.synchronizeBoundaries(f);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Recopie les vecteurs périphériques (1 pixel de profondeur) vers les bords des voisins.
64
+ */
65
+ private synchronizeBoundaries(f: number) {
66
+ const s = this.cubeSize;
67
+ const s_minus_1 = s - 1;
68
+ const s_minus_2 = s - 2;
69
+
70
+ // PASS 1: X-axis (Left/Right)
71
+ for (let y = 0; y < this.rows; y++) {
72
+ for (let x = 0; x < this.cols; x++) {
73
+ const cube = this.cubes[y][x]!;
74
+ const data = cube.faces[f];
75
+
76
+ // Right neighbor
77
+ if (x < this.cols - 1 || this.isPeriodic) {
78
+ const rightCube = this.cubes[y][(x + 1) % this.cols]!;
79
+ const rightData = rightCube.faces[f];
80
+ for (let row = 1; row < s_minus_1; row++) {
81
+ rightData[row * s + 0] = data[row * s + s_minus_2];
82
+ }
83
+ }
84
+
85
+ // Left neighbor
86
+ if (x > 0 || this.isPeriodic) {
87
+ const leftCube = this.cubes[y][(x - 1 + this.cols) % this.cols]!;
88
+ const leftData = leftCube.faces[f];
89
+ for (let row = 1; row < s_minus_1; row++) {
90
+ leftData[row * s + s_minus_1] = data[row * s + 1];
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ // PASS 2: Y-axis (Top/Bottom) - Copying full rows which perfectly transfers the corners (ghost columns from PASS 1)
97
+ // to diagonal neighbors automatically!
98
+ for (let y = 0; y < this.rows; y++) {
99
+ for (let x = 0; x < this.cols; x++) {
100
+ const cube = this.cubes[y][x]!;
101
+ const data = cube.faces[f];
102
+
103
+ // Bottom neighbor
104
+ if (y < this.rows - 1 || this.isPeriodic) {
105
+ const bottomCube = this.cubes[(y + 1) % this.rows][x]!;
106
+ const bottomData = bottomCube.faces[f];
107
+ bottomData.set(data.subarray(s_minus_2 * s, s_minus_2 * s + s), 0);
108
+ }
109
+
110
+ // Top neighbor
111
+ if (y > 0 || this.isPeriodic) {
112
+ const topCube = this.cubes[(y - 1 + this.rows) % this.rows][x]!;
113
+ const topData = topCube.faces[f];
114
+ topData.set(data.subarray(1 * s, 1 * s + s), s_minus_1 * s);
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,37 @@
1
+ export class TriadeMasterBuffer {
2
+ public readonly buffer: ArrayBuffer;
3
+ private offset: number = 0;
4
+
5
+ /**
6
+ * Alloue un unique bloc de mémoire vive (ArrayBuffer) pour l'ensemble du système.
7
+ * @param totalBytes Taille totale de la RAM allouée (par défaut 100 MB).
8
+ */
9
+ constructor(totalBytes: number = 100 * 1024 * 1024) {
10
+ this.buffer = new ArrayBuffer(totalBytes);
11
+ }
12
+
13
+ /**
14
+ * Alloue les octets nécessaires pour un Cube de 6 Faces en O(1) sans fragmentation.
15
+ * @param mapSize Résolution (ex: 400x400)
16
+ * @returns L'offset de départ dans l'ArrayBuffer
17
+ */
18
+ allocateCube(mapSize: number, numFaces: number = 6): number {
19
+ const floatsPerFace = mapSize * mapSize;
20
+ const bytesPerFace = floatsPerFace * Float32Array.BYTES_PER_ELEMENT; // 4 octets
21
+ const totalCubeBytes = bytesPerFace * numFaces;
22
+
23
+ if (this.offset + totalCubeBytes > this.buffer.byteLength) {
24
+ throw new Error(`[TriadeMasterBuffer] Out Of Memory. Impossible d'allouer ${totalCubeBytes} bytes supplémentaires.`);
25
+ }
26
+
27
+ const startOffset = this.offset;
28
+ this.offset += totalCubeBytes;
29
+
30
+ return startOffset;
31
+ }
32
+
33
+ /** Retourne la quantité de RAM consommée */
34
+ getUsedMemoryInMB(): string {
35
+ return (this.offset / (1024 * 1024)).toFixed(2) + ' MB';
36
+ }
37
+ }
@@ -0,0 +1,134 @@
1
+ import type { ITriadeEngine } from "./ITriadeEngine";
2
+
3
+ export class AerodynamicsEngine implements ITriadeEngine {
4
+ public dragScore: number = 0;
5
+ private initialized: boolean = false;
6
+
7
+ public get name(): string {
8
+ return "Lattice Boltzmann D2Q9 (O(1))";
9
+ }
10
+
11
+ public compute(faces: Float32Array[], mapSize: number): void {
12
+ const N = mapSize;
13
+ const obstacles = faces[18];
14
+ const ux_out = faces[19];
15
+ const uy_out = faces[20];
16
+ const curl_out = faces[21];
17
+
18
+ // Vecteurs du modèle D2Q9
19
+ const cx = [0, 1, 0, -1, 0, 1, -1, -1, 1];
20
+ const cy = [0, 0, 1, 0, -1, 1, 1, -1, -1];
21
+ const w = [4.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 36.0, 1.0 / 36.0, 1.0 / 36.0, 1.0 / 36.0];
22
+ const opp = [0, 3, 4, 1, 2, 7, 8, 5, 6]; // Rebonds opposés
23
+
24
+ const u0 = 0.12; // Vitesse de Mach 0.12 pour générer de sublimes tourbillons
25
+ const omega = 1.95; // Relaxation (Haute turbulence si proche de 2.0)
26
+
27
+ // 0. INITIALISATION (F_eq)
28
+ if (!this.initialized) {
29
+ for (let idx = 0; idx < N * N; idx++) {
30
+ const rho = 1.0;
31
+ const ux = u0; const uy = 0.0;
32
+ const u_sq = ux * ux + uy * uy;
33
+ for (let i = 0; i < 9; i++) {
34
+ const cu = cx[i] * ux + cy[i] * uy;
35
+ const feq = w[i] * rho * (1.0 + 3.0 * cu + 4.5 * cu * cu - 1.5 * u_sq);
36
+ faces[i][idx] = feq;
37
+ faces[i + 9][idx] = feq;
38
+ }
39
+ }
40
+ this.initialized = true;
41
+ }
42
+
43
+ let frameDrag = 0;
44
+
45
+ // 1. LBM CORE (Collision & Streaming O(1))
46
+ for (let y = 1; y < N - 1; y++) {
47
+ for (let x = 1; x < N - 1; x++) {
48
+ const idx = y * N + x;
49
+
50
+ if (obstacles[idx] > 0) {
51
+ // BOUNCE BACK: L'air se cogne contre l'Aileron et repart en arrière
52
+ for (let i = 1; i < 9; i++) {
53
+ const originX = x - cx[i];
54
+ const originY = y - cy[i];
55
+ const originIdx = originY * N + originX;
56
+ // Renvoi de la distribution
57
+ faces[opp[i] + 9][originIdx] = faces[i][originIdx];
58
+
59
+ // On mesure la puissance de l'impact direct (Portance/Traînée)
60
+ if (i === 1) frameDrag += faces[1][originIdx];
61
+ }
62
+ ux_out[idx] = 0;
63
+ uy_out[idx] = 0;
64
+ continue; // Skip the fluid equations for solid material
65
+ }
66
+
67
+ // Macroscopique : Densité et Vitesse
68
+ let rho = 0;
69
+ let ux = 0;
70
+ let uy = 0;
71
+ for (let i = 0; i < 9; i++) {
72
+ const f_val = faces[i][idx];
73
+ rho += f_val;
74
+ ux += cx[i] * f_val;
75
+ uy += cy[i] * f_val;
76
+ }
77
+
78
+ // INLET : Vent continu forcé à gauche (Tunnel Aerodynamique)
79
+ if (x === 1) {
80
+ ux = u0 * rho;
81
+ uy = 0.0;
82
+ }
83
+
84
+ if (rho > 0) {
85
+ ux /= rho;
86
+ uy /= rho;
87
+ }
88
+ ux_out[idx] = ux;
89
+ uy_out[idx] = uy;
90
+
91
+ // BGK COLLISION EQUATION
92
+ const u_sq = ux * ux + uy * uy;
93
+ for (let i = 0; i < 9; i++) {
94
+ const cu = cx[i] * ux + cy[i] * uy;
95
+ const feq = w[i] * rho * (1.0 + 3.0 * cu + 4.5 * cu * cu - 1.5 * u_sq);
96
+
97
+ // Relaxation de Navier-Stokes (Le secret profond du gaz)
98
+ const f_post = faces[i][idx] * (1.0 - omega) + feq * omega;
99
+
100
+ // STREAMING : Propagation vers les 8 voisins (+ le centre)
101
+ let nx = x + cx[i];
102
+ let ny = y + cy[i];
103
+
104
+ // Effet de bord (Enroulement du tunnel en haut / en bas)
105
+ if (ny < 1) ny = N - 2;
106
+ else if (ny > N - 2) ny = 1;
107
+
108
+ // OUTLET : Sortie libre (Gradient zero)
109
+ if (nx > N - 2) nx = N - 2;
110
+
111
+ faces[i + 9][ny * N + nx] = f_post;
112
+ }
113
+ }
114
+ }
115
+
116
+ // 2. SWAP BUFFERS
117
+ for (let i = 0; i < 9; i++) {
118
+ faces[i].set(faces[i + 9]);
119
+ }
120
+
121
+ // Exagération du calcul de drag pour UI
122
+ this.dragScore = this.dragScore * 0.9 + (frameDrag * 1000) * 0.1;
123
+
124
+ // 3. VORTICITY (Curl) POUR L'EFFET "WOW"
125
+ for (let y = 1; y < N - 1; y++) {
126
+ for (let x = 1; x < N - 1; x++) {
127
+ const idx = y * N + x;
128
+ const dUy_dx = uy_out[idx + 1] - uy_out[idx - 1];
129
+ const dUx_dy = ux_out[idx + N] - ux_out[idx - N];
130
+ curl_out[idx] = dUy_dx - dUx_dy;
131
+ }
132
+ }
133
+ }
134
+ }
@@ -0,0 +1,73 @@
1
+ import type { ITriadeEngine } from "./ITriadeEngine";
2
+
3
+ export class EcosystemEngineO1 implements ITriadeEngine {
4
+ public get name(): string {
5
+ return "Guerre des Triades (Rouge vs Bleu)";
6
+ }
7
+
8
+ public compute(faces: Float32Array[], mapSize: number): void {
9
+ const current = faces[1];
10
+ const next = faces[2];
11
+
12
+ // Automate Cellulaire Combat (Jeu de la vie + Bataille de Factions)
13
+ // STRICT O(1) : Sans mémoire asymétrique ni allocation.
14
+ for (let y = 0; y < mapSize; y++) {
15
+ for (let x = 0; x < mapSize; x++) {
16
+ const idx = y * mapSize + x;
17
+ const state = current[idx];
18
+
19
+ let blues = 0;
20
+ let reds = 0;
21
+
22
+ const yM = y > 0 ? y - 1 : mapSize - 1;
23
+ const yP = y < mapSize - 1 ? y + 1 : 0;
24
+ const xM = x > 0 ? x - 1 : mapSize - 1;
25
+ const xP = x < mapSize - 1 ? x + 1 : 0;
26
+
27
+ // Comptage de voisinage (Moore)
28
+ const n1 = current[yM * mapSize + xM]; if (n1 === 2) blues++; else if (n1 === 3) reds++;
29
+ const n2 = current[yM * mapSize + x]; if (n2 === 2) blues++; else if (n2 === 3) reds++;
30
+ const n3 = current[yM * mapSize + xP]; if (n3 === 2) blues++; else if (n3 === 3) reds++;
31
+ const n4 = current[y * mapSize + xM]; if (n4 === 2) blues++; else if (n4 === 3) reds++;
32
+ const n5 = current[y * mapSize + xP]; if (n5 === 2) blues++; else if (n5 === 3) reds++;
33
+ const n6 = current[yP * mapSize + xM]; if (n6 === 2) blues++; else if (n6 === 3) reds++;
34
+ const n7 = current[yP * mapSize + x]; if (n7 === 2) blues++; else if (n7 === 3) reds++;
35
+ const n8 = current[yP * mapSize + xP]; if (n8 === 2) blues++; else if (n8 === 3) reds++;
36
+
37
+ const total = blues + reds;
38
+
39
+ if (state !== 2 && state !== 3) { // Vide (ou anciens restes de map végétale)
40
+ // Règle de Naissance GOL (Exactement 3 parents vivants)
41
+ if (total === 3) {
42
+ next[idx] = (blues > reds) ? 2 : 3; // L'Allégeance de l'enfant va au camp majoritaire
43
+ }
44
+ // Renforts aéroportés aléatoires pour garantir l'agitation infinie (Noise factor)
45
+ else if (Math.random() < 0.0005) {
46
+ next[idx] = Math.random() < 0.5 ? 2 : 3;
47
+ }
48
+ else {
49
+ next[idx] = 0; // Reste un champ de bataille vide
50
+ }
51
+ }
52
+ else if (state === 2) { // Troupe BLEUE
53
+ // Combat: Frappe fatale ! Si 2 Rouges sont au contact, le Bleu se fait annihiler
54
+ if (reds >= 2) next[idx] = 0;
55
+ // Règle de Survie GOL classique (2 ou 3 camarades de vie)
56
+ else if (total === 2 || total === 3) next[idx] = 2;
57
+ // Isolement, ou Étouffement par surpopulation GOL
58
+ else next[idx] = 0;
59
+ }
60
+ else if (state === 3) { // Troupe ROUGE
61
+ // Combat: Frappe fatale ! Si 2 Bleus sont au contact, le Rouge se fait annihilé
62
+ if (blues >= 2) next[idx] = 0;
63
+ // Règle de Survie GOL classique
64
+ else if (total === 2 || total === 3) next[idx] = 3;
65
+ // Isolement ou Surpopulation
66
+ else next[idx] = 0;
67
+ }
68
+ }
69
+ }
70
+
71
+ current.set(next); // Synchronisation du Front O(1)
72
+ }
73
+ }
@@ -0,0 +1,61 @@
1
+ import type { ITriadeEngine } from "./ITriadeEngine";
2
+
3
+ export class GameOfLifeEngine implements ITriadeEngine {
4
+ public get name(): string {
5
+ return "Ecosystème Tensoriel (Plantes, Herbivores, Carnivores)";
6
+ }
7
+
8
+ public compute(faces: Float32Array[], mapSize: number): void {
9
+ const current = faces[1]; // Face 1: État actuel (t)
10
+ const next = faces[2]; // Face 2: État futur (t+1)
11
+
12
+ // Double boucle optimisée pour accès mémoires continus
13
+ for (let y = 0; y < mapSize; y++) {
14
+
15
+ const top = (y === 0) ? mapSize - 1 : y - 1;
16
+ const bottom = (y === mapSize - 1) ? 0 : y + 1;
17
+
18
+ const topRow = top * mapSize;
19
+ const midRow = y * mapSize;
20
+ const botRow = bottom * mapSize;
21
+
22
+ for (let x = 0; x < mapSize; x++) {
23
+ const left = (x === 0) ? mapSize - 1 : x - 1;
24
+ const right = (x === mapSize - 1) ? 0 : x + 1;
25
+
26
+ const idx = midRow + x;
27
+ const state = current[idx]; // 0: Vide, 1: Plante, 2: Herbi, 3: Carni
28
+
29
+ // Le prédateur / successeur de l'état actuel
30
+ let targetState = state + 1;
31
+ if (targetState > 3) targetState = 0;
32
+
33
+ let predators = 0;
34
+
35
+ // Von Neumann Neighborhood (Plus organique pour la croissance)
36
+ if (current[topRow + x] === targetState) predators++;
37
+ if (current[midRow + left] === targetState) predators++;
38
+ if (current[midRow + right] === targetState) predators++;
39
+ if (current[botRow + x] === targetState) predators++;
40
+
41
+ // Moore Neighborhood (Diagonales) avec moins de poids
42
+ if (current[topRow + left] === targetState) predators++;
43
+ if (current[topRow + right] === targetState) predators++;
44
+ if (current[botRow + left] === targetState) predators++;
45
+ if (current[botRow + right] === targetState) predators++;
46
+
47
+ // Seuil à 1 ou 2 selon l'état offre une dynamique d'essaim très organique
48
+ const threshold = (state === 0) ? 1 : 2; // Le vide est colonisé vite, les autres survivent plus
49
+
50
+ if (predators >= threshold) {
51
+ next[idx] = targetState;
52
+ } else {
53
+ next[idx] = state;
54
+ }
55
+ }
56
+ }
57
+
58
+ // Swap / Recopie mémoire ultra-rapide de l'état (t+1) vers (t)
59
+ current.set(next);
60
+ }
61
+ }
@@ -0,0 +1,60 @@
1
+ import type { ITriadeEngine } from './ITriadeEngine';
2
+
3
+ export class HeatmapEngine implements ITriadeEngine {
4
+ public readonly name = "Heatmap (O1 Spatial Convolution)";
5
+ public radius: number;
6
+ public weight: number;
7
+
8
+ /**
9
+ * @param radius Rayon d'influence en cellules
10
+ * @param weight Coefficient multiplicateur à l'arrivée
11
+ */
12
+ constructor(radius: number = 10, weight: number = 1.0) {
13
+ this.radius = radius;
14
+ this.weight = weight;
15
+ }
16
+
17
+ /**
18
+ * Exécute le Summed Area Table Algorithm (Face 5) suivi
19
+ * d'un Box Filter O(1) vers la Synthèse (Face 3).
20
+ */
21
+ compute(faces: Float32Array[], mapSize: number): void {
22
+ const face2 = faces[1]; // Contexte Binaire d'entrée
23
+ const face3 = faces[2]; // Synthèse de Diffusion
24
+ const face5 = faces[4]; // Cheat-code O(1) SAT
25
+
26
+ // 1. O(N) : Génération Cristallisée (Integral Image)
27
+ for (let y = 0; y < mapSize; y++) {
28
+ for (let x = 0; x < mapSize; x++) {
29
+ const idx = y * mapSize + x;
30
+ const val = face2[idx];
31
+ const top = y > 0 ? face5[(y - 1) * mapSize + x] : 0;
32
+ const left = x > 0 ? face5[y * mapSize + (x - 1)] : 0;
33
+ const topLeft = (y > 0 && x > 0) ? face5[(y - 1) * mapSize + (x - 1)] : 0;
34
+
35
+ face5[idx] = val + top + left - topLeft;
36
+ }
37
+ }
38
+
39
+ // 2. O(N) : Extraction d'Influence Indépendante du Rayon
40
+ for (let y = 0; y < mapSize; y++) {
41
+ for (let x = 0; x < mapSize; x++) {
42
+ // Clamping (Borne Map) rapide
43
+ const minX = Math.max(0, x - this.radius);
44
+ const minY = Math.max(0, y - this.radius);
45
+ const maxX = Math.min(mapSize - 1, x + this.radius);
46
+ const maxY = Math.min(mapSize - 1, y + this.radius);
47
+
48
+ // Récupération O(1) des Opcodes d'angles
49
+ const A = (minX > 0 && minY > 0) ? face5[(minY - 1) * mapSize + (minX - 1)] : 0;
50
+ const B = (minY > 0) ? face5[(minY - 1) * mapSize + maxX] : 0;
51
+ const C = (minX > 0) ? face5[maxY * mapSize + (minX - 1)] : 0;
52
+ const D = face5[maxY * mapSize + maxX];
53
+
54
+ // Différence des coins
55
+ const sum = D - B - C + A;
56
+ face3[y * mapSize + x] = sum * this.weight;
57
+ }
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Type alias to explicitly signal to AI that this array is a Flat Tensor (1D array simulating 2D/3D).
3
+ * @AI_RULE DO NOT iterate using functional array methods (map, forEach, reduce). DO NOT use `push` or `splice`.
4
+ */
5
+ export type FlatTensorView = Float32Array;
6
+
7
+ /**
8
+ * Interface fondamentale (Le Contract) pour tout algorithme physique dans Triade Engine.
9
+ * Implémentée par les moteurs LBM, GameOfLife, SAT.
10
+ */
11
+ export interface ITriadeEngine {
12
+ /**
13
+ * @description Exécute un pas de simulation mathématique à l'intérieur d'un TriadeCube.
14
+ * @param faces Les 6 (ou N) "Views" (FlatTensorView) pointant vers la VRAM continue.
15
+ * @param mapSize La dimension N (N x N) de ce chunk spatial.
16
+ *
17
+ * @AI_RULE CRITICAL_PERFORMANCE: This method runs inside a hot loop (like 60 FPS).
18
+ * @AI_RULE ZERO_ALLOCATION: DO NOT instantiate any object (`new Object()`, `[]`, `{}`) inside this scope to avoid Garbage Collection.
19
+ * @AI_RULE MEMORY_ACCESS: Use 1D sequential iterations `for (let i = 0; i < length; i++)`. Read/Write values via `faces[index][i]`. Map 2D coords using `index = y * mapSize + x`.
20
+ * @AI_RULE ERROR_HANDLING: If generating errors, be highly verbose for self-correction. Example: `throw new Error("[Triade] Out of bounds: index ${i} exceeds size ${length}")`
21
+ */
22
+ compute(faces: FlatTensorView[], mapSize: number): void;
23
+
24
+ /**
25
+ * Identifiant explicite du moteur algorithmique.
26
+ * @AI_RULE This MUST match the semantic context of the Engine (e.g., 'Navier-Stokes-LBM-D2Q9', 'Game-Of-Life').
27
+ */
28
+ get name(): string;
29
+ }
package/src/index.ts ADDED
@@ -0,0 +1,26 @@
1
+ export * from './Triade';
2
+
3
+ // Core
4
+ export * from './core/TriadeCubeV2';
5
+ export * from './core/TriadeGrid';
6
+ export * from './core/TriadeMasterBuffer';
7
+
8
+ // Engines
9
+ export * from './engines/ITriadeEngine';
10
+ export * from './engines/AerodynamicsEngine';
11
+ export * from './engines/EcosystemEngineO1';
12
+ export * from './engines/GameOfLifeEngine';
13
+ export * from './engines/HeatmapEngine';
14
+
15
+ // IO / Adapters
16
+ export * from './io/CanvasAdapter';
17
+ export * from './io/WebGLAdapter';
18
+
19
+ // Addons
20
+ export * from './addons/ocean-simulation/OceanEngine';
21
+ export { OceanSimulatorAddon } from './addons/ocean-simulation/OceanSimulatorAddon';
22
+ export * from './addons/ocean-simulation/OceanWebGLRenderer';
23
+ export * from './addons/ocean-simulation/OceanWorld';
24
+
25
+ // Templates
26
+ export * from './templates/BlankEngine';
@@ -0,0 +1,41 @@
1
+ export class CanvasAdapter {
2
+ /**
3
+ * Lit un Tenseur Plat (Face de Float32Array) et le peint sur un contexte Canvas Native.
4
+ * Cette interface sépare la logique de rendu (UI) du moteur mathématique (Triade).
5
+ */
6
+ static renderFaceToCanvas(
7
+ faceData: Float32Array,
8
+ mapSize: number,
9
+ ctx: CanvasRenderingContext2D,
10
+ options: { colorScheme: 'heat' | 'grayscale', normalizeMax?: number } = { colorScheme: 'grayscale' }
11
+ ) {
12
+ const imgData = ctx.getImageData(0, 0, mapSize, mapSize);
13
+ const data = imgData.data;
14
+
15
+ // Auto-normalization si l'utilisateur ne connait pas le Max possible de sa matrice.
16
+ let max = options.normalizeMax || 0.0001;
17
+ if (!options.normalizeMax) {
18
+ for (let i = 0; i < faceData.length; i++) {
19
+ if (faceData[i] > max) max = faceData[i];
20
+ }
21
+ }
22
+
23
+ // Colorisation O(N)
24
+ for (let i = 0; i < faceData.length; i++) {
25
+ const val = faceData[i] / max;
26
+ const p = i * 4;
27
+
28
+ if (options.colorScheme === 'heat') {
29
+ data[p] = val * 255; // R
30
+ data[p + 1] = (val > 0.5 ? (val - 0.5) * 510 : 0); // G (jaunit si forte intensité)
31
+ data[p + 2] = val * 50; // B
32
+ data[p + 3] = 255; // Alpha
33
+ } else {
34
+ const c = val * 255;
35
+ data[p] = c; data[p + 1] = c; data[p + 2] = c;
36
+ data[p + 3] = 255;
37
+ }
38
+ }
39
+ ctx.putImageData(imgData, 0, 0);
40
+ }
41
+ }