Skip to content

Commit 3aaef66

Browse files
Support export
1 parent db9c9a3 commit 3aaef66

7 files changed

Lines changed: 530 additions & 0 deletions

File tree

public/app.mjs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { installAutomaginariumPacked } from "./generated/automate_packed_runtime.mjs";
22

33
await installAutomaginariumPacked();
4+
try {
5+
window.AutomaginariumUniversVivant = await import("./generated/automate_universel/browser_module.mjs");
6+
} catch {
7+
window.AutomaginariumUniversVivant = null;
8+
}
49

510
const PRESETS = [
611
{ id: "wolfram-30", label: "Wolfram 30" },
@@ -25,6 +30,8 @@ const ruleDetailView = document.querySelector("#rule-table-detail");
2530
const transitionSignals = document.querySelector("#transition-signals");
2631
const universeLiveSummary = document.querySelector("#universe-live-summary");
2732
const transitionLiveSummary = document.querySelector("#transition-live-summary");
33+
const processLiveSummary = document.querySelector("#process-live-summary");
34+
const processJsonView = document.querySelector("#process-json");
2835
const syncIndicatorDot = document.querySelector("#sync-indicator-dot");
2936
const syncIndicatorText = document.querySelector("#sync-indicator-text");
3037
const heroSyncDot = document.querySelector("#hero-sync-dot");
@@ -526,6 +533,7 @@ function render() {
526533

527534
function describe(config) {
528535
const description = window.AutomaginariumCore.describeConfiguration(config);
536+
const semanticSummary = window.AutomaginariumCore.resumerUniversVivant(config);
529537
title.textContent = description.title;
530538
meta.textContent = description.metaText;
531539
tableView.innerHTML = description.ruleTableHtml;
@@ -537,6 +545,12 @@ function describe(config) {
537545
if (transitionLiveSummary) {
538546
transitionLiveSummary.innerHTML = `<span class="live-summary-label">Reponse</span><strong>${window.AutomaginariumCore.summarizeTransition(config)}</strong>`;
539547
}
548+
if (processLiveSummary) {
549+
processLiveSummary.innerHTML = `<span class="live-summary-label">Comportement</span><strong>${semanticSummary.rule} | ${semanticSummary.topology} | ${semanticSummary.schedule}</strong>`;
550+
}
551+
if (processJsonView) {
552+
processJsonView.value = JSON.stringify(window.AutomaginariumCore.construireUniversVivant(config), null, 2);
553+
}
540554
}
541555

542556
function updateRuleSpaceDisplay(config) {
@@ -747,6 +761,13 @@ controls.importJson.addEventListener("change", async () => {
747761
document.querySelector("#export-json").addEventListener("click", () => {
748762
downloadText(`${state.config?.nom || "automaginarium"}.json`, JSON.stringify(state.config, null, 2));
749763
});
764+
document.querySelector("#export-process-json").addEventListener("click", () => {
765+
const manifest = window.AutomaginariumCore.construireUniversVivant(state.config);
766+
downloadText(`${state.config?.nom || "automaginarium"}.semantic-core-v1.json`, JSON.stringify(manifest, null, 2));
767+
});
768+
document.querySelector("#export-process-source").addEventListener("click", () => {
769+
downloadText(`${state.config?.nom || "automaginarium"}.process.multi`, window.AutomaginariumCore.sourceUniversVivant(state.config));
770+
});
750771
document.querySelector("#export-png").addEventListener("click", () => {
751772
const link = document.createElement("a");
752773
link.download = `${state.universe?.configuration.nom || "automaginarium"}.png`;

public/automate-core.js

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,208 @@ function transitionSignalEntries(configuration, limit = 6) {
851851
}));
852852
}
853853

854+
// Univers vivant browser mirror.
855+
//
856+
// Canonical source lives in src/automate_universel.multi:
857+
// - valeur_semantique
858+
// - resumer_univers_vivant
859+
// - construire_univers_vivant
860+
// - source_univers_vivant
861+
//
862+
// This JS block exists only because the static browser app still needs a rich
863+
// JSON/list/dictionary bridge until the Multilingual runtime can expose the
864+
// full core directly in the browser.
865+
function symbolToSemanticValue(symbol, alphabet) {
866+
const index = alphabet.findIndex((candidate) => String(candidate) === String(symbol));
867+
return {
868+
symbol,
869+
index: index >= 0 ? index : null,
870+
};
871+
}
872+
873+
function canonicalRuleEntries(configuration) {
874+
const config = normaliserConfiguration(configuration);
875+
const keys = toutesClesVoisinage(config.alphabet_entree, config.taille_voisinage);
876+
if (config.mode_regle === "table") {
877+
return keys.map((key) => {
878+
const voisinage = JSON.parse(key);
879+
return {
880+
neighborhood: voisinage.map((value) => symbolToSemanticValue(value, config.alphabet_entree)),
881+
output: normaliserSortie(
882+
config.table_transition[key] ?? config.table_transition[ancienneCleVoisinage(voisinage)] ?? sortieDefaut(config),
883+
config,
884+
).map((value) => symbolToSemanticValue(value, config.alphabet_sortie)),
885+
};
886+
});
887+
}
888+
if (config.mode_regle === "totalistique") {
889+
return keys.map((key) => {
890+
const voisinage = JSON.parse(key);
891+
return {
892+
neighborhood: voisinage.map((value) => symbolToSemanticValue(value, config.alphabet_entree)),
893+
output: transition_totalistique_compatible(voisinage, config).map((value) => symbolToSemanticValue(value, config.alphabet_sortie)),
894+
};
895+
});
896+
}
897+
return [];
898+
}
899+
900+
function transition_totalistique_compatible(voisinage, configuration) {
901+
const sum = voisinage.reduce((acc, value) => acc + Number(value), 0);
902+
const baseIndex = Number.isFinite(sum)
903+
? ((Math.trunc(sum) % configuration.alphabet_sortie.length) + configuration.alphabet_sortie.length) % configuration.alphabet_sortie.length
904+
: voisinage.length % configuration.alphabet_sortie.length;
905+
return Array.from(
906+
{ length: configuration.nombre_canaux_sortie },
907+
(_, channel) => configuration.alphabet_sortie[(baseIndex + channel) % configuration.alphabet_sortie.length],
908+
);
909+
}
910+
911+
function initialLociForSemanticCore(configuration) {
912+
const config = normaliserConfiguration(configuration);
913+
const ligne = creerGenerationInitiale(config);
914+
return ligne.map((value, x) => ({
915+
id: `cell_${x}`,
916+
locus: [x, 0],
917+
state: symbolToSemanticValue(value, config.alphabet_entree),
918+
}));
919+
}
920+
921+
function semanticCoreTier(configuration) {
922+
const config = normaliserConfiguration(configuration);
923+
if (config.mode_regle === "aleatoire") return 3;
924+
if (config.mode_regle === "numerique") return 2;
925+
if (config.taille_voisinage > 3 || config.alphabet_entree.length > 2 || config.nombre_canaux_sortie > 1) return 2;
926+
return 1;
927+
}
928+
929+
function semanticCoreTierName(tier) {
930+
return [
931+
"static",
932+
"elementary rewrite",
933+
"finite-state rewrite",
934+
"stochastic rewrite",
935+
"open-ended process",
936+
][tier] || "process";
937+
}
938+
939+
function semanticCoreSummary(configuration) {
940+
if (window.AutomaginariumUniversVivant?.resumer_univers_vivant) {
941+
return window.AutomaginariumUniversVivant.resumer_univers_vivant(configuration);
942+
}
943+
const config = normaliserConfiguration(configuration);
944+
const tier = semanticCoreTier(config);
945+
return {
946+
profile: "automaginarium-1d-ca-v1",
947+
tier,
948+
tier_name: semanticCoreTierName(tier),
949+
topology: `${config.largeur}x1 ${config.frontiere === "circulaire" ? "wrapped" : "bounded"} lattice`,
950+
schedule: `${Math.max(0, config.hauteur - 1)} synchronous step(s)`,
951+
rule: `${config.mode_regle} / neighborhood ${config.taille_voisinage} / ${config.nombre_canaux_sortie} channel(s)`,
952+
};
953+
}
954+
955+
function buildSemanticCoreV1(configurationBrute) {
956+
if (window.AutomaginariumUniversVivant?.construire_univers_vivant) {
957+
return window.AutomaginariumUniversVivant.construire_univers_vivant(configurationBrute);
958+
}
959+
const config = normaliserConfiguration(configurationBrute);
960+
const summary = semanticCoreSummary(config);
961+
const tableEntries = canonicalRuleEntries(config);
962+
return {
963+
kind: "semantic-core-v1",
964+
profile: summary.profile,
965+
name: config.nom,
966+
metadata: {
967+
generator: "Automaginarium",
968+
profile: summary.profile,
969+
tier: summary.tier,
970+
tier_name: summary.tier_name,
971+
notes: "Automaginarium 1D cellular-automaton profile for Multilingual 0.8 process tooling.",
972+
},
973+
state: {
974+
population: "fixed",
975+
empty: {
976+
state: symbolToSemanticValue(config.alphabet_entree[0], config.alphabet_entree),
977+
},
978+
alphabet: {
979+
input: config.alphabet_entree.map((value) => symbolToSemanticValue(value, config.alphabet_entree)),
980+
output: config.alphabet_sortie.map((value) => symbolToSemanticValue(value, config.alphabet_sortie)),
981+
},
982+
loci: initialLociForSemanticCore(config),
983+
},
984+
topology: {
985+
kind: "lattice",
986+
dimensionality: 1,
987+
width: config.largeur,
988+
height: 1,
989+
wrap: config.frontiere === "circulaire",
990+
neighborhood: "automaginarium-1d",
991+
radius: Math.floor(config.taille_voisinage / 2),
992+
boundary_value: symbolToSemanticValue(config.valeur_frontiere, config.alphabet_entree),
993+
},
994+
rule: {
995+
kind: "automaginarium-transition",
996+
mode: config.mode_regle,
997+
neighborhood_size: config.taille_voisinage,
998+
output_channels: config.nombre_canaux_sortie,
999+
numeric_rule: config.numero_regle !== undefined ? String(config.numero_regle) : undefined,
1000+
table: tableEntries,
1001+
},
1002+
schedule: {
1003+
kind: "synchronous",
1004+
steps: Math.max(0, config.hauteur - 1),
1005+
projection: "rows-as-time",
1006+
},
1007+
rendering: {
1008+
cell_size: config.rendu?.taille_cellule || 5,
1009+
palette: config.rendu?.palette || null,
1010+
gradient: config.rendu?.gradient || null,
1011+
},
1012+
source_configuration: {
1013+
...config,
1014+
numero_regle: config.numero_regle !== undefined ? String(config.numero_regle) : undefined,
1015+
},
1016+
};
1017+
}
1018+
1019+
function semanticCoreSource(configurationBrute) {
1020+
if (window.AutomaginariumUniversVivant?.source_univers_vivant) {
1021+
return window.AutomaginariumUniversVivant.source_univers_vivant(configurationBrute);
1022+
}
1023+
const config = normaliserConfiguration(configurationBrute);
1024+
const tableName = `${String(config.nom || "univers").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "") || "univers"}_rule`;
1025+
return [
1026+
"# Automaginarium semantic-core-v1 profile source",
1027+
"# Generated from the active Automaginarium configuration.",
1028+
"",
1029+
`let width = ${config.largeur}`,
1030+
`let steps = ${Math.max(0, config.hauteur - 1)}`,
1031+
`let alphabet_input = ${JSON.stringify(config.alphabet_entree)}`,
1032+
`let alphabet_output = ${JSON.stringify(config.alphabet_sortie)}`,
1033+
`let initial_loci = ${JSON.stringify(initialLociForSemanticCore(config))}`,
1034+
"",
1035+
`def ${tableName}():`,
1036+
` return automaginarium_transition(${JSON.stringify({
1037+
mode: config.mode_regle,
1038+
neighborhood_size: config.taille_voisinage,
1039+
output_channels: config.nombre_canaux_sortie,
1040+
table: canonicalRuleEntries(config),
1041+
})})`,
1042+
"",
1043+
"let process = build_process_core(",
1044+
" {\"loci\": initial_loci, \"population\": \"fixed\"},",
1045+
` lattice_topology(width, 1, ${config.frontiere === "circulaire" ? "True" : "False"}, "automaginarium-1d"),`,
1046+
` ${tableName}(),`,
1047+
" synchronous_schedule()",
1048+
")",
1049+
].join("\n");
1050+
}
1051+
1052+
const resumerUniversVivant = semanticCoreSummary;
1053+
const construireUniversVivant = buildSemanticCoreV1;
1054+
const sourceUniversVivant = semanticCoreSource;
1055+
8541056
function ruleSpaceLabel(configuration) {
8551057
const { s, k, t, m, maxRule } = ruleConfiguration(configuration);
8561058
return `${t}^(${m}·${s}^${k}) = ${maxRule} regles possibles`;
@@ -921,6 +1123,12 @@ window.AutomaginariumCore = {
9211123
ruleSpaceLabel,
9221124
summarizeConfig,
9231125
summarizeTransition,
1126+
semanticCoreSummary,
1127+
buildSemanticCoreV1,
1128+
semanticCoreSource,
1129+
resumerUniversVivant,
1130+
construireUniversVivant,
1131+
sourceUniversVivant,
9241132
universeHudMetrics,
9251133
randomRuleNumber,
9261134
cleVoisinage,

public/index.html

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ <h1 id="app-title">Automaginarium</h1>
7979
<button type="button" class="inspector-tab-btn" data-panel="gradients-panel" role="tab" aria-selected="false">D&eacute;grad&eacute;s</button>
8080
<button type="button" class="inspector-tab-btn active" data-panel="universe-panel" role="tab" aria-selected="true">Univers</button>
8181
<button type="button" class="inspector-tab-btn" data-panel="rules-panel" role="tab" aria-selected="false">R&egrave;gles</button>
82+
<button type="button" class="inspector-tab-btn" data-panel="process-panel" role="tab" aria-selected="false">Exporter</button>
8283
<button type="button" class="inspector-tab-btn" data-panel="json-panel" role="tab" aria-selected="false">JSON</button>
8384
<button type="button" class="inspector-tab-btn" data-panel="genetic-panel" role="tab" aria-selected="false">G&eacute;n&eacute;tique</button>
8485
<button type="button" class="inspector-tab-btn" data-panel="perturb-panel" role="tab" aria-selected="false">Perturbation</button>
@@ -260,6 +261,27 @@ <h2>Espace de transition</h2>
260261
</div>
261262
</div>
262263

264+
<div id="process-panel" class="inspector-panel" role="tabpanel">
265+
<div class="config-panel json-panel expert-panel">
266+
<div class="panel-heading inline-heading">
267+
<div>
268+
<p class="eyebrow">Univers vivant</p>
269+
<h2>Exporter le comportement</h2>
270+
<p class="meta">Sauvegardez l'univers courant avec ses états, ses règles et son évolution pour le rejouer ou l'explorer avec d'autres outils.</p>
271+
</div>
272+
<div class="button-row">
273+
<button id="export-process-json" type="button">Exporter univers</button>
274+
<button id="export-process-source" type="button">Exporter source</button>
275+
</div>
276+
</div>
277+
<div id="process-live-summary" class="live-summary-card">
278+
<span class="live-summary-label">Comportement</span>
279+
<strong>Univers exportable en attente</strong>
280+
</div>
281+
<textarea id="process-json" spellcheck="false" readonly aria-label="Données exportables de l'univers vivant"></textarea>
282+
</div>
283+
</div>
284+
263285
<div id="json-panel" class="inspector-panel" role="tabpanel">
264286
<div class="config-panel json-panel expert-panel">
265287
<div class="panel-heading inline-heading">

scripts/build_wasm_bundle.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
ROOT = Path(__file__).resolve().parents[1]
1515
GENERATED = ROOT / "public" / "generated"
1616
PACKED = GENERATED / "automate_packed"
17+
RICH_BROWSER = GENERATED / "automate_universel"
1718

1819

1920
def run(command: list[str]) -> None:
@@ -55,6 +56,23 @@ def build_french_core() -> None:
5556
)
5657
(GENERATED / "automate_universel.py").write_text(result.stdout, encoding="utf-8")
5758

59+
RICH_BROWSER.mkdir(parents=True, exist_ok=True)
60+
run(
61+
[
62+
sys.executable,
63+
"-m",
64+
"multilingualprogramming",
65+
"build-browser-module",
66+
"--lang",
67+
"fr",
68+
"src/automate_universel.multi",
69+
"--export",
70+
"resumer_univers_vivant,construire_univers_vivant,source_univers_vivant",
71+
"--out",
72+
str(RICH_BROWSER / "browser_module.mjs"),
73+
]
74+
)
75+
5876

5977
def build_packed_wasm() -> None:
6078
"""Build the narrow browser ABI and write WASM from the generated WAT."""

0 commit comments

Comments
 (0)