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-timegenerated/*.ts (ts-rs) : plus aucun miroir TS écrit à la main ;
  • contrat runtime agnostique (façon template.json de 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 /narrateStubNarratorHttpNarrator
  • Publication — DownloadPublisherHttpPublisher

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/