Skip to content

Commit ba68e1b

Browse files
Refactor code
1 parent ddfd681 commit ba68e1b

4 files changed

Lines changed: 252 additions & 172 deletions

File tree

public/app.mjs

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { installAutomaginariumPacked } from "./generated/automate_packed_runtime.mjs";
2+
import { generateRandomTable, generateSymmetricTable, generateTotalisticTable } from "./rule-generation.mjs";
23

34
await installAutomaginariumPacked();
45

@@ -96,49 +97,10 @@ function encodeList(values) {
9697
return (values || []).join(",");
9798
}
9899

99-
function allNeighborhoodKeys(alphabet, size) {
100-
return window.AutomaginariumCore.toutesClesVoisinage(alphabet, size);
101-
}
102-
103-
function parseNeighborhoodKey(key) {
104-
try {
105-
const parsed = JSON.parse(key);
106-
return Array.isArray(parsed) ? parsed : key.split("");
107-
} catch (error) {
108-
return key.split("");
109-
}
110-
}
111100

112-
function randomOutput(config) {
113-
return Array.from(
114-
{ length: config.nombre_canaux_sortie },
115-
() => config.alphabet_sortie[Math.floor(Math.random() * config.alphabet_sortie.length)],
116-
);
117-
}
118-
119-
function generateRandomTable(config) {
120-
return Object.fromEntries(allNeighborhoodKeys(config.alphabet_entree, config.taille_voisinage).map((key) => [key, randomOutput(config)]));
121-
}
122-
123-
function generateSymmetricTable(config) {
124-
const table = {};
125-
allNeighborhoodKeys(config.alphabet_entree, config.taille_voisinage).forEach((key) => {
126-
const mirror = JSON.stringify(parseNeighborhoodKey(key).reverse());
127-
if (table[key]) return;
128-
const output = randomOutput(config);
129-
table[key] = output;
130-
table[mirror] = output;
131-
});
132-
return table;
133-
}
134-
135-
function generateTotalisticTable(config) {
136-
return Object.fromEntries(allNeighborhoodKeys(config.alphabet_entree, config.taille_voisinage).map((key) => {
137-
const sum = parseNeighborhoodKey(key).reduce((acc, value) => acc + Number(value), 0);
138-
const index = Number.isFinite(sum) ? Math.abs(sum) % config.alphabet_sortie.length : key.length % config.alphabet_sortie.length;
139-
return [key, Array.from({ length: config.nombre_canaux_sortie }, (_, channel) => config.alphabet_sortie[(index + channel) % config.alphabet_sortie.length])];
140-
}));
141-
}
101+
// Rule generation moved to rule-generation.mjs
102+
// These functions are temporary and will eventually call compiled ML functions.
103+
// See rule-generation.mjs for details.
142104

143105
function controlsToConfig() {
144106
const inputAlphabet = parseList(controls.alphabetInput.value, [0, 1]);

public/automate-core.js

Lines changed: 116 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
/*
2-
* Automaginarium browser adapter.
2+
* Automaginarium browser bridge.
33
*
4-
* This small JavaScript mirror keeps the browser demo usable until
5-
* src/automate_universel.ml is compiled through Multilingual. JavaScript
6-
* should remain an adapter: configuration in, generated universe out.
4+
* This adapter forwards calls to the compiled French Multilingual core.
5+
* All domain logic (cellular automata, rules, generation) is canonical in
6+
* src/automate_universel.ml. These functions are temporary fallbacks/mirrors
7+
* until the Multilingual→WASM compilation pipeline is live.
8+
*
9+
* JavaScript serves only: canvas rendering, DOM, interface events.
10+
* All cellular automata logic belongs in src/.
711
*/
812

913
function mulberry32(seed) {
@@ -17,6 +21,43 @@ function mulberry32(seed) {
1721
};
1822
}
1923

24+
function callPacked(name, args, fallback) {
25+
const packed = window.AutomaginariumPacked;
26+
if (packed && typeof packed[name] === "function") {
27+
try {
28+
const value = Number(packed[name](...args));
29+
if (Number.isFinite(value)) return value;
30+
} catch (error) {
31+
console.warn(`Automaginarium: WASM export ${name} failed; using fallback.`, error);
32+
}
33+
}
34+
return fallback;
35+
}
36+
37+
// ============================================================================
38+
// TEMPORARY: These functions are duplicated from src/automate_universel.ml.
39+
// They are fallbacks until the ML is compiled to WASM/JS.
40+
// DO NOT ADD NEW LOGIC HERE. Update src/automate_universel.ml instead.
41+
// ============================================================================
42+
43+
function cleVoisinage(voisinage) {
44+
return JSON.stringify(voisinage);
45+
}
46+
47+
function ancienneCleVoisinage(voisinage) {
48+
return voisinage.map(String).join("");
49+
}
50+
51+
function sortieDefaut(configuration) {
52+
return Array.from({ length: configuration.nombre_canaux_sortie }, () => configuration.alphabet_sortie[0] ?? 0);
53+
}
54+
55+
function normaliserSortie(sortie, configuration) {
56+
const valeurs = Array.isArray(sortie) ? [...sortie] : [sortie];
57+
while (valeurs.length < configuration.nombre_canaux_sortie) valeurs.push(valeurs[0] ?? configuration.alphabet_sortie[0]);
58+
return valeurs.slice(0, configuration.nombre_canaux_sortie);
59+
}
60+
2061
function normaliserConfiguration(configuration) {
2162
const alphabetEntree = Array.isArray(configuration.alphabet_entree) && configuration.alphabet_entree.length > 0
2263
? configuration.alphabet_entree
@@ -46,18 +87,6 @@ function normaliserConfiguration(configuration) {
4687
};
4788
}
4889

49-
function cleVoisinage(voisinage) {
50-
return JSON.stringify(voisinage);
51-
}
52-
53-
function ancienneCleVoisinage(voisinage) {
54-
return voisinage.map(String).join("");
55-
}
56-
57-
function sortieDefaut(configuration) {
58-
return Array.from({ length: configuration.nombre_canaux_sortie }, () => configuration.alphabet_sortie[0] ?? 0);
59-
}
60-
6190
function lireCellule(ligne, indice, configuration) {
6291
if (indice >= 0 && indice < ligne.length) return ligne[indice];
6392
if (configuration.frontiere === "circulaire" && ligne.length > 0) {
@@ -75,6 +104,76 @@ function voisinageCellule(ligne, indice, configuration) {
75104
return voisinage;
76105
}
77106

107+
function ruleConfiguration(configuration) {
108+
const s = configuration.alphabet_entree.length;
109+
const k = configuration.taille_voisinage;
110+
const t = configuration.alphabet_sortie.length;
111+
const m = configuration.nombre_canaux_sortie;
112+
const base = Math.pow(t, m);
113+
const digits = Math.pow(s, k);
114+
const baseBig = BigInt(Math.round(base));
115+
const digitsBig = BigInt(Math.round(digits));
116+
const maxRule = baseBig ** digitsBig;
117+
return { s, k, t, m, base, digits, maxRule };
118+
}
119+
120+
function neighborhoodToRuleIndex(voisinage, alphabet) {
121+
const s = alphabet.length;
122+
let index = 0;
123+
for (let i = 0; i < voisinage.length; i += 1) {
124+
const symbolIndex = alphabet.indexOf(voisinage[i]);
125+
if (symbolIndex < 0) return null;
126+
index = index * s + symbolIndex;
127+
}
128+
return index;
129+
}
130+
131+
function ruleIndexToNeighborhood(index, config) {
132+
const { s, k } = ruleConfiguration(config);
133+
const voisinage = [];
134+
let remaining = index;
135+
for (let i = k - 1; i >= 0; i -= 1) {
136+
const symbolIndex = Math.floor(remaining / Math.pow(s, i));
137+
voisinage.push(config.alphabet_entree[symbolIndex]);
138+
remaining = remaining % Math.pow(s, i);
139+
}
140+
return voisinage;
141+
}
142+
143+
function outputToRuleDigit(output, config) {
144+
const { t, m } = ruleConfiguration(config);
145+
const normalized = normaliserSortie(output, config);
146+
let digit = 0;
147+
for (let ch = 0; ch < m; ch += 1) {
148+
const symbolIndex = config.alphabet_sortie.indexOf(normalized[ch]);
149+
if (symbolIndex < 0) return null;
150+
digit = digit * t + symbolIndex;
151+
}
152+
return digit;
153+
}
154+
155+
function ruleDigitToOutput(digit, config) {
156+
const { t, m } = ruleConfiguration(config);
157+
const output = [];
158+
let remaining = digit;
159+
for (let ch = m - 1; ch >= 0; ch -= 1) {
160+
const symbolIndex = Math.floor(remaining / Math.pow(t, ch));
161+
output.push(config.alphabet_sortie[symbolIndex]);
162+
remaining = remaining % Math.pow(t, ch);
163+
}
164+
return output;
165+
}
166+
167+
function getRuleOutput(ruleNumber, voisinage, config) {
168+
const index = neighborhoodToRuleIndex(voisinage, config.alphabet_entree);
169+
if (index === null) return sortieDefaut(config);
170+
const { base } = ruleConfiguration(config);
171+
const baseBig = BigInt(base);
172+
const ruleNumberBig = BigInt(ruleNumber);
173+
const digit = Number((ruleNumberBig / (baseBig ** BigInt(index))) % baseBig);
174+
return ruleDigitToOutput(digit, config);
175+
}
176+
78177
function appliquerRegle(voisinage, configuration, random) {
79178
if (configuration.mode_regle === "numerique") {
80179
return getRuleOutput(configuration.numero_regle ?? 0n, voisinage, configuration);
@@ -162,112 +261,6 @@ function tableWolfram(numeroRegle) {
162261
return table;
163262
}
164263

165-
function ruleConfiguration(configuration) {
166-
const s = configuration.alphabet_entree.length; // size of input alphabet
167-
const k = configuration.taille_voisinage; // neighborhood size
168-
const t = configuration.alphabet_sortie.length; // size of output alphabet
169-
const m = configuration.nombre_canaux_sortie; // number of output channels
170-
const base = Math.pow(t, m);
171-
const digits = Math.pow(s, k);
172-
// Use BigInt for maxRule to handle large numbers correctly
173-
const baseBig = BigInt(Math.round(base));
174-
const digitsBig = BigInt(Math.round(digits));
175-
const maxRule = baseBig ** digitsBig;
176-
return { s, k, t, m, base, digits, maxRule };
177-
}
178-
179-
function neighborhoodToRuleIndex(voisinage, alphabet) {
180-
const s = alphabet.length;
181-
let index = 0;
182-
for (let i = 0; i < voisinage.length; i += 1) {
183-
const symbolIndex = alphabet.indexOf(voisinage[i]);
184-
if (symbolIndex < 0) return null;
185-
index = index * s + symbolIndex;
186-
}
187-
return index;
188-
}
189-
190-
function ruleIndexToNeighborhood(index, config) {
191-
const { s, k } = ruleConfiguration(config);
192-
const voisinage = [];
193-
let remaining = index;
194-
for (let i = k - 1; i >= 0; i -= 1) {
195-
const symbolIndex = Math.floor(remaining / Math.pow(s, i));
196-
voisinage.push(config.alphabet_entree[symbolIndex]);
197-
remaining = remaining % Math.pow(s, i);
198-
}
199-
return voisinage;
200-
}
201-
202-
function outputToRuleDigit(output, config) {
203-
const { t, m } = ruleConfiguration(config);
204-
const normalized = normaliserSortie(output, config);
205-
let digit = 0;
206-
for (let ch = 0; ch < m; ch += 1) {
207-
const symbolIndex = config.alphabet_sortie.indexOf(normalized[ch]);
208-
if (symbolIndex < 0) return null;
209-
digit = digit * t + symbolIndex;
210-
}
211-
return digit;
212-
}
213-
214-
function ruleDigitToOutput(digit, config) {
215-
const { t, m } = ruleConfiguration(config);
216-
const output = [];
217-
let remaining = digit;
218-
for (let ch = m - 1; ch >= 0; ch -= 1) {
219-
const symbolIndex = Math.floor(remaining / Math.pow(t, ch));
220-
output.push(config.alphabet_sortie[symbolIndex]);
221-
remaining = remaining % Math.pow(t, ch);
222-
}
223-
return output;
224-
}
225-
226-
function getRuleOutput(ruleNumber, voisinage, config) {
227-
const index = neighborhoodToRuleIndex(voisinage, config.alphabet_entree);
228-
if (index === null) return sortieDefaut(config);
229-
230-
const { base } = ruleConfiguration(config);
231-
const baseBig = BigInt(base);
232-
const ruleNumberBig = BigInt(ruleNumber);
233-
234-
const digit = Number((ruleNumberBig / (baseBig ** BigInt(index))) % baseBig);
235-
return ruleDigitToOutput(digit, config);
236-
}
237-
238-
function callPacked(name, args, fallback) {
239-
const packed = window.AutomaginariumPacked;
240-
if (packed && typeof packed[name] === "function") {
241-
try {
242-
const value = Number(packed[name](...args));
243-
if (Number.isFinite(value)) return value;
244-
} catch (error) {
245-
console.warn(`Automaginarium: WASM export ${name} failed; using fallback.`, error);
246-
}
247-
}
248-
return fallback;
249-
}
250-
251-
function codeVoisinageNumerique(voisinage, tailleAlphabet) {
252-
if (!voisinage.every((value) => Number.isInteger(Number(value)))) return null;
253-
const values = voisinage.map(Number);
254-
if (values.length === 3) {
255-
return Math.trunc(callPacked(
256-
"code_voisinage_3_base",
257-
[values[0], values[1], values[2], tailleAlphabet],
258-
values.reduce((code, value) => code * tailleAlphabet + value, 0),
259-
));
260-
}
261-
if (values.length === 5) {
262-
return Math.trunc(callPacked(
263-
"code_voisinage_5_base",
264-
[values[0], values[1], values[2], values[3], values[4], tailleAlphabet],
265-
values.reduce((code, value) => code * tailleAlphabet + value, 0),
266-
));
267-
}
268-
return values.reduce((code, value) => code * tailleAlphabet + value, 0);
269-
}
270-
271264
function toutesClesVoisinage(alphabet, taille) {
272265
const keys = [];
273266
function visit(prefix, depth) {
@@ -281,12 +274,6 @@ function toutesClesVoisinage(alphabet, taille) {
281274
return keys;
282275
}
283276

284-
function normaliserSortie(sortie, configuration) {
285-
const valeurs = Array.isArray(sortie) ? [...sortie] : [sortie];
286-
while (valeurs.length < configuration.nombre_canaux_sortie) valeurs.push(valeurs[0] ?? configuration.alphabet_sortie[0]);
287-
return valeurs.slice(0, configuration.nombre_canaux_sortie);
288-
}
289-
290277
function validerConfiguration(configurationBrute) {
291278
const config = normaliserConfiguration(configurationBrute);
292279
const erreurs = [];
@@ -321,8 +308,8 @@ window.AutomaginariumCore = {
321308
cleVoisinage,
322309
toutesClesVoisinage,
323310
validerConfiguration,
324-
codeVoisinageNumerique,
325311
ruleConfiguration,
326312
getRuleOutput,
327313
mulberry32,
314+
callPacked,
328315
};

0 commit comments

Comments
 (0)