# Composition et coordination

Entrée générique `alloframe/composition`, réexportée depuis `alloframe`.
Adaptateur navigateur `createActorComposition` dans `alloframe/actors`.

## Responsabilités

Le contenu reste une déclaration `composition`, la présentation une déclaration
`decor`, et les usages d’acteurs/scénarios/directeur/chorégraphies une déclaration
`staging`. Le coordinateur utilise seulement les façades publiques des instances.
Les définitions chargées ne sont pas modifiées. Chaque création possède ses
instances et abonnements ; mêmes IDs autorisés entre deux compositions indépendantes.

Les signatures ci-dessous décrivent l’API publique de composition.

## API

```js
import {createActorComposition} from 'alloframe';
const result = createActorComposition({
  loaded,                         // résultat disponible de loadComposition
  id: 'composition_main_view',
  reference: 'demo:composition_main',
  staging: 'demo:staging_main',     // référence explicite, facultative
  // decor: 'demo:decor_main',
  containers: {actor_audio_view: audioBox, actor_image_view: imageBox},
  reactions: [{
    id: 'reaction_end', source: 'actor_audio_view', event: 'ended',
    actions: {instance_id: 'actor_image_view', command: 'presentationSet',
      args: [{alt: 'Lecture terminée'}]},
  }],
});
if (!result.ok) throw new Error(JSON.stringify(result.diagnostics));
const composition = result.value;
if ((await composition.init()).ok) await composition.activate();
await composition.command('actor_audio_view', 'play');
await composition.stop();
await composition.destroy();
```

| Membre | Fonction |
|---|---|
| `id`, `definition`, `status` | Identité locale, définition partagée, cycle des instances |
| `init/activate/stop/destroy()` | Résultats asynchrones diagnostiqués ; appels identiques en cours partagent leur promesse |
| `getInstance(instance_id)` | Façade exacte du registre local ; undefined après destruction |
| `listInstances()` | Copie figée du registre vivant de la composition |
| `inspect()` | Instantané figé du cycle, membres, ressources et diagnostics |
| `command(instance_id, name, ...args)` | Commande publique ciblée |
| `execute(plan)` | Plan de commandes, séquence ou parallèle explicite |

Les options communes sont `loaded`, `id`, `reference`, `namespace?`, `staging?`,
`decor?`, `contracts?`, `reactions?`, `timeouts?` et `commandTimeout?`.
`commandTimeout` vaut 10 000 ms par commande ; il doit être fini et positif.
Les délais du cycle sont ceux du cycle de vie. `createActorComposition` ajoute
`containers`, `fetchDocument?` et les substitutions explicites `factories?`.
Une référence absente n’est jamais remplacée par la première définition trouvée.
Déclarer un staging dans le manifeste le charge ; le paramètre explicite `staging`
détermine son assemblage dans cette instance.

## Assemblage, propriété et associations

La création contrôle les références, collisions et associations sans appeler les
fabriques. `init` crée puis initialise chaque occurrence dans un registre local.
`ref` désigne une définition, `instance_id` une occurrence, `config` ses réglages.
Les rôles de `bindings` pointent exactement sur un `instance_id` local. Plusieurs
rôles peuvent viser la même instance. Les dépendances sont initialisées avant
leurs consommateurs ; un cycle est refusé avant toute acquisition.

`createComposition` accepte des `factories` par type : `scene`, `actor`, `scenario`,
`director`, `choreography`. Chaque fabrique synchrone reçoit :

```js
({id, definition, config, bindings, presentations, loaded, usage}) => facade
```

`bindings` contient les façades publiques déjà initialisées ; `presentations`
contient les entrées du décor visant cette occurrence. `usage`, `config` et
`presentations` sont des instantanés figés. La fabrique prépare une façade fraîche
sans acquérir de ressources : acquisition dans `init`, avec les outils de gestion des instances.
La façade doit exposer l’identité et le cycle des instances, `inspect()`, et, si elle accepte
commandes/événements, `capabilities`, `command()`, `events`, `on()`.
`command()` rend `{ok, value?, diagnostics}` ; `on()` rend un nettoyeur.
Une instance déjà initialisée ou d’une autre identité est refusée sans destruction.

Les scénarios déclarés sont associés à leur acteur par le rôle `actor`. Les
fabriques de scénario/directeur/chorégraphie restent explicitement injectées :
le coordinateur ne devine pas leur langage ni leurs commandes. Une fabrique
manquante bloque l’assemblage. Aucun moteur fictif ne remplace une implémentation
absente. L’exécution commune fournie est celle des plans décrits ci-dessous.

Les contrats de rôle sont optionnels, mais chaque rôle déclaré y est obligatoire
par défaut. Exemple :

```js
contracts: {
  scenario_main: {
    actor: {type: 'actor', commands: ['play', 'pause'], events: ['ended']},
    preview: {required: false, commands: ['presentationSet']},
  },
}
```

Tout binding fourni est vérifié, même sans contrat. Les capacités et types sont
revérifiés avant activation ; chaque commande revérifie sa cible au moment de
l’envoi. Les réactions vérifient aussi la capacité d’événement à la préparation
et à l’activation. Les champs inconnus des plans, réactions et contrats produisent
`E_UNKNOWN_FIELD` ; une valeur connue invalide bloque. Seule l’absence applique
le défaut, pas `null`.

## Acteurs intégrés et décor

`createActorComposition` crée ses propres contextes d’acteurs intégrés et de scènes
génériques. Les scènes par défaut sont des instances de données ; les scènes intégrées
à timeacte restent créées par son adaptateur timeacte à partir de `data.scenes`.
Les cinq acteurs ont maintenant une liste publique immuable `events`, également
exportée dans `ACTOR_EVENTS`, à côté de `ACTOR_CAPABILITIES`.

Le décor des acteurs natifs superpose `presentation.config` aux réglages de
l’usage, dans l’ordre déclaré. Un décor demandant `renderer` ou `template`, une
présentation de scène autonome, ou un binding spécialisé d’acteur exige une
fabrique explicite ; l’adaptateur refuse de les ignorer silencieusement.
Les extensions spécialisées sont donc possibles sans coupler le coordinateur
aux modèles internes des acteurs.

L’activation d’une composition ne démarre aucun média ni scénario. L’adaptateur
refuse `autoplay: true` : envoyer explicitement `play` après activation.
Les politiques natives de lecture et les codecs restent ceux du navigateur.

## Commandes, événements et asynchronisme

```js
await composition.execute({sequence: [
  {instance_id: 'actor_timeline', command: 'boundsSet', args: [{deb: 0, fin: 100}]},
  {parallel: [
    {instance_id: 'actor_audio_view', command: 'volumeSet', args: [0.5]},
    {instance_id: 'actor_image_view', command: 'presentationSet', args: [{alt: 'Étape 2'}]},
  ]},
]});
```

Un plan a exactement une forme : commande (`instance_id`, `command`, `args`),
`sequence`, ou `parallel`. Les listes vides réussissent sans effet. `args` vaut
`[]` en cas d’absence. Le plan entier est copié et contrôlé avant la première
commande. La séquence attend chaque résultat ; un échec interdit les étapes
suivantes. Le parallèle explicite attend ses branches et rassemble leurs résultats,
sans annuler rétroactivement les branches déjà lancées. Aucun retry ni file cachée.

Une seule commande en cours par cible dans ce coordinateur. Un second envoi est
refusé, même après timeout tant que l’opération sous-jacente ne s’est pas terminée.
Les appels directs à la façade d’un acteur restent possibles, mais contournent
cette exclusion et doivent être organisés par l’application.

Une réaction possède `id`, `source` (instance locale), `event` et `actions` (plan).
Les arguments sont explicites et constants : aucun transfert automatique de la
charge utile de l’événement, ni conversion entre domaines. Pour une adaptation
spécialisée, injecter un acteur/directeur qui l’expose par son interface publique.
Une réaction déjà en cours refuse une nouvelle entrée ; les commandes concurrentes
sur la même cible sont également refusées. Cela coupe les rétroactions en cours,
sans inventer une synchronisation automatique entre acteurs.

## Arrêt, destruction et limites

`init` prépare sans montage ; `activate` active les membres puis installe les
réactions, sans rejouer les événements de préparation. L’arrêt rend immédiatement
la coordination inerte ; le signal du cycle de vie annule ses attentes. Les nettoyeurs retirent
les réactions puis arrêtent les membres en ordre inverse de dépendance. Réactiver
conserve les instances et installe un seul abonnement par réaction.

La destruction libère les membres possédés, même après un échec partiel. Tous les
nettoyeurs sont tentés ; un échec reste visible et réessayable explicitement selon
le cycle de vie. Les compositions voisines, sources chargées et éléments DOM externes sont
préservés. Les IDs ne sont pas réattribués à de nouvelles instances.

L’annulation coupe les suites et la publication des résultats tardifs ; elle ne
peut pas interrompre de force du JavaScript tiers. Les fabriques doivent respecter
les signaux de leur cycle et posséder leurs ressources. Un effet déjà exécuté n’est
pas restauré automatiquement. Les plans communs n’ajoutent pas de pause, restauration
ou horloge de scénario ; ces capacités appartiennent aux interfaces spécialisées.

Tous les nouveaux diagnostics ont le préfixe `E_COMPOSITION_` et distinguent
configuration, binding, capacité, état, commande, timeout, annulation, réentrance
et membre. `inspect().diagnostics` conserve l’historique, y compris après arrêt.

## Vérification

`npm run test:composition`, `npm test`, `npm run check`, `npm run test:browser`.
L’exemple local `examples/composition/` montre les cinq acteurs et les deux modes.

