Documentation
Architecture du moteur narratif
Lecture DDD / hexagonale. Le moteur (cn-core, Rust pur) est
la référence des données : les consommateurs lisent son
schéma et s’adaptent — jamais l’inverse. Le cœur est compilable en WASM
et en natif ; il n’a aucune I/O.
Les couches
- Domaine —
cn-core - Modèle et règles purs, sans I/O : état (event-sourced), directeur, verifier, catalogue de gestes, contrat. C’est l’autorité du modèle de données.
- Adaptateurs
-
cn-wasm(port JS), l’orchestration TypeScript, les pages Astro. Ils n’inventent aucune donnée : ils consomment ce que le domaine publie.
Le schéma publié (« dpc »)
Émis depuis le domaine et versionné (build et déploiement sans toolchain Rust), sous trois formes :
- types compile-time →
generated/*.ts(ts-rs) : plus aucun miroir TS écrit à la main ; - contrat runtime agnostique (façon
template.jsonde FoundryVTT) →generated/contract.json: les « possibles » (catalogue de gestes, domaines d’enums, bornes, version) que tout consommateur lit pour se data-driver ; - JSON Schema de validation (schemars) →
generated/schema.json: la « dpc » contre laquelle tout consommateur non-Rust valide. Les types internes porteurs de canon n’y figurent pas — le mur tient jusque dans le schéma.
Les deux murs
Mur d’entrée — le paquet
packet.rs est le contrat CN ↔ narrateur. Seul le
paquet canon-free franchit la frontière vers le
narrateur ; le canon (le secret) ne quitte jamais le moteur. Le mur est
structurel (aucun champ canon, champs surnuméraires
rejetés), pas sémantique — la fuite de sens est coupée par le verifier.
Mur de sortie — la membrane
Engine::export décide ce qui sort après le jeu.
Un secret resté caché exige une décision éditeur : révéler
(seule la formulation de l’éditeur sort) ou retirer (rien ne
sort). L’export refuse tant qu’un secret est indécis.
La boucle
Engine::restore(snapshot?) -> Engine
engine.prepare(action) -> Prepared { packet, n } // directeur → paquet canon-free
engine.resolve(candidates) -> Outcome // verifier → Commit | ResampleNeeded
engine.snapshot() -> bytes Le verifier écarte le candidat qui fuiterait le secret ou contredirait le canon. Un tour « tous invalides » déclenche un resample invisible avant le commit — la couture reste propre côté joueur.
Coutures externes
Résolues par runtime-config.ts : sans configuration, on retombe
sur des stubs (démo) ; avec configuration, sur des implémentations HTTP —
aucun changement de code. Le token (portefeuille) n’est jamais en build : il
vit en localStorage.
- Hub
/narrate—StubNarrator→HttpNarrator - Publication —
DownloadPublisher→HttpPublisher
Build & WASM
Le pkg/ WASM (glue JS + .wasm) et les
generated/ sont versionnés : builder ou
déployer le site n’exige aucune toolchain Rust. On ne les régénère que
lorsque le code Rust change.
pnpm gen:types # réécrit generated/ depuis les types Rust (ts-rs)
pnpm test:engine # cargo test du crate
pnpm build:engine # tests + (re)build wasm → src/scripts/narrative/pkg/