# Game

# Commun

# Vue d’ensemble du Game

## Rôle de cette page

Cette page est la carte d'entrée du projet **Game**. La documentation suit les frontières réelles du code : **Commun**, **By Module** et **By Year**.

```mermaid
flowchart TD
GAME[Unity Game] --> COM[Commun]
GAME --> BM[By Module]
GAME --> BY[By Year]
COM --> CORE[Bootstrap / Controllers / UI]
COM --> BC[BrainCloud / Documents / RTT]
COM --> PIPE[Static Data / Textes / Build]
BM --> ACC[Accounting: acc_base + acc1 + acc2]
BY --> GAMES[ENT1 / HR1 / MGT1 / MK1 / MK2]

```

## Comment choisir le bon chapitre

<table id="bkmrk-chapitreresponsabili"><thead><tr><th>Chapitre</th><th>Responsabilité</th><th>Exemples</th></tr></thead><tbody><tr><td>**Commun**</td><td>Tout ce qui est partagé entre familles ou structure le runtime général.</td><td>`Game.cs`, Factory, controllers, state machine, backend, Custom Entities, RTT, textes, build.</td></tr><tr><td>**By Module**</td><td>Framework de modules/pages et domaine Accounting.</td><td>`teamsModules`, Module Type Data, bills, inventory, answer key, grading, acc1/acc2.</td></tr><tr><td>**By Year**</td><td>Framework d'années/décisions et implémentations des jeux annuels.</td><td>cycle annuel, jobs, génération résultats, bots, ENT1/HR1/MGT1/MK1/MK2.</td></tr></tbody></table>

## Architecture globale

```mermaid
sequenceDiagram
actor User as Utilisateur
participant Game as Unity Game
participant Factory as Factory
participant Ctrl as Controllers
participant BE as BackEndBrainCloud
participant CS as Cloud Scripts
participant DB as Custom Entities
participant RTT as BrainCloud RTT
User->>Game: lance / interagit
Game->>Factory: crée classes selon GameUid + SimType
Factory-->>Ctrl: modèles/controllers concrets
Ctrl->>BE: opération métier
BE->>CS: RunScript
CS->>DB: read / update / save
CS->>RTT: message si synchronisation nécessaire
CS-->>BE: response
RTT-->>BE: changement distant
BE-->>Ctrl: MessageData / ServerMessage
Ctrl-->>Game: modèle + UI mis à jour

```

## Points d'entrée Unity

- `Assets/Src/Game/Game.cs` — bootstrap, environnement, services globaux, GameUid et SimType.
- `Assets/Src/Game/Factory.cs` — composition des classes concrètes.
- `Assets/Src/Game/GameStates/` — login, chargement et navigation professeur/étudiant.
- `Assets/Src/Game/Controllers/` — modèles runtime, caches et listeners.
- `Assets/Src/Game/Data/` — données sérialisées communes, By Module et By Year.
- `Assets/Src/Game/BackEnd/` — contrat backend et implémentation BrainCloud.
- `Assets/Src/UI/` — UI commune et spécialisations.
- `Assets/GameSpecific/` — code, Resources et prefabs propres à chaque jeu.

## Points d'entrée Node / BrainCloud

- `backend/braincloud/server_scripts/_command/` — Cloud Scripts communs appelables.
- `backend/braincloud/server_scripts/_utils/` — documents, messages, eval, rankings et logique partagée.
- `.../_utils/by_module/` — moteurs By Module.
- `.../acc_base`, `acc1`, `acc2` — Accounting.
- `.../ent1`, `hr1`, `mgt1`, `mk1`, `mk2` — moteurs By Year game-specific.
- `node.js/exports/` — Google Sheets → JSON → BrainCloud et pipeline des textes.
- `backend/braincloud/debugging/` — exécution locale des Server Scripts.
- `backend/braincloud/tests/` — tests automatisés.

## Méthode pour tracer une feature

1. Identifier le contrôle UI et le controller Unity.
2. Trouver la méthode correspondante dans `BackEndBase`/`BackEndBrainCloud`.
3. Identifier le Cloud Script commun ou game-specific réellement appelé.
4. Suivre ses `bridge.include()`.
5. Identifier chaque document lu ou sauvegardé.
6. Identifier le message RTT envoyé.
7. Retrouver le listener Unity qui consomme ce message.
8. Si la donnée est générée/configurée, remonter aussi au JSON/Google Sheet source.

## Ordre de lecture conseillé

Pour prendre le projet en main : **Bootstrap** → **Machine d'état** → **Controllers** → **Simulations/activités/équipes** → **Backend Unity ↔ BrainCloud** → **Documents/RTT**, puis entrer dans **By Module** ou **By Year**.

## Carte fonctionnelle des jeux

Pour comprendre rapidement le rôle de chaque simulation avant d’entrer dans le code, voir [**Les jeux UnikSim — rôle, contenu et navigation de la documentation**](https://wiki.uniksim.com/books/game/page/les-jeux-uniksim-role-contenu-et-navigation-de-la-documentation). Cette page résume ENT1, HR1, MGT1, MK1, MK2, ACC1 et ACC2 et indique quelles pages techniques ouvrir ensuite.

# Bootstrap et composition runtime

## Responsabilité

Le bootstrap commun décide **où le client se connecte**, **quel jeu est chargé**, **quelle famille de simulation est utilisée** et **quelles classes concrètes sont créées**. Le point central est `Assets/Src/Game/Game.cs`.

```mermaid
flowchart TD
AWAKE[Game.Awake] --> ENV[SetConnectionType]
AWAKE --> SERVICES[Services globaux]
ENV --> BC[BrainCloudWrapper]
LOGIN[Login] --> UID[SetGameUid]
UID --> SIMTYPE[CurrentSimType]
UID --> TEXTS[LoadGameSpecificTexts]
UID --> FACTORY[Factory.CreateStaticDataController]
FACTORY --> BM[By Module]
FACTORY --> BY[By Year]

```

## Game.Awake()

`Awake()` initialise le singleton `Game.Instance`, PoolsManager et les objets backend. En Editor, `SetConnectionType(GameConnectionType.eDev)` est utilisé par défaut. Les wrappers GameSparks/BrainCloud sont désactivés avant la sélection du backend puis le wrapper nécessaire est activé/configuré.

## Environnements BrainCloud

`SetConnectionType()` sélectionne l'application BrainCloud correspondant à l'environnement courant (Dev, Dev2, Demo ou Live), initialise `BrainCloudWrapper`, active son GameObject et configure notamment la profondeur JSON maximale.

Les identifiants/secrets d'environnement sont du code opérationnel sensible : la documentation décrit leur emplacement et le flux, mais ne doit pas recopier les secrets.

## InitBackendState → AuthState

```mermaid
sequenceDiagram
participant S as InitBackendState
participant G as Game
participant B as Backend
participant A as AuthState
S->>G: SetBackend(backEnd)
S->>G: GlobalTime.GetTime()
S-->>A: username + pass + gameUid
A->>B: Login(...)
B-->>A: profileId + createdAt + displayName
A-->>G: LoadUserState

```

## SetGameUid()

Après chargement de `UserDetails`, `Game.SetGameUid(gameUid, skinName)` :

1. convertit le string en `SharedConstants.GameUid.eGameUid`;
2. renseigne `CurrentGameUidEnum` et `CurrentGameUid`;
3. détermine `CurrentSimType` avec `SharedConstants.SimType.GetSimTypeFromGameUid()`;
4. charge les textes game-specific;
5. crée le controller de static data via `Factory.CreateStaticDataController()`;
6. détermine le type d'école et poursuit l'initialisation.

## Factory

`Assets/Src/Game/Factory.cs` est le point de composition. Il sélectionne les classes concrètes selon le GameUid, au lieu de disperser des `if (gameUid)` dans le projet.

<table id="bkmrk-gameuidfamillestatic"><thead><tr><th>GameUid</th><th>Famille</th><th>StaticDataController</th></tr></thead><tbody><tr><td>ent1</td><td>By Year</td><td>`StaticDataControllerEnt1`</td></tr><tr><td>mk1</td><td>By Year</td><td>`StaticDataControllerMk1`</td></tr><tr><td>hr1</td><td>By Year</td><td>`StaticDataControllerHr1`</td></tr><tr><td>mk2</td><td>By Year</td><td>`StaticDataControllerMk2`</td></tr><tr><td>mgt1</td><td>By Year</td><td>`StaticDataControllerMgt1`</td></tr><tr><td>acc1</td><td>By Module</td><td>`StaticDataControllerByModule` / spécialisation Acc1</td></tr><tr><td>acc2</td><td>By Module</td><td>`StaticDataControllerByModule` / spécialisation Acc2</td></tr></tbody></table>

## Factory : autres objets construits

La Factory ne sert pas seulement à la static data. Les controllers communs appellent aussi des méthodes comme `CreateSimulationBase()`, `CreateActivityBase()`, `CreateTeamBase()`, `CreateTeam()`, `CreateStudentStub()`, `CreateTeamStub()` et `CreateResults()`. Le GameUid/SimType choisit ainsi le modèle concret à tous les niveaux.

## LoadUserState

`LoadUserState` charge `UserDetails`, crée `Teacher` ou `Student`, applique la langue, appelle `SetGameUid()`, démarre le heartbeat hors Editor et, en WebGL, tente de mettre à jour dynamiquement textes et écoles.

## Version client

Le runtime charge `Assets/Resources/data/version.txt` et `sub_version.txt`. Le workflow de build et l'absence d'incrément automatique sont documentés dans **Build Unity, WebGL et versioning client**.

## Ajouter un nouveau GameUid

1. Ajouter le GameUid dans `SharedConstants`.
2. Mapper le GameUid vers le bon `SimType`.
3. Ajouter les classes concrètes nécessaires dans `Factory`.
4. Créer `Assets/GameSpecific/<gameUid>` et ses Resources.
5. Ajouter les static data/textes dans les pipelines Node.
6. Ajouter les Server Scripts game-specific et les factories serveur si la logique diffère.
7. Tester le flow complet : login → SetGameUid → static data → simulation → commande serveur.

## Diagnostic

Si le mauvais jeu, la mauvaise UI ou le mauvais modèle est chargé, vérifier dans l'ordre : `UserDetails.gameUid` → `SetGameUid()` → `CurrentSimType` → `Factory` → `StaticData*.Initialize()`.

# Machine d’état client — login, chargement et navigation

## Architecture

Le démarrage et la navigation principale utilisent des `GameState`. Chaque état effectue une opération asynchrone puis retourne `Result.TransitionTo(...)` lorsque la condition de transition est remplie.

```mermaid
flowchart TD
INIT[InitBackendState] --> AUTH[AuthState]
AUTH --> USER[LoadUserState]
USER -->|prof sans école| TS[TeacherSetSchoolState]
USER -->|prof| TLS[TeacherLoadSimStubsState]
TLS --> TLOAD[TeacherLoadSimState]
TLOAD --> TSCENE[TeacherLoadSceneState]
USER -->|étudiant sans école| CHOOSE[StudentChooseTeacherAndSimState]
USER -->|étudiant| SLOAD[StudentLoadSimState]
SLOAD --> WAIT[StudentWaitForSimStartedState]
SLOAD --> SCEN[Scenario State]
SLOAD --> TEAM[StudentChooseTeamState]
SLOAD --> STUD[StudentState]

```

## InitBackendState

Sélectionne l'implémentation backend, récupère l'heure globale, dérive le username si nécessaire, puis passe à `AuthState`.

## AuthState

Appelle `Game.Backend.Login()`. En cas de succès, passe à `LoadUserState`. En Editor, un échec peut revenir vers `EditorLoginState`.

## LoadUserState

Charge `UserDetails`, crée `Teacher` ou `Student`, applique langue et GameUid, initialise heartbeat, met à jour textes/écoles sur WebGL puis choisit le prochain état.

## StudentLoadSimState

Après chargement de la simulation, l'état choisit entre attente de démarrage, scénario By Year, scénario By Module, activité non commencée, choix d'équipe ou entrée directe dans `StudentState`.

## Teacher flow

Le professeur charge d'abord les simulation stubs. S'il en possède, `TeacherLoadSimState` charge la simulation sélectionnée; sinon il va directement vers la scène de gestion.

## Debug

Un client “bloqué” sur un écran n'est pas nécessairement un bug UI. Identifier d'abord le `GameState` courant et la condition de son `Update()` qui n'est jamais satisfaite.

## Créer un nouvel état de A à Z

Un `GameState` doit représenter une **étape de navigation ou de chargement**, pas une logique métier arbitraire. Avant d'en créer un, vérifier si le besoin est réellement une transition d'état ou simplement une méthode d'un controller/UI.

### 1. Choisir la classe de base

Les états sont sous `Assets/Src/Game/GameStates/`. Reprendre le state le plus proche du contexte : étudiant, professeur, login, simulation, etc. Le state doit retourner des `Result` de la machine commune.

### 2. Mettre l'initialisation dans TransitionIn()

`TransitionIn()` sert à lancer l'opération asynchrone une seule fois : backend request, ouverture d'un panel, initialisation d'un flag. Éviter de déclencher la même requête dans `Update()`.

```csharp
public class MyLoadState : GameState
{
    private bool _loaded;

    public override void TransitionIn()
    {
        _loaded = false;
        Game.Backend.SomeRequest(() => _loaded = true);
    }

    public override Result Update()
    {
        if (_loaded)
            return Result.TransitionTo(new NextState());

        return Result.Continue;
    }
}
```

### 3. Garder Update() déterministe

`Update()` doit essentiellement tester les conditions de sortie. Un state qui envoie une requête chaque frame, crée un popup continuellement ou démarre plusieurs tutoriels est presque toujours mal structuré.

### 4. Brancher la transition depuis le state précédent

La nouvelle classe n'est utile que si un state existant peut la retourner. Rechercher `Result.TransitionTo(...)` dans le flow concerné et brancher la nouvelle condition au bon endroit.

### 5. Nettoyer dans TransitionOut()/Dispose si nécessaire

Si le state attache des callbacks temporaires, listeners UI ou abonnements globaux, ils doivent être retirés lorsqu'il sort. Sinon un retour ultérieur dans le flow peut exécuter le callback plusieurs fois.

### 6. Tester toutes les branches

- succès backend;
- réponse vide ou utilisateur sans simulation/équipe;
- retour arrière / relog;
- professeur vs étudiant si le state est partagé;
- By Module vs By Year si le flow dépend du `SimType`;
- WebGL, où les erreurs de message sont plus sévèrement protégées.

## Debug d'un state bloqué

1. Identifier la classe courante dans la machine.
2. Mettre un breakpoint/log dans `TransitionIn()` pour confirmer qu'il n'est appelé qu'une fois.
3. Inspecter chaque bool/condition testée dans `Update()`.
4. Vérifier que le callback backend est réellement exécuté.
5. Vérifier qu'un popup/tutoriel n'empêche pas la condition UI attendue.
6. Si le state dépend du modèle, vérifier que le controller a reçu son RTT avant de changer de state.

# Controllers, modèles et listeners runtime

## Pattern général

Le projet sépare les objets de données sérialisables des controllers runtime. Les classes de `Assets/Src/Game/Data` stockent les champs; les classes de `Assets/Src/Game/Controllers` ajoutent chargement, callbacks, caches, listeners RTT et opérations métier côté client.

```mermaid
classDiagram
class BaseData
class BaseController
class SimulationBase
class SimulationBaseController
class ActivityBase
class ActivityBaseController
class TeamBase
class TeamBaseController
BaseData <|-- SimulationBase
BaseData <|-- ActivityBase
BaseData <|-- TeamBase
BaseController <|-- SimulationBaseController
BaseController <|-- ActivityBaseController
BaseController <|-- TeamBaseController
SimulationBaseController --> SimulationBase
ActivityBaseController --> ActivityBase
TeamBaseController --> TeamBase

```

## BaseController

Le constructeur appelle `SetListeners()`. La classe définit les hooks `Deserialize`, `PreLoad`, `PostLoad`, `SetListeners` et `RemoveListeners`. Une sous-classe qui change de contexte doit donc retirer ses listeners pour éviter les doubles callbacks.

## SimulationBaseController

Ce controller est le hub runtime de la simulation. Il maintient notamment :

- simulation courante, activité et équipe courantes;
- stubs d'étudiants et équipes;
- permissions, ponderations et custom evaluations;
- présence/heartbeat;
- caches de stubs d'évaluation;
- listeners pour join/remove team, changement de nom, activité suivante, notifications, ponderations, etc.

## ActivityBaseController

Il charge `ActDataGameSpec`, expose `GenerateResults()`, écoute `ACT_UPDATED_FIELDS` et sert de base aux variantes By Module et By Year.

## TeamBaseController

Il porte les membres, activités/décisions, cash, logbook, résultats, last chat time et TeamEvaluation. Il écoute notamment les changements de cash, membres, logbook et création d'activité d'équipe.

## Factory

Les controllers de base ne construisent pas directement leurs modèles. Ils passent par `Factory.CreateSimulationBase()`, `CreateActivityBase()`, `CreateTeamBase()`, etc. C'est ce qui permet aux deux familles de partager le même contrat runtime.

## Règle de maintenance

Une donnée pure appartient au modèle; un accès backend, cache, listener ou événement UI appartient au controller. Éviter de mettre des appels BrainCloud directement dans les classes Data.

## Créer ou étendre un controller de A à Z

Le controller est la couche qui transforme une donnée sérialisée en objet runtime vivant. Il peut charger le backend, maintenir des caches et écouter RTT. La donnée sérialisable elle-même reste dans `Data/`.

### 1. Créer/identifier le modèle Data

Le modèle doit savoir `Deserialize()` ses champs sans appeler BrainCloud. Si une nouvelle propriété est persistée, ajouter d'abord le champ ici et vérifier sa clé JSON.

### 2. Créer le controller

Hériter du controller de base le plus proche. Le constructeur crée normalement le modèle concret via `Factory`.

### 3. Respecter le cycle Deserialize → PreLoad → modèle → PostLoad

Les controllers principaux suivent ce pattern pour invalider les caches et recalculer les dépendances autour d'une nouvelle payload. Ne pas mettre des listeners dans `Deserialize()`.

### 4. Ajouter les listeners symétriquement

```csharp
public override void SetListeners()
{
    base.SetListeners();
    Game.MessagesDispatcher.RegisterListener(
        MessageTypeBase.MY_EVENT,
        OnMyEvent
    );
}

public override void RemoveListeners()
{
    base.RemoveListeners();
    Game.MessagesDispatcher.UnRegisterListener(
        MessageTypeBase.MY_EVENT,
        OnMyEvent
    );
}
```

**Toute inscription doit avoir sa désinscription miroir.** Le remplacement d'une simulation appelle `oldSim.RemoveListeners()`; si une sous-classe oublie un listener, l'ancien controller continuera à recevoir des RTT.

### 5. Mettre à jour le modèle dans le handler avant de prévenir l'UI

Le handler doit interpréter la payload, vérifier l'UID de simulation/activity/team concerné, mettre à jour le modèle/cache, puis déclencher les events locaux nécessaires.

### 6. Brancher Factory si la classe varie par famille/jeu

Si le controller ou son modèle diffère entre By Module et By Year, ajouter la sélection dans `Factory` plutôt que des tests GameUid dispersés.

## Quand utiliser un cache

Les stubs d'évaluation et listes lourdes sont mis en cache puis marqués dirty lorsqu'un RTT pertinent arrive. Appliquer ce pattern lorsqu'une donnée coûte cher à recharger mais peut être invalidée précisément.

## Checklist controller

- Modèle pur séparé du controller.
- Deserialize tolère les champs optionnels/anciens.
- Chaque RegisterListener possède UnRegisterListener.
- Le handler vérifie le contexte/UID avant de modifier l'état.
- Les caches sont invalidés sur tous les messages pertinents.
- Factory mise à jour si polymorphisme.
- Relog/changement de simulation ne produit pas de double callback.

# Simulations, activités, équipes et utilisateurs

## Hiérarchie métier commune

```mermaid
flowchart TD
USER[UserDetails] --> SIM[Simulation]
SIM --> ACT[Activities]
SIM --> ST[Students / Team stubs]
ACT --> TEAM[Teams]
TEAM --> MEMBER[TeamMember + Roles]
ACT --> GS[ActDataGameSpec]
TEAM --> DEC[Decisions / Modules]

```

## Simulation

La simulation contient notamment teacher, étudiants, équipes, ordre des activités, activité courante, mode async, permissions, ponderations et custom evaluations. Les commandes communes couvrent création, lecture, modification, activation et récupération des stubs.

## Activity

Une activité appartient à une simulation et possède son propre document `activity`, plus un document `actDataGameSpec`. Le contenu game-specific est volontairement séparé du socle commun.

## Team

Le document `teams` contient membres, rôles, activités/décisions, cash et métadonnées runtime. By Module ajoute des documents `teamsModules`; By Year garde davantage de décisions annuelles dans l'état équipe/activité.

## Join / create / remove

Les commandes communes incluent `join_sim`, `join_team`, `create_team`, `remove_user_from_sim`, `remove_user_from_team` et `change_team_name`. Ces opérations mettent à jour plusieurs documents et émettent des messages RTT.

## Rôles

`set_user_role`, `set_user_roles` et `unset_user_role` modifient les rôles d'équipe. Le runtime Unity propage ces changements via `TeamBaseController`.

## Heartbeat / présence online

Les controllers simulation/activity gardent les derniers heartbeats et considèrent un utilisateur online selon l'âge du dernier heartbeat. Les listeners RTT mettent à jour les statuts visibles.

## Écoles et access codes

Le backend commun gère aussi école, teacher lookup et access codes. `save_access_code` / `get_access_code` persistent le code dans `userDetails`.

## Point de debug

Lorsqu'un utilisateur “disparaît” ou reste dans une mauvaise équipe, comparer au minimum `userDetails`, `simulation` et `teams`. Le problème peut être une incohérence multi-document et non un simple cache Unity.

## Ajouter un champ de simulation/activity/team de A à Z

Un nouveau champ traversera souvent plusieurs couches. Le piège classique est de l'ajouter au document serveur mais d'oublier le modèle Unity, ou l'inverse.

### 1. Choisir le bon document

- `simulation` : configuration globale qui survit aux activités;
- `activity` : état commun d'une activité;
- `actDataGameSpec` : données spécifiques au jeu pour une activité;
- `teams` : état de l'équipe;
- `teamsModules` : données détaillées By Module;
- `userDetails` : préférences/état du compte utilisateur.

### 2. Ajouter le champ au modèle Unity

Ajouter la constante de clé et le champ dans la classe Data correspondante, puis le lire dans `Deserialize()`. Tolérer l'absence du champ pour les vieux documents lorsque nécessaire.

### 3. Ajouter l'écriture serveur

Utiliser une commande dédiée ou une commande générique `update_*_fields` si le champ s'y prête. Sauvegarder via `utils_documents`.

### 4. Ajouter la migration si le champ est structurel

Si le document peut exister longtemps, initialiser le nouveau champ dans `version_simulation.js`, `version_activity.js`, `version_teams.js`, etc.

### 5. Synchroniser les autres clients

Ajouter un RTT `*_updated_fields` ou spécialisé. Le controller doit filtrer sur l'UID du document avant d'appliquer la mutation.

### 6. Tester tous les rôles

Pour simulation/team, tester professeur + étudiant, join tardif et relog. Un client qui était offline doit récupérer la valeur correcte depuis le document, pas dépendre d'avoir reçu le RTT historique.

## Opérations multi-documents

Join team, remove user, create team et suppression d'utilisateur touchent plusieurs documents. Pour toute nouvelle opération de ce type, écrire explicitement la liste des documents modifiés avant de coder. Un succès partiel peut laisser `userDetails`, `simulation` et `teams` incohérents.

## Debug d'une incohérence utilisateur/équipe

1. Lire `userDetails.teamUid/simUid/actUid`.
2. Lire `simulation.students`/`teams`.
3. Lire le document `teams` et ses members.
4. Comparer les trois UIDs.
5. Vérifier le dernier RTT join/remove.
6. Recharger complètement la simulation : si le bug disparaît, suspecter le controller/cache plutôt que la persistance.

# Infrastructure runtime et utils Unity

## Dossier

`Assets/Src/Utils/` contient l'infrastructure Unity qui ne relève pas directement d'un domaine métier.

<table id="bkmrk-composantr%C3%B4le-contex"><thead><tr><th>Composant</th><th>Rôle</th></tr></thead><tbody><tr><td>`ContextState`, `State`, `Machine`</td><td>Fondations des machines d'état.</td></tr><tr><td>`ScheduledMachine`, `ContextScheduledMachine`, `Scheduler`</td><td>Orchestration d'états/tâches planifiées.</td></tr><tr><td>`Coroutiner`</td><td>Point d'accès aux coroutines pour les classes non-MonoBehaviour.</td></tr><tr><td>`GameEventMessageUtils`</td><td>Événements locaux UI/runtime distincts des messages RTT serveur.</td></tr><tr><td>`GlobalTime`</td><td>Temps serveur/global.</td></tr><tr><td>`SharedConstants`</td><td>GameUid, SimType et constantes transversales.</td></tr><tr><td>`Utils` / `UtilsBase`</td><td>Helpers client communs.</td></tr><tr><td>`Extensions`</td><td>Méthodes d'extension C#.</td></tr><tr><td>`ObjectPool`, `PoolableObject`, `PoolsManager`</td><td>Pooling d'objets Unity.</td></tr><tr><td>`ImageLoader`</td><td>Chargement d'images.</td></tr><tr><td>`SoundsManager`</td><td>Audio commun.</td></tr><tr><td>`TextsManager`</td><td>Localisation; documenté dans la page Textes.</td></tr><tr><td>`GameSkinReloader`</td><td>Rechargement du skin.</td></tr><tr><td>`ExcelUtils`</td><td>Helpers d'export tableur utilisés notamment en By Module/WebGL.</td></tr><tr><td>`Tweener`</td><td>Animation/tween utilitaire.</td></tr><tr><td>`HashInformation*`</td><td>Informations/erreurs liées aux contrôles d'intégrité.</td></tr></tbody></table>

## CommandManager

`CommandManager.cs` est un gestionnaire historique des commandes **GameSparks**. Sa méthode `SendCommand()` refuse l'appel si le backend courant n'est pas `eGameSparks`. La voie BrainCloud moderne passe par `BackEndBrainCloud.SendCommand()`; ne pas confondre les deux systèmes.

## Événements locaux vs RTT

```mermaid
flowchart LR
LOCAL[GameEventMessageUtils] --> UI[UI / transitions locales]
SERVER[BrainCloud RTT] --> DISPATCH[MessagesDispatcher]
DISPATCH --> CTRL[Controllers]
CTRL --> UI

```

Un événement UI local n'est pas une synchronisation serveur. Pour une feature multi-utilisateur, il faut généralement persister côté serveur puis recevoir un message RTT.

## Règle de maintenance

Éviter d'ajouter de grosses règles métier dans `Utils.cs`. Une fonction Accounting, MK1, HR1, etc. doit vivre avec son domaine. Le dossier Utils doit rester transversal.

## Carte réelle de l’infrastructure runtime

<table id="bkmrk-composant-r%C3%B4le-%C3%80-uti"><thead><tr><th>Composant</th><th>Rôle</th><th>À utiliser pour</th></tr></thead><tbody><tr><td>`Game`</td><td>Composition root Unity et accès aux services globaux.</td><td>Services uniques réellement globaux au runtime.</td></tr><tr><td>`Scheduler`</td><td>Ordonnancement déterministe Update/LateUpdate par priorité.</td><td>FSM et callbacks qui doivent respecter un ordre de frame.</td></tr><tr><td>`Coroutiner`</td><td>MonoBehaviour hôte pour coroutines lancées par du code non-MonoBehaviour.</td><td>Backend IEnumerator, délais UI, séquences async Unity.</td></tr><tr><td>`ObjectPool<T>` / `PoolsManager`</td><td>Réutilisation de prefabs/contrôles.</td><td>Rows, pages, items et objets UI fréquemment créés/détruits.</td></tr><tr><td>`GameEventMessageUtils`</td><td>Bus d’événements Doozy local au client.</td><td>Navigation/UI locale.</td></tr><tr><td>`MessagesDispatcher`</td><td>Distribution des messages RTT serveur.</td><td>Synchronisation backend → client.</td></tr><tr><td>`Extensions`</td><td>Extensions génériques Action/UI/collections.</td><td>Helpers sans état métier.</td></tr><tr><td>`UtilsBase`</td><td>Helpers bas niveau stables, temps/UIDs.</td><td>Logique pure sans dépendance UI.</td></tr><tr><td>`Utils`</td><td>Helpers applicatifs historiques très larges.</td><td>Compatibilité/usage existant; éviter d’y ajouter automatiquement toute nouvelle logique.</td></tr><tr><td>`CommandManager`</td><td>Throttling/loading des anciennes requêtes GameSparks.</td><td>**Legacy GameSparks seulement.**</td></tr></tbody></table>

## Composition dans Game.Awake()

`Game` est le composition root principal. Il initialise notamment :

- `PoolsManager` depuis une référence sérialisée;
- `Scheduler = new Scheduler()`;
- `MessagesDispatcher = new MessagesDispatcher()`;
- `FxMgr`, `CommandManager`, `Toaster`;
- `NotificationManager`;
- `TextsManager`;
- la machine d’état via `ContextScheduledMachine<GameStateDTO>`.

`Game.Update()` appelle `Scheduler.Update()`; `Game.LateUpdate()` appelle `Scheduler.LateUpdate()`. Un service enregistré au Scheduler n’a donc pas besoin de son propre `MonoBehaviour.Update()`.

## Ajouter un service runtime global de A à Z

Ne rendre un service global que s’il doit réellement exister une seule fois pendant toute la session. Un service propre à une simulation, équipe ou popup doit rester dans son controller/contexte.

### 1. Choisir MonoBehaviour ou classe pure

- classe pure : pas besoin de scene lifecycle, inspector ou coroutine;
- `MonoBehaviour` : dépend de références Unity sérialisées, coroutines, callbacks Unity ou scene objects.

### 2. Ajouter la propriété dans Game seulement si elle est globale

```csharp
public static MyService MyService { get; private set; }
```

Pour une classe pure, l’instancier dans `Game.Awake()`. Pour un MonoBehaviour, injecter une référence sérialisée depuis la scène/prefab puis assigner la propriété.

### 3. Définir explicitement son lifecycle

Décider :

- quand il est initialisé;
- s’il dépend de `Backend` ou de `CurrentGameUid`;
- ce qui doit être reset lors d’un changement de simulation;
- ce qui doit être nettoyé dans `OnDestroy()` ou logout.

### 4. Ne pas créer un deuxième singleton caché

Si le service est déjà accessible via `Game`, éviter un second `static Instance` sauf raison Unity spécifique. Les doubles singletons rendent les tests et changements de scène difficiles à tracer.

### 5. Tester relog / changement de jeu

Un service global qui garde une référence au vieux `TeamController`, à une ancienne simulation ou à un prefab détruit produit des bugs fantômes. Tester au minimum : login → simulation A → logout/reload → simulation B.

## Scheduler — ordonnancement par priorité

Les priorités sont exécutées dans cet ordre :

```
BeginFrame
ServerEvent
Fsm
World
Render
Ui
EndFrame
```

Le même ordre existe pour le registry de `LateUpdate`.

### Ajouter un callback planifié

```csharp
Game.Scheduler.Register(
    Scheduler.Priority.Ui,
    UpdateMyControl
);

// nettoyage obligatoire
Game.Scheduler.Unregister(
    Scheduler.Priority.Ui,
    UpdateMyControl
);
```

`Scheduler.Unregister()` sait gérer une désinscription pendant qu’une priorité est en cours d’itération : le callback est placé dans `_toRemove` puis retiré après la boucle. Il faut néanmoins utiliser **exactement le même delegate** lors du unregister.

### ScheduledMachine

`ScheduledMachine.Start()` s’enregistre automatiquement dans la priorité `Fsm` pour Update et LateUpdate. `Stop()` se désenregistre puis déclenche `onStop`. C’est la raison pour laquelle la machine d’état principale n’a pas son propre `MonoBehaviour.Update()`.

## Coroutiner — lancer une coroutine hors MonoBehaviour

`Game.Instance.coroutiner` sert de point d’ancrage lorsque le code appelant n’est pas lui-même un MonoBehaviour :

```csharp
Coroutine c = Game.Instance.coroutiner.StartLocalCoroutine(
    MyRoutine()
);

// si nécessaire
Game.Instance.coroutiner.StopLocalCoroutine(c);
```

Ne pas laisser une coroutine continuer après destruction logique de son owner. Si elle modifie un popup/controller qui peut disparaître, conserver la référence et l’arrêter au teardown.

## ObjectPool et PoolsManager

`ObjectPool<T>` possède deux modes :

- **local pool** — défaut actuel : le template et les instances restent gérés par le pool local;
- **global PoolsManager** — `isLocalPool = false` : le template est enregistré par son `Type` dans `Game.PoolsManager`.

### Créer un objet poolable

```csharp
public class MyRow : PoolableObject
{
    public override void Alloc()
    {
        // remettre l’état nécessaire pour une nouvelle utilisation
    }

    public override void Free()
    {
        // retirer listeners, vider données temporaires, arrêter tweens
    }
}
```

### Utilisation

```csharp
_pool = new ObjectPool<MyRow>(rowTemplate, contentRoot);

MyRow row = _pool.AllocTemplate();
row.SetData(data);

// plus tard
_pool.FreeTemplate(row);
// ou
_pool.FreeAllTemplates();
```

### Règle critique

`Free()` doit remettre l’objet dans un état réutilisable. Un pool masque facilement des bugs de lifecycle : listener ajouté à chaque `Alloc()`, tween encore actif, référence à l’ancien modèle, event global non retiré.

## GameEventMessageUtils vs RTT vs Action locale

<table id="bkmrk-besoin-outil-changer"><thead><tr><th>Besoin</th><th>Outil</th></tr></thead><tbody><tr><td>Changer un panel/navigation dans le même client</td><td>`GameEventMessageUtils.SendEvent()`.</td></tr><tr><td>Prévenir des objets C# directement liés</td><td>`Action`/`event` local.</td></tr><tr><td>Synchroniser un changement venant du serveur</td><td>`MessagesDispatcher` + RTT.</td></tr><tr><td>Communiquer entre deux utilisateurs</td><td>Serveur + persistance + RTT, jamais Doozy local.</td></tr></tbody></table>

`GameEventMessageUtils` contient par exemple les événements de tabs, login/scenario/choose team et quelques événements By Module/MK. Ce bus ne persiste rien et ne traverse pas le réseau.

## Extensions.Fire() et diagnostic des callbacks

Les extensions `Action.Fire()` ne sont pas de simples `?.Invoke()` : elles parcourent la liste des delegates et encadrent chaque appel avec `CrashMessages.CrashMessage.StackPush/StackPop`. Cela permet de conserver plus de contexte lorsqu’un callback plante.

Pour les events existants qui utilisent ce pattern, préférer `.Fire()` afin de conserver ce diagnostic.

## Temps : GlobalTime, UtilsBase et temps serveur

`GlobalTime.UtcNow` retourne actuellement directement `DateTime.UtcNow` converti en epoch ms. L’ancien mécanisme de resynchronisation périodique serveur est commenté.

En parallèle, `Game.RefreshCurrentServerTime()` charge explicitement l’heure via le backend et `Game.Update()` fait avancer `currentServerTime`.

Donc, pour une règle métier sensible au serveur, ne pas supposer que `GlobalTime.UtcNow` est une horloge serveur synchronisée.

## Où placer un nouvel helper

<table id="bkmrk-type-d%E2%80%99helper-emplac"><thead><tr><th>Type d’helper</th><th>Emplacement recommandé</th></tr></thead><tbody><tr><td>Conversion temps/UID pure et partagée</td><td>`UtilsBase`.</td></tr><tr><td>Extension naturelle d’un type C# / Unity</td><td>`Extensions`.</td></tr><tr><td>Helper d’un domaine précis</td><td>Classe/domain utils du domaine, pas `Utils.cs`.</td></tr><tr><td>Helper UI très spécifique</td><td>Contrôle/base UI concerné.</td></tr><tr><td>Helper game-specific</td><td>`GameSpecific/<gameUid>`.</td></tr><tr><td>Compatibilité avec un ancien helper déjà centralisé</td><td>`Utils`, seulement si cela reste cohérent avec l’existant.</td></tr></tbody></table>

## CommandManager : ne pas l’utiliser pour BrainCloud

`CommandManager.SendCommand()` accepte des `GSTypedRequest` et vérifie explicitement que `Game.Backend.BackEndType == eGameSparks`. Il gère l’ancien throttling GameSparks et le loading circle.

Une nouvelle feature BrainCloud doit passer par `BackEndBrainCloud.SendCommand()` / les méthodes de `BackEndBase`, jamais par `CommandManager`.

## Checklist avant d’ajouter une nouvelle infrastructure

- Le besoin est-il vraiment global ?
- Peut-il vivre dans un controller existant ?
- Le lifecycle de cleanup est-il défini ?
- Scheduler plutôt qu’un Update supplémentaire ?
- Coroutine arrêtée si owner détruit ?
- Pool : `Alloc()`/`Free()` symétriques ?
- Event local ou RTT correctement choisi ?
- Helper placé dans le bon domaine plutôt que dans `Utils.cs` ?
- Aucune dépendance GameSparks introduite dans une nouvelle feature BrainCloud ?
- Relog/changement de simulation testé ?

## Debug rapide

<table id="bkmrk-sympt%C3%B4me-zone-probab"><thead><tr><th>Symptôme</th><th>Zone probable</th></tr></thead><tbody><tr><td>Callback exécuté deux fois après navigation</td><td>listener/Scheduler non désinscrit ou pool `Free()` incomplet.</td></tr><tr><td>Objet réutilisé avec anciennes données</td><td>`PoolableObject.Free/Alloc`.</td></tr><tr><td>FSM cesse d’avancer</td><td>`ScheduledMachine` stoppée/désinscrite ou exception dans callback Fsm.</td></tr><tr><td>UI locale ne bouge pas</td><td>Doozy event / Action locale.</td></tr><tr><td>Autre client ne bouge pas</td><td>Ce n’est pas `GameEventMessageUtils`; suivre RTT/persistance.</td></tr><tr><td>Heure différente du serveur</td><td>`GlobalTime` vs `Game.currentServerTime`.</td></tr><tr><td>Exception “Cannot use gamesparks”</td><td>Nouvelle logique branchée par erreur sur `CommandManager`.</td></tr></tbody></table>

# Backend Unity ↔ BrainCloud

## Contrat côté Unity

`BackEndBase` définit le contrat backend utilisé par le reste du client. Les controllers ne doivent pas connaître l'implémentation BrainCloud : ils appellent des opérations métier comme `GetTeamModule`, `SaveModule`, `GenerateResults`, `GetEvaluation`, etc.

`BackEndBrainCloud` implémente ce contrat avec le SDK BrainCloud et les Cloud Scripts du dépôt Node.

```mermaid
flowchart LR
CTRL[Controller ou State Unity] --> BASE[BackEndBase]
BASE --> BC[BackEndBrainCloud]
BC -->|commun| C[_command/script]
BC -->|game-specific| G[gameUid/command/gameUid_script]
C --> U[_utils]
G --> GU[utils game-specific]
GU --> U

```

## Convention des Cloud Scripts

Le helper privé `BackEndBrainCloud.SendCommand()` construit le chemin du script :

- commande commune : `_command/<scriptName>`;
- commande game-specific : `<gameUid>/command/<gameUid>_<scriptName>`.

Exemple : `SendCommand(true, "generate_results", ...)` dans Acc1 exécute `acc1/command/acc1_generate_results`.

## Authentification et RTT

```mermaid
sequenceDiagram
participant G as Game
participant B as BackEndBrainCloud
participant BC as BrainCloud
G->>B: Login(user, pass, gameUid...)
B->>BC: AuthenticateUniversal
BC-->>B: profile + session
B->>B: EnableRTT()
B-->>G: utilisateur authentifié

```

Après authentification réussie, `EnableRTT()` est appelé. Le client conserve la fonction d'authentification pour pouvoir réagir à une perte de session et réactive RTT lorsqu'une connexion se ferme.

## GetDocument()

`GetDocument(entityType, uid, forceCreate)` appelle `_command/get_document`. `GetTeamModule()` l'utilise pour lire `teamsModules` avec l'UID produit par `UtilsBase.GetTeamModuleUid(teamUid, actModuleUid)`.

## Gestion des appels

- `SendCommand()` incrémente un compteur et pilote le loading circle.
- Les réponses sont désérialisées vers `MessageData`.
- Les erreurs sont transmises au callback d'échec.
- Une session invalide ou une connexion RTT morte peut déclencher une réauthentification ou une resynchronisation.

## Ajouter une commande

1. Créer l'appel métier au bon controller/service.
2. Ajouter le contrat à `BackEndBase` si l'opération doit faire partie de l'abstraction.
3. Implémenter dans `BackEndBrainCloud`.
4. Choisir commande commune ou game-specific.
5. Créer le Cloud Script wrapper.
6. Déplacer la logique réutilisable dans `_utils`.
7. Sauvegarder les documents via `utils_documents`.
8. Envoyer un message RTT si d'autres clients doivent être synchronisés.

## Ajouter une commande Unity → BrainCloud de A à Z

Une nouvelle opération métier traverse normalement quatre couches : **controller Unity → BackEndBase/BackEndBrainCloud → Cloud Script → utils/documents**. Si plusieurs clients doivent voir le changement, ajouter ensuite RTT.

### 1. Définir le contrat Unity

Ajouter la méthode dans `BackEndBase` lorsque l'opération appartient réellement au contrat backend. Utiliser des types simples/`MessageData` compatibles avec les deux backends historiques.

### 2. Implémenter BackEndBrainCloud

Construire la payload avec les UIDs minimaux nécessaires et appeler `SendCommand()`.

- `isGameSpecific = false` → `_command/<scriptName>`;
- `isGameSpecific = true` → `<gameUid>/command/<gameUid>_<scriptName>`.

### 3. Créer un wrapper Cloud Script mince

Le fichier command doit valider/extraire `data`, inclure les utils nécessaires et déléguer le calcul. Une logique de centaines de lignes n'a pas sa place dans le wrapper.

### 4. Lire les documents avec utils\_documents

Utiliser `getDocumentData()` / gather helpers. Une modification doit conserver la version de Custom Entity et passer par `saveDocumentData()` ou l'helper adapté.

### 5. Sauvegarder avant d'envoyer RTT

Le message temps réel doit représenter un état déjà persisté. Sinon un autre client peut recevoir le message, relire le document et obtenir encore l'ancienne valeur.

### 6. Choisir la stratégie RTT

- petite payload : envoyer directement les champs nécessaires;
- grosse payload : envoyer une instruction de refresh / `get_data` / `execute_request` selon le système;
- choisir user/team/team+teacher/sim/activity comme audience.

### 7. Mettre à jour le controller Unity

Le callback de la commande est utile pour l'appelant local, mais les autres clients doivent passer par le listener RTT. Éviter deux chemins de mise à jour qui appliquent deux fois la même mutation.

## Exemple de checklist avant merge

1. Contrat `BackEndBase` ajouté si requis.
2. `BackEndBrainCloud` utilise le bon namespace common/game-specific.
3. Wrapper command créé avec le nom exact attendu.
4. Logic métier dans `_utils` ou game-specific utils.
5. Document sauvegardé avec gestion de version.
6. RTT envoyé après la sauvegarde.
7. `MessageType` et listener Unity ajoutés si nécessaire.
8. Chemin testé avec deux clients si la feature est collaborative.
9. Server Script importé vers le bon environnement BrainCloud.

## Debug d'une commande

Si Unity reçoit une erreur : logger `scriptName` et payload, exécuter le wrapper dans **Debug local des Server Scripts BrainCloud**, puis inspecter le document avant/après. Si la commande réussit mais l'autre client ne bouge pas, le problème est probablement dans la chaîne RTT plutôt que dans la commande.

# Commandes, documents et persistance BrainCloud

## Architecture serveur commune

Le backend du Game repose principalement sur les Cloud Scripts dans `node.js/backend/braincloud/server_scripts`. Les dossiers `_command`, `_admin_command`, `_job` et `_utils` constituent le socle partagé.

## Familles de scripts

<table id="bkmrk-dossierr%C3%B4le-_command"><thead><tr><th>Dossier</th><th>Rôle</th></tr></thead><tbody><tr><td>`_command/`</td><td>Entrées appelées par le client : simulations, activités, équipes, évaluations, logbook, chat, rankings, static data, etc.</td></tr><tr><td>`_admin_command/`</td><td>Outils administratifs comme création de documents en bulk et récupération de joueurs.</td></tr><tr><td>`_job/`</td><td>Travaux planifiés : génération et migrations/versioning de documents.</td></tr><tr><td>`_utils/`</td><td>Logique réutilisable : documents, messages, eval, rankings, RNG, By Year et By Module.</td></tr><tr><td>`<gameUid>/command/`</td><td>Wrappers game-specific.</td></tr></tbody></table>

## utils\_documents.js

Ce fichier est la couche d'accès commune aux Custom Entities. Ses fonctions structurantes sont `getDocumentData`, `gatherDocumentsData`, `gatherDocumentsDataWithQuery`, `saveDocumentData`, `saveDocumentDataFields`, `saveDocumentIncrementData`, `createDocumentData`, `deleteDocumentData`, `initializeDocument` et `updateDocumentVersion`.

```mermaid
sequenceDiagram
participant CMD as Cloud Script
participant UD as utils_documents
participant CE as Custom Entity
participant VER as version/*
CMD->>UD: getDocumentData(type, uid)
UD->>CE: lecture
CE-->>UD: data + version
UD->>VER: initialize / migrate
UD-->>CMD: objet métier
CMD->>CMD: modifications
CMD->>UD: saveDocumentData(type, uid)
UD->>CE: écriture

```

## Versioning et migrations

Les scripts `_utils/version/version_*.js` portent les migrations par type de document. Chaque type peut enregistrer une fonction d'initialisation et une suite de transformations successives.

`version_teams_modules.js` est un bon exemple : il contient plusieurs migrations `teamModuleDataToV*`. Un vieux document peut donc être transformé jusqu'au schéma actuel avant d'être resauvegardé.

## Documents principaux

- `simulation` : état/configuration de simulation;
- `activity` : activité en cours;
- `actDataGameSpec` : données propres au jeu pour l'activité;
- `teams` : état commun d'équipe;
- `teamsModules` : données By Module d'une équipe et d'un module;
- `results`, `evaluation`, `evaluationTeam`;
- `teamsLogbooks`, `userDetails`, `simComments`.

## Règle lorsqu'un schéma change

1. Définir le nouveau schéma attendu.
2. Ajouter la migration dans le bon `version_<entity>.js`.
3. Garantir le passage de toutes les versions antérieures supportées.
4. Tester lecture, migration et resauvegarde.
5. Ne lancer un job de migration globale qu'après validation sur des documents représentatifs.

## Static data serveur

`_command/update_static_data.js` est l'entrée utilisée par les outils d'export pour publier les JSON dans BrainCloud. La chaîne complète est documentée dans **Données statiques et pipeline import/export**.

## Diagnostic concurrence

Un bug qui n'apparaît qu'avec plusieurs utilisateurs peut venir d'écritures concurrentes et de versions de Custom Entities. Dans ce cas, suivre le document concerné, sa version lue, la fonction de sauvegarde utilisée et les éventuels retries avant d'accuser l'UI.

## Ajouter ou faire évoluer une Custom Entity

Le point critique n'est pas seulement de créer un nouveau JSON : il faut définir son UID, son propriétaire logique, son lifecycle, son versioning et tous les documents qui le référencent.

### Nouvelle entity type

1. Choisir un `entityType` stable et un schéma de `data.uid` déterministe.
2. Créer un helper d'initialisation si le document peut être absent.
3. Passer les lectures/écritures par `utils_documents.js`.
4. Ajouter un fichier `version_<entity>.js` si le schéma doit survivre à plusieurs versions du code.
5. Brancher `initializeDocument()`/`updateDocumentVersion()` pour ce type.
6. Ajouter le document aux scripts de copie/migration si une simulation copiée doit l'emporter.

### Faire évoluer une entity existante

Ne pas écrire du code qui suppose simplement que le nouveau champ existe. Ajouter une migration `Vn → Vn+1` qui :

- tolère les documents anciens;
- initialise les nouveaux champs avec une valeur métier correcte;
- ne détruit pas les données inconnues;
- peut être appliquée dans l'ordre après toutes les migrations précédentes.

### Concurrence et version BrainCloud

Une Custom Entity possède une version serveur. Une écriture issue d'une lecture ancienne peut être rejetée. Lorsqu'un bug n'apparaît qu'avec deux utilisateurs, logger au minimum : `entityType`, `uid`, version lue, version sauvegardée et commande à l'origine de l'écriture.

### Copie entre environnements

`exports/data_importer_braincloud.js` copie plusieurs documents liés à une simulation : simulation, activity, results, actDataGameSpec, teams, teamsLogbooks, evaluationTeam, teamsModules, userDetails et evaluation. **Si un nouveau type de document fait partie d'une simulation copiée, ce script doit être audité et potentiellement étendu.**

## Checklist migration

1. Créer un vieux document représentatif.
2. Le charger avec le code actuel.
3. Vérifier chaque transformation de version.
4. Resauvegarder puis recharger.
5. Tester une simulation active avec plusieurs clients.
6. Tester le job global de migration seulement après validation ciblée.
7. Tester la copie Dev → autre environnement si le document doit suivre une simulation.

# Référence des Cloud Scripts communs

## But

Cette page est l'index des points d'entrée BrainCloud communs sous `server_scripts/_command`. Pour comprendre l'implémentation d'une commande, partir de ce wrapper puis suivre ses `bridge.include()` vers `_utils`.

## Simulation et activités

`create_simulation`, `get_simulation`, `get_simulations`, `get_active_simulations`, `get_active_simulations_simple`, `get_teacher_active_simulations`, `set_simulation_active`, `update_simulation_fields`, `create_activity`, `get_activity`, `get_activity_current_year`, `get_all_acts_stub`, `update_activity_fields`, `update_act_region`, `copy_act_modules`, `get_teacher_acts_and_modules`.

## Utilisateurs, écoles et équipes

`create_user_details`, `get_user_details`, `update_user_details_fields`, `get_display_name`, `edit_student`, `get_student_stub`, `get_simulation_students`, `get_teachers`, `get_schools`, `set_school`, `join_sim`, `join_team`, `remove_user_from_sim`, `remove_user_from_team`, `get_team`, `get_teams_list`, `change_team_name`.

## Rôles, état utilisateur et présence

`set_user_role`, `set_user_roles`, `unset_user_role`, `set_user_state`, `set_presence`, `set_language`, `set_volume`, `set_has_image`, `set_last_chat_time`.

## Évaluations

`get_evaluation`, `get_student_evaluation_stub`, `get_sim_evaluations_stubs`, `get_act_perf_evaluation`, `ignore_year_from_average`, `save_act_perf_ponderation`, `save_ponderation`, `save_overrided_eval_for_student`, `get_presence_evaluation`, `get_scenario_evaluation`, `save_eval_segment`, `save_segment`.

## Custom evaluations et pair evaluation

`create_custom_eval`, `get_custom_evaluation`, `save_custom_eval`, `update_custom_eval_fields`, `delete_custom_eval`, `get_pair_eval`, `save_pair_eval`, `teacher_get_pair_eval`.

## Logbook, chat et commentaires

`get_logbook`, `save_logbook`, `restore_logbook_history`, `get_logbook_evaluation`, `save_logbook_eval`, `save_logbook_ponderation_type`, `get_chats`, `add_chat`, `save_sim_comment`, `get_sim_comment`, `get_sim_comments`.

## Scénarios et tutoriels

`save_scenario_answers`, `set_scenario_step`, `set_year_sequence_seen`, `skip_the_tutorial`.

## Documents et données

`get_document`, `gather_documents`, `get_data`, `get_act_data_game_spec`, `update_static_data`, `get_results`, `get_time`.

## By Module exposé dans le socle

`save_module`, `remove_module`, `save_module_additional_time`, `restore_module_backup`.

## By Year exposé dans le socle

`change_year_state`, `student_take_decisions`, `save_market_size_modifier`. Les wrappers game-specific de génération/réouverture sont documentés dans By Year.

## Rankings

`generate_all_rankings`, `get_global_rankings`, `get_school_rankings`, `get_sim_rankings`.

## Access code et device/linking

`save_access_code`, `get_access_code`, `get_link_device_info`, `get_link_device_request`.

## Autres intégrations

`send_message_to_teacher_web` relie certains événements au backend Web. `ai_create_and_launch_product` expose une action d'aide/automatisation produit utilisée dans le contexte marketing.

## Admin commands

- `_admin_command/create_documents_in_bulk.js`
- `_admin_command/get_or_create_user.js`
- `_admin_command/get_player_names.js`

## Jobs

- `_job/_scheduled_job.js` : scheduler commun;
- `_job/generate_results_job.js` : génération planifiée By Year;
- `_job/update_documents_versions_job.js` : mise à jour de versions de documents;
- `_job/utils/update_documents_version.js` : logique utilitaire de migration.

## Règle de lecture

```mermaid
flowchart LR
UNITY[BackEndBrainCloud method] --> CMD[_command/*.js]
CMD --> INCLUDE[bridge.include]
INCLUDE --> UTILS[_utils / game-specific utils]
UTILS --> DOC[Custom Entities]
UTILS --> MSG[RTT messages]

```

# Référence des utils serveur BrainCloud

## Carte générale

<table id="bkmrk-fichier%2Fdossierrespo"><thead><tr><th>Fichier/dossier</th><th>Responsabilité</th></tr></thead><tbody><tr><td>`utils.js`</td><td>Helpers transversaux, UIDs, calculs simples, copie/merge, équipes/étudiants, rôles, CRC, logging.</td></tr><tr><td>`utils_documents.js`</td><td>Lecture, query, création, sauvegarde, suppression, increments et versioning des Custom Entities.</td></tr><tr><td>`utils_message.js`</td><td>Envoi de messages RTT à user/team/sim/activity et messages spécialisés.</td></tr><tr><td>`utils_eval.js`</td><td>Stubs et scores d'évaluation communs.</td></tr><tr><td>`utils_rankings.js`</td><td>Scores, tops et mise à jour des rankings.</td></tr><tr><td>`utils_RNG.js`</td><td>Random déterministe utilisé par la génération serveur.</td></tr><tr><td>`utils_redis.js`</td><td>Helpers d'accès Redis lorsque la feature l'utilise.</td></tr><tr><td>`utils_game_specific_base.js`</td><td>Base des utils game-specific et accès aux static data/chunks.</td></tr><tr><td>`utils_by_module.js`</td><td>Charge/initialise le socle By Module.</td></tr><tr><td>`utils_by_year.js`</td><td>Cycle annuel, jobs, bots et transitions By Year.</td></tr><tr><td>`by_module/`</td><td>Moteurs Accounting : création data, bills, answer key, grading, results, inventory.</td></tr><tr><td>`command/`</td><td>Implémentations réutilisables appelées par les wrappers command.</td></tr><tr><td>`factory/`</td><td>Sélection game-specific des utils, évaluations et générateurs de résultats.</td></tr><tr><td>`version/`</td><td>Migrations par type de document.</td></tr></tbody></table>

## utils.js — fonctions structurantes

Parmi les helpers importants : `getTeamActUid`, `getActUid`, dates/mois/années, `deleteTeam`, `getDisplayName`, `getActEvalTeamData`, `getTeamDecisionsData`, `getTeamsOfActivity`, `getStudentsOfActivity`, `hasRole`, `getUserUid`, `mergeObjects`, `copyOf/deepCopyOf`, logging Discord et validation CRC avant sauvegarde équipe.

## command/

- `utils_create_activity.js` : logique partagée de création d'activité;
- `utils_create_team.js` : création/initialisation d'équipe;
- `utils_custom_command_by_module.js` : routeur By Module;
- `utils_custom_command_by_year.js` : routeur By Year;
- `utils_join_simulation.js` : logique de join simulation;
- `utils_reopen_module.js` : rollback/réouverture By Module;
- `utils_reopen_year.js` : rollback/réouverture By Year;
- `utils_start_next_activity.js` : transition d'activité.

## factory/

- `utils_factory_game_specific_utils.js` : instancie `utilsByGameUid`;
- `utils_factory_generate_results.js` : moteur de résultats par jeu;
- `utils_factory_eval.js` : moteur d'évaluation par jeu.

## Principe de placement

Un wrapper sous `_command` doit rester mince. Dès qu'une logique est partagée entre plusieurs entrées, elle doit être placée dans `_utils` ou dans la famille métier adéquate. Les règles propres à ENT1/MK1/etc. restent dans leurs dossiers game-specific.

# RTT, messages et synchronisation

## Chaîne de synchronisation

BrainCloud RTT permet au serveur d'avertir les clients déjà connectés lorsqu'un document ou un état métier change. Côté serveur, la couche commune est `_utils/utils_message.js`. Côté Unity, `BackEndBrainCloud` reçoit les messages et les distribue au runtime.

```mermaid
sequenceDiagram
participant S as Cloud Script
participant M as utils_message
participant RTT as BrainCloud RTT
participant B as BackEndBrainCloud
participant D as MessagesDispatcher
participant C as Controller
S->>M: sendMessageToTeam / User / Sim
M->>RTT: message type + data
RTT-->>B: OnRTTMessageReceived
B->>D: ServerMessage
D-->>C: listener
C->>C: refresh du modèle / UI

```

## Helpers serveur

`utils_message.js` expose notamment `sendMessageToTeam`, `sendMessageToMemberList`, `sendMessageToSim`, `sendMessageToActStudents`, `sendMessageToUser`, `sendMessageToTeamAndTeacher` et des messages spécialisés comme `sendLedgerPageChangedMessage`.

## Connexion RTT

Après `AuthenticateUniversal`, `BackEndBrainCloud.EnableRTT()` appelle `BrainCloud.RTTService.EnableRTT`. Sur succès, le client enregistre `RegisterRTTMessagingCallback(OnRTTMessageReceived)`. Si la socket est fermée, le code tente de réactiver RTT; si l'authentification n'est plus valide, il réutilise la fonction d'authentification.

## Règle pour une nouvelle feature

Une écriture serveur réussie ne suffit pas si plusieurs utilisateurs voient le même état. Après la sauvegarde, déterminer qui doit être averti : utilisateur, équipe, équipe + professeur, activité ou simulation. Le listener Unity doit ensuite mettre à jour le modèle local avant l'UI.

## Diagnostic

1. Vérifier que le Cloud Script envoie bien un message.
2. Vérifier le type et la payload.
3. Vérifier que RTT est connecté.
4. Trouver le `MessageType` côté Unity.
5. Trouver le listener dans le controller.
6. Vérifier si le listener applique la donnée reçue ou redemande un document.

## Ajouter un nouveau message RTT de A à Z

### 1. Définir le message côté Unity

Ajouter la constante dans `MessageTypeBase.cs` si le message est commun, ou `MessageTypeByModule.cs` s'il appartient au framework By Module. La string doit être exactement la même que celle envoyée par le serveur.

### 2. Envoyer depuis le serveur après la sauvegarde

Utiliser l'helper d'audience approprié dans `utils_message.js` :

- `sendMessageToUser`;
- `sendMessageToTeam`;
- `sendMessageToTeamAndTeacher`;
- `sendMessageToSim`;
- `sendMessageToActStudents`.

```javascript
saveDocumentData("teams", teamData.uid);

sendMessageToTeamAndTeacher(teamData, "my_data_changed", {
    teamUid: teamData.uid,
    value: teamData.myValue
});
```

### 3. Enregistrer le listener dans le controller

```csharp
public override void SetListeners()
{
    base.SetListeners();
    Game.MessagesDispatcher.RegisterListener(
        MessageTypeBase.MY_DATA_CHANGED,
        OnMyDataChanged
    );
}

public override void RemoveListeners()
{
    base.RemoveListeners();
    Game.MessagesDispatcher.UnRegisterListener(
        MessageTypeBase.MY_DATA_CHANGED,
        OnMyDataChanged
    );
}
```

### 4. Traiter la payload

Le handler vérifie l'UID/context, applique les données au modèle puis déclenche l'event UI nécessaire. Ne pas recharger tout le document si quelques champs suffisent.

### 5. Grosse payload : ne pas l'envoyer en RTT

Pour des résultats/module data volumineux, envoyer une instruction de récupération. Le code By Module utilise par exemple `execute_request` pour demander au client de récupérer les résultats plutôt que d'envoyer tout le résultat dans le message.

## Ordonnancement réel côté client

`BackEndBrainCloud.OnRTTMessageReceived()` transforme la payload en `ServerMessage`. `MessagesDispatcher` met les messages dans une queue, attend brièvement, les trie par timestamp puis appelle chaque listener enregistré. Ce buffer réduit les problèmes d'ordre entre messages proches.

## Le cas get\_data

`OnRTTMessageReceived()` reconnaît les messages qui demandent un fetch supplémentaire. Dans ce cas le client charge la donnée avant de construire le `ServerMessage` final. Utiliser ce pattern lorsque la source de vérité doit rester le document serveur.

## Test minimal multi-client

1. Ouvrir deux clients sur la même équipe/simulation.
2. Faire l'action sur A.
3. Vérifier que le document est sauvegardé.
4. Vérifier le log RTT de B.
5. Vérifier que le bon listener B est exécuté une seule fois.
6. Changer de simulation sur B puis refaire l'action : aucun ancien controller ne doit encore recevoir le message.

# Système d’évaluation commun

## Couche commune

Les deux familles utilisent un socle d'évaluation partagé : `StudentEvaluationBase`, `TeamEvaluationBase`, leurs controllers, les stubs et les commandes BrainCloud communes.

## Sources de notes

- performance d'activité;
- présence;
- logbook;
- scenario / segments;
- pair evaluation;
- custom evaluations;
- teacher overrides;
- By Module : scores de modules;
- By Year : scores annuels game-specific.

## Commandes communes

Le contrat backend inclut notamment `GetEvaluation`, `GetStudentEvaluationStub`, `GetActPerfEvaluation`, `GetLogbookEvaluation`, `GetCustomEvaluation`, `GetPresenceEvaluation`, création/modification/suppression de custom eval, pair eval et teacher overrides.

## Pondérations

La simulation conserve les ponderations d'évaluation et d'ActPerf. Les changements sont persistés serveur puis propagés en RTT afin que les contrôles professeur/étudiant se mettent à jour.

## Stubs

`SimulationBaseController` maintient des caches de `EvalStudentStub` et `EvalTeamStub`. Ils sont marqués dirty lorsque certains messages arrivent afin d'éviter de recharger toutes les évaluations en permanence.

## Spécialisation

```mermaid
flowchart TD
BASE[Évaluation commune] --> BM[By Module]
BASE --> BY[By Year]
BM --> MOD[Module scores / correction]
BY --> YR[Year results / game-specific Eval]
BASE --> LOG[Logbook]
BASE --> PRES[Presence]
BASE --> CUST[Custom / Pair / Overrides]

```

## Règle de maintenance

Les calculs propres à un jeu restent dans son moteur Eval; la composition, les ponderations, les stubs et les mécanismes de sauvegarde/override restent communs.

## Ajouter une nouvelle composante d’évaluation

Avant d'ajouter une note, décider si elle appartient à **evaluation** (étudiant), **evaluationTeam** (équipe), **ActPerf**, ou si elle est seulement un détail de résultat game-specific.

### 1. Définir la source de vérité

- score individuel → document `evaluation`;
- score d'équipe/performance → `evaluationTeam`;
- score de module → généré dans le pipeline By Module puis propagé vers `evaluationTeam.actPerf`;
- score annuel game-specific → calcul du moteur Eval du jeu.

### 2. Définir la pondération

Les évaluations supportent plusieurs stratégies : annuelle/global, individuelle/équipe. Pour ActPerf, `getActPerfScore()` utilise la pondération enregistrée dans la simulation ou, à défaut, la static data `act_perf_pond`.

### 3. Sauvegarder la note

Ajouter/adapter la commande serveur responsable. Après sauvegarde, envoyer le stub RTT approprié plutôt que de forcer tous les clients à recharger toutes les évaluations.

### 4. Ajouter le calcul de stub

`utils_eval.js` contient les fonctions de score compact : `getActPerfScore`, `getModuleTypeScore`, `getPresenceScore`, `getLogbookScore`, `getCustomEvalScore` et les builders de stubs étudiant/équipe.

### 5. Invalider les caches Unity

`SimulationBaseController` et les controllers d'évaluation cachent certains stubs. Le nouveau message doit soit mettre à jour directement la valeur, soit marquer le cache dirty.

## Valeur -1 : signification importante

Plusieurs calculs retournent `-1` pour signifier “ignorer / pas encore évalué”, tandis que `0` signifie réellement une note nulle. Ne pas convertir automatiquement un `-1` en 0 dans l'UI ou dans un nouvel agrégat.

## Checklist évaluation

- Document correct choisi.
- Pondération et type annuel/global définis.
- Cas ongoing vs finished testé.
- `-1` conservé lorsque la note doit être ignorée.
- RTT/stub envoyé.
- Cache Unity invalidé.
- Export Excel/rapport vérifié si la nouvelle note doit y apparaître.
- By Year sync, async et By Module testés selon le scope.

# Données statiques et pipeline import/export

## Vue complète

La static data n'est pas maintenue uniquement à la main dans Unity. Le dépôt contient un pipeline qui peut lire Google Sheets, générer les JSON du client puis publier la même donnée dans BrainCloud.

```mermaid
flowchart LR
S[Google Sheets] --> I[exports/importer.js]
I --> J[Assets/GameSpecific/.../Resources/data_*/*.json]
J --> E[exports/exportAllData.js]
E --> BCE[BrainCloudExporter.js]
BCE --> CMD[_command/update_static_data]
CMD --> BC[(Static Data BrainCloud)]
J --> U[Unity Resources]

```

## importer.js

`exports/importer.js` télécharge les feuilles via Google Sheets API, nettoie les headers, infère ou force les types, convertit `string/double/long/bool/json` et écrit les JSON. Un header peut forcer son type avec une notation comme `(str)`, `(long)`, `(double)`, `(bool)` ou `(json)`.

## exportAllData.js

`exports/exportAllData.js` contient la carte des jeux, Spreadsheet IDs, GIDs et fichiers additionnels. Il connaît les jeux By Year, Acc1/Acc2, le dataset partagé `byModule`, les tables fiscales annuelles et les données de bots.

Pour un jeu Accounting, la pipeline met à jour les données propres au jeu puis le dataset partagé `byModule`. Les données sont écrites sous `../game/Assets/GameSpecific/<gameUid>/Resources/data_<gameUid>/`.

## Publication BrainCloud

`BrainCloudExporter.exportJson()` initialise un wrapper BrainCloud et appelle `_command/update_static_data`. Certaines sources utilisent `separateTabs` et un `collectionType` dédié, notamment les chunks fiscaux.

## Données By Module partagées

Le dataset `byModule` inclut notamment accounts, account types, taxes, employer taxes, interests, immobilizations, contract regularizations, ainsi que des fichiers additionnels comme `modules`, `module_pages`, `regions_settings`, `act_settings`, `modules_settings`, `pages_settings` et `modules_contexts`.

## Checklist de modification

1. Identifier si la source officielle est Google Sheets ou un JSON additionnel.
2. Modifier la source correcte.
3. Importer/générer le JSON.
4. Inspecter le diff du JSON Unity.
5. Publier vers le bon environnement BrainCloud si nécessaire.
6. Tester le chargement Unity et le runtime serveur.
7. Pour By Module, vérifier aussi les filtres régionaux et settings décrits dans la page de configuration Accounting.

## Ajouter un nouveau dataset Google Sheets de A à Z

### 1. Définir la source dans exportAllData.js

Ajouter le mapping du jeu vers la Spreadsheet et le GID/tab attendu. Si la donnée appartient au socle Accounting partagé, déterminer si elle doit vivre sous `byModule` plutôt que sous `acc1`/`acc2`.

### 2. Définir le nom du JSON Unity

Le nom doit correspondre à ce que `StaticData*.Initialize()` charge. Une nouvelle feuille importée mais jamais référencée par le loader ne sera jamais utilisée.

### 3. Définir les types de colonnes

Dans Google Sheets, utiliser les suffixes forcés `(str)`, `(long)`, `(double)`, `(bool)` ou `(json)` lorsque l'inférence est ambiguë. Une erreur ici peut produire un JSON valide mais un mauvais type runtime.

### 4. Importer localement

Depuis `node.js/exports/` :

```
importDataGoogleSheet.bat
# ou directement
node exportAllData.js importAllGoogleSheet <gameUid>
```

Le batch valide une liste de GameUid; si un nouveau GameUid/dataset n'y apparaît pas, mettre aussi ce batch à jour.

### 5. Inspecter le diff JSON

Ne jamais pousser l'export sans regarder le diff. Vérifier notamment UID, types numériques, booléens, tableaux JSON, lignes supprimées et ordre des données importantes.

### 6. Publier dans BrainCloud

```
exportToBrainCloud.bat
# ou
node exportAllData.js exportAllBrainCloud <gameUid>
```

Le chemin passe par `BrainCloudExporter.js` puis `_command/update_static_data`.

### 7. Valider les deux côtés

- Unity : le JSON est présent dans le bon `Resources/data_*` et le controller le charge.
- Serveur : `utils_game_specific_base`/`utilsByGameUid` retourne la nouvelle donnée.
- Accounting : tester les overrides région/module/page si le dataset est filtré par settings.

## Ne pas éditer le JSON généré comme source officielle

Si le fichier vient d'une Google Sheet, une modification manuelle du JSON sera écrasée au prochain import. Modifier la Sheet puis regénérer. Les seuls JSON maintenus directement sont ceux explicitement traités comme fichiers additionnels par `exportAllData.js`.

# Textes, localisation et mise à jour

## Pipeline

```mermaid
flowchart LR
S[Google Sheets] --> I[importUploadTexts.js]
I --> JSON[shared_texts / gameUid_texts]
JSON --> UNITY[TextsManager]
JSON --> WEB[Upload serveur web]
WEB --> API[/getTexts/gameUid/lang/timestamp]
API --> UNITY
UNITY --> TL[TextLoader / UI]

```

## Fichiers générés

- partagés : `Assets/Resources/data/shared_texts_fr.json` et `shared_texts_en.json`;
- jeu : `Assets/GameSpecific/<gameUid>/Resources/data_<gameUid>/<gameUid>_texts_fr.json` et EN.

## Import Node

`exports/importUploadTexts.js` associe chaque GameUid à ses feuilles Google et appelle `importer.importTexts()`. Les commandes exposées permettent l'import game-specific, shared, ou l'import suivi de l'upload.

Pour Accounting, le script traite Acc1 et Acc2 ensemble via `accountingTextGameUids = ["acc1", "acc2"]`, afin d'éviter de mettre à jour une seule variante.

## Chargement Unity

`TextsManager.Initialize()` charge les textes shared. `Game.SetGameUid()` appelle `LoadGameSpecificTexts()`. `SetLang()` recharge les deux couches puis appelle `TextLoader.ReloadAll()`. Un UID absent retourne `[uid]`.

## Mise à jour sans rebuild

`UpdateTextsFromWebServer()` interroge `/getTexts/<gameUid>/<locale>/<timestamp>`. Le client garde timestamp/hash et peut donc recevoir des textes plus récents que ceux embarqués dans le build.

## Modifier un texte

1. Trouver l'UID utilisé par `TextLoader` ou `Game.Texts.GetText()`.
2. Modifier la source Google Sheets appropriée.
3. Lancer l'import.
4. Vérifier FR + EN.
5. Publier si le changement doit être disponible sans rebuild.
6. Sinon inclure les JSON générés dans le prochain build Unity.

## Procédure exacte pour ajouter ou modifier un texte

1. Choisir un UID stable; ne pas utiliser la phrase FR elle-même comme identifiant.
2. Ajouter/modifier FR et EN dans la Google Sheet de textes appropriée.
3. Depuis `node.js/exports/`, lancer `importTexts.bat` et entrer le GameUid, ou `shared` pour les textes communs.
4. Inspecter les JSON générés.
5. Si le texte doit être disponible sans rebuild, lancer `importThenUploadTextsToUniksimServer.bat`.
6. Tester le changement de langue dans un client déjà ouvert via `TextsManager.SetLang()`.

### Commandes directes équivalentes

```
node importUploadTexts.js shared
node importUploadTexts.js game_specific mk1
node importUploadTexts.js uploadTexts mk1
```

### Cas Accounting

`getGameUidsToImport()` traite Accounting comme une paire : un import de textes Acc1/Acc2 met à jour les deux jeux concernés. Ne pas maintenir deux traductions divergentes par erreur.

### Choisir shared ou game-specific

- texte réutilisé par plusieurs jeux/UI commune → shared;
- vocabulaire/règle propre à un jeu → game-specific;
- ne pas copier un même UID dans plusieurs couches juste pour contourner un problème de chargement.

## Debug d'un UID affiché comme \[my\_uid\]

1. Vérifier que l'UID exact existe dans la Sheet.
2. Vérifier que le JSON correspondant a été regénéré.
3. Vérifier shared vs `data_<gameUid>`.
4. Vérifier la locale courante.
5. Si le serveur web doit fournir la mise à jour, vérifier l'upload et le timestamp/hash.
6. Si le texte est dans Resources seulement, rebuilder Unity.

# UI, prefabs et composants récurrents

## Frontière

Cette page décrit uniquement les briques UI réellement communes. Les écrans Data-Driven Accounting vont dans By Module; les écrans de cycle annuel vont dans By Year; les prefabs propres à un jeu restent dans sa page game-specific.

## Dossiers

- `Assets/Src/UI/` : base commune des contrôles;
- `Assets/Src/UI/SimByModule/` : spécialisation By Module;
- `Assets/Src/UI/SimByYear/` : spécialisation By Year;
- `Assets/GameSpecific/<gameUid>/Resources/Prefabs/UI/` : prefabs propres au jeu.

## Composants communs importants

<table id="bkmrk-familleclasses-repr%C3%A9"><thead><tr><th>Famille</th><th>Classes représentatives</th></tr></thead><tbody><tr><td>Navigation / panels</td><td>`MainControlStudent`, `MainControlTeacher`, `MainContentPanel`, `SimPanel`, `SubPanelBase`</td></tr><tr><td>Popups / wizards</td><td>`WizardPopupControlBase`, `WizardPageControlBase`, `YesNoPopupControl`, popups d'édition</td></tr><tr><td>Inputs</td><td>`FormattedTMPInputFieldBase`, inputs nombres/percentages/positifs, date picker, autocomplete</td></tr><tr><td>Sélecteurs</td><td>`SelectorControl`, grouped/non-grouped, dropdown overlays, toggles</td></tr><tr><td>Profil / équipes</td><td>`ProfilePopupControl`, `StudentCardControl`, `GroupStudentsControl`, `GroupTeamsControl`</td></tr><tr><td>Communication</td><td>`ChatPopupControl`, `NotificationsListPopupControl`, toasters</td></tr><tr><td>Scénario</td><td>`ScenarioPanelControlBase`, `ScenarioPopupControl`, `ScenarioItemControl`</td></tr><tr><td>Tooltips / tables</td><td>Tooltip base, table rows/cells, scroll helpers et layout helpers</td></tr></tbody></table>

## Règle de prefab

Un prefab commun doit rester générique. S'il commence à contenir des références spécifiques à Accounting, MK1, HR1, etc., créer/maintenir une variante dans le dossier approprié plutôt que d'ajouter des branches GameUid au prefab commun.

## Diagnostic UI

1. Identifier le prefab instancié.
2. Repérer le contrôle C# attaché.
3. Remonter vers sa classe Base.
4. Identifier le controller/model qui fournit les données.
5. Vérifier `TextLoader` si le souci est textuel.
6. Vérifier le message RTT si l'UI est seulement désynchronisée.

## Créer un composant UI commun de A à Z

### 1. Vérifier qu'il est réellement commun

Si le contrôle connaît un `modulePageUid`, un segment MK, un employee HR ou une règle ENT, il n'est probablement pas commun. Le mettre dans la famille ou le GameSpecific concerné.

### 2. Créer le script de contrôle

Placer le C# sous `Assets/Src/UI/` dans le dossier logique existant. Exposer les références Unity avec `[SerializeField]` lorsque possible plutôt que des `Find()` runtime.

### 3. Créer/modifier le prefab

Le prefab réutilisable doit vivre dans les Resources/prefabs communs adaptés. Ne pas modifier une instance de scène sans propager les changements au prefab.

### 4. Brancher les textes

Utiliser `TextLoader` ou `Game.Texts`; ne pas hardcoder un texte utilisateur dans le prefab. Ajouter FR/EN via le pipeline Textes.

### 5. Ajouter les events/listeners

Les listeners de buttons doivent être ajoutés dans `Start/OnEnable` selon le lifecycle du contrôle et retirés dans `OnDestroy/OnDisable` lorsqu'ils ciblent des events statiques/globaux.

### 6. Si le composant doit être ciblable par un tutoriel

Ajouter `TutorialUIObject` ou `TutorialUIButton` au prefab avec un UID stable. Voir la page **Tutoriels — création de A à Z et architecture**.

### 7. Si la donnée est live

L'UI ne devrait pas écouter BrainCloud directement. Le controller reçoit le RTT, modifie le modèle, puis l'UI observe l'event/controller.

## Checklist prefab

- références sérialisées non null;
- anchors/Canvas adaptés aux résolutions WebGL;
- TextLoader/UIDs présents;
- aucune dépendance game-specific cachée;
- listeners nettoyés;
- état initial correct lors d'une réouverture du popup/panel;
- test dans prefab variant et scene réelle;
- build si prefab/asset modifié.

## Debug UI

Si l'UI affiche une mauvaise valeur : inspecter d'abord le modèle/controller. Si le modèle est correct, inspecter le binding/control. Si le modèle est faux seulement en live, suivre le RTT. Si le contrôle fonctionne en Editor mais pas WebGL, vérifier les références sérialisées et les exceptions dans la console navigateur.

# Tutoriels — création de A à Z et architecture

## But de cette page

Cette page explique **comment créer, déclencher, persister, tester et déboguer un tutoriel Unity dans UnikSim**. Elle ne décrit pas Timeline : ce sont deux systèmes séparés dans le code.

## Choisir le bon type de tutoriel avant de coder

<table id="bkmrk-besoin-solution-affi"><thead><tr><th>Besoin</th><th>Solution</th></tr></thead><tbody><tr><td>Afficher un message + éventuellement surligner une zone, puis laisser l'utilisateur cliquer pour fermer</td><td>`GenericTutorial`. Ne pas créer une nouvelle classe.</td></tr><tr><td>Forcer l'utilisateur à cliquer sur un vrai bouton/toggle et enchaîner plusieurs étapes</td><td>Créer une classe qui hérite de `MenuTutorialSequence`.</td></tr><tr><td>Tutoriel commun à tous les jeux</td><td>`Assets/Src/Game/Tutorial/`.</td></tr><tr><td>Tutoriel propre au framework By Module</td><td>`Assets/Src/UI/SimByModule/Tutorial/`.</td></tr><tr><td>Tutoriel propre à MK1, HR1, ENT1, etc.</td><td>`Assets/GameSpecific/<gameUid>/Src/Tutorial/` ou dossier UI du jeu si c'est déjà la convention de ce jeu.</td></tr><tr><td>Tutoriel joué une seule fois par utilisateur</td><td>Le déclenchement doit tester un `Scenario.eScenarioStep`, puis appeler `SetScenarioStep()` lorsque le tutoriel est réellement complété.</td></tr><tr><td>État propre à une simulation plutôt qu'au compte utilisateur global</td><td>Utiliser le système `UserState` plutôt qu'ajouter automatiquement un ScenarioStep.</td></tr></tbody></table>

## Architecture réelle

```mermaid
flowchart TD
TRIGGER[GameState / UI / événement] --> SEQ[MenuTutorialSequence]
SEQ --> CHARACTER[TutorialCharacter]
SEQ --> LOOKUP[TutorialUIObject.GetUiObject]
LOOKUP --> TARGET[RectTransform actif]
SEQ --> HIGHLIGHT[TutorialSquareHighlight]
SEQ --> ARROW[TutorialArrow]
SEQ --> CLICK{Type d'attente}
CLICK -->|clic overlay| CATCHER[TutorialClickCatcherControl]
CLICK -->|vrai bouton| BUTTON[TutorialUIButton.clicked]
SEQ --> DONE[SetScenarioStep / SetUserState]
DONE --> STOP[Status.Stop]
STOP --> CLEAN[cleanup automatique]

```

## Composants à connaître

<table id="bkmrk-classe-responsabilit"><thead><tr><th>Classe</th><th>Responsabilité</th></tr></thead><tbody><tr><td>`TutorialControl`</td><td>Singleton contenant character, highlight, blocker, click catcher, arrow et bouton Skip.</td></tr><tr><td>`MenuTutorialSequence`</td><td>Classe de base des tutoriels complexes. Gère Start/Stop, nettoyage et helpers.</td></tr><tr><td>`GenericTutorial`</td><td>Tutoriel simple “message + highlight éventuel + clic pour continuer”.</td></tr><tr><td>`TutorialUIObject`</td><td>Enregistre un `RectTransform` actif sous un UID afin qu'un tutoriel puisse le retrouver.</td></tr><tr><td>`TutorialUIButton`</td><td>Étend `TutorialUIObject` et détecte les clics d'un `Button`, `BasicButton` ou `Toggle`.</td></tr><tr><td>`TutorialSquareHighlight`</td><td>Cadre/masque visuel autour d'une cible.</td></tr><tr><td>`TutorialArrow`</td><td>Place la flèche relativement à la cible.</td></tr><tr><td>`TutorialClickCatcherControl`</td><td>Overlay cliquable utilisé par `WaitForClick()`.</td></tr><tr><td>`TutorialInputBlockerControl`</td><td>Permet de bloquer une partie ou la totalité des inputs.</td></tr></tbody></table>

## Créer un tutoriel de zéro — procédure complète

### 1. Décider ce qui déclenche le tutoriel

Commencer par le **trigger**, pas par la classe du tutoriel. Exemples réels :

- `StudentState.TransitionIn()` lance `FirstDecisionTutorial` selon l'état de l'utilisateur;
- `TeacherCreateSimState` lance le tutoriel de création de simulation;
- `TeacherChooseTutorialPopupControl` permet de rejouer manuellement certains tutoriels;
- une validation UI peut lancer directement un `GenericTutorial`.

Déterminer également si le tutoriel doit être joué une seule fois. Si oui, choisir son mécanisme de persistance avant d'écrire le contenu.

### 2. Rendre les éléments UI trouvables par le tutoriel

Pour chaque élément à surligner ou suivre, ouvrir le prefab correspondant dans Unity et ajouter `TutorialUIObject` au GameObject possédant le `RectTransform`. Dans l'Inspector, remplir le champ `uiObejctName` avec un UID stable, par exemple :

```
create_product_button
calculator_button
cash_label
my_new_feature_button
```

Le tutoriel pourra ensuite récupérer la cible avec :

```csharp
RectTransform target = TutorialUIObject.GetUiObject("my_new_feature_button");
```

**Important :** `TutorialUIObject` s'enregistre dans le dictionnaire statique uniquement dans `OnEnable()`. Si le GameObject/prefab/panel n'est pas actif, `GetUiObject()` retourne `null`. Il faut alors ouvrir le panel avant le lookup ou attendre jusqu'à ce que la cible existe.

Si plusieurs variantes d'UI peuvent représenter la même étape, utiliser :

```csharp
string[] candidates = { "button_practice", "button_summative", "button_consultation" };
RectTransform target = TutorialUIObject.GetFirstUiObject(candidates);
```

**Éviter les UID dupliqués sans raison :** le dictionnaire garde une liste mais `GetUiObject()` retourne le premier élément actif. Deux objets actifs portant le même UID peuvent donc viser la mauvaise cible.

### 3. Si l'utilisateur doit cliquer sur la vraie cible, utiliser TutorialUIButton

Un simple `TutorialUIObject` permet de trouver/surligner l'objet, mais il ne sait pas si l'utilisateur l'a activé. Pour une étape qui doit attendre un vrai clic, utiliser `TutorialUIButton`.

Dans l'Inspector, il faut assigner explicitement l'un des champs correspondant au contrôle réel :

- `button` pour un `UnityEngine.UI.Button`;
- `basicButton` pour un `BasicButton`;
- `toggle` pour un `Toggle`.

La classe ne fait pas automatiquement un `GetComponent<Button>()`. Si aucune référence n'est assignée, `clicked` ne passera jamais à true.

Pattern réel :

```csharp
RectTransform target = TutorialUIObject.GetUiObject("my_new_feature_button");
TutorialUIButton tutoButton = target.GetComponent<TutorialUIButton>();

while (!tutoButton.clicked)
{
    ShowSquareHighlight(target);
    ShowArrow(target);
    yield return Status.Running;
}

HideArrow();
RemoveSquareHighlight();
```

`TutorialUIButton.clicked` s'auto-reset après avoir été lu comme true. Il faut donc l'utiliser comme un signal ponctuel, typiquement dans un `while (!tutoButton.clicked)`.

### 4. Ajouter les textes

Les textes doivent normalement passer par le système de localisation existant. Ajouter les UID FR/EN dans la Google Sheet appropriée puis utiliser le pipeline documenté dans **Textes, localisation et mise à jour**.

Dans le code :

```csharp
ShowCharacter(
    Game.Texts.GetText("my_tutorial_step_1"),
    TutorialCharacter.eCharacterPos.eTop,
    true
);
```

`GenericTutorial` accepte soit un UID existant, soit une chaîne déjà résolue. Il teste `Game.Texts.HasText()` avant de résoudre le texte.

### 5. Créer la classe pour un tutoriel multi-étapes

Créer une classe qui hérite de `MenuTutorialSequence` et surcharger `Content()`. Le moteur est une séquence coroutine : tant qu'une étape attend quelque chose, retourner `Status.Running`; à la fin, retourner `Status.Stop`.

```csharp
using System.Collections.Generic;
using UnityEngine;

namespace UnikSim
{
    public class MyFeatureTutorial : MenuTutorialSequence
    {
        public override IEnumerator<Status> Content()
        {
            ShowCharacter(
                Game.Texts.GetText("my_feature_tuto_1"),
                TutorialCharacter.eCharacterPos.eTop,
                true
            );

            WaitForClick(0.1f);
            while (!_isClicked)
            {
                yield return Status.Running;
            }

            RectTransform target = null;
            while (target == null)
            {
                target = TutorialUIObject.GetUiObject("my_feature_button");
                yield return Status.Running;
            }

            TutorialUIButton tutoButton = target.GetComponent<TutorialUIButton>();

            ShowCharacter(
                Game.Texts.GetText("my_feature_tuto_2"),
                TutorialCharacter.eCharacterPos.eTop,
                false
            );

            while (!tutoButton.clicked)
            {
                ShowSquareHighlight(target);
                ShowArrow(target);
                yield return Status.Running;
            }

            HideArrow();
            RemoveSquareHighlight();
            RemoveCharacter();

            Game.User.SetScenarioStep(Scenario.eScenarioStep.eMyFeatureTutorial);

            yield return Status.Stop;
        }
    }
}
```

### 6. Comprendre WaitForClick : ce n'est PAS un clic sur la cible

`WaitForClick()` active `TutorialClickCatcherControl`, un overlay global. L'utilisateur clique sur l'overlay pour continuer. Ce helper est donc parfait pour une étape informative, mais **pas pour exiger qu'un bouton métier soit réellement pressé**.

<table id="bkmrk-comportement-voulu-%C3%80"><thead><tr><th>Comportement voulu</th><th>À utiliser</th></tr></thead><tbody><tr><td>“Lis ce message puis clique n'importe où pour continuer”</td><td>`WaitForClick()` + `_isClicked`</td></tr><tr><td>“Clique exactement sur ce bouton pour continuer”</td><td>`TutorialUIButton.clicked`</td></tr><tr><td>“Attends quelques secondes”</td><td>`WaitForTime()` + `TimeIsReached`</td></tr></tbody></table>

### 7. Choisir la bonne caméra

Les helpers utilisent une caméra pour convertir le `RectTransform` en position écran.

- UI normale : `Game.Instance.UICamera` — utilisé par défaut;
- popup : `Game.Instance.PopupCamera`;
- certains cas game-specific peuvent fournir explicitement une autre caméra.

```csharp
ShowSquareHighlight(target, Game.Instance.PopupCamera);
ShowArrow(target, Game.Instance.PopupCamera);
```

Un highlight décalé ou une flèche au mauvais endroit est souvent simplement une mauvaise caméra.

### 8. Déclencher le tutoriel

Le lancement standard est :

```csharp
new MyFeatureTutorial().Start();
```

Pour un tutoriel one-shot :

```csharp
if (!Game.User.IsScenarioStepDone(Scenario.eScenarioStep.eMyFeatureTutorial))
{
    new MyFeatureTutorial().Start();
}
```

Le meilleur endroit est généralement l'endroit où l'état nécessaire vient d'être atteint : un `GameState`, l'ouverture d'un panel, la création d'une donnée ou la réception d'un résultat. Ne pas lancer un tutoriel dans `Update()` sans garde : il serait recréé continuellement.

### 9. Ajouter un ScenarioStep si le tutoriel doit être mémorisé

`Scenario.eScenarioStep` est un enum `[Flags]`. Chaque valeur représente un bit. Les valeurs existantes utilisent `1 << n`.

Pour ajouter une nouvelle étape persistante :

```csharp
eMyFeatureTutorial = 1 << 18
```

Prendre le prochain bit libre et ne jamais réutiliser un bit existant. `eFinishQuestions = 1` doit rester égal à 1 : le commentaire du code indique que le serveur dépend de cette valeur.

À la fin réelle du tutoriel :

```csharp
Game.User.SetScenarioStep(Scenario.eScenarioStep.eMyFeatureTutorial);
```

`SetScenarioStep()` ajoute le bit au masque local puis, par défaut, appelle `Game.Backend.SetScenarioStep()` pour sauvegarder le masque côté backend.

Si le statut doit être propre à une simulation plutôt qu'au compte utilisateur global, examiner `User.SetUserState(simUid, stateUid)` / `IsUserStateDone()` au lieu d'utiliser un nouveau ScenarioStep.

### 10. Cas simple : ne PAS créer une nouvelle classe

Pour un message de validation ou une aide one-shot sans workflow :

```csharp
new GenericTutorial(
    "not_enough_budget_body",
    "games_budget",
    TutorialCharacter.eCharacterPos.eDown
).Start();
```

Avec popup :

```csharp
new GenericTutorial(
    "loan_impossible",
    "loan_max",
    TutorialCharacter.eCharacterPos.eTop,
    Game.Instance.PopupCamera
).Start();
```

Pour chaîner les options de `GenericTutorial`, utiliser `StartGeneric()` :

```csharp
new GenericTutorial(
    "not_enough_budget_body",
    "games_budget",
    TutorialCharacter.eCharacterPos.eDown
).StartGeneric().ShowFlashing();
```

## Ce que MenuTutorialSequence fait automatiquement

`Start()` arrête le tutoriel actuellement actif avant de démarrer le nouveau. `Stop()` nettoie character, square highlight, arrow, input blocker, click catcher et bouton Skip.

Le bouton Skip est activé par défaut pour le professeur via `Game.User.IsTeacher`. Certains tutoriels “force watch”, comme la création initiale de simulation, le désactivent explicitement.

## Exemples du code à copier selon le besoin

<table id="bkmrk-besoin-exemple-exist"><thead><tr><th>Besoin</th><th>Exemple existant</th></tr></thead><tbody><tr><td>Premier tutoriel étudiant simple</td><td>`Assets/Src/Game/Tutorial/Student/FirstDecisionTutorial.cs`</td></tr><tr><td>Tutoriel By Module avec plusieurs cibles alternatives</td><td>`FirstDecisionByModuleTutorial.cs`</td></tr><tr><td>Long tutoriel professeur composé de sous-séquences</td><td>`TeacherCreateSimTutorial.cs`</td></tr><tr><td>Extension spécifique By Module</td><td>`TeacherCreateSimTutorialByModule.cs`</td></tr><tr><td>Tutoriel game-specific complexe</td><td>`GameSpecific/mk1/Src/Tutorial/CreateFirstProductTutorial.cs`</td></tr><tr><td>Tutoriel Accounting complexe</td><td>`GameSpecific/acc1/src/AccGeneralTutorials.cs` et `GenericAcc1Tutorial.cs`</td></tr><tr><td>Aide/validation très simple</td><td>Rechercher `new GenericTutorial` dans les contrôles UI.</td></tr></tbody></table>

## Checklist avant de considérer le tutoriel terminé

1. Le ou les textes existent en FR et EN.
2. Chaque cible possède un UID `TutorialUIObject` stable.
3. Chaque étape qui exige un vrai clic possède un `TutorialUIButton` correctement branché au Button/BasicButton/Toggle.
4. Les cibles existent réellement au moment du lookup; sinon le code attend leur apparition.
5. La bonne caméra est utilisée pour chaque popup/panel.
6. Le trigger ne peut pas relancer le tutoriel en boucle.
7. Le ScenarioStep/UserState n'est marqué terminé qu'après la vraie réussite.
8. Skip/fermeture d'écran ne laisse ni highlight ni blocker.
9. Le tutoriel fonctionne après logout/login.
10. Le tutoriel fonctionne dans le build WebGL, pas uniquement dans l'Editor.

## Debug — symptômes fréquents

<table id="bkmrk-sympt%C3%B4me-cause-proba"><thead><tr><th>Symptôme</th><th>Cause probable</th></tr></thead><tbody><tr><td>`GetUiObject()` retourne null</td><td>Prefab/panel inactif, UID erroné, ou composant non enregistré.</td></tr><tr><td>Le tutoriel attend éternellement un clic</td><td>`TutorialUIButton` absent ou sa référence `button/basicButton/toggle` n'est pas assignée.</td></tr><tr><td>Highlight/flèche décalé</td><td>Mauvaise caméra, souvent `UICamera` utilisée sur une popup.</td></tr><tr><td>Le clic sur la cible ne fait rien</td><td>Le code utilise `WaitForClick()` alors qu'il devrait attendre `TutorialUIButton.clicked`.</td></tr><tr><td>Le tutoriel rejoue à chaque login</td><td>ScenarioStep/UserState jamais sauvegardé.</td></tr><tr><td>Le tutoriel est considéré terminé trop tôt</td><td>`SetScenarioStep()` est appelé au démarrage au lieu de la fin.</td></tr><tr><td>La mauvaise cible est surlignée</td><td>Plusieurs objets actifs utilisent le même `uiObejctName`.</td></tr><tr><td>Un nouveau tutoriel coupe l'ancien</td><td>Comportement normal : `MenuTutorialSequence.Start()` arrête le tutoriel courant.</td></tr></tbody></table>

## Règle de conception

Un tutoriel ne devrait pas dupliquer la logique métier. Il doit **observer l'UI et guider l'utilisateur**. L'action réelle reste dans les contrôles/controllers existants; le tutoriel attend simplement que cette action se produise.

# Build Unity, WebGL et versioning client

## Build WebGL

Le build est défini dans `Assets/Src/Editor/Builder.cs`. Le menu Unity **Uniksim → Build WebGL** appelle `WebGLBuild()`, qui build `Assets/game.unity` vers le dossier `Build/`.

```mermaid
flowchart LR
CODE[Code + Prefabs + Resources] --> VER[version.txt / sub_version.txt]
VER --> MENU[Uniksim / Build WebGL]
MENU --> SCENE[Assets/game.unity]
SCENE --> BUILD[game/Build/Build/*]
BUILD --> DEPLOY[node.js/deploy/deployToCloud.bat]
DEPLOY --> SPACE[uniksim-data/version/*]
SPACE --> DB[constants table: active WebGL version]
DB --> WEB[Web app loads that version]

```

## Version du client

- `Assets/Resources/data/version.txt` : version principale utilisée pour le dossier de déploiement;
- `Assets/Resources/data/sub_version.txt` : sous-version utilisée hors live.

`Game.cs` et l'UI de profil lisent ces fichiers. `Assets/Src/Editor/AutoVersion.cs` contient un ancien incrément automatique, mais son code est commenté : **le build n'incrémente pas automatiquement `version.txt`**.

## Script de déploiement

Le déploiement du build WebGL est géré par :

- `node.js/deploy/deployToCloud.bat` — point d'entrée;
- `node.js/deploy/deployBuild.js` — script Node qui effectue l'upload.

`deployToCloud.bat` exécute `node deploybuild.js`. `deployBuild.js` lit automatiquement `../game/Assets/Resources/data/version.txt` puis upload les artefacts dans `uniksim-data` sous un dossier portant cette version.

### Fichiers uploadés

<table id="bkmrk-fichier-local-cl%C3%A9-di"><thead><tr><th>Fichier local</th><th>Clé distante</th></tr></thead><tbody><tr><td>`game/Build/Build/Build.data.unityweb`</td><td>`<version>/Build.data.unityweb`</td></tr><tr><td>`game/Build/Build/Build.wasm.unityweb`</td><td>`<version>/Build.wasm.unityweb`</td></tr><tr><td>`game/Build/Build/Build.framework.js.unityweb`</td><td>`<version>/Build.framework.js.unityweb`</td></tr><tr><td>`game/Build/Build/Build.loader.js`</td><td>`<version>/Build.loader.js`</td></tr></tbody></table>

**Important :** modifier `version.txt` *avant* le build et le déploiement. Le dossier distant est choisi à partir de cette valeur.

## Version active dans la base de données

Après le déploiement, mettre à jour la version WebGL active dans la table `constants` de la base `uniksim`, enregistrement `id = 1`. Le backend la lit dans `GameService.getVersion()`.

phpMyAdmin : `https://db.uniksim.com/phpmyadmin/index.php?db=uniksim&table=constants&target=sql.php`

<table id="bkmrk-instance-colonne-%C3%A0-m"><thead><tr><th>Instance</th><th>Colonne à modifier</th><th>Propriété backend</th></tr></thead><tbody><tr><td>`alpha`</td><td>`dev_version`</td><td>`devVersion`</td></tr><tr><td>`alpha2`</td><td>`dev2_version`</td><td>`dev2Version`</td></tr><tr><td>`demo`</td><td>`demo_version`</td><td>`demoVersion`</td></tr><tr><td>`demoLive`</td><td>`demoLive_version`</td><td>`demoLiveVersion`</td></tr><tr><td>production / `game`</td><td>`live_version`</td><td>`liveVersion`</td></tr></tbody></table>

La valeur en DB doit correspondre exactement au contenu de `version.txt` qui vient d'être déployé. Sinon le frontend tentera de charger un autre dossier WebGL.

## Quand rebuilder

<table id="bkmrk-changement-rebuild-u" style="width: 50%;"><thead><tr><th style="width: 64.446%;">Changement</th><th style="width: 35.554%;">Rebuild Unity ?</th></tr></thead><tbody><tr><td style="width: 64.446%;">C# Unity, scène, prefab, ScriptableObject</td><td style="width: 35.554%;">Oui</td></tr><tr><td style="width: 64.446%;">JSON dans Resources à embarquer</td><td style="width: 35.554%;">Oui</td></tr><tr><td style="width: 64.446%;">Cloud Script BrainCloud uniquement</td><td style="width: 35.554%;">Non</td></tr><tr><td style="width: 64.446%;">Texte publié dynamiquement sur le serveur</td><td style="width: 35.554%;">Pas nécessairement</td></tr></tbody></table>

## Procédure complète

1. Vérifier le diff Git et les assets/Resources sauvegardés.
2. Choisir le nouveau numéro et modifier `Assets/Resources/data/version.txt`. Vérifier `sub_version.txt` si nécessaire.
3. Lancer **Uniksim → Build WebGL**.
4. Confirmer zéro erreur dans le `BuildReport` et vérifier les quatre artefacts dans `game/Build/Build/`.
5. Depuis `node.js/deploy/`, lancer `deployToCloud.bat`.
6. Dans phpMyAdmin, table `uniksim.constants`, mettre à jour la colonne de l'environnement ciblé avec la même version.
7. Charger l'instance cible et confirmer que le nouveau build est utilisé.
8. Tester login, chargement de simulation, ouverture du jeu et une commande BrainCloud.

## Diagnostic rapide

<table id="bkmrk-sympt%C3%B4me-%C3%80-v%C3%A9rifier-"><thead><tr><th>Symptôme</th><th>À vérifier</th></tr></thead><tbody><tr><td>L'ancien build se charge</td><td>Colonne `*_version` de `constants`.</td></tr><tr><td>404 sur les fichiers Unity</td><td>Version DB vs dossier uploadé dans `uniksim-data`.</td></tr><tr><td>Mauvais dossier déployé</td><td>`version.txt` avant `deployToCloud.bat`.</td></tr><tr><td>Changement Cloud Script absent</td><td>Importer les Server Scripts BrainCloud; un rebuild Unity ne suffit pas.</td></tr></tbody></table>

# Tests automatisés des Server Scripts

## But

Les tests automatisés valident la logique métier des Cloud Scripts sans passer par l'UI Unity. Ils servent surtout à protéger les moteurs complexes By Module : création des données, bills, answer key, grading, inventaire et factories.

## Emplacement

Les tests se trouvent sous `node.js/backend/braincloud/tests/`.

<table id="bkmrk-dossierce-qui-est-te"><thead><tr><th>Dossier</th><th>Ce qui est testé</th></tr></thead><tbody><tr><td>`unit/by_module_tests/answer_key_tests/`</td><td>Génération des bonnes réponses, fonctions auxiliaires, petty cash.</td></tr><tr><td>`unit/by_module_tests/bills_tests/`</td><td>Création et utilitaires des bills.</td></tr><tr><td>`unit/by_module_tests/classes_tests/`</td><td>Classes métier comme l'inventaire.</td></tr><tr><td>`unit/by_module_tests/create_module_type_data_tests/`</td><td>Génération initiale du Module Type Data.</td></tr><tr><td>`unit/by_module_tests/factories_tests/`</td><td>Factories serveur, notamment `BillFactory`.</td></tr><tr><td>`unit/by_module_tests/grade_module_tests/`</td><td>Correction, états financiers, formulaires fiscaux et modules spécialisés.</td></tr></tbody></table>

## Infrastructure de test

- `tests/lib/test_base.js` : base commune;
- `tests/lib/unit_test_core.js` : orchestration des tests;
- `tests/utils/test_data.js` : helpers/données de test;
- `tests/data/` : fixtures (companies, conditions, employees, partners, products, salaries, services);
- `tests/utils/billtypedata.json` et `reference.json` : références utilisées par les suites.

## Quand ajouter un test

Ajouter ou modifier un test dès qu'un changement touche une fonction pure ou déterministe du moteur serveur. Les zones à protéger en priorité sont :

- nouveau type de bill;
- nouvelle règle d'answer key;
- nouvelle règle de grading;
- modification de création du Module Type Data;
- modification d'inventaire ou de calcul qui influence plusieurs pages;
- nouvelle factory ou branche de factory.

## Stratégie

```mermaid
flowchart LR
CHANGE[Changement métier] --> UNIT[Test ciblé]
UNIT --> EDGE[Cas limites]
EDGE --> REG[Régression du module]
REG --> DEBUG[Debug local si le problème dépend de documents réels]

```

## Tests vs debug local

Les tests automatisés ne remplacent pas **Debug local des Server Scripts BrainCloud**. Un test unitaire valide une fonction avec des fixtures contrôlées; le debug local permet d'exécuter un vrai Cloud Script avec des documents issus de MongoDB ou récupérés depuis BrainCloud.

## Lancer réellement les tests

Le projet possède un runner Web, pas seulement des fichiers `.test.js`.

### Runner Web recommandé

Depuis `node.js/backend/braincloud/` :

```
node tests/web-runner/server.js
```

Le serveur écoute par défaut sur `http://localhost:3000` et ouvre automatiquement le navigateur. L'UI liste les suites sous `tests/unit/by_module_tests`, permet de sélectionner des tests individuels et de lancer en mode normal ou debug.

### Mode normal vs debug

- **normal** : les fichiers By Module sont instrumentés avec Istanbul et un rapport HTML est généré sous `tests/test-reports/`;
- **debug** : `global.DEBUG_MODE = true`, l'instrumentation coverage est désactivée et aucun rapport n'est généré.

### Dépendance MongoDB locale

`tests/lib/test_base.js` crée `MongoDBClient` et s'authentifie avant d'initialiser les utils. Si les tests ne démarrent même pas, vérifier d'abord le MongoDB local et sa configuration avant de chercher une régression métier.

### Point à connaître : game-specific de test

Le bootstrap de test inclut actuellement explicitement `/acc2/utils/acc2_utils`. Si une nouvelle suite dépend réellement d'une différence Acc1/Acc2, ne pas supposer que le runner change automatiquement de game-specific.

## Créer un nouveau test

1. Placer le fichier sous la catégorie appropriée dans `tests/unit/by_module_tests/`.
2. Créer un `TestSuite`.
3. Ajouter les cas avec `suite.addTest(name, fn)`.
4. Initialiser la fixture commune avec `suite.init()` si nécessaire.
5. Utiliser les assertions de `unit_test_core.js` : `areEqual`, `isTrue`, `inRange`, etc.
6. Ajouter les fixtures réutilisables dans `tests/data` ou `tests/utils`, pas en duplicata dans chaque test.
7. Lancer le test seul, puis la suite complète du domaine.

## Quand un test doit être ajouté

Tout changement Answer Key/Grading/Create Module Type Data/BillFactory/inventory doit avoir au minimum un test qui aurait échoué avant le changement. Un bug corrigé sans test de régression est considéré incomplet lorsque la fonction est testable localement.

# Debug local des Server Scripts BrainCloud

## Pourquoi ce système existe

Le dossier `node.js/backend/braincloud/debugging/` recrée localement une partie de l'environnement d'exécution BrainCloud. C'est l'outil principal pour exécuter un Cloud Script du dépôt, suivre ses `include()`, inspecter ses variables et utiliser de vrais documents sans devoir déclencher toute la simulation depuis Unity.

## Fichiers clés

<table id="bkmrk-fichierr%C3%B4le-debuggin"><thead><tr><th>Fichier</th><th>Rôle</th></tr></thead><tbody><tr><td>`debugging/server_scripts_debugging_base.js`</td><td>Bootstrap du runtime local : crée `include()`, initialise MongoDB et le client BrainCloud, charge le bridge puis le script de debug.</td></tr><tr><td>`debugging/server_scripts_debugging.js`</td><td>Scratchpad d'exécution : définir `data`, appeler un script, tester un module ou écrire une fonction de diagnostic.</td></tr><tr><td>`debugging/utils/bridge.js`</td><td>Émule l'objet global `bridge` disponible dans les Cloud Scripts BrainCloud.</td></tr><tr><td>`debugging/utils/custom_entity_service_proxy.js`</td><td>Émule l'accès aux Custom Entities en utilisant MongoDB local et BrainCloud en fallback.</td></tr><tr><td>`utils/MongoDBClient.js`</td><td>Connexion au MongoDB local `localhost:27017`, base `uniksim`.</td></tr><tr><td>`utils/BraincloudClient.js`</td><td>Accès à l'environnement BrainCloud choisi lorsque le document n'existe pas localement.</td></tr></tbody></table>

## Bootstrap

```mermaid
sequenceDiagram
participant N as Node
participant BASE as server_scripts_debugging_base
participant M as MongoDB local
participant BC as BrainCloudClient
participant BR as bridge local
participant DBG as server_scripts_debugging
N->>BASE: exécution
BASE->>BASE: crée global.include()
BASE->>M: authenticate()
BASE->>BC: crée le client
BASE->>BR: include debugging/utils/bridge.js
BASE->>DBG: include server_scripts_debugging.js
DBG->>DBG: exécute la fonction de test choisie

```

## Comment include() fonctionne

`server_scripts_debugging_base.js` expose un `global.include(path)`. Pour un Server Script, le chemin est résolu vers `/server_scripts<path>.js`. Le fichier est lu avec `fs.readFileSync()` puis exécuté dans le même contexte Node avec `vm.runInThisContext()`.

La liste `includedScripts` évite de réinclure deux fois le même script lorsque `includeOnce` est actif. Cela reproduit suffisamment le modèle BrainCloud pour suivre la majorité des dépendances du dépôt.

## Lancer le debugger

Depuis le dossier `node.js`, le point d'entrée est :

```
node backend/braincloud/debugging/server_scripts_debugging_base.js
```

Le bootstrap change lui-même le working directory vers `backend/braincloud`.

## Préparer server\_scripts\_debugging.js

Le fichier est volontairement un scratchpad. La méthode la plus simple est :

1. choisir `serverInstanceType` (par défaut `dev`);
2. définir l'objet global `data` exactement comme BrainCloud le recevrait;
3. appeler `include("/_command/...")` ou un script game-specific;
4. inspecter `response`, les variables ou les documents produits.

```javascript
data = {
  commandUid: "get_module_grade",
  commandData: {
    teamUid: "...",
    actModuleUid: "...",
    moduleType: "practice",
    actUid: "..."
  }
};

include("/acc1/command/acc1_custom_command");

```

## testScript()

Le helper `testScript({ scriptName, scriptData })` copie `scriptData` dans `data` puis appelle directement `include("/" + scriptName)`. C'est utile lorsqu'on veut reproduire une requête Cloud Script presque telle qu'elle apparaît dans les logs.

## Tester le moteur By Module sans document existant

`testGeneralFunctionnalities(moduleUid)` est particulièrement important. Il reconstruit le pipeline complet :

```mermaid
flowchart LR
SET[Static settings] --> CREATE[Create Module Type Data]
CREATE --> AK[Answer Key]
AK --> GRADE[Grade Module]
GRADE --> RE[Reassemble Answers]
RE --> OUT[Scores / Errors]

```

Il crée un seed, génère les données, prend les bonnes décisions, grade le résultat puis réassemble les scores. `testAllModuleGeneralFunctionnalities()` applique ce flux à tous les modules configurés et permet de trouver rapidement une régression globale.

## Accès aux documents : MongoDB d'abord, BrainCloud ensuite

`CustomEntityServiceProxy` cherche d'abord le document dans le MongoDB local. Si rien n'est trouvé, il authentifie le `BraincloudClient` dans `serverInstanceType` et récupère le document distant. Les lectures suivantes peuvent donc être déboguées localement tout en utilisant un vrai document de Dev.

```mermaid
flowchart TD
GET[getDocumentData] --> LOCAL{Document dans MongoDB local ?}
LOCAL -->|oui| USE[Utiliser document local]
LOCAL -->|non| BC[Lire depuis BrainCloud]
BC --> USE
USE --> SCRIPT[Exécuter le Server Script]
SCRIPT --> SAVE[Écritures via proxy local]

```

## Limites du bridge local

Le bridge n'émule pas tout BrainCloud. Plusieurs proxies retournent volontairement `501` ou ne sont pas implémentés :

- certaines opérations User/Player State;
- scheduled scripts;
- présence réelle;
- messaging réel (le contenu est surtout loggé);
- HTTP client;
- `getProfileId()` et certains contextes utilisateur.

Si un Server Script dépend fortement d'une de ces APIs, le debug local peut nécessiter d'étendre le proxy ou de valider la dernière étape directement sur BrainCloud.

## Cas d'usage recommandés

- reproduire un `get_module_grade` avec un vrai `teamUid`;
- inspecter une génération de Module Type Data;
- tester l'answer key d'un module précis;
- chercher un seed qui casse une balance;
- tester tous les modules Accounting en boucle;
- lire un document via `/_command/get_document`;
- reproduire une custom command sans passer par Unity.

## Important avant de modifier

**Ne pas laisser un appel de test accidentel actif en haut de `server_scripts_debugging.js`.** Le fichier exécute directement le code présent lorsqu'il est inclus par le bootstrap. Garder une seule fonction de test explicitement appelée pendant la session de debug.

# Outils BrainCloud et import/export des scripts

## But

Cette page documente les outils qui synchronisent les Cloud Scripts entre le dépôt et les applications BrainCloud. Ils sont séparés du debugger local : ici on parle de **déploiement/synchronisation des scripts**, pas d'exécution métier.

## Fichiers principaux

- `backend/braincloud/api_builder_utils.js`;
- `backend/braincloud/import_server_scripts.bat`;
- `backend/braincloud/export_server_scripts.bat`;
- `backend/braincloud/api_builder/` : CLI BrainCloud Builder + configuration.

## api\_builder\_utils.js

Ce script enveloppe le CLI `bc-builder`. Il peut récupérer la liste des scripts, lire un script distant, comparer les versions et importer/exporter selon l'application sélectionnée.

Les environnements gérés explicitement incluent Dev, Dev2 et Demo. Le script permet aussi des options de remplacement forcé selon le workflow choisi.

## Flux d'import

```mermaid
flowchart LR
LOCAL[server_scripts/] --> UTIL[api_builder_utils.js]
UTIL --> CLI[bc-builder]
CLI --> APP[BrainCloud Dev / Dev2 / Demo]

```

## Flux d'export

```mermaid
flowchart LR
APP[BrainCloud] --> CLI[bc-builder]
CLI --> UTIL[api_builder_utils.js]
UTIL --> LOCAL[server_scripts/]

```

## Avant un import

1. Vérifier l'environnement ciblé.
2. Vérifier le diff Git des Server Scripts.
3. Vérifier si des fichiers ont aussi changé directement dans BrainCloud.
4. Éviter un *force override* global sans comprendre les différences.
5. Après import, exécuter une commande représentative ou le debugger local avec la même logique.

## Relation avec les autres outils

<table id="bkmrk-besoinoutil-valider-"><thead><tr><th>Besoin</th><th>Outil</th></tr></thead><tbody><tr><td>Valider une fonction métier isolée</td><td>Tests automatisés</td></tr><tr><td>Exécuter un Cloud Script avec de vraies données</td><td>Debug local des Server Scripts</td></tr><tr><td>Envoyer/récupérer les scripts BrainCloud</td><td>api\_builder / import-export scripts</td></tr><tr><td>Importer les static data Google Sheets</td><td>`node.js/exports/`</td></tr></tbody></table>

## Commandes exactes

### Importer les Cloud Scripts depuis BrainCloud vers le dépôt

```
cd node.js/backend/braincloud
import_server_scripts.bat
# équivalent :
node api_builder_utils.js import_scripts
```

### Exporter/publier les Cloud Scripts vers BrainCloud

```
cd node.js/backend/braincloud
export_server_scripts.bat
# équivalent :
node api_builder_utils.js export_scripts
```

`api_builder_utils.js` reconnaît aussi les cibles Demo/Dev2 selon les arguments du workflow. Vérifier le prompt/log de destination avant toute publication.

## Copier une simulation entre environnements

`exports/data_importer_braincloud.js` est un outil distinct de la static data. Il copie une **simulation runtime** et les documents associés.

```
cd node.js
node exports/data_importer_braincloud.js copySim <simUid> <source> <destination>
```

Sources autorisées : `live`, `dev`, `dev2`, `demo`. Destinations : `dev`, `dev2`, `demo`. **Le script refuse explicitement une destination live.**

### Documents copiés

- `simulation`;
- `activity`;
- `results`;
- `actDataGameSpec`;
- `teams`;
- `teamsLogbooks`;
- `evaluationTeam`;
- `teamsModules`;
- `userDetails`;
- `evaluation`.

Le script crée/authentifie aussi les users nécessaires dans la destination et maintient les mappings de profils copiés.

### Sync vs async

Le script a deux chemins : `copyActs()` pour le flow classique et `copyAllActsAsync()`/`copyAsyncTeam()` pour les simulations async, où certains UIDs d'activity sont construits par équipe.

## Copier une activité vers MongoDB pour debug

`api_builder_utils.js` expose aussi `copyAct [actUid] [backEndType]`. C'est utile pour préparer des données réelles dans le MongoDB utilisé par le debugger local.

## Checklist opérationnelle

1. Confirmer source et destination.
2. Ne jamais recopier de credentials du script dans la documentation/log public.
3. Copier.
4. Vérifier simulation + teams + activity + results dans la destination.
5. Tester login avec un utilisateur copié.
6. Pour By Module, vérifier également les `teamsModules`.
7. Si un nouveau entity type est ajouté au produit, vérifier si cet outil doit le copier.

# Scénarios runtime — configuration, réponses et évaluation

## Rôle du système Scenario

Le système **Scenario** gère le contenu d'introduction/contexte, les questions, les réponses de segments et leur évaluation. Il ne doit pas être confondu avec :

- **Tutoriels** — guides interactifs qui montrent l'UI et attendent des actions;
- **Timeline** — séquences Unity/PlayableDirector audiovisuelles;
- **ScenarioStep** — flags de progression persistés dans `userDetails`.

```mermaid
flowchart LR
SD[Static data / ScenarioPages] --> UI[ScenarioPanelControlBase]
UI --> Q[Questions / Segment grid]
Q --> SAVE[SaveScenarioAnswers / SaveEvalSegment]
SAVE --> EVAL[(evaluation.scenario)]
EVAL --> COMPUTE[compute_scenario_score / compute_segment_score]
COMPUTE --> SCORE[evaluation.scores]
SCORE --> CORRECT[ScenarioEval / corrected UI]
UI --> STEP[eFinishQuestions]
STEP --> USER[(userDetails.scenarioStep)]

```

## Modèles principaux

<table id="bkmrk-fichier-responsabili"><thead><tr><th>Fichier</th><th>Responsabilité</th></tr></thead><tbody><tr><td>`Scenario.cs`</td><td>Réponses booléennes et réponses de segments de l'étudiant.</td></tr><tr><td>`ScenarioAnswer.cs`</td><td>Réponse attendue par UID.</td></tr><tr><td>`ScenarioEval.cs`</td><td>Scenario + nombre de bonnes réponses scénario/segments.</td></tr><tr><td>`ScenarioPages.cs`</td><td>ScriptableObject qui décrit les pages affichées.</td></tr><tr><td>`ScenarioPanelControlBase.cs`</td><td>Moteur UI principal, navigation, validation et sauvegarde.</td></tr><tr><td>`ScenarioSegGridControlBase.cs`</td><td>Base de la grille de segments.</td></tr><tr><td>`EvalCorrectedPopupControl.cs`</td><td>Vue de correction.</td></tr></tbody></table>

## ScenarioPages

Chaque `PagesDefinition` contient :

- `headerText`;
- `segGridUid`;
- `pages[]`;
- `questionsStartIndex`;
- `numQuestions`.

### Chargement By Year

```csharp
ScenarioPages = Resources.Load<ScenarioPages>(
    $"data_{GameUid}/ScenarioPages"
);
```

Ce chargement est fait dans `StaticDataByYearController.Initialize()`.

### Chargement By Module

```csharp
string moduleUid = moduleByUid.Key.SnakeCaseToPascalCase();

ScenarioPages scenarioPages =
    Resources.Load<ScenarioPages>(
        $"data_{GameUid}/{moduleUid}ScenarioPages"
    );
```

`StaticDataByModuleController` charge donc un asset ScenarioPages distinct par module lorsqu'il existe.

## Créer ou modifier un Scenario de A à Z

### 1. Définir le contenu et les réponses attendues

Les réponses des questions sont stockées sous des UIDs comme `question_0`, `question_1`, etc. Vérifier que `questionsStartIndex` et `numQuestions` correspondent aux réponses statiques attendues.

### 2. Modifier le bon ScenarioPages asset

- By Year : `data_<gameUid>/ScenarioPages`;
- By Module : `data_<gameUid>/<ModuleUid>ScenarioPages`.

`ScenarioPagesEditor.cs` masque la référence de segment grid lorsque `useSegmentsInScenario` est false.

### 3. Ajouter les textes

Ajouter les titres, paragraphes, questions et choix via le pipeline de localisation. Ne pas hardcoder le contenu dans `ScenarioPanelControlBase`.

### 4. Ajouter un composant game-specific si nécessaire

Le socle Scenario est commun, mais certains jeux possèdent des rendus propres :

- MK1/MK2/HR1 : `ScenarioSegGridControl`;
- MGT1 : tables scenario spécialisées pour segments, employés, features et discipline.

Un nouveau rendu métier reste sous `Assets/GameSpecific/<gameUid>/Src/UI`.

### 5. Vérifier InitRuntimePages()

`ScenarioPanelControlBase.InitRuntimePages()` construit les pages runtime. En By Year, il peut insérer les segments dans la séquence lorsque `useSegmentsInScenario` est activé.

### 6. Sauvegarder les réponses de questions

```mermaid
flowchart LR
UI[ScenarioPanelControlBase] --> STUD[Student.SaveScenarioAnswers]
STUD --> BE[BackEnd.SaveScenarioAnswers]
BE --> CMD[_command/save_scenario_answers]
CMD --> E[(evaluation.scenario.answers)]

```

`save_scenario_answers.js` merge les clés reçues dans `evaluation.scenario.answers`, puis sauvegarde le document `evaluation`.

### 7. Sauvegarder les segments

`SaveEvalSegment` appelle `_command/save_eval_segment`. Le script :

1. écrit le segment dans `evaluation.scenario[segmentUid]`;
2. sauvegarde `evaluation`;
3. envoie au professeur un message `get_data` qui devient `update_eval`.

### 8. Calculer les scores

Le client appelle les commandes game-specific :

- `<gameUid>_compute_scenario_score`;
- `<gameUid>_compute_segment_score`.

Elles délèguent au moteur `Eval<Game>`, sauvegardent dans `evaluation.scores`, puis retournent les informations utilisées par `ScenarioEval`.

### 9. Marquer la fin

```csharp
Game.Student.SetScenarioStep(
    Scenario.eScenarioStep.eFinishQuestions
);
```

**`eFinishQuestions` doit rester la valeur 1.** Le commentaire du code indique que le serveur suppose explicitement cette valeur.

### 10. Tester la correction

Tester `GetScenarioEvaluation`, `ScenarioEval.Deserialize()`, la vue corrigée et la vue professeur. Ajouter une question sans corriger ce chemin laisse la feature incomplète.

## ScenarioStep — progression persistée

`Scenario.eScenarioStep` est un enum de flags stocké sous forme de bitmask. Il suit la progression utilisateur, pas les réponses de Scenario.

Exemples :

- `eFinishQuestions`;
- `eFinishFirstProduct`;
- `eStudentAfterFirstResults`;
- `eFinishFirstRealLaunch`;
- `eStudentFirstModule`;
- `eStudentFirstCorrection`;
- `eByModuleHomepageTutorial`;
- `eStudentFirstFormula`;
- `eStudentFirstFormulaActivation`.

## Ajouter un ScenarioStep

1. Ajouter un nouveau bit libre avec `1 << n`.
2. **Ne jamais renuméroter un ancien bit** déjà persisté.
3. Tester `IsScenarioStepDone()` avant le flow concerné.
4. À la fin, appeler `SetScenarioStep()`.
5. Si la séquence peut être rejouée volontairement, décider si `UnSetScenarioStep()` est nécessaire.

`User.SetScenarioStep()` OR le nouveau bit dans le masque local puis appelle `_command/set_scenario_step`, qui écrit le masque complet dans `userDetails.scenarioStep`.

## States Scenario

- `StudentLoadScenarioState` charge l'évaluation;
- `StudentPlayScenarioByYearState` attend `eFinishQuestions` puis choisit le state suivant;
- `StudentPlayScenarioByModuleState` lance le welcome By Module puis attend le même flag.

## Scenario réouvert après le début

`ScenarioPopupControl` permet de consulter le scénario après le flow initial. Il conserve la dernière page consultée et réutilise `ScenarioPanelControl`. En By Module, `ScenarioWindowControl` adapte les titres selon le module et ses settings.

## Debug Scenario

<table id="bkmrk-sympt%C3%B4me-v%C3%A9rifier-pa"><thead><tr><th>Symptôme</th><th>Vérifier</th></tr></thead><tbody><tr><td>Page absente</td><td>Nom/path de `ScenarioPages`, `pagesDefinitions`, asset dans Resources.</td></tr><tr><td>Question visible mais non persistée</td><td>`question_N` → SaveScenarioAnswers → `evaluation.scenario.answers`.</td></tr><tr><td>Segment non mis à jour chez le prof</td><td>`save_eval_segment` → `get_data/update_eval`.</td></tr><tr><td>Score faux</td><td>Good answers static data + `Eval<Game>`.</td></tr><tr><td>Étudiant bloqué après validation</td><td>`userDetails.scenarioStep` et bit `eFinishQuestions`.</td></tr><tr><td>Correction vide</td><td>`get_scenario_evaluation`, `ScenarioEval`, corrected popup.</td></tr></tbody></table>

## Checklist avant merge

- ScenarioPages au bon nom/path;
- textes FR/EN;
- réponses sauvegardées puis relues après relog;
- segments synchronisés professeur;
- score scenario correct;
- score segments correct si utilisé;
- correction correcte;
- `eFinishQuestions` déclenche le bon state;
- aucun ancien ScenarioStep renuméroté;
- By Year / By Module testés selon le scope.

# Timelines Unity — architecture et création de séquences

## Rôle des Timelines Unity

Les **Timelines** sont des séquences audiovisuelles Unity basées sur `PlayableDirector`. Elles servent à présenter une animation de résultats, de première ouverture ou de lancement produit tout en bloquant temporairement l'interaction normale avec l'UI.

Elles sont différentes des **Tutoriels** : un tutoriel guide activement l'utilisateur dans l'interface et attend souvent ses clics; une Timeline joue une séquence Unity et appelle un callback lorsqu'elle se termine.

```mermaid
flowchart LR
TRIGGER[Controller / UI / State] --> TL[Timeline subclass]
TL --> BASE[TimelineBase.Play]
BASE --> CAM[Switch cameras + block raycasts]
BASE --> DIR[PlayableDirector.Play]
DIR --> STOP[PlayableDirector.stopped]
STOP --> RESTORE[TimelineBase.Stop]
RESTORE --> CB[onFinish]

```

## TimelineBase — contrat commun

Le socle est `Assets/Src/Game/Timeline/TimelineBase.cs`.

`TimelineBase` gère :

- `uiCamera`, `offscreenCamera` et éventuellement `camera3D`;
- activation de `scene3dRoot`;
- changement de `Canvas.worldCamera`;
- adaptation du `screenTransform` selon le ratio d'écran;
- `UICanvasGroup.blocksRaycasts = false` pendant la séquence;
- `PlayableDirector.Play()`;
- abonnement à `director.stopped`;
- restauration de l'UI dans `Stop()`;
- appel du callback `onFinish`.

## Ce que Play() fait réellement

1. active `scene3dRoot`;
2. désactive la caméra UI;
3. active la caméra offscreen;
4. redirige le Canvas vers cette caméra;
5. copie le viewport de la caméra UI;
6. calcule le scale horizontal entre les valeurs 4:3 et 16:9;
7. bloque les raycasts UI;
8. lance le `PlayableDirector`;
9. s'abonne à `director.stopped`.

Lorsque le director s'arrête, `OnPlayFinish()` se désabonne, appelle `Stop()`, puis déclenche le callback.

## Timelines communes existantes

<table id="bkmrk-classe-responsabilit"><thead><tr><th>Classe</th><th>Responsabilité</th></tr></thead><tbody><tr><td>`CreateProductTimeline`</td><td>Séquence générique après création/lancement d'un premier objet métier.</td></tr><tr><td>`YearResultsTimeline`</td><td>Animation de résultats annuelle By Year avec graph, profit/perte et anti-bankruptcy.</td></tr><tr><td>`FirstModuleTimeline`</td><td>Première ouverture d'un module By Module.</td></tr><tr><td>`ModuleResultsTimeline`</td><td>Animation de correction/résultats d'un module summative.</td></tr></tbody></table>

## Timelines game-specific

Les jeux peuvent spécialiser le socle :

- `Assets/GameSpecific/mk1/Src/Timeline/TimelinesMk1.cs` — extension de `YearResultsTimeline` + lancement produit;
- `Assets/GameSpecific/ent1/Src/Timeline/LaunchProductTimeline.cs` — séquences lancement/résultats ENT1;
- les assets `.playable` sont présents sous les Resources des jeux concernés.

Une Timeline qui dépend d'une structure game-specific doit rester dans le dossier du jeu, pas dans le socle commun.

## Assets .playable

Une Timeline Unity est composée de deux parties :

- le **script C#** qui choisit le director, prépare les données et appelle `Play()`;
- l'**asset .playable** qui contient les tracks, clips et animations.

Changer uniquement le C# sans vérifier l'asset peut laisser des bindings cassés. Changer uniquement le `.playable` nécessite aussi de vérifier que le prefab/scene fournit toutes les références utilisées par les tracks.

## Créer une Timeline de zéro — procédure complète

### 1. Vérifier qu'une Timeline est le bon outil

Utiliser une Timeline si le besoin est principalement une **séquence visuelle/animée**. Pour guider un utilisateur vers un bouton, attendre un clic réel ou surligner une zone UI, utiliser le système Tutoriels.

### 2. Créer l'asset Timeline

Dans Unity :

1. créer un asset Timeline `.playable` dans le dossier Resources approprié;
2. ajouter les tracks nécessaires;
3. configurer clips, animations et bindings;
4. tester la séquence directement dans l'éditeur.

### 3. Créer la classe C#

```csharp
public class MyTimeline : TimelineBase
{
    public static MyTimeline Instance;

    [SerializeField]
    private PlayableDirector director;

    private void Awake()
    {
        Instance = this;
    }

    public void PlaySequence(
        string message,
        Action onFinish
    )
    {
        character3dText.text = message;
        Play(onFinish, director);
    }
}
```

### 4. Brancher les références du prefab/scene

Le composant hérité de `TimelineBase` doit avoir des références valides vers :

- `uiCamera`;
- `offscreenCamera`;
- `scene3dRoot`;
- `screenTransform`;
- `UICanvasGroup`;
- `character3dText` si la Timeline affiche une bulle;
- le ou les `PlayableDirector`.

### 5. Choisir le bon point de déclenchement

Exemples réels :

- `DecisionsControlByModule` déclenche `FirstModuleTimeline`;
- le même contrôle déclenche `ModuleResultsTimeline` lorsque le module summative est généré;
- `StudentState` et plusieurs TeamControllers déclenchent `YearResultsTimeline`;
- les contrôles de produit/posting peuvent déclencher `CreateProductTimeline` ou une Timeline game-specific.

### 6. Préparer les données avant Play()

Une Timeline ne devrait pas aller chercher arbitrairement toutes ses données elle-même. Le trigger prépare les valeurs nécessaires, puis les passe à la Timeline.

Exemple `YearResultsTimeline` :

- reçoit `teamUid`;
- reçoit les `RankingsData`;
- reçoit `yearId`;
- calcule profit/perte;
- choisit `goodResultsDirector` ou `badResultsDirector`.

### 7. Gérer la fin dans onFinish

Le code qui ouvre l'écran suivant ou change la progression doit être exécuté dans le callback.

```csharp
MyTimeline.Instance.PlaySequence(
    message,
    () =>
    {
        OpenNextPanel();
    }
);
```

Ne pas écrire :

```csharp
MyTimeline.Instance.PlaySequence(message, null);
OpenNextPanel(); // s'exécute immédiatement
```

## Empêcher une Timeline de rejouer

`TimelineBase` ne persiste aucun état par lui-même. Le mécanisme anti-replay dépend du contexte.

<table id="bkmrk-cas-m%C3%A9canisme-premi%C3%A8"><thead><tr><th>Cas</th><th>Mécanisme</th></tr></thead><tbody><tr><td>Première ouverture de module</td><td>`Scenario.eScenarioStep.eStudentFirstModule`.</td></tr><tr><td>Résultats annuels</td><td>`SetYearSequenceSeen(yearId)`.</td></tr><tr><td>Résultats d'un module</td><td>`UserState.ModuleUserState.STUDENT_RESULTS`.</td></tr><tr><td>Premier produit / premier launch</td><td>Condition métier + éventuellement ScenarioStep.</td></tr></tbody></table>

## FirstModuleTimeline

`FirstModuleTimeline.PlayFirstModule()` :

1. injecte le texte;
2. joue `firstModuleDirector`;
3. à la fin, sauvegarde `eStudentFirstModule`;
4. appelle le callback qui ouvre le module.

Le trigger est présent dans les contrôles By Module lorsque l'étudiant n'a jamais vu cette séquence.

## ModuleResultsTimeline

Cette Timeline est plus complexe car les données peuvent ne pas être encore disponibles lorsqu'on veut la jouer.

### Attente des résultats

Si `TryGetTeamByModuleResult()` ne trouve pas encore les résultats :

1. la Timeline s'abonne à `onNewTeamModuleResults`;
2. attend le bon `actModuleUid` / module type;
3. se désabonne dès réception;
4. prépare puis joue la séquence.

Ce pattern est à réutiliser lorsqu'une nouvelle Timeline dépend d'une donnée asynchrone : abonnement temporaire + filtre strict + désabonnement immédiat.

### Choix du director

Le score moyen du module choisit entre :

- `goodResultsDirector` à partir de 85 %;
- `okayResultsDirector` à partir de 70 %;
- `badResultsDirector` sinon.

### Pause et report

`ChangePlayableSpeed(false)` met le root playable à vitesse 0 et affiche les options. La Timeline peut alors montrer les notes via `reportControl.ShowGrades()` avant de continuer.

## YearResultsTimeline

`PlayNewResults()` :

1. récupère le `RankingsData` de l'année;
2. met à jour le whiteboard/graph;
3. compare la valeur actuelle à l'année précédente ou à `GraphFirstValue`;
4. détermine profit/perte;
5. choisit le director;
6. construit le texte localisé;
7. marque la séquence annuelle comme vue via `SetEndOfYearSequenceSeen(yearId)`.

## Créer une Timeline avec plusieurs variantes

Pour un même événement avec plusieurs résultats visuels, garder une seule classe et plusieurs `PlayableDirector` lorsque les inputs/lifecycle sont identiques.

```csharp
PlayableDirector director =
    score >= threshold
        ? goodDirector
        : badDirector;

Play(onFinish, director);
```

Créer plusieurs classes uniquement si les séquences ont des données et un lifecycle réellement différents.

## Interaction avec les caméras et l'UI

`TimelineBase.Play()` désactive la caméra UI et empêche les raycasts. Un bug pendant la séquence peut donc laisser toute l'application visuellement ou fonctionnellement bloquée si `Stop()` n'est jamais exécuté.

### Stop()

`Stop()` doit toujours rétablir :

- `uiCamera.enabled = true`;
- `offscreenCamera.enabled = false`;
- `scene3dRoot.SetActive(false)`;
- `Canvas.worldCamera = uiCamera`;
- `UICanvasGroup.blocksRaycasts = true`.

## Gestion du ratio d'écran

`TimelineBase` interpole le scale horizontal entre `scale_4_3` et `scale_16_9` selon `Screen.width / Screen.height`, puis clamp le résultat.

Si une séquence est correctement cadrée en 16:9 mais coupée sur un autre ratio, vérifier ces deux valeurs sur le prefab avant de modifier la caméra ou les clips.

## Timelines game-specific

### MK1

`TimelinesMk1` hérite de `YearResultsTimeline` et ajoute un `launchProductDirector` ainsi qu'un whiteboard MK1.

### ENT1

`LaunchProductTimeline` gère un whiteboard de lancement produit et ses propres directors de résultats.

## Build et assets

Modifier :

- un script Timeline;
- un prefab qui la contient;
- un asset `.playable`;
- un binding d'animation;

nécessite un nouveau build Unity/WebGL.

## Debug Timeline

<table id="bkmrk-sympt%C3%B4me-%C3%80-v%C3%A9rifier-"><thead><tr><th>Symptôme</th><th>À vérifier</th></tr></thead><tbody><tr><td>Écran noir après la séquence</td><td>`director.stopped` a-t-il été déclenché ? `Stop()` a-t-il tourné ?</td></tr><tr><td>UI visible mais non cliquable</td><td>`UICanvasGroup.blocksRaycasts`.</td></tr><tr><td>Timeline rejouée à chaque ouverture</td><td>Mécanisme anti-replay non persisté.</td></tr><tr><td>Callback plusieurs fois</td><td>Abonnement `director.stopped` ou event résultats non désabonné.</td></tr><tr><td>Mauvaise variante good/okay/bad</td><td>Valeur métier passée à la Timeline et seuils de sélection.</td></tr><tr><td>Animation avec anciennes données</td><td>Le trigger passe-t-il les données actuelles avant `Play()` ?</td></tr><tr><td>Résultats module jamais affichés</td><td>`onNewTeamModuleResults`, filtres actModuleUid/moduleType.</td></tr><tr><td>Layout déformé</td><td>`scale_4_3`, `scale_16_9`, `screenTransform`.</td></tr><tr><td>Playable ne joue pas</td><td>Director / asset `.playable` / bindings Unity.</td></tr></tbody></table>

## Checklist avant merge

- asset `.playable` sauvegardé;
- tous les bindings valides;
- références caméra/canvas/director non null;
- séquence testée en Editor;
- `Stop()` restaure l'UI;
- callback de fin continue correctement le flow;
- anti-replay sauvegardé;
- relog ne rejoue pas une séquence déjà vue sauf si voulu;
- résolutions/ratios d'écran vérifiés;
- WebGL testé.

# Les jeux UnikSim — rôle, contenu et navigation de la documentation

## Pourquoi cette page existe

Cette page est une **carte fonctionnelle du produit**. Elle explique ce que chaque jeu simule, ce que l'étudiant y fait et quelles pages de documentation ouvrir en premier. Elle ne remplace pas les pages d'architecture détaillées.

## Les deux grandes familles

```mermaid
flowchart TD
GAME[UnikSim Game] --> YEAR[By Year]
GAME --> MODULE[By Module]
YEAR --> ENT1
YEAR --> HR1
YEAR --> MGT1
YEAR --> MK1
YEAR --> MK2
MODULE --> ACC1
MODULE --> ACC2

```

<table id="bkmrk-famille-logique-prin"><thead><tr><th>Famille</th><th>Logique principale</th><th>Jeux</th></tr></thead><tbody><tr><td>**By Year**</td><td>Les équipes prennent des décisions pendant une année; le moteur génère les résultats; l'état est transporté vers l'année suivante.</td><td>ENT1, HR1, MGT1, MK1, MK2.</td></tr><tr><td>**By Module**</td><td>L'activité est composée de modules configurables avec pages data-driven, Answer Key, Grading et résultats de module.</td><td>ACC1, ACC2.</td></tr></tbody></table>

## ENT1 — entreprise / gestion intégrée

**Contexte :** équipe de direction d'une entreprise qui produit des téléphones intelligents.

**Objectif fonctionnel :** fabriquer et commercialiser des téléphones adaptés aux consommateurs tout en contrôlant les coûts de production.

### Ce que le jeu combine

- marketing et segments;
- développement/lancement de produits;
- usines et capacité de production;
- matières premières;
- ressources humaines;
- financement/cash;
- pays/marchés;
- événements et résultats annuels.

### Où regarder d'abord

- [ENT1 — architecture et logique game-specific](https://wiki.uniksim.com/books/game/page/ent1-architecture-et-logique-game-specific);
- [Décisions et génération des résultats By Year](https://wiki.uniksim.com/books/game/page/decisions-et-generation-des-resultats-by-year);
- [Cycle d'une année](https://wiki.uniksim.com/books/game/page/cycle-dune-annee-calendrier-et-jobs).

**Repère mental :** ENT1 est le jeu By Year le plus transversal : une décision peut traverser marketing → production → RH → finance → ventes.

## MK1 — marketing produit

**Contexte :** entreprise de vêtements.

**Objectif fonctionnel :** faire prospérer l'entreprise en commercialisant des produits adaptés aux consommateurs.

### Concepts importants

- produits;
- lancements;
- segments;
- product score;
- communication score;
- distribution score;
- prix/coûts;
- ventes générées;
- calculator products;
- événements et bots.

### Où regarder d'abord

- [MK1 — architecture et logique game-specific](https://wiki.uniksim.com/books/game/page/mk1-architecture-et-logique-game-specific);
- [Décisions et génération des résultats By Year](https://wiki.uniksim.com/books/game/page/decisions-et-generation-des-resultats-by-year);
- [Bots et simulations asynchrones](https://wiki.uniksim.com/books/game/page/bots-et-simulations-asynchrones-by-year).

**Repère mental :** MK1 est centré sur le mix marketing et la performance comparative des produits/lancements.

## MK2 — marketing avancé / international

**Contexte :** entreprise qui produit des skis.

**Objectif fonctionnel :** commercialiser des produits adaptés aux consommateurs et faire croître l'entreprise.

### Ce qui le distingue de MK1

- pays;
- launch type;
- relaunch;
- stock associé aux lancements;
- redistribution/refund du stock;
- loyalty;
- notoriety;
- rentabilité par lancement/pays.

### Où regarder d'abord

- [MK2 — architecture et logique game-specific](https://wiki.uniksim.com/books/game/page/mk2-architecture-et-logique-game-specific);
- [MK1](https://wiki.uniksim.com/books/game/page/mk1-architecture-et-logique-game-specific) pour comparer les concepts partagés;
- [Décisions et génération des résultats By Year](https://wiki.uniksim.com/books/game/page/decisions-et-generation-des-resultats-by-year).

**Repère mental :** ne pas traiter MK2 comme un simple “MK1 avec d'autres textes”; ses pays, relaunchs, stocks, loyalty et notoriety changent le moteur.

## HR1 — ressources humaines

**Contexte :** département RH d'une entreprise d'organisation et de production de spectacles musicaux.

**Objectif fonctionnel :** contrôler les dépenses RH tout en permettant à l'entreprise de fonctionner et de maximiser ses résultats.

### Concepts importants

- postings;
- embauche;
- dismissals;
- salaires;
- avantages;
- contrats;
- CEO bids;
- retention/belonging;
- segments RH;
- coûts et cash.

### Où regarder d'abord

- [HR1 — architecture et logique game-specific](https://wiki.uniksim.com/books/game/page/hr1-architecture-et-logique-game-specific);
- [Évaluations et rankings By Year](https://wiki.uniksim.com/books/game/page/evaluations-logbook-scenarios-et-rankings-by-year);
- [Commandes serveur / réouverture By Year](https://wiki.uniksim.com/books/game/page/commandes-serveur-reouverture-et-transitions-by-year).

**Repère mental :** beaucoup d'actions HR1 modifient immédiatement le cash avant même la génération annuelle.

## <s>MGT1 — processus de gestion</s> (DEPRECATED)

<s>**Contexte :** équipe de cadres d'une entreprise de création de jeux vidéo.</s>

<s>**Objectif fonctionnel :** appliquer le processus de gestion — planification, organisation, direction et contrôle — afin d'utiliser correctement les ressources financières.</s>

### <s>Concepts importants</s>

- <s>création/lancement de jeux;</s>
- <s>features;</s>
- <s>employés et niveaux;</s>
- <s>allocation du temps de travail;</s>
- <s>social budget;</s>
- <s>remote budget;</s>
- <s>feature ratios;</s>
- <s>énergie;</s>
- <s>discipline;</s>
- <s>bonheur;</s>
- <s>travail requis et complétion.</s>

### <s>Où regarder d'abord</s>

- <s>[MGT1 — architecture et logique game-specific](https://wiki.uniksim.com/books/game/page/mgt1-architecture-et-logique-game-specific);</s>
- <s>[Décisions et génération des résultats By Year](https://wiki.uniksim.com/books/game/page/decisions-et-generation-des-resultats-by-year);</s>
- <s>[UI et prefabs By Year](https://wiki.uniksim.com/books/game/page/ui-et-prefabs-by-year).</s>

<s>**Repère mental :** MGT1 est fortement structuré autour des employés → temps de travail → features → résultat de l'organisation.</s>

## ACC1 — Accounting / entreprise de piscines

**Contexte :** département de comptabilité d'une entreprise spécialisée dans la vente de piscines.

**Objectif fonctionnel :** compléter le cycle comptable à travers des modules pédagogiques.

### Concepts importants

- pièces justificatives / bills;
- journaux auxiliaires et journal général;
- ledger;
- subledgers;
- taxes;
- paie / retenues;
- inventaire;
- immobilisations;
- contrats;
- régularisations;
- états financiers;
- Answer Key et Grading.

### Où regarder d'abord

- [Architecture By Module / Accounting](https://wiki.uniksim.com/books/game/page/architecture-by-module-accounting);
- [Configuration Accounting — guide des JSON](https://wiki.uniksim.com/books/game/page/configuration-accounting-guide-des-json);
- [Contextes Accounting](https://wiki.uniksim.com/books/game/page/contextes-accounting-cycle-heritage-et-donnees-entre-periodes);
- [Pipeline des Bills](https://wiki.uniksim.com/books/game/page/pipeline-des-bills-architecture-et-ajout-dun-nouveau-type).

## ACC2 — Accounting / café et machines à café

**Contexte :** département de comptabilité d'une entreprise spécialisée dans la vente de café et de machines à café.

**Objectif fonctionnel :** comme ACC1, compléter le cycle comptable à travers des modules By Module.

### Différence de haut niveau

ACC2 réutilise le même moteur comptable qu'ACC1, mais son univers de companies/partners/products/services diffère et il contient notamment du contenu **Petite caisse**.

### Où regarder d'abord

- [Accounting 1 vs Accounting 2 — différences réelles](https://wiki.uniksim.com/books/game/page/accounting-1-vs-accounting-2-differences-reelles);
- [Architecture By Module / Accounting](https://wiki.uniksim.com/books/game/page/architecture-by-module-accounting);
- [Contextes Accounting](https://wiki.uniksim.com/books/game/page/contextes-accounting-cycle-heritage-et-donnees-entre-periodes);
- [UI data-driven By Module](https://wiki.uniksim.com/books/game/page/ui-data-driven-pages-tables-et-prefabs-by-module).

## Pages Commun à connaître peu importe le jeu

<table id="bkmrk-si-tu-touches...-lir"><thead><tr><th>Si tu touches...</th><th>Lire...</th></tr></thead><tbody><tr><td>Login / navigation / changement d'état</td><td>[Machine d'état client](https://wiki.uniksim.com/books/game/page/machine-detat-client-login-chargement-et-navigation).</td></tr><tr><td>Nouvelle commande serveur</td><td>[Backend Unity ↔ BrainCloud](https://wiki.uniksim.com/books/game/page/backend-unity-braincloud).</td></tr><tr><td>Custom Entity / persistance</td><td>[Documents et persistance BrainCloud](https://wiki.uniksim.com/books/game/page/commandes-documents-et-persistance-braincloud).</td></tr><tr><td>Synchronisation live</td><td>[RTT, messages et synchronisation](https://wiki.uniksim.com/books/game/page/rtt-messages-et-synchronisation).</td></tr><tr><td>Static data</td><td>[Données statiques et import/export](https://wiki.uniksim.com/books/game/page/donnees-statiques-et-pipeline-importexport).</td></tr><tr><td>Textes</td><td>[Textes, localisation et mise à jour](https://wiki.uniksim.com/books/game/page/textes-localisation-et-mise-a-jour).</td></tr><tr><td>Tutoriel</td><td>[Tutoriels — création de A à Z](https://wiki.uniksim.com/books/game/page/tutoriels-creation-de-a-a-z-et-architecture).</td></tr><tr><td>Scenario</td><td>[Scénarios runtime](https://wiki.uniksim.com/books/game/page/scenarios-runtime-configuration-reponses-et-evaluation).</td></tr><tr><td>Animation Timeline</td><td>[Timelines Unity](https://wiki.uniksim.com/books/game/page/timelines-unity-architecture-et-creation-de-sequences).</td></tr><tr><td>Build / déploiement</td><td>[Build Unity, WebGL et versioning client](https://wiki.uniksim.com/books/game/page/build-unity-webgl-et-versioning-client).</td></tr><tr><td>Test serveur</td><td>[Tests automatisés des Server Scripts](https://wiki.uniksim.com/books/game/page/tests-automatises-des-server-scripts).</td></tr><tr><td>Debug serveur</td><td>[Debug local des Server Scripts BrainCloud](https://wiki.uniksim.com/books/game/page/debug-local-des-server-scripts-braincloud).</td></tr></tbody></table>

## Comment choisir où chercher quand un bug arrive

1. **Le bug existe dans plusieurs jeux ?** Commencer par Commun ou le framework By Year/By Module.
2. **Il existe dans ACC1 et ACC2 ?** Commencer par By Module / `acc_base`, pas les dossiers game-specific.
3. **Il existe seulement dans un GameUid ?** Regarder static data, GameSpecific Unity et utils serveur du jeu.
4. **La donnée est bonne après reload mais pas live ?** Regarder RTT/controller.
5. **La donnée est fausse même après reload ?** Regarder commande/persistance/génération.
6. **Le calcul serveur est bon mais l'écran faux ?** Regarder modèle Unity/UI.

## Structure documentaire recommandée pour naviguer

Pour une feature inconnue, lire dans cet ordre :

1. cette page pour identifier le jeu et le domaine;
2. la page d'architecture de la famille (**By Year** ou **By Module**);
3. la page game-specific si By Year, ou la page domaine Accounting si ACC;
4. la page workflow spécialisée : bills, contextes, Answer Key, grading, RTT, tutoriels, Scenario, Timeline, build, etc.

# Logbook — sauvegarde, historique et évaluation

## Structure des données

Le logbook est stocké par équipe dans `teamsLogbooks`, avec `teamUid` comme UID du document. Chaque activité possède deux rubriques : `student` et `teacher`. Chaque rubrique contient une entrée courante par `yearId` et un historique de versions.

```
logbook[actUid][role][yearId] = { text, date }
logbookHistory[actUid][role][yearId] = [{ text, date }, ...]
```

Côté Unity, `Logbook.logbookRoles` indexe les activités et les rôles. `LogbookRole.entries` contient les entrées courantes; `historyEntries` contient les listes de versions. `LogEntry` porte le texte et une date en millisecondes depuis l'époque, convertie en heure locale pour l'affichage.

## Chargement et cache

`TeamBaseController.Logbook` expose le modèle chargé. `LoadLogbook()` appelle `BackEndBrainCloud.GetLogbook()`, puis `_command/get_logbook.js`, qui retourne le document `teamsLogbooks`.

Le controller conserve un cache. Des demandes concurrentes de chargement sont regroupées par callbacks et `_logbookIsLoading`. Si le cache existe, le callback est appelé sans relire le serveur.

## Édition et sauvegarde

`LogbookRoleControl` gère l'édition, la sauvegarde, l'annulation, le collage, Undo/Redo et la sélection d'une version. Le bouton Save applique `Trim()` au texte et évite l'envoi si le texte est identique à l'entrée actuelle.

L'accès étudiant à l'édition dépend du rôle : avec `HasLogbookDir`, le directeur du logbook est requis; sinon, en équipe, le rôle temporaire `logbook_editor` sert à coordonner l'édition. Il peut être repris après confirmation et est libéré à la sortie du mode édition.

1. `TeamBaseController.SaveLogbookEntry()` normalise `actUid` en asynchrone via `UtilsBase.GetActUid()`.
2. Le rôle envoyé au serveur est déterminé par l'utilisateur : professeur → `teacher`, étudiant → `student`.
3. `BackEndBrainCloud.SaveLogbook()` envoie `teamUid`, `actUid`, `yearId`, `date`, `text` et `role` à `_command/save_logbook`.
4. Le controller met aussi à jour son entrée locale immédiatement. Cette mise à jour locale ne constitue pas un accusé de sauvegarde serveur.
5. Le script persiste les champs de `teamsLogbooks`, puis envoie `logbook_changed` à l'équipe et au professeur.

## Historique : 10 versions précédentes

`save_logbook.js` copie l'entrée précédente dans l'historique si son texte ou sa date diffère de la nouvelle entrée. La version précédente est insérée en tête. `MAX_LOGBOOK_HISTORY = 10` limite la liste; la version la plus ancienne est retirée au-delà de cette limite. La première sauvegarde n'a pas d'entrée précédente à archiver.

Les piles Undo/Redo du champ d'édition sont locales et distinctes de cet historique persistant.

## Restauration

La liste déroulante présente un choix initial, puis les versions historiques. Sélectionner une version ouvre `HistoryPreviewPopupControl`; la confirmation appelle `RestoreLogbookHistory()` avec l'index de version, après normalisation de l'activité en asynchrone.

`restore_logbook_history.js` retire la version choisie de l'historique, place l'entrée courante en tête, puis remplace l'entrée courante par le texte et la date d'origine de la version restaurée. Il sauvegarde le document et émet `logbook_changed`.

## Synchronisation entre clients

Le message contient `teamUid`, `actUid`, `yearId` et `role`. Le texte complet et l'historique sont relus depuis le serveur.

`TeamBaseController.OnLogbookChanged()` ignore les autres équipes et les logbooks qui ne sont pas encore chargés. Pour un logbook chargé de la bonne équipe, il invalide le cache, appelle `LoadLogbook()`, puis déclenche `onLogbookChanged(role)`. Le chargement initial reste donc nécessaire.

## Évaluation distincte du texte

`save_logbook_eval.js` enregistre la note dans le document `evaluation` de chaque étudiant concerné. `simulation.logbookPonderationType` choisit un mode annuel ou global, en équipe ou individuel. En équipe, la note est appliquée aux membres; en individuel, elle vise `studentUid`.

- Annuel : `evaluation.logbookEval[actUid][yearId]`.
- Global : `evaluation.logbookEval[actUid]`.
- La valeur `note = -1` supprime la note concernée.

`getLogbookScore()`, dans `utils_eval.js`, calcule en annuel la moyenne arrondie des notes présentes jusqu'à `currentYear`. Sans note, il retourne `0` si l'année est terminée, sinon `-1`. En global, il retourne la note stockée.

La sauvegarde des notes émet `logbook_eval_changed_stub` et, pour les notes annuelles, `logbook_eval_by_year_changed`. `get_logbook_evaluation.js` charge les notes annuelles d'un étudiant. Sauvegarder le texte ne sauvegarde pas sa note.

## Réouverture annuelle

`utils_reopen_year.js` appelle `deleteYearLogbook(actData, teamUid)` après avoir ajusté l'état et, selon le cas, l'année courante. Le helper cible précisément `yearId = actData.currentYear + 1`.

Il supprime les notes de cette année pour les pondérations annuelles, puis les entrées courantes et leurs historiques pour les rôles concernés. Il sauvegarde les documents et émet les messages de changement du logbook et d'évaluation. Les notes globales ne sont pas supprimées par ce traitement annuel.

## Fichiers de référence

Dans `master/game/Assets/Src` : `Game/Data/Logbook.cs`, `Game/Controllers/TeamBaseController.cs`, `Game/BackEnd/BackEndBrainCloud.cs`, `UI/SimByYear/LogbookRoleControl.cs`.

Dans `master/node.js/backend/braincloud/server_scripts` : `_command/get_logbook.js`, `save_logbook.js`, `restore_logbook_history.js`, `save_logbook_eval.js`, `get_logbook_evaluation.js`, `_utils/utils_eval.js`, `utils_by_year.js` et `_utils/command/utils_reopen_year.js`.

Vérifié le 30 septembre 2026 dans le code Game et BrainCloud de la branche `refactor_notifications`. Les fichiers de logbook et d’évaluation décrits ici sont identiques à ceux de `master` pour les chemins inspectés. Les parcours décrits ont été contrôlés par lecture du code; aucune sauvegarde ou réouverture de simulation réelle n'a été exécutée pour cette vérification.

# By Module

# Architecture By Module / Accounting

## Rôle

**By Module** est le framework où une activité est composée de modules configurables. Accounting (`acc1` et `acc2`) est l'implémentation principale de ce framework.

```mermaid
flowchart LR
ACT[Activity] --> AM[ActModule]
AM --> MS[ModuleSettings]
AM --> TM[teamsModules]
TM --> MTD[TeamModuleTypeData]
MTD --> P[pagesData]
MTD --> DD[dynamicDropdownsData]
MTD --> B[bills]
MTD --> INV[inventory]
MTD --> IMM[immobilizations]
MTD --> C[contracts]
P --> UI[UI data-driven]
TM --> RES[results]

```

## Frontières

<table id="bkmrk-responsabilit%C3%A9emplac"><thead><tr><th>Responsabilité</th><th>Emplacement</th></tr></thead><tbody><tr><td>Modèles Unity</td><td>`Assets/Src/Game/Data/ByModule/`</td></tr><tr><td>Controllers Unity</td><td>`Assets/Src/Game/Controllers/*ByModule*` et `Assets/Src/UI/SimByModule/Controllers/`</td></tr><tr><td>UI By Module</td><td>`Assets/Src/UI/SimByModule/`</td></tr><tr><td>Static data partagée</td><td>`Assets/GameSpecific/byModule/Resources/data_byModule/`</td></tr><tr><td>Données propres Acc1/Acc2</td><td>`Assets/GameSpecific/acc1|acc2/Resources/data_acc*/`</td></tr><tr><td>Moteur serveur</td><td>`server_scripts/_utils/by_module/`</td></tr><tr><td>Base Accounting</td><td>`server_scripts/acc_base/`</td></tr><tr><td>Wrappers game-specific</td><td>`server_scripts/acc1|acc2/command/`</td></tr></tbody></table>

## Chargement Unity

`StaticDataControllerByModule` initialise `StaticDataByModule`, puis les classes Acc1/Acc2 spécialisent au besoin. Le runtime charge à la fois la static data `byModule` et la static data du GameUid concret.

## Flux d'une modification étudiante

```mermaid
sequenceDiagram
participant UI as Page By Module
participant C as TeamByModuleController
participant B as BackEndBrainCloud
participant GS as acc1/acc2 custom_command
participant U as utils_custom_command_by_module
participant D as teamsModules
UI->>C: modification
C->>B: CustomCommand(commandUid, data)
B->>GS: game-specific Cloud Script
GS->>U: executeCommandByModule
U->>D: lire / modifier / sauvegarder
U-->>C: réponse + message RTT
C-->>UI: mise à jour

```

## Pages spécialisées déjà existantes

Les pages **Système de formules**, **Configuration Accounting — guide des JSON**, **Pipeline des Bills** et **Procédure annuelle pour updater les taxes** restent les références spécialisées et ne sont pas dupliquées ici.

## Ajouter un nouveau module Accounting — vue end-to-end

Cette checklist est volontairement transversale. Les pages spécialisées donnent le détail de chaque moteur.

1. **Static data** : ajouter le module dans `modules.json` / settings associés et vérifier `regions_settings`.
2. **Pages** : déclarer les `module_pages` et `pages_settings`.
3. **Bills** : configurer les bills; si nouveau type, suivre la page protégée Pipeline des Bills.
4. **Module Type Data** : vérifier que les données initiales générées contiennent tout ce dont le module a besoin.
5. **UI** : vérifier que chaque page est rendue par un type/prefab existant ou ajouter le support manquant.
6. **Answer Key** : ajouter le calcul de chaque nouvelle page évaluée.
7. **Grading** : ajouter le correcteur symétrique et les pondérations.
8. **Results** : vérifier que le module produit les scores et evaluationTeam attendus.
9. **Textes** : ajouter titres, labels, erreurs et tutoriels éventuels.
10. **Tutoriel** : si le module introduit un nouveau flow, ajouter les TutorialUIObject/ScenarioStep nécessaires.
11. **Tests** : create module data → answer key → grade à 100 % → mauvaises réponses.
12. **Région** : tester au moins une région où le module est visible et une où il ne l'est pas.

## Test de cohérence global recommandé

Le meilleur smoke test serveur est de générer le Module Type Data avec un seed fixe, construire l'Answer Key, utiliser cette Answer Key comme décisions étudiant, puis lancer grading + reassemble. Le résultat doit être cohérent à 100 % sur les pages évaluées.

## ACC1 vs ACC2

Pour les différences réelles entre les deux jeux — contexte métier, datasets, petite caisse, tutoriels, wrappers et assets — voir [**Accounting 1 vs Accounting 2 — différences réelles**](https://wiki.uniksim.com/books/game/page/accounting-1-vs-accounting-2-differences-reelles).

# acc_base, acc1 et acc2 — séparation des responsabilités

## Principe

La vraie logique Accounting partagée côté serveur vit dans `acc_base`. `acc1` et `acc2` sont des wrappers qui instancient la même base avec un GameUid différent et exposent les commandes sous le namespace propre au jeu.

## Utils

```mermaid
classDiagram
class GameSpecificUtilsAccBase
class GameSpecificUtilsAcc1
class GameSpecificUtilsAcc2
GameSpecificUtilsAccBase <|-- GameSpecificUtilsAcc1
GameSpecificUtilsAccBase <|-- GameSpecificUtilsAcc2

```

`GameSpecificUtilsAcc1()` appelle `GameSpecificUtilsAccBase.call(this, "acc1")`; Acc2 fait la même chose avec `"acc2"`. Les deux enregistrent ensuite leur instance dans `utilsByGameUid` et exposent `utilsAccGameSpecific`.

## Ce qui appartient à acc\_base

- constantes et préférences Accounting;
- mapping bill → buy/sell;
- partenaires et comptes impactés;
- seed de Team Module;
- amortissement d'immobilisations;
- calculs contrats/régularisations;
- settings régionaux;
- résolution act/module/page settings;
- comptes utilisés et structure de pages;
- conditions/visibilité;
- prix et helpers partagés.

## Command wrappers

Les dossiers `acc_base/command`, `acc1/command` et `acc2/command` contiennent des scripts de mêmes familles : answer key, generate results, custom command, reopen module, correction mode, etc. Les wrappers Acc1/Acc2 assurent surtout le namespace et la sélection des static data game-specific.

## Unity

La même règle s'applique : le framework se trouve dans `Assets/Src/Game/Data/ByModule` et `Assets/Src/UI/SimByModule`; seules les vraies différences de contenu/assets vont sous `Assets/GameSpecific/acc1` ou `acc2`.

## Règle de maintenance

Si une modification doit fonctionner de la même façon dans Acc1 et Acc2, elle ne doit pas être dupliquée dans deux fichiers game-specific. La placer dans le framework By Module ou `acc_base` selon qu'elle est générique au framework ou spécifique au domaine Accounting.

## Où placer une nouvelle règle Accounting

<table id="bkmrk-r%C3%A8gle-emplacement-va"><thead><tr><th>Règle</th><th>Emplacement</th></tr></thead><tbody><tr><td>Valable pour tout By Module, même un futur jeu non Accounting</td><td>`_utils/by_module`.</td></tr><tr><td>Spécifique à la comptabilité mais commune Acc1/Acc2</td><td>`acc_base`.</td></tr><tr><td>Différence réelle Acc1 seulement</td><td>`acc1`.</td></tr><tr><td>Différence réelle Acc2 seulement</td><td>`acc2`.</td></tr></tbody></table>

## Ajouter un helper Accounting partagé

1. Ajouter la fonction à `GameSpecificUtilsAccBase`.
2. Utiliser `this.*` lorsque la fonction fait partie de l'API de l'instance.
3. Réutiliser les constantes/preferences déjà exposées par la base.
4. Vérifier que `GameSpecificUtilsAcc1` et Acc2 héritent automatiquement du comportement.
5. Tester les deux jeux, car leurs static data peuvent différer même si le code est commun.

## Éviter la duplication acc1/acc2

Si deux fichiers game-specific contiennent la même fonction à quelques valeurs près, déplacer la logique dans `acc_base` et passer la différence par static data/settings. Les wrappers game-specific doivent rester minces.

## Debug d'un “utilsAccGameSpecific is not defined”

1. Vérifier que le wrapper game-specific inclut son fichier utils.
2. Vérifier que l'instance `GameSpecificUtilsAcc1/Acc2` a été construite.
3. Vérifier l'ordre des `bridge.include()`.
4. Dans le debugger local, inclure le même game-specific que le script réel.
5. Ne pas supposer que le test runner est neutre : il inclut actuellement Acc2 dans son bootstrap.

# Modèle de données et cycle de vie d’un Team Module

## Document central

Le document persistant central est `teamsModules`. Côté Unity, `BackEndBrainCloud.GetTeamModule(teamUid, actModuleUid, ...)` appelle `get_document` avec l'UID généré par `UtilsBase.GetTeamModuleUid(teamUid, actModuleUid)`.

## Objets principaux

- `TeamModuleBase` : module d'une équipe;
- `TeamModuleTypeData` : données métier d'un type de module;
- `TeamModuleDecisionsData` : pages et dropdowns d'un jeu de décisions;
- `TeamModulePageData` : données persistées d'une page;
- `DataDrivenTableData`, `DataDrivenSectionData`, `DataDrivenInputData` : structure éditable;
- `ResultsByModule` : résultat/correction récupéré par le client.

```mermaid
classDiagram
class TeamModuleBase
class TeamModuleTypeData
class TeamModuleDecisionsData
class TeamModulePageData
class DataDrivenTableData
class DataDrivenSectionData
class DataDrivenInputData
TeamModuleBase --> TeamModuleTypeData
TeamModuleTypeData --> TeamModuleDecisionsData
TeamModuleDecisionsData --> TeamModulePageData
TeamModulePageData --> DataDrivenTableData
DataDrivenTableData --> DataDrivenSectionData
DataDrivenSectionData --> DataDrivenInputData

```

## Ce que TeamModuleTypeData peut porter

Selon le module, la structure contient notamment seed, bills, finished bills, pagesData, dynamicDropdownsData, comptes initiaux, inventory, immobilizations, contracts, seeded products/services, taxes, ownership et snapshots de contextes.

## Practice / Summative

Un même Team Module peut avoir plusieurs `moduleTypesData`, notamment `practice` et `summative`. La plupart des custom commands reçoivent donc systématiquement `moduleType` en plus de `teamUid` et `actModuleUid`.

## Contexts

Le framework supporte des données héritées entre contextes. Les champs `savedPagesDataByContextUids`, `initialPagesData` et les équivalents de dropdowns permettent de transporter ou préremplir l'état d'un contexte antérieur.

## Versioning

Tout changement structurel du document doit être accompagné d'une migration dans `_utils/version/version_teams_modules.js`. Ce fichier contient déjà une chaîne de migrations successives.

## Ajouter un nouveau champ TeamModuleTypeData

1. Définir si le champ est **généré**, **édité par l'étudiant**, **dérivé** ou **hérité d'un contexte précédent**.
2. Ajouter le champ côté serveur dans l'objet produit par Create Module Type Data.
3. Ajouter le champ dans le modèle Unity `TeamModuleTypeData.cs` si le client doit le lire.
4. Ajouter la désérialisation avec une valeur par défaut pour les vieux documents.
5. Si l'étudiant peut modifier le champ, ajouter une custom command dédiée ou réutiliser le système data-driven approprié.
6. Ajouter RTT si les coéquipiers doivent voir le changement immédiatement.
7. Ajouter une migration dans `version_teams_modules.js` si le champ doit exister dans les documents persistés historiques.
8. Vérifier practice et summative séparément.
9. Vérifier les backups/reopen/restart module si ce champ doit être restauré.

## Choisir où mettre une donnée

<table id="bkmrk-donn%C3%A9e-emplacement-r"><thead><tr><th>Donnée</th><th>Emplacement recommandé</th></tr></thead><tbody><tr><td>Structure immuable du module</td><td>Static data / settings.</td></tr><tr><td>Donnée générée par seed pour une équipe</td><td>`TeamModuleTypeData`.</td></tr><tr><td>Valeurs saisies dans les pages</td><td>`TeamModuleDecisionsData.pagesData`.</td></tr><tr><td>Options dynamiques de dropdown</td><td>`dynamicDropdownsData`.</td></tr><tr><td>Résultat/correction</td><td>document teams / results structures, pas dans les décisions.</td></tr><tr><td>Donnée à partager entre contextes</td><td>saved/initial context data ou champ cumulatif explicitement géré.</td></tr></tbody></table>

## Reopen / restart / backup

Une nouvelle donnée persistante doit être testée avec `restartStudentModule`, `restore_module_backup` et `reopen_module` si ces flows doivent la restaurer ou la réinitialiser. C'est un trou classique lorsqu'on ajoute seulement la génération initiale.

# Configuration Accounting — guide des JSON

## Objectif

Cette page sert de carte des fichiers de configuration du système Accounting / ByModule. Le point important est de distinguer trois familles : les **données statiques** utilisées par la simulation, les **settings** qui activent/configurent les modules, et les **definitions** qui décrivent surtout les champs disponibles dans les écrans d'édition/wizards.

## 1. Vue générale du chargement

```mermaid
flowchart LR
    JSON[Resources/data_byModule/*.json] --> STATIC[StaticDataByModule.Initialize]
    STATIC --> CATALOG[Catalogues statiques]
    STATIC --> SETTINGS[ModulesSettings / PagesSettings]
    STATIC --> DEFINITIONS[Definitions pour éditeur / wizard]

    CATALOG --> GAME[Runtime Accounting]
    SETTINGS --> GAME
    DEFINITIONS --> EDITOR[Configuration des activités/modules]
    EDITOR --> MODULECFG[Configuration sauvegardée du module]
    MODULECFG --> GAME

```

Le point d'entrée Unity est `Assets/Src/Game/Data/ByModule/StaticDataByModule.cs`. Il charge les fichiers du dossier `Assets/GameSpecific/byModule/Resources/data_byModule` via `Resources.Load`.

## 2. Les JSON principaux

<table id="bkmrk-fichier-r%C3%B4le-quand-l"><thead><tr><th>Fichier</th><th>Rôle</th><th>Quand le modifier</th></tr></thead><tbody><tr><td>`modules.json`</td><td>Catalogue des modules disponibles : uid, nom, description, type de module, editor, icône/image, limites et métadonnées d'affichage.</td><td>Quand on ajoute un nouveau module ou qu'on change son identité dans le catalogue.</td></tr><tr><td>`modules_settings.json`</td><td>Valeurs/settings par défaut des modules : pages, préférences, bills, outils accounting, durée, options de validation, etc.</td><td>Quand on change le comportement par défaut d'un module existant ou sa configuration Accounting.</td></tr><tr><td>`modules_settings_definitions.json`</td><td>Décrit quels champs/préférences/bill definitions sont exposés pour chaque type de module dans les écrans de configuration.</td><td>Quand un nouveau paramètre doit devenir configurable dans le wizard/editor.</td></tr><tr><td>`module_pages.json`</td><td>Structure statique des pages et tableaux : pages, tables, rows, inputs, types d'inputs et outils comme journal général, grand livre, balance, états financiers, cash flow, etc.</td><td>Quand la structure visuelle ou fonctionnelle d'une page Accounting change.</td></tr><tr><td>`pages_settings.json`</td><td>Comportement d'une page : rangées/sections dynamiques, row editor, formules, navigation et autres options runtime.</td><td>Quand une page existante doit accepter un comportement dynamique différent.</td></tr><tr><td>`pages_settings_definitions.json`</td><td>Définitions des champs éditables de `pages_settings`.</td><td>Quand on ajoute un nouveau setting de page configurable.</td></tr><tr><td>`preferences_settings_definitions.json`</td><td>Définitions des préférences de module utilisables dans l'éditeur.</td><td>Quand une nouvelle préférence doit être sélectionnable/configurable.</td></tr><tr><td>`bills_settings_definitions.json`</td><td>Schéma des données configurables **par type de bill** : type, nombre de bills, dates, comptes, taxes, partner subtype, produit/service, règles spécifiques, etc.</td><td>Quand un nouveau type de bill ou un nouveau paramètre de génération doit être exposé à la configuration.</td></tr><tr><td>`bills_category_settings_definitions.json`</td><td>Schéma des options **par catégorie de bills**. Dans la version actuelle, ces definitions décrivent les options que l'éditeur peut présenter.</td><td>Quand une catégorie de bills reçoit une nouvelle option commune.</td></tr><tr><td>`accounts.json`</td><td>Plan comptable statique : comptes par défaut/by-sector/custom, numéro, nom, type, unique\_identifier, ordre, balance par défaut et metadata cash-flow.</td><td>Quand on ajoute/modifie un compte ou son comportement comptable.</td></tr><tr><td>`account_types.json`</td><td>Définitions des types/catégories de comptes.</td><td>Quand la taxonomie des comptes change.</td></tr><tr><td>`taxes.json`</td><td>Taxes de vente et données associées chargées dans `SalesTaxes`.</td><td>Quand une taxe de vente ou sa configuration statique change.</td></tr><tr><td>`income_taxes.json`</td><td>Définitions générales des impôts sur le revenu.</td><td>Quand les types/règles générales d'impôt changent.</td></tr><tr><td>`employer_income_taxes.json`</td><td>Données de charges/taxes employeur.</td><td>Quand les règles employeur changent.</td></tr><tr><td>`income_tax_*_YEAR.json`</td><td>Tables fiscales annuelles fédérales/provinciales, chargées à la demande selon région et année.</td><td>Lors d'une mise à jour fiscale annuelle.</td></tr><tr><td>`income_taxes_codes.json`</td><td>Codes liés aux calculs/configurations d'impôt.</td><td>Quand la correspondance des codes fiscaux change.</td></tr><tr><td>`interests.json`</td><td>Définitions de taux/catégories d'intérêts.</td><td>Quand le calcul d'intérêts doit utiliser de nouvelles données statiques.</td></tr><tr><td>`immobilizations.json`</td><td>Catalogue/configuration des immobilisations.</td><td>Quand un type d'immobilisation ou ses données changent.</td></tr><tr><td>`contract_regularizations.json`</td><td>Données de régularisation des contrats.</td><td>Quand les règles de régularisation de contrats changent.</td></tr><tr><td>`regions.json`</td><td>Catalogue des régions supportées.</td><td>Quand une région est ajoutée/modifiée.</td></tr><tr><td>`regions_settings.json`</td><td>Comportements propres à une région : options fiscales/employeur/employé, congés et autres paramètres régionaux.</td><td>Quand une région doit avoir un comportement Accounting différent.</td></tr><tr><td>`act_settings.json`</td><td>Settings globaux d'activité utilisés par les modules.</td><td>Pour une configuration transversale à l'activité plutôt qu'à une page/module précis.</td></tr><tr><td>`constants.json`</td><td>Constantes globales ByModule utilisées par les systèmes runtime.</td><td>Quand un comportement dépend d'une constante partagée plutôt que d'un module particulier.</td></tr><tr><td>`dropdown_data.json`</td><td>Données de dropdown statiques utilisées dans les configurateurs.</td><td>Quand une liste de choix statique change.</td></tr><tr><td>`dynamic_dropdown_data_structure.json`</td><td>Structure des dropdowns dynamiques.</td><td>Quand une donnée sélectionnable dépend d'autres données/configurations.</td></tr></tbody></table>

## 3. Relation entre modules\_settings et les definitions

```mermaid
flowchart TD
    DEF[modules_settings_definitions.json] -->|liste des preferences| PREF[preferences_settings_definitions.json]
    DEF -->|liste des bills| BILLDEF[bills_settings_definitions.json]
    BILLDEF --> WIZ[BillsDataWizardPageControl]
    PREF --> WIZARD[Module settings wizard]

    DEFAULT[modules_settings.json] --> MS[ModuleSettingsBase]
    WIZ --> SAVED[Configuration du module/activité]
    WIZARD --> SAVED
    SAVED --> MS

    MS --> PREFS[preferences]
    MS --> BTD[bills_type_data]
    MS --> BCS[bills_category_settings]
    MS --> HIDDEN[bills_types_hidden_total]

```

**À retenir :** un fichier `*_definitions.json` ne représente pas forcément la valeur runtime. Il définit souvent *comment configurer* cette valeur. Les valeurs effectives d'un module finissent dans `ModuleSettingsBase`.

## 4. Configuration des bills dans un module

`ModuleSettingsBase` expose deux structures principales :

- `bills_type_data` → dictionnaire de `BillTypeData` / données détaillées de génération par bill;
- `bills_category_settings` → options communes par catégorie, par exemple le nombre de bills aléatoires à sélectionner dans une catégorie.

Le backend lit notamment `_moduleSettings.bills_category_settings` lors de la génération et utilise `bills_type_data` comme source réelle pour `BillFactory`.

### bills\_settings\_definitions.json

Ce fichier est la grosse table de définition des champs disponibles pour les bills. On y retrouve notamment :

- `type`, `num_bills`, `min_num_entries`, `max_num_entries`;
- dates, fréquences, occurrences, délais et termes;
- comptes impactés : sales, expenses, payable, receivable, cash, loan, amortization, etc.;
- taxes : taxes de vente, déductions, taxes récupérables;
- relations : product/service, contract, immobilization, employee, supplier, investment;
- règles de création/édition et données spécifiques au type de bill.

## 5. Configuration des pages Accounting

```mermaid
flowchart LR
    M[modules.json] --> UID[module uid]
    UID --> MS[modules_settings.json]
    MS --> ORDER[pages_order]
    ORDER --> MP[module_pages.json]
    MP --> STRUCT[Structure UI: page/table/input]
    STRUCT --> PS[pages_settings.json]
    PS --> BEHAVIOR[Rows/sections dynamiques, formules, navigation]

```

`module_pages.json` répond principalement à la question **« qu'est-ce qui existe dans la page ? »**, tandis que `pages_settings.json` répond à **« comment cette page se comporte ? »**.

## 6. Plan comptable et calculs

```mermaid
flowchart LR
    ACC[accounts.json] --> STATIC[StaticDataByModule.Accounts]
    TYPES[account_types.json] --> STATIC
    TAX[taxes / income_taxes / employer_income_taxes] --> STATIC
    REG[regions + regions_settings] --> STATIC
    STATIC --> BILL[Bills]
    STATIC --> JOURNAL[General Journal]
    STATIC --> LEDGER[General Ledger]
    STATIC --> TB[Trial Balance]
    STATIC --> FS[Financial Statements]

```

## 7. `regions_settings.json` : filtre régional critique

**À ne pas oublier :** ajouter une donnée dans son JSON principal ne suffit pas toujours à la rendre disponible dans le jeu. Pour plusieurs données Accounting, `regions_settings.json` agit comme un **filtre/routeur par région**.

### Comptes

Pour les comptes, c'est particulièrement important. `UtilsByModule.GetRegionAccounts()` lit la région de l'activité, choisit un type via `global.accounts.type_by_regions`, puis prend uniquement les UIDs présents dans `global.accounts.data_by_type[type]`. Ensuite seulement, ces UIDs sont résolus dans `accounts.json`.

Donc un compte peut exister parfaitement dans `accounts.json` et **ne jamais apparaître** dans le jeu s'il n'est pas ajouté dans la liste appropriée de `regions_settings.json`. Après ce filtre régional, `GetUsedAccounts()` applique encore les `visibilityConditions` du compte et `preferences.hiddenAccounts`.

```mermaid
flowchart LR
    A[accounts.json] --> ALL[Catalogue complet des comptes]
    R[act_settings.region] --> RS[regions_settings.json]
    RS --> LIST[Liste de comptes autorisés pour la région]
    ALL --> FILTER[GetRegionAccounts]
    LIST --> FILTER
    FILTER --> VIS[visibilityConditions + hiddenAccounts]
    VIS --> USED[Comptes réellement disponibles]

```

### Ce que `regions_settings.json` pilote aussi

- **Taxes de vente** via `global.taxes` : par exemple GST/QST au Québec versus HST selon la province.
- **Retenues / impôts employés** via `global.income_taxes`.
- **Charges employeur** via `global.employer_income_taxes`.
- **Intérêts** via `global.interests`.
- **Colonnes de certaines pages/journaux** via `pages.<page_uid>.column_inputs_order`. L'ordre et même les colonnes affichées peuvent changer selon la région.
- **Prefab de certaines pages/formulaires** via `pages.<page_uid>.prefab_reference`. Exemple : le formulaire de paiement des taxes utilise un contrôle différent selon la province.

### Pattern utilisé

La plupart de ces sections utilisent le même pattern :

```
{
  "type_by_regions": {
    "ca_on": "ca_on"
  },
  "data_by_type": {
    "default": [...],
    "ca_on": [...]
  }
}
```

Si la région courante n'est pas présente dans `type_by_regions`, le code retombe sur `default`.

### Checklist quand on ajoute une donnée régionale

- Créer/modifier la donnée source : `accounts.json`, `taxes.json`, tables fiscales, etc.
- Vérifier `regions_settings.json` et ajouter l'UID dans chaque `data_by_type` où il doit être disponible.
- Si une nouvelle région/variante est nécessaire, ajouter le mapping dans `type_by_regions`.
- Pour une page régionale, vérifier aussi `column_inputs_order` ou `prefab_reference`.
- Tester au minimum la région `default` et une région explicitement mappée.

**Exemple classique de bug :** « j'ai ajouté le compte dans `accounts.json`, il charge sans erreur, mais il n'apparaît nulle part ». Le premier endroit à vérifier est `regions_settings.json → global.accounts`.

## 8. Règle pratique : quel fichier modifier ?

<table id="bkmrk-je-veux...-commencer"><thead><tr><th>Je veux...</th><th>Commencer par...</th></tr></thead><tbody><tr><td>Ajouter un nouveau module</td><td>`modules.json` + `modules_settings.json` + sa définition/settings/pages selon le besoin.</td></tr><tr><td>Changer la structure d'une page</td><td>`module_pages.json`.</td></tr><tr><td>Changer le comportement d'une page</td><td>`pages_settings.json`.</td></tr><tr><td>Ajouter une préférence configurable</td><td>`preferences_settings_definitions.json` puis consommation dans le code/settings.</td></tr><tr><td>Ajouter un paramètre de bill configurable</td><td>`bills_settings_definitions.json` puis le code serveur qui consomme le champ.</td></tr><tr><td>Changer combien/quels bills un module génère</td><td>Configuration effective `bills_type_data`/`bills_category_settings` du module.</td></tr><tr><td>Ajouter/modifier un compte</td><td>`accounts.json`.</td></tr><tr><td>Mettre à jour une table fiscale annuelle</td><td>`income_tax_*_YEAR.json` correspondant.</td></tr><tr><td>Changer une règle propre à une région</td><td>`regions_settings.json`.</td></tr></tbody></table>

## Fichiers de code qui chargent cette configuration

- `Assets/Src/Game/Data/ByModule/StaticDataByModule.cs` — chargement central des JSON de `data_byModule`.
- `Assets/Src/Game/Data/ByModule/ModuleSettingsBase.cs` — désérialisation des settings runtime, incluant bills.
- `Assets/Src/UI/SimByModule/Data/ModuleSettingsDefinition.cs` — relie une définition de module aux définitions de préférences et de bills.
- `Assets/Src/UI/SimByModule/Data/BillsDataWizardPageControl.cs` — éditeur des données de bills.
- `Assets/Src/UI/SimByModule/Data/BillDataSettingDefinitionData.cs` — représentation Unity d'une définition de bill.
- `Assets/Src/UI/SimByModule/Data/BillCategorySettingDefinitionData.cs` — représentation Unity des options de catégorie.

## Point important

Il n'existe pas dans cette branche de fichier statique `bills_settings.json` dans `Resources/data_byModule`. Les fichiers statiques présents sont `bills_settings_definitions.json` et `bills_category_settings_definitions.json`; les valeurs effectives des bills sont intégrées aux settings du module/activité sous `bills_type_data` et `bills_category_settings`.

# Constantes, préférences et conditions By Module

## Source serveur

`_utils/by_module/byModule_utils.js` construit un cache de constantes utilisé par l'ensemble du moteur. Les strings définies ici doivent correspondre aux clés des JSON de configuration.

## Module preferences

Les préférences actuellement reconnues incluent :

```
num_decimals
our_company_type
company_legal_form
stock_type
has_taxes
has_deductions
has_decimals
has_formulas
uses_auxiliary_journals
has_employees
has_credit_notes
hidden_accounts
accounts_uses_last_financial_statement
uses_accounting_closing
unique_client_preference
unique_provider_preference
last_year_partners_with_empty_balance
uses_bill_id_in_general_journal
uses_new_regularizations
has_new_immobilizations
has_new_contracts
uses_regularizations
has_amortizations
regularization_accounts_count
pages
affected_regularizations_reports
has_immobilizations
has_immobilizations_taxes_report
uses_taxes_payment_form
uses_stock_regularizations
rate_num_bills_with_deductions
has_banking_transactions
has_land_immobilizations
manual_income_taxes
has_net_profit
permanent_stock_method
receive_or_send_taxes
decimal_money_tolerance_range
uses_bank_reconciliation
bank_reconciliation_form_numbers
module_time_limit
single_owner
partnership
joint_stock_a
joint_stock_ab
retry_summative
has_debts
has_interests
module_uid
ignore_grading_underline
ignore_grading_cash
ignore_grading_indent
ignore_grading_bold
auto_report_to_ledger
```

## Page preferences

- `ignored_options`;
- `ignored_grading_column_uids`;
- `ignored_grading_relation_uids`;
- `money_tolerance_range`;
- `num_columns`;
- `has_dynamic_section`.

## UIDs par défaut

Le framework utilise `default` comme UID standard pour table, section et row lorsque la structure ne nécessite pas d'identifiant spécifique.

## Conditions de visibilité

Les conditions reconnues comprennent `condition_equal`, `or` et `and`, avec le préfixe `not_` pour l'inversion. Côté Unity, les classes `VisibilityCondition*` représentent ces conditions.

## Résolution d'une préférence

Ne pas lire un JSON arbitrairement depuis un calcul. Le moteur passe par les helpers `getModulePreference()`, `getPagePreference()` et les settings du contexte courant, ce qui permet de gérer valeurs par défaut et overrides.

## Où définir la valeur

Les commentaires du code indiquent la source attendue : plusieurs defaults sont dans `constants.json`, les préférences de pages dans `pages_settings.json` et certaines valeurs globales d'activité dans `act_settings.json`. La page protégée **Configuration Accounting — guide des JSON** reste la référence pour le détail des fichiers.

## Ajouter une nouvelle préférence By Module

### 1. Déterminer le scope

- valeur globale par défaut → `constants.json`;
- valeur par module → `modules_settings.json` / definitions;
- valeur par page → `pages_settings.json` / definitions;
- valeur activité/région → `act_settings`/`regions_settings` selon le besoin.

### 2. Ajouter la constante string côté serveur

Ajouter la clé dans la table `modulesPreferences` ou `pagesPreferences` de `byModule_utils.js`. La string doit correspondre exactement au JSON.

### 3. Définir une valeur par défaut

Une preference manquante ne doit pas produire un comportement aléatoire. Ajouter le default dans la source appropriée et vérifier `getModulePreference()`/`getPagePreference()`.

### 4. Consommer via les helpers

Ne pas lire directement le JSON statique dans un moteur métier si l'helper de preference gère déjà overrides/context. Cela permet de conserver les régions et settings spécifiques.

### 5. Si Unity en a besoin

Ajouter la constante/lecture côté client seulement si le frontend doit réellement changer son comportement. Éviter de dupliquer une preference purement serveur.

## Ajouter une condition de visibilité

Les conditions utilisent notamment `condition_equal`, `and`, `or` et le préfixe `not_`. Si un nouveau type de condition est nécessaire, il faut mettre à jour le parser/évaluateur côté Unity et, si utilisé côté serveur, sa logique équivalente.

## Test minimal d'une nouvelle préférence

1. default absent d'override;
2. override module;
3. override région si applicable;
4. valeur false/0 explicite — ne pas la confondre avec “absente”;
5. ancien module sans la nouvelle clé;
6. Acc1 + Acc2 si la preference est partagée.

# UI data-driven, pages, tables et prefabs By Module

## Rôle

L'UI By Module est générée à partir de `module_pages.json`, des `pages_settings` et des données runtime du `TeamModuleTypeData`. La structure visuelle n'est donc pas entièrement codée en dur dans les prefabs.

## Hiérarchie UI

```mermaid
flowchart TD
M[TeamModuleControl] --> PAGE[ModulePageControlBase]
PAGE --> TABLE[ModulePageTableControlBase]
TABLE --> SEC[Section / EditableSection]
SEC --> ROW[Row / EditableRow]
ROW --> CELL[CellControlBase]
CELL --> INPUT[InputControlBase]
INPUT --> TEXT[TextInput]
INPUT --> DROP[Dropdown / AutoComplete]
INPUT --> CHECK[Checkbox / Toggle]

```

## Classes structurantes

<table id="bkmrk-responsabilit%C3%A9classe"><thead><tr><th>Responsabilité</th><th>Classes</th></tr></thead><tbody><tr><td>Liste/modules</td><td>`TeamModuleListControl`, `TeamModuleItemControl`, `TeamModuleControl`, `TeamModuleMenuControl`</td></tr><tr><td>Pages</td><td>`ModulePageControlBase`, `ModulePageInputsControl`, `ModulePageTableControl*`</td></tr><tr><td>Tables data-driven</td><td>`DataDrivenTableControl`, `DataDrivenSectionControl`, `DataDrivenRowControl`, `DataDrivenInputsControl`</td></tr><tr><td>Rows/sections dynamiques</td><td>`EditableRowControlBase`, `EditableSectionControlBase`, `CustomRowControl`</td></tr><tr><td>Inputs</td><td>`InputControlBase`, `InputCellControl`, `TextInputControl`, `DropdownInputControl`, autocomplete, checkbox</td></tr><tr><td>Correction</td><td>`CorrectedTeamModuleWindowControl`, `ModuleErrorsPopupControl`, `CellErrorButtonControl`</td></tr></tbody></table>

## Types de pages

Le modèle Unity expose plusieurs classes de données : `ModulePageDataInputs`, `ModulePageDataTable`, `ModulePageDataTableWithInputs`, `ModulePageDataTableWithDropdown`, etc. Le type configuré dans la static data détermine quel contrôle Unity est utilisé.

## Pages Accounting spécialisées

En plus du moteur générique, le dossier `Assets/Src/UI/SimByModule/UI/` contient les vues spécialisées : trial balance, balance sheet, income statement, statement of equity, bank reconciliation, formulaires de taxes, reports d'employés, immobilisations, contrats, bills et autres documents.

## Frontend JSON → runtime

```mermaid
flowchart LR
MP[module_pages.json] --> SD[StaticDataByModule]
PS[pages_settings.json] --> SD
SD --> PC[ModulePageControl]
TM[TeamModuleTypeData.pagesData] --> PC
PC --> INPUTS[Rows / Cells / Inputs]
INPUTS --> CTRL[TeamByModuleController]
CTRL --> SERVER[Custom Command]

```

## Quand modifier quoi

- Structure d'une page : `module_pages.json`.
- Comportement dynamique/validation : `pages_settings.json` et definitions associées.
- Nouveau rendu visuel réutilisable : classe/prefab sous `SimByModule/UI`.
- Donnée persistée : modèle `DataDriven*` + custom command serveur.
- Nouvelle page Accounting réellement spécialisée : ajouter le contrôle dédié et le brancher dans le système de page correspondant.

## Ajouter un nouveau type de page ou de contenu

Avant de créer un nouveau type, vérifier si la page peut être exprimée avec les types existants : `table`, `table_with_dropdown`, `table_with_dynamic_dropdown`, `table_with_inputs`, `inputs` ou `table_inputs_with_dropdown`.

### Si un type existant suffit

1. Déclarer la page dans `module_pages.json`.
2. Configurer structure/inputs/columns et `pages_settings`.
3. Ajouter un prefab custom seulement si le type `inputs` en a besoin via les références de prefab.
4. Vérifier rendu normal + rendu corrected.

### Si un vrai nouveau ModulePageType est nécessaire

1. Ajouter la string dans `ModulePageTypeConstants`.
2. Ajouter la valeur dans `ModulePageBase.ModulePageType`.
3. Mettre à jour `GetModulePageType()` et `GetModulePageTypeName()`.
4. Ajouter le modèle `ModulePageData*` si la structure JSON diffère.
5. Mettre à jour `Factory.CreateModulePageData(type)`.
6. Créer/adapter le `ModulePageControlBase` correspondant.
7. Ajouter le prefab template aux listes utilisées par `ReferencedModuleWindowControl` et `CorrectedTeamModuleWindowControl`.
8. Mettre à jour `UtilsByModule.GetModulePageControlPoolType()` si le nouveau type peut partager un pool.
9. Ajouter le rendu de contenu dans `AllocModulePageContentTemplate()`.
10. Ajouter Answer Key + Grading si la page est évaluée.

## Ajouter un nouvel input data-driven

Un nouvel input implique généralement quatre zones : modèle de définition static data, modèle runtime de valeur/options, contrôle Unity, et custom command de sauvegarde. Si l'input possède des options de correction, vérifier aussi le grading options.

## Rendu corrected à ne pas oublier

Une page qui fonctionne pendant la prise de décision mais plante/est vide en correction est souvent un nouveau type branché seulement dans la fenêtre normale. Toujours tester `CorrectedTeamModuleWindowControl` et les `CellErrorButtonControl`/`expectedValues`.

## Checklist UI By Module

- page normale;
- page corrected;
- practice;
- summative;
- resize/window;
- export Excel si applicable;
- dynamic rows/sections;
- formules si l'input les supporte;
- RTT avec deux clients.

# Custom Commands et édition des pages By Module

## Routeur serveur

La majorité des actions étudiantes passent par `_utils/command/utils_custom_command_by_module.js`. La fonction `executeCommandByModule()` route `commandUid` vers la fonction métier appropriée.

## Commandes majeures

<table id="bkmrk-fonctionresponsabili"><thead><tr><th>Fonction</th><th>Responsabilité</th></tr></thead><tbody><tr><td>`startStudentModule`</td><td>Initialise/démarre un module pour l'équipe.</td></tr><tr><td>`saveStudentModuleRowInputValue`</td><td>Sauvegarde une valeur d'input dans une row.</td></tr><tr><td>`saveStudentModuleRowInputOptions`</td><td>Sauvegarde les options d'un input.</td></tr><tr><td>`saveStudentModuleInputFormula`</td><td>Sauvegarde une formule; voir page dédiée Formules.</td></tr><tr><td>`add/remove/edit ... Row`</td><td>Gestion des rows dynamiques et de leur ordre.</td></tr><tr><td>`add/remove/edit ... Section`</td><td>Gestion des sections dynamiques.</td></tr><tr><td>`saveDynamicDropdownData`</td><td>Persistance des données de dropdown dynamique.</td></tr><tr><td>`getModuleGrade`</td><td>Calcule/récupère la correction.</td></tr><tr><td>`getTeamModuleTypeResults`</td><td>Retourne les résultats déjà générés.</td></tr><tr><td>`restartStudentModule`</td><td>Réinitialise un essai de module selon les règles.</td></tr><tr><td>`ignoreErrorFromTeamModuleResults`</td><td>Permet l'override/ignorance d'une erreur de correction.</td></tr></tbody></table>

## Rows et sections dynamiques

Lorsqu'une row/section temporaire doit être persistée, le serveur peut lui attribuer un nouvel ObjectId. Il renvoie alors les alias et index à l'UI afin que le client remplace l'UID local. Le serveur maintient aussi les références de formules associées.

## Synchronisation du ledger

Après certaines modifications de pages, le code vérifie `utilsByModule.shouldSyncLedger(moduleSettings, pageUid)`. Si nécessaire, il appelle `rebuildLedgerFromJournals()` puis envoie `sendLedgerPageChangedMessage()`. Une édition dans un journal peut donc modifier indirectement la page ledger.

## RTT

Chaque commande émet un message précis, par exemple `team_module_row_input_options_changed`, `team_module_row_removed`, `dynamic_dropdown_data_changed` ou `team_module_input_formula_changed`. Les gros objets ne sont pas toujours envoyés directement : le message `get_data` peut demander au client de relire seulement un chemin d'entité.

## Côté Unity

`TeamByModuleController` envoie ces commandes, reçoit les messages et met à jour `TeamModuleTypeData`. Les classes `DataDrivenTableControl`, `DataDrivenRowControl`, `DataDrivenSectionControl` et les contrôles d'input construisent l'interface à partir des données statiques + runtime.

## Ajouter une Custom Command By Module de A à Z

### 1. Définir commandUid et payload

Le commandUid doit être stable et décrire l'action, pas le contrôle UI qui la déclenche. La payload contient généralement `teamUid`, `actModuleUid`, `moduleType` et les données minimales de l'opération.

### 2. Ajouter le wrapper game-specific si nécessaire

Acc1/Acc2 passent par leur `<game>_custom_command`, qui délègue au routeur By Module commun.

### 3. Brancher executeCommandByModule()

Ajouter le nouveau cas dans `utils_custom_command_by_module.js`. Extraire la logique complexe dans un helper séparé si elle dépasse une opération simple de page.

### 4. Modifier le Team Module

Lire le bon `teamsModules`, sélectionner `moduleTypesData[moduleType]`, valider que la page/row/section visée existe puis appliquer la mutation.

### 5. Recalculer les dépendances

Si l'action touche un journal/formule/dropdown/ledger, exécuter les rebuilds nécessaires avant la sauvegarde. Ne pas compter sur l'UI pour recomposer un état dérivé.

### 6. Sauvegarder et envoyer RTT

Choisir un message précis dans `MessageTypeByModule`. Pour une grosse structure, préférer un refresh ciblé par chemin plutôt qu'une payload massive.

### 7. Ajouter le handler Unity

`TeamByModuleController` doit mettre à jour la structure locale exactement comme le serveur. Tester avec deux étudiants dans la même équipe.

## Concurrence rows/sections

Les rows/sections dynamiques peuvent être créées avec un UID temporaire côté client puis recevoir un ObjectId serveur. Toute nouvelle commande structurelle doit gérer ce remplacement et les références de formules associées.

## Checklist custom command

- mauvais moduleType rejeté proprement;
- UID row/section inexistant géré;
- document sauvegardé avant RTT;
- ledger/formules recalculés si dépendants;
- second client synchronisé;
- relog recharge exactement le même état;
- practice et summative vérifiés.

# Système de formules

## Vue d’ensemble

Le système de formules permet à un étudiant de calculer automatiquement la valeur d'une cellule à partir d'autres cellules numériques d'un tableau de module. Une formule est une liste de références signées : chaque référence est ajoutée ou soustraite du total.

Le calcul est effectué dans Unity, tandis que la définition de la formule est persistée côté backend. La valeur calculée reste stockée dans `DataDrivenInputData.value`; la structure de la formule est stockée séparément dans `DataDrivenInputData.formula`.

## 1. Architecture générale

```mermaid
flowchart LR
USER[Étudiant] --> EDITOR[DataDrivenTableEditorControl]
EDITOR --> TABLE[DataDrivenTableControl]
TABLE --> CELL[InputCellControl]
TABLE --> FORMULA[DataDrivenInputFormulaData]
FORMULA --> REF[DataDrivenInputFormulaReferenceData]
TABLE --> CALC[Calcul + graphe de dépendances]
CALC --> VALUE[DataDrivenInputData.value]
VALUE --> CELL
TABLE --> CTRL[TeamByModuleController]
CTRL --> CMD[save_student_module_input_formula]
CMD --> BACK[Backend Node.js / BrainCloud]
BACK --> STORE[(teamsModules)]
BACK --> EVENT[team_module_input_formula_changed]
EVENT --> CTRL
CTRL --> TABLE
```

## 2. Modèle de données

Une cellule peut contenir une formule optionnelle. La formule contient une liste de références; chaque référence identifie complètement une cellule et contient son signe.

```mermaid
classDiagram
class DataDrivenInputData {
 string value
 Dictionary options
 DataDrivenInputFormulaData formula
}
class DataDrivenInputFormulaData {
 List references
 Serialize()
 Deserialize()
 Clone()
}
class DataDrivenInputFormulaReferenceData {
 string pageUid
 string tableUid
 string sectionUid
 string rowUid
 string inputUid
 int sign
}
DataDrivenInputData --> DataDrivenInputFormulaData
DataDrivenInputFormulaData --> "0..*" DataDrivenInputFormulaReferenceData
```

### Adresse complète d’une référence

Un `inputUid` seul n'est pas suffisant, puisque le même input peut exister dans plusieurs sections ou rangées. Une référence conserve donc `pageUid`, `tableUid`, `sectionUid`, `rowUid`, `inputUid` et `sign`. Le signe vaut `1` pour additionner et `-1` pour soustraire.

## 3. Création d’une formule dans l’interface

```mermaid
sequenceDiagram
actor U as Étudiant
participant E as DataDrivenTableEditorControl
participant T as DataDrivenTableControl
participant C as InputCellControl
U->>C: Sélectionne la cellule résultat
U->>E: Active le mode formule
E->>T: onFormulaModeValueChanged(true)
T->>T: signe initial = PLUS
T->>C: met les cellules compatibles en mode sélection
U->>E: choisit + ou -
E->>T: onFormulaReferenceSignChanged(sign)
U->>C: clique une cellule référence
C->>T: onSelected(inputUid)
T->>T: SelectFormulaReference(...)
T->>T: ajoute / change / retire la référence
T->>C: outline vert (+) ou rouge (-)
U->>E: désactive le mode formule / Enter
E->>T: onFormulaModeValueChanged(false)
T->>T: sauvegarde la formule
```

### Comportement lors d’un clic sur une référence

- si la référence n'existe pas, elle est ajoutée;
- si elle existe avec l'autre signe, son signe est remplacé;
- si elle existe déjà avec le même signe, elle est retirée;
- la cellule résultat ne peut pas se référencer elle-même;
- seules les cellules numériques compatibles sont sélectionnables en mode formule;
- une cellule qui dépend directement de la cellule résultat est rendue non sélectionnable afin d'éviter une dépendance circulaire directe.

Les références positives sont affichées avec un contour vert et les références négatives avec un contour rouge via `InputCellControl.SetFormulaOutline()`.

## 4. Calcul d’une formule

```mermaid
flowchart TD
F[DataDrivenInputFormulaData] --> LOOP[Pour chaque référence]
LOOP --> GET[TryGetInputData]
GET --> PARSE[Parse inputData.value en double]
PARSE -->|échec| ZERO[Valeur = 0]
PARSE --> MOD{value_modifier présent ?}
ZERO --> MOD
MOD -->|Oui| APPLY[referenceValue *= valueModifier]
MOD -->|Non| SIGN
APPLY --> SIGN[Appliquer sign +1 / -1]
SIGN --> SUM[Ajouter au total]
SUM --> LOOP
LOOP -->|fin| VALUE[Écrire le résultat dans inputData.value]
VALUE --> UI[Mettre à jour InputCellControl]
VALUE --> TOTAL[Mettre à jour total row / journal si applicable]
```

Les valeurs impossibles à parser sont traitées comme `0`. Si l'option serveur `value_modifier` est présente sur une cellule référencée, elle est appliquée avant le signe de la référence.

## 5. Propagation des dépendances

Le système construit un index inverse des dépendances dans `_formulaListenersByReference`. La clé d'une cellule est construite sous la forme `sectionUid|rowUid|inputUid`.

```mermaid
flowchart LR
A[Cellule A change] --> LOOKUP[_formulaListenersByReference A]
LOOKUP --> B[Formule B dépend de A]
LOOKUP --> C[Formule C dépend de A]
B --> RB[RecalculateFormula B]
C --> RC[RecalculateFormula C]
RB -->|valeur changée| NEXTB[Notifier les formules dépendantes de B]
RC -->|valeur changée| NEXTC[Notifier les formules dépendantes de C]
NEXTB --> D[Formule D]
NEXTC --> E[Formule E]
```

La propagation est récursive : lorsqu'une formule change de valeur, les formules qui dépendent d'elle sont recalculées à leur tour. Un `HashSet` de clés déjà visitées empêche la propagation de boucler indéfiniment pendant un même cycle de notification.

### Initialisation

Lors du chargement ou d'un refresh important, `InitializeFormulasByReference()` reconstruit entièrement l'index inverse, puis `RefreshFormulasByReferenceAndRecalculateAll()` recalcule toutes les cellules possédant une formule.

## 6. Sauvegarde et synchronisation backend

```mermaid
sequenceDiagram
participant T as DataDrivenTableControl
participant C as TeamByModuleController
participant B as Backend Node.js
participant DB as teamsModules
participant M as Messages équipe/prof
T->>C: SaveTeamModuleInputFormula(..., formula)
C->>B: custom command save_student_module_input_formula
B->>B: localise page/table/section/row/input
alt formule avec références
 B->>DB: inputData.formula = formula
else formule vide
 B->>DB: delete inputData.formula
end
B->>B: updateFormulaReferences(...)
B->>DB: saveDocumentData
B->>M: team_module_input_formula_changed
M-->>C: message temps réel
C-->>T: OnTeamModuleInputFormulaChanged(...)
T->>T: reconstruit listeners + recalcule
```

Le backend persiste uniquement la définition de la formule. Si la liste de références est vide, la propriété `formula` est supprimée de l'input.

## 7. Sections et rangées dynamiques

Une section ou une rangée temporaire peut recevoir un nouvel UID lors de sa création côté serveur. Dans ce cas, les références de formules existantes doivent suivre le nouvel identifiant.

`utilsByModule.updateFormulaReferences()` parcourt les inputs de la table, des sections et des rangées et remplace les anciens `sectionUid`/`rowUid` par les UID persistés.

Lorsqu'une rangée ou une section est supprimée, les références qui pointent vers celle-ci sont retirées. Si une formule ne possède ensuite plus aucune référence, la formule est supprimée.

## 8. Activation et tutoriel

Le système est activé par la préférence `has_formulas`. Lorsque cette option est désactivée, les contrôles de formule ne sont pas affichés.

Deux séquences de tutoriel existent : `ModuleFormulaTutorial` introduit l'activation du mode formule, et `ModuleFormulaReferencesTutorial` explique les boutons + et - et la sélection des références.

## Fichiers principaux

- `Assets/Src/UI/SimByModule/DataDrivenTableControl.cs` — logique principale, calcul et dépendances.
- `Assets/Src/UI/SimByModule/DataDrivenTableEditorControl.cs` — toggle formule et boutons + / -.
- `Assets/Src/UI/SimByModule/UI/InputCellControl.cs` — interaction cellule et contours visuels.
- `Assets/Src/Game/Data/ByModule/DataDrivenInputData.cs` — valeur calculée et formule optionnelle.
- `Assets/Src/Game/Data/ByModule/DataDrivenInputFormulaData.cs` — liste des références.
- `Assets/Src/Game/Data/ByModule/DataDrivenInputFormulaReferenceData.cs` — adresse complète et signe.
- `Assets/Src/UI/SimByModule/Controllers/TeamByModuleController.cs` — sauvegarde et réception de l'événement.
- `Assets/Src/UI/SimByModule/Tutorial/ModuleFormulaTutorial.cs` — tutoriels.
- `master/node.js/.../utils_custom_command_by_module.js` — persistance backend.
- `master/node.js/.../byModule_utils.js` — mise à jour/suppression des références lors des changements d'UID.

## Points d’attention

- Le résultat numérique est dans `value`; la formule ne remplace pas ce champ.
- Une formule doit utiliser l'adresse complète de la cellule, pas seulement `inputUid`.
- Toute modification d'UID dynamique doit mettre à jour les références persistées.
- Lorsqu'une cellule référencée change, il faut notifier les dépendances pour maintenir les formules en cascade.
- Lorsqu'une formule est modifiée depuis un autre client, l'événement temps réel doit reconstruire ses listeners avant le recalcul.

# Création du Module Type Data

## Point d'entrée

Le moteur est `_utils/by_module/byModule_utils_create_module_type_data.js`. Il fabrique les données déterministes d'un module à partir du seed, des act settings, module settings et page settings.

```mermaid
flowchart TD
IN[seed + settings] --> INIT[initialisation RNG et contexte]
INIT --> GEN[génération données métier]
GEN --> BILL[bills / partenaires / produits]
GEN --> INV[inventory]
GEN --> IMM[immobilizations]
GEN --> CON[contracts]
INIT --> PREV[contextes précédents]
PREV --> FIX[fixed pages]
PREV --> INITIAL[initial pages]
GEN --> PRE[precomputed pages]
FIX --> OUT[TeamModuleTypeData]
INITIAL --> OUT
PRE --> OUT

```

## Initial pages

`getInitialPagesData()` lit `current_module.initial_pages_data`. Une référence préfixée par `previous_` permet de réutiliser une page d'un contexte antérieur sous l'UID du contexte courant.

## Fixed et precomputed data

`utilsByModule.getFixedData()` extrait des données fixes depuis l'answer key précédente. `getPrecomputedData()` peut calculer des données attendues à l'avance à partir de l'answer key courante.

## Données cumulatives

Le moteur peut transporter entre contextes l'inventaire, les immobilisations/dépréciations, les contrats et des snapshots de pages/dropdowns. C'est ici qu'il faut chercher un bug où un nouveau module démarre avec de mauvaises valeurs héritées.

## Fichiers liés

- `byModule_utils_create_module_type_data.js`;
- `functions/byModule_utils_create_module_type_data_functions.js`;
- `byModule_utils.js`;
- `classes/InventoryProduct.js`;
- `classes/bills/*` et `BillFactory.js`;
- `acc_base/utils/acc_base_utils.js`.

## Validation

Utiliser `tests/unit/by_module_tests/create_module_type_data_tests` et, pour une reproduction complète, `testGeneralFunctionnalities(moduleUid)` dans le debugger local des Server Scripts.

## Carte interne du moteur de création

Le fichier principal est `byModule_utils_create_module_type_data.js`, avec les helpers dans `functions/byModule_utils_create_module_type_data_functions.js`. Pour debugger un module qui “naît mal”, il faut identifier à quelle étape la mauvaise valeur apparaît.

<table id="bkmrk-fonction-responsabil"><thead><tr><th>Fonction</th><th>Responsabilité</th></tr></thead><tbody><tr><td>`byModuleCreateModuleTypeData()`</td><td>Entrée publique appelée avec seed + settings.</td></tr><tr><td>`createModuleTypeData()`</td><td>Orchestration principale.</td></tr><tr><td>`initCreateModuleTypeData()`</td><td>Initialise RNG, settings, dates, static data et contexte de calcul.</td></tr><tr><td>`getContextData()`</td><td>Résout les données nécessaires au contexte courant.</td></tr><tr><td>`createInitialModuleTypeData()`</td><td>Construit les structures initiales métier.</td></tr><tr><td>`createInventory()`</td><td>Initialise l'inventaire lorsque le module utilise du stock.</td></tr><tr><td>`createBills()`</td><td>Génère les bills du contexte via settings/factories.</td></tr><tr><td>`postProcessContextAccounts()`</td><td>Finalise les comptes après génération du contexte.</td></tr><tr><td>`getInitialPagesData()`</td><td>Construit les pages initiales, y compris les références `previous_*`.</td></tr><tr><td>`initPagesData()`</td><td>Crée la structure runtime des pages/inputs.</td></tr><tr><td>`getCurrentDepreciations()`</td><td>Récupère les amortissements à propager.</td></tr><tr><td>`getCumulativeDepreciationFromYear()`</td><td>Calcule la dépréciation cumulée à transporter.</td></tr></tbody></table>

## Ajouter une nouvelle donnée générée de A à Z

1. **Définir sa source** : static data, preference, previous context ou RNG.
2. **Définir où elle vit** dans `TeamModuleTypeData`.
3. **Initialiser la valeur** dans le moteur de création au bon moment.
4. **Si elle dépend d'un contexte précédent**, ajouter la logique de copie/translation dans `getContextData()`/`getInitialPagesData()` ou l'helper spécialisé.
5. **Si elle dépend du seed**, utiliser le RNG existant; ne pas appeler `Math.random()`.
6. **Si elle alimente les bills**, la créer avant `createBills()`.
7. **Si elle alimente l'Answer Key**, vérifier que le moteur de correction retrouve exactement la même structure.
8. **Ajouter la désérialisation Unity** si le client doit la lire.
9. **Ajouter une migration teamsModules** si des documents existants doivent recevoir le nouveau champ.
10. **Ajouter un test** dans `create_module_type_data_tests`.

## Déterminisme : règle absolue

Le seed sert à pouvoir recréer les mêmes données, Answer Key et résultats. Une nouvelle branche de génération ne doit pas introduire un appel aléatoire hors du RNG contrôlé ni changer silencieusement l'ordre des tirages d'un module existant sans mesurer l'impact.

## Contextes précédents

Les champs `previous_*`, `savedPagesDataByContextUids`, `savedDynamicDropdownsDataByContextUids`, inventory, immobilizations et contracts peuvent traverser les contextes. Avant de créer une nouvelle donnée “cumulative”, définir explicitement :

- est-elle copiée telle quelle ?
- recalculée ?
- transformée vers l'UID du nouveau contexte ?
- ignorée en practice/summative ?

## Debug pas à pas

1. Fixer un seed reproductible.
2. Logger/inspecter les settings résolus.
3. Comparer les données seedées avant bills.
4. Comparer après `createBills()`.
5. Inspecter inventory/immobilizations/contracts.
6. Inspecter `initialPagesData` et dynamic dropdowns.
7. Seulement ensuite lancer Answer Key.

Le helper `testGeneralFunctionnalities(moduleUid)` du debugger local est idéal pour rejouer cette chaîne sans passer par Unity.

# Pipeline des Bills — architecture et ajout d’un nouveau type

## Comment ça fonctionne

Le système de bills traverse **la configuration, le backend Node.js/BrainCloud, la sérialisation, les données Unity et l'UI**. Ajouter un nouveau bill seulement dans un de ces endroits produit typiquement un bill qui n'est jamais généré, qui ne peut pas être désérialisé, ou qui s'affiche avec le mauvais template.

Cette page décrit le pipeline complet et sert surtout de checklist lorsqu'un nouveau type de bill est ajouté.

## 1. Architecture complète

```mermaid
flowchart LR
    DEF[bills_settings_definitions.json] --> WIZ[Configuration module / wizard]
    MOD[modules_settings_definitions.json] --> WIZ
    WIZ --> CFG[bills_type_data + bills_category_settings]

    CFG --> CREATE["createBills()"]
    CREATE --> CTX[BillCreationContext]
    CTX --> FACTORY[BillFactory.BILL_MAPPING]
    FACTORY --> CLASS[Classe Bill JS concrète]
    CLASS --> BCTX[BillContext]
    BCTX --> SERIAL[Serialized billsData + billsOrder + partners]

    SERIAL --> TMTD[Unity TeamModuleTypeData]
    TMTD --> BILL[Bill.cs]
    BILL --> DATA[BillDataBase.CreateBillData]
    DATA --> WINDOW[BillWindowControl]
    WINDOW --> TEMPLATE[BillTemplateType / BillControlBase]
    TEMPLATE --> USER[Bill affiché à l'utilisateur]

```

## 2. Phase A — configuration

La configuration d'un bill commence dans `bills_settings_definitions.json`. Chaque clé représente un bill configurable et décrit notamment son `category`, son `num_bills`, ses conditions de visibilité et ses champs supplémentaires.

`modules_settings_definitions.json` choisit ensuite quelles definitions de bills sont disponibles pour chaque type de module. Les valeurs réellement choisies finissent dans les settings du module sous `bills_type_data`. Les options communes à une famille sont dans `bills_category_settings`.

```mermaid
flowchart TD
    BSD[bills_settings_definitions.json] --> BILLDEF[Definition du type]
    MSD[modules_settings_definitions.json] --> EXPOSE[Expose le type dans le module]
    BILLDEF --> WIZ[BillsDataWizardPageControl]
    EXPOSE --> WIZ
    WIZ --> BTD[bills_type_data]
    WIZ --> BCS[bills_category_settings]
    BTD --> SERVER[Backend de génération]
    BCS --> SERVER

```

## 3. Phase B — génération backend

Le point de départ serveur est `createBills()` dans `byModule_utils_create_module_type_data_functions.js`. Il initialise `BillCreationContext`, regroupe la configuration par `type`, applique les catégories aléatoires, puis génère les types dans un ordre déterministe.

### Ordre de calcul

`BillCreationContext._billTypeComputingOrder` n'est pas décoratif. Plusieurs bills dépendent de bills créés avant eux : checks, credit notes, reports, notes receivable, régularisations, etc. Un nouveau type qui dépend d'un autre doit donc être placé **après sa dépendance**.

```mermaid
sequenceDiagram
    participant C as createBills()
    participant BC as BillCreationContext
    participant F as BillFactory
    participant B as Classe Bill
    participant X as BillContext

    C->>BC: init(random, settings, context, dates)
    C->>C: groupe bills_type_data par type
    loop _billTypeComputingOrder
        C->>F: createBills(billTypeData)
        F->>F: BILL_MAPPING[type]
        F->>B: new ConcreteBill(...)
        B-->>BC: addBill(uid, bill)
    end
    C->>X: init(... bills générés ...)
    X->>X: post-processing / tri / sérialisation
    X-->>C: billsData + billsOrder + partners + taxes

```

### Factory backend

`BillFactory.BILL_MAPPING` est le dispatch principal `string type → fonction de création`. Une entrée peut pointer directement vers une fonction ou passer par `conditionallyCreateBills(...)` pour respecter une feature flag/préférence comme employés, taxes, banking, immobilisations ou forme juridique.

Les fonctions de création choisissent les partenaires, dates, produits/services, montants et objets dépendants, construisent la classe de bill puis appellent `BillCreationContext.addBill()`.

## 4. Phase C — classes et sérialisation serveur

Les classes serveur se trouvent sous :

`master/node.js/backend/braincloud/server_scripts/_utils/by_module/classes/bills/`

Il existe des classes de base et des familles spécialisées : checks, reports, contracts, immobilizations, etc. Une classe concrète est nécessaire lorsqu'un type possède une structure, un calcul ou des références spécifiques qui ne peuvent pas être représentés par une classe existante.

```mermaid
flowchart TD
    BASE[BillBase JS] --> EXIST{Structure existante suffisante ?}
    EXIST -->|Oui| REUSE[Réutiliser classe existante]
    EXIST -->|Non| NEW[Créer ConcreteBill.js]
    NEW --> SER["serialize()"]
    NEW --> DES["deserialize()"]
    NEW --> CALC[calculs / références spécifiques]
    REUSE --> CTX[BillContext]
    SER --> CTX
    CTX --> JSON[bills.billsData]

```

`BillContext._deserializeBills()` utilise `utilsByModule.getBillClassFromType()` pour reconstruire les bonnes classes à partir de JSON déjà sérialisé. C'est donc un **deuxième mapping backend** à ne pas oublier lorsqu'un nouveau type nécessite une classe.

## 5. Phase D — chargement dans Unity

`TeamModuleTypeData.Deserialize()` charge d'abord les partenaires, services, dépenses, contrats, immobilisations, taxes et autres dépendances. Ensuite il parcourt `bills.billsData` et construit chaque `Bill`.

`Bill.Deserialize()` lit le `billType`, puis appelle `BillDataBase.CreateBillData(billType)` afin de créer la bonne structure de données Unity avant de désérialiser `billData`.

```mermaid
sequenceDiagram
    participant JSON as Team module JSON
    participant T as TeamModuleTypeData
    participant B as Bill
    participant F as BillDataBase.CreateBillData
    participant D as Concrete BillData

    JSON->>T: billsData + partners + dépendances
    T->>B: new Bill + Deserialize(...)
    B->>B: BillBase.Deserialize billType
    B->>F: CreateBillData(billType)
    F-->>B: instance Concrete BillData
    B->>D: Deserialize(billData, dependencies...)
    B-->>T: Bill complet
    T->>T: bills[uid] + billsOrder

```

## 6. Phase E — affichage Unity

`BillWindowControl.GetBillTemplateTypeFromBillType()` choisit le template UI à utiliser. Plusieurs types partagent le même template : Sales, Check, OperatingCost, Memo, Letter, Payslip, CreditNote, etc.

Le template est ensuite alloué depuis un pool et reçoit le bill via `BillControlBase.SetData()`. Les contrôles spécialisés castent le `billData` lorsque des champs spécifiques doivent être affichés.

```mermaid
flowchart LR
    B[Bill + BillData] --> MAP[BillWindowControl.GetBillTemplateTypeFromBillType]
    MAP --> SALES[Sales]
    MAP --> CHECK[Check]
    MAP --> OP[OperatingCost]
    MAP --> LETTER[Letter]
    MAP --> MEMO[Memo]
    MAP --> REPORT[Report spécialisé]
    SALES --> SET[BillControlBase.SetData]
    CHECK --> SET
    OP --> SET
    LETTER --> SET
    MEMO --> SET
    REPORT --> SET
    SET --> UI[UI finale]

```

## 7. État « finished »

Le fait qu'un étudiant ait terminé de traiter un bill est séparé du bill lui-même. Unity conserve `finishedBills` dans `TeamModuleTypeData`. `BillWindowControl` permet de modifier l'état et le backend persiste la liste via la custom command gérée par `saveTeamModuleFinishedBills()`.

## 8. Ajouter un nouveau type de bill — checklist obligatoire

Exemple : ajout d'un type serveur `new_bill`.

<table id="bkmrk-%23-fichier-%2F-zone-mod"><thead><tr><th>\#</th><th>Fichier / zone</th><th>Modification</th><th>Obligatoire ?</th></tr></thead><tbody><tr><td>1</td><td>`Assets/.../data_byModule/bills_settings_definitions.json`</td><td>Créer la definition `new_bill` avec category, num\_bills, visibilité et champs nécessaires.</td><td>Oui si le bill est configurable/généré par settings.</td></tr><tr><td>2</td><td>`modules_settings_definitions.json`</td><td>Ajouter `new_bill` dans la liste `bills` des types de modules qui doivent pouvoir le configurer.</td><td>Oui pour l'exposer dans le wizard concerné.</td></tr><tr><td>3</td><td>`BillCreationContext.js`</td><td>Ajouter `new_bill` dans `_billTypeComputingOrder`, à l'endroit correct selon ses dépendances.</td><td>Oui pour un bill généré par le pipeline standard.</td></tr><tr><td>4</td><td>`BillFactory.js`</td><td>Ajouter `BILL_MAPPING["new_bill"]` et implémenter/réutiliser la fonction de création.</td><td>Oui.</td></tr><tr><td>5</td><td>`classes/bills/.../*.js`</td><td>Créer une classe JS si le bill ne peut pas réutiliser une classe existante; implémenter sérialisation/désérialisation et logique spécifique.</td><td>Conditionnel.</td></tr><tr><td>6</td><td>`BillFactory.js` includes</td><td>Inclure la nouvelle classe serveur si une nouvelle classe est créée.</td><td>Conditionnel.</td></tr><tr><td>7</td><td>`byModule_utils.js → getBillClassFromType()`</td><td>Mapper `new_bill` vers la classe JS utilisée pour désérialiser les bills existants/contextes.</td><td>Oui si nouvelle classe/type sérialisé.</td></tr><tr><td>8</td><td>`Assets/Src/Game/Data/ByModule/BillBase.cs`</td><td>Ajouter le nom string, l'enum `BillType`, `GetBillNameFromType()`, `GetBillTypeFromName()` et vérifier les switches de taxes/partenaires/type simplifié.</td><td>Oui.</td></tr><tr><td>9</td><td>`Assets/Src/UI/SimByModule/Data/Runtime/BillDataBase.cs`</td><td>Ajouter le mapping dans `CreateBillData()` vers une classe existante ou une nouvelle classe `BillData...`.</td><td>Oui.</td></tr><tr><td>10</td><td>`Assets/Src/UI/SimByModule/Data/Runtime/BillData*.cs`</td><td>Créer la classe data Unity si le JSON `billData` a une forme ou des calculs propres.</td><td>Conditionnel.</td></tr><tr><td>11</td><td>`Assets/Src/UI/SimByModule/UI/BillWindowControl.cs`</td><td>Mapper le nouveau `BillType` vers le bon `BillTemplateType`.</td><td>Oui.</td></tr><tr><td>12</td><td>`BillControlBase.cs` / contrôle spécialisé</td><td>Vérifier titre, sender/receiver, taxes, total et raw data. Créer/modifier un contrôle si le template existant ne suffit pas.</td><td>Conditionnel mais à vérifier systématiquement.</td></tr><tr><td>13</td><td>Prefab/template Unity du bill</td><td>Créer ou brancher un prefab si un nouveau `BillTemplateType` ou une mise en page spécifique est nécessaire.</td><td>Conditionnel.</td></tr><tr><td>14</td><td>Textes/localisation</td><td>Ajouter les clés utilisées par `GetSimplifiedBillTypeName()`, titre, memo, descriptions ou labels spécialisés.</td><td>Selon UI.</td></tr><tr><td>15</td><td>Accounting switches / Utils</td><td>Rechercher le nouveau type dans les règles de taxes, partenaires, journaux, inventory, regularizations, reports et autres traitements.</td><td>À auditer systématiquement.</td></tr></tbody></table>

## 9. Fichiers critiques — ne pas oublier

### Backend Node.js / BrainCloud

- `backend/braincloud/server_scripts/_utils/by_module/functions/byModule_utils_create_module_type_data_functions.js` — orchestration `createBills()`.
- `backend/braincloud/server_scripts/_utils/by_module/contexts/BillCreationContext.js` — contexte global, ordre de calcul, partenaires/dates/dépendances.
- `backend/braincloud/server_scripts/_utils/by_module/factories/BillFactory.js` — mapping type → création et includes des classes.
- `backend/braincloud/server_scripts/_utils/by_module/contexts/BillContext.js` — post-processing, désérialisation, ordre et sérialisation finale.
- `backend/braincloud/server_scripts/_utils/by_module/byModule_utils.js` — `getBillClassFromType()` et plusieurs règles Accounting transversales.
- `backend/braincloud/server_scripts/_utils/by_module/classes/bills/` — implémentations concrètes.
- `backend/braincloud/server_scripts/_utils/command/utils_custom_command_by_module.js` — actions runtime persistées, notamment `finishedBills` et autres mutations de module.

### Unity / Game

- `Assets/Src/Game/Data/ByModule/BillBase.cs` — enum et conversion string/type, taxes et helpers communs.
- `Assets/Src/Game/Data/ByModule/TeamModuleTypeData.cs` — désérialisation du payload complet de bills.
- `Assets/Src/UI/SimByModule/Data/Runtime/Bill.cs` — objet runtime principal.
- `Assets/Src/UI/SimByModule/Data/Runtime/BillDataBase.cs` — factory du `billData`.
- `Assets/Src/UI/SimByModule/Data/Runtime/BillData*.cs` — structures spécifiques.
- `Assets/Src/UI/SimByModule/UI/BillWindowControl.cs` — sélection du template et affichage/finalisation.
- `Assets/Src/UI/SimByModule/UI/BillControlBase.cs` — affichage commun, taxes/total, titres et raw data.
- `Assets/Src/UI/SimByModule/UI/*BillControl.cs` — contrôles spécialisés.
- `Assets/Src/Game/Data/ByModule/ModuleSettingsBase.cs` — settings runtime des bills.
- `Assets/Src/UI/SimByModule/Data/BillsDataWizardPageControl.cs` — configuration dans l'éditeur.

### Configuration

- `Assets/GameSpecific/byModule/Resources/data_byModule/bills_settings_definitions.json`
- `Assets/GameSpecific/byModule/Resources/data_byModule/bills_category_settings_definitions.json`
- `Assets/GameSpecific/byModule/Resources/data_byModule/modules_settings_definitions.json`
- `Assets/GameSpecific/byModule/Resources/data_byModule/modules_settings.json` pour les defaults applicables.
- `accounts.json`, `taxes.json`, `interests.json`, `immobilizations.json`, `contract_regularizations.json` lorsque le nouveau bill dépend de ces domaines.

## 10. Audit obligatoire après ajout

Après avoir ajouté le string du nouveau type, faire une recherche globale sur les endroits qui switchent sur les bill types. Le code actuel contient plusieurs décisions distribuées qui ne passent pas toutes par une factory unique.

```
git grep -n "new_bill" master/game master/node.js

git grep -n "BillType.eNewBill" master/game

git grep -n "switch.*billType|switch.*bill.billType" master/game master/node.js
```

Les hotspots les plus importants sont : taxes, sélection du partenaire fiscal, type simplifié/titre, mapping du template, génération d'entrées comptables, stock/inventaire, reports/régularisations et dépendances entre bills.

## 11. Test mental minimal d’un nouveau bill

```mermaid
flowchart TD
    CFG[Le wizard peut le configurer ?] --> GEN[Le serveur le génère ?]
    GEN --> SER[Il se serialize puis se deserialize ?]
    SER --> UNITY[Unity reconnait son BillType ?]
    UNITY --> DATA[CreateBillData retourne le bon data ?]
    DATA --> UI[BillWindow choisit le bon template ?]
    UI --> NUM[Totaux/taxes/entries corrects ?]
    NUM --> STATE[Finished state et reload corrects ?]
    STATE --> MULTI[Reload / autre client / contexte précédent correct ?]

```

Si une seule étape échoue, le nouveau bill n'est pas réellement intégré de bout en bout.

# Inventaire et stocks

## Modèle serveur

`_utils/by_module/classes/InventoryProduct.js` représente un produit d'inventaire. Il gère les entrées de stock, achats, ventes, méthode FIFO/non-FIFO et post-traitement selon le type de stock.

## Fonctions structurantes

- `deserialize()` / `serialize()`;
- `buyItem(quantity, price, billUid)`;
- `sellItem(quantity, deleteEntry)`;
- `addInventoryEntry(...)`;
- `initializeInventoryProduct(...)`;
- `postProcessInventoryEntries(...)`;
- `getSellReport(quantity)`;
- helpers de recherche/déduction par bill ou balance.

## Modèle Unity

`Assets/Src/UI/SimByModule/Data/Runtime/InventoryProduct.cs` désérialise `isFIFO`, `sellPrice`, `numDecimals` et la liste `InventoryEntries`. Il expose notamment le prix de vente et le coût d'achat courant.

## Flux

```mermaid
flowchart LR
STATIC[products / module settings] --> CREATE[Create Module Type Data]
CREATE --> INV[InventoryProduct]
BILL[Purchase/Sale Bills] --> INV
INV --> ENTRIES[Inventory entries]
ENTRIES --> COST[Cost of goods / stock]
COST --> REPORT[End Stock of Goods Report]
COST --> AK[Answer Key / accounts]

```

## Permanent vs periodic stock

Les constantes By Module distinguent `permanent_stock` et `periodic_stock`. Les préférences de module et les traitements de fin de période déterminent comment l'inventaire influence les comptes et rapports.

## Report de fin de stock

Le serveur possède `EndStockOfGoodsReportBill`, qui conserve les variations de stock. Côté Unity, `BillDataEndStockOfGoodsReport` représente ces données.

## Tests

`tests/unit/by_module_tests/classes_tests/inventory.test.js` couvre la classe d'inventaire. Les changements qui touchent l'inventaire via les bills doivent aussi passer les tests bills + answer key.

## Modifier une règle d’inventaire ou ajouter un comportement stock

Une règle de stock peut impacter génération, bills, account state, reports, Answer Key et Grading. Toujours tracer le cycle complet avant de modifier `InventoryProduct`.

### Flux à respecter

```mermaid
flowchart LR
CFG[stock_type / settings] --> CREATE[createInventory]
CREATE --> INV[InventoryProduct]
BILLS[Purchase/Sale Bills] --> INV
INV --> COGS[Cost / balances]
COGS --> REPORT[EndStockOfGoodsReport]
COGS --> ACC[Account State]
ACC --> AK[Answer Key]
AK --> G[Grading]

```

### Permanent vs periodic

`stock_type` distingue `permanent_stock` et `periodic_stock`. Ne pas ajouter une branche métier qui ignore ce setting : les comptes et la régularisation de fin de période ne se comportent pas de la même façon.

### Ajouter/modifier une méthode de consommation

1. Modifier `InventoryProduct.sellItem()`/`postProcessInventoryEntries()` selon le besoin.
2. Vérifier FIFO/non-FIFO.
3. Vérifier quantités partielles et stock insuffisant.
4. Vérifier que `getSellReport()` retourne encore les coûts attendus.
5. Mettre à jour les bills/reports qui consomment ces données.
6. Vérifier l'account state et les écritures de stock.
7. Ajouter des tests dans `classes_tests/inventory.test.js`.

## Cas limites à tester

- stock initial = 0;
- plusieurs achats à prix différents;
- vente traversant plusieurs lots FIFO;
- quantités décimales selon `numDecimals`;
- periodic stock avec régularisation de fin;
- contextes suivants qui reprennent l'inventaire précédent;
- reopen/restart module.

# Immobilisations et amortissement

## Responsabilité

Les immobilisations couvrent la génération des achats d'immobilisations, leur durée de vie, leur mode d'amortissement, les rapports d'immobilisations, les taxes associées et les écritures de régularisation.

## Static data

La configuration principale se trouve dans `immobilizations.json` et les settings/modules qui activent les préférences liées aux immobilisations. La page **Configuration Accounting — guide des JSON** décrit la source de configuration.

## Serveur

- `classes/bills/immobilizations/ImmobilizationsBill.js` : bill d'acquisition;
- `ImmobilizationsTaxesBill.js` : taxes municipales/scolaires ou autre taxe configurée;
- `reports/ImmobilizationsReportBill.js` : rapport basé sur les immobilisations référencées;
- `utilsByModule.getImmobilizationDepreciation()` : calcul d'amortissement selon le type d'unité;
- `BillCreationContext._usedImmobilizationsByType` : contexte utilisé pendant la génération.

## Modèles Unity

- `ImmobilizationData`;
- `SeededImmobilization` et `SeededImmobilizationPlus`;
- `BillDataImmobilization`;
- `ImmobilizationBillEntry`;
- `ImmobilizationTaxe` et `ImmobilizationTaxesBillEntry`.

## UI

Les rapports possèdent des contrôles dédiés : `ImmobilizationsReportControl`, variante Plus, rapports degressive/by usage et `ImmobilizationsTaxesReportControl`. Le contrôle détecte notamment le type d'amortissement pour adapter le contenu affiché.

```mermaid
flowchart LR
CFG[immobilizations.json] --> CREATE[Create Module Type Data]
CREATE --> SEED[SeededImmobilization]
SEED --> BILL[ImmobilizationsBill]
BILL --> ACC[Accounts / Journal]
SEED --> DEP[Depreciation calculation]
DEP --> REPORT[Immobilizations Report]
REPORT --> REG[Regularization entries]

```

## Points d'attention

- Le type d'immobilisation détermine le compte impacté.
- Le calcul dépend des dates fiscales de l'activité.
- Les contextes suivants peuvent réutiliser les immobilisations précédentes.
- Un changement doit être testé dans création des données, answer key et grading.

## Ajouter un type d’immobilisation ou une méthode d’amortissement

### Nouveau type d'immobilisation

1. Ajouter/configurer l'entrée dans `immobilizations.json`.
2. Ajouter les comptes nécessaires dans le plan comptable si le type crée de nouveaux UIDs : actif, depreciation, cumulative depreciation, etc.
3. Vérifier la génération dans Create Module Type Data et `ImmobilizationsBill`.
4. Vérifier `ImmobilizationsReportBill` et les données Unity `SeededImmobilization*`.
5. Vérifier le report UI correspondant.
6. Vérifier les relations d'Answer Key et Grading qui construisent les écritures d'amortissement.
7. Tester le passage au contexte suivant et la dépréciation cumulative.

### Nouvelle méthode d'amortissement

Le calcul central est `utilsByModule.getImmobilizationDepreciation()`. Les branches existantes sont `by_usage_km`, `linear` et `degressive`.

1. Ajouter le nouvel `unit_type` dans le calcul central.
2. Définir précisément l'unité stockée dans `total_units_used`.
3. Mettre à jour la propagation entre contextes dans Create Module Type Data.
4. Adapter le report Unity s'il affiche des colonnes différentes.
5. Vérifier Answer Key et Grading, qui appellent tous deux `getImmobilizationDepreciation()`.
6. Tester acquisition avant, pendant et près de la fin de l'exercice.

## Dates fiscales

Le calcul utilise `actSettings.year`, `startFiscalDateObj` et `endFiscalDateObj`. Un bug “un mois de trop/de moins” doit être reproduit avec les timestamps exacts et les bornes fiscales, pas uniquement avec le prix de l'actif.

## Debug

Comparer dans l'ordre : seeded immobilization → bill entry → résultat de `getImmobilizationDepreciation()` → report → account relation → Answer Key → Grading. Cela localise rapidement si l'erreur vient de la génération ou de la correction.

# Contrats

## Responsabilité

Les contrats Accounting sont des données métier persistées/générées qui servent à créer des bills et rapports de contrat, puis à calculer les montants payés/reçus d'avance ou restant à payer/recevoir en fin de période.

## Serveur

- `classes/bills/contracts/ContractBill.js` : création du contrat/bill;
- `classes/bills/reports/ContractsReportBill.js` : rapport des contrats utilisés;
- `BillCreationContext._usedContractsByType` : registre des contrats générés;
- `utilsByModule.getContractPaidOrReceivedInAdvance()`;
- `utilsByModule.getContractToPayOrReceive()`.

## Types de contrats

Le code traite notamment des contrats fournisseur, client, investissement et prêt court terme. Le type de contrat est stocké dans `billData.contractType` et influence taxes, comptes et sens de l'écriture.

## Modèle Unity

`SeededContract` contient date, `contractRegularizationData`, liste de `SeededContractBillEntry` et unités. Il peut calculer son subtotal pour un type de bill donné.

## Relation avec les produits/services

`ContractBill` réutilise les classes de bill data produit ou service pour stocker les entrées du contrat. Le contrat conserve aussi l'UID de régularisation correspondant.

```mermaid
flowchart LR
CFG[Contract config] --> CB[ContractBill]
CB --> USED[_usedContractsByType]
USED --> REPORT[ContractsReportBill]
USED --> REG[Contract Regularization]
REG --> GJ[Journal / Accounts]

```

## Debug

Pour un montant de contrat incorrect, inspecter d'abord le bill et ses entries, puis `contractType`, les dates fiscales, `contract_regularization_uid` et enfin le calcul de régularisation.

## Ajouter un type de contrat ou une règle de contrat

1. Définir le type dans la configuration/static data concernée.
2. Définir si le contrat représente fournisseur, client, investissement, prêt ou une nouvelle catégorie réellement différente.
3. Vérifier `ContractBill` et le type de `BillData` utilisé pour ses entries.
4. Enregistrer le contrat dans `BillCreationContext._usedContractsByType`.
5. Ajouter/adapter `ContractsReportBill`.
6. Définir le `contract_regularization_uid` correspondant.
7. Vérifier les calculs `getContractPaidOrReceivedInAdvance()` et `getContractToPayOrReceive()`.
8. Mettre à jour les relations de comptes Answer Key/Grading.
9. Ajouter le rendu/report Unity si les colonnes diffèrent.

## Dates et durée

Les calculs utilisent la date du contrat, le nombre d'unités/années et les bornes fiscales. Tester un contrat :

- commencé avant l'exercice;
- commencé pendant l'exercice;
- qui se termine pendant l'exercice;
- qui continue après la fin de l'exercice;
- sans fin explicite lorsque le helper accepte `-1`.

## Debug

Si un contrat apparaît dans le report mais régularise mal, le problème est probablement après `ContractBill`. Si le contrat lui-même manque, commencer par la génération/bill factory et `_usedContractsByType`.

# Régularisations de fin de période

## Rôle

Les régularisations transforment les données accumulées pendant le module en écritures de fin de période. Elles concernent notamment amortissements, contrats, stocks et comptes explicitement configurés pour être régularisés.

## Préférences structurantes

Les constantes By Module exposent plusieurs flags :

- `uses_regularizations`;
- `uses_new_regularizations`;
- `regularization_accounts_count`;
- `affected_regularizations_reports`;
- `uses_stock_regularizations`;
- `has_new_immobilizations`;
- `has_new_contracts`.

## Calcul générique

`utilsByModule.getAmountToRegularize()` calcule la portion à régulariser en fonction du montant, de la date d'acquisition/début, de la fin de vie/contrat et des bornes de l'exercice. Les helpers spécialisés contrats et immobilisations s'appuient sur cette logique temporelle.

## Static data

`contract_regularizations.json` fournit les types de régularisation de contrats. D'autres comportements sont pilotés par les settings de modules/pages et les comptes définis dans les données Accounting.

## Runtime Unity

- `RegularizationDataBase`;
- `RegularizationBillEntryBase`;
- `ContractRegularizationBillEntry`;
- `BillDataContractRegularizationsReport`.

## UI

- `ContractRegularizationsReportControl`;
- `ContractWithInterestRegularizationsReportControl`;
- rows spécialisées correspondantes.

```mermaid
flowchart TD
DATA[Immobilizations / Contracts / Stock] --> PERIOD[End of fiscal period]
PERIOD --> CALC[Regularization calculations]
CALC --> REPORT[Regularization Reports]
CALC --> ENTRY[Journal entries]
ENTRY --> LEDGER[Ledger]
LEDGER --> FS[Financial statements]

```

## Pourquoi cette page est séparée

Une régularisation n'est ni “un contrat”, ni “une immobilisation”, ni “un bill” au sens architectural. C'est une étape transversale de fin de période qui consomme ces domaines et produit des écritures.

## Ajouter une nouvelle régularisation

### 1. Identifier la source

Une régularisation doit venir d'une donnée métier précise : contrat, immobilisation, taxe, stock ou compte explicitement configuré. Ne pas créer une écriture “magique” directement dans l'état financier.

### 2. Définir le calcul

Réutiliser `getAmountToRegularize()` lorsqu'il s'agit de répartir un montant sur une période. Le helper prend acquisition, fin de vie éventuelle, exercice fiscal, précision et sens `inAdvance`.

### 3. Définir les comptes

Ajouter les relations debit/credit dans la couche Account State appropriée. L'ordre compte : `getRegularizationsAccountsOrdersAndRelations()` est exécuté avant certaines étapes de closing/états.

### 4. Ajouter le report si l'utilisateur doit voir/calculer la régularisation

Créer ou étendre la Bill/report data serveur, le modèle Unity et le contrôle UI. Configurer la page dans les JSON.

### 5. Answer Key + Grading

Le même calcul de montant doit être utilisé des deux côtés. Le grading possède des branches pouvant utiliser les régularisations saisies par l'étudiant (`useUserRegularizations`) : vérifier si la nouvelle règle appartient à ce chemin.

## Test minimal

- montant entièrement dans l'exercice;
- prorata début/fin d'exercice;
- in advance vs to pay/receive;
- arrondi selon `numDecimals`;
- impact trial balance;
- impact income statement/balance sheet;
- context suivant.

# Answer Key — génération des bonnes réponses

## Rôle

`byModule_utils_answer_key_module.js` construit les décisions correctes complètes d'un module. Ce n'est pas une simple liste de réponses : le moteur reconstruit les livres, journaux, états, formulaires et rapports nécessaires pour obtenir l'état comptable attendu.

## Pipeline

```mermaid
flowchart TD
MTD[Module Type Data] --> INIT[init answer key]
INIT --> PAGE[compute each module page]
PAGE --> AJ[Auxiliary journals]
PAGE --> GJ[General Journal]
AJ --> STATE[Account state]
GJ --> STATE
STATE --> LED[General Ledger]
LED --> TB[Trial Balance]
TB --> FS[Financial Statements]
PAGE --> FORMS[Tax / payroll / bank forms]
PAGE --> REG[Regularizations]
FS --> OUT[pagesData + dropdowns + finished bills]
FORMS --> OUT
REG --> OUT

```

## Calculs couverts

Le moteur contient des calculateurs spécialisés pour achats, ventes, encaissements, décaissements, payroll, petty cash, formulaires de taxes employé/employeur, subledgers fournisseur/client/stock, general journal, ledger, bank statements, trial balance, income statement, retained earnings/equity, balance sheet, accounting journal et bank reconciliation.

## Ordre des dépendances

L'ordre est critique : les journaux alimentent l'état des comptes, qui alimente ledger/trial balance, qui alimente les états financiers. Une modification d'un calcul “local” peut donc modifier plusieurs pages suivantes.

## Dropdowns dynamiques

L'answer key produit aussi des `dynamicDropdownsData` lorsque les options dépendent des données générées (partenaires, documents, relations ou lignes existantes).

## Tests

- `answer_key_module.test.js`;
- `answer_key_module_functions.test.js`;
- `petty_cash_answer_key_module.test.js`.

## Debug

Dans `server_scripts_debugging.js`, `testGeneralFunctionnalities()` enchaîne création du Module Type Data → answer key → grading → reassemble. C'est le test manuel le plus direct lorsqu'un module complet ne grade plus à 100 % avec ses bonnes réponses.

## Index détaillé des calculateurs Answer Key

`byModule_utils_answer_key_module.js` est un moteur de plusieurs milliers de lignes. Il faut le lire par **type de page**, pas du haut vers le bas.

### Orchestration

- `byModuleAnswerKeyModule()` — entrée publique;
- `answerKeyModule()` — orchestration;
- `initAnswerKeyModuleVariables()` — contexte/settings/pages;
- `computeAnswerKeyModule()` — boucle globale;
- `computeAnswerKeyModulePage(modulePageUid)` — routeur par page.

### Journaux

- `computeCorrectPurchasesJournal()`;
- `computeCorrectSalesJournal()`;
- `computeCorrectCashingsJournal()`;
- `computeCorrectWithdrawalsJournal()`;
- `computeCorrectPayrollJournal()`;
- `computeCorrectPettyCashJournal()`;
- `computeCorrectGeneralJournal()`.

### Subledgers / ledger / banque

- `computeCorrectProvidersSubledger()`;
- `computeCorrectClientsSubledger()`;
- `computeCorrectStocksSubledger()`;
- `computeCorrectLedger()`;
- `computeCorrectLedgerAutoReport()`;
- `computeCorrectLedgerForBankReconciliation()`;
- `computeCorrectBankStatement*`;
- `computeCorrectBankReconciliation()`.

### États financiers

- `computeCorrectTrialBalance()`;
- `computeCorrectStatementIncome()` / Services;
- `computeCorrectStatementRetainedEarnings()`;
- `computeCorrectStatementEquityPartnership()`;
- `computeCorrectStatementEquity()`;
- `computeCorrectBalanceSheet()`;
- `computeCorrectAccountingJournal()`.

## Ajouter une nouvelle page à l'Answer Key

1. Ajouter/configurer la page dans les JSON et l'UI — voir Configuration Accounting et UI data-driven.
2. Ajouter le `modulePageUid` au routeur `computeAnswerKeyModulePage()`.
3. Créer une fonction `computeCorrect...` qui lit uniquement les données dont elle dépend.
4. Construire la réponse via les helpers `getOrCreateTableData*`, section/row/input plutôt qu'en réinventant le format.
5. Pour une page dérivée des comptes, utiliser l'account state partagé.
6. Pour une page basée sur des bills, réutiliser `addBillAnswers()`.
7. Ajouter les options/dynamic dropdowns si la correction en dépend.
8. Ajouter le grading symétrique dans `byModule_utils_grade_module.js`.
9. Ajouter un test Answer Key + un test grading.

## Account State — cœur transversal

Trial balance et états financiers dépendent de l'état des comptes, lui-même construit en appliquant les relations dans un ordre précis :

- `getFormsAccountsRelations()`;
- `getRegularizationsAccountsOrdersAndRelations()`;
- `getClosingAccountsOrdersAndRelations()`;
- `getAccountStatesOrdersAndRelations()`;
- `updateAccount()` / `updateAccountsState()`.

Si plusieurs pages financières deviennent fausses en même temps, debugger l'account state avant chaque page individuelle.

## Guide symptôme → zone

<table id="bkmrk-sympt%C3%B4me-commencer-i"><thead><tr><th>Symptôme</th><th>Commencer ici</th></tr></thead><tbody><tr><td>Achats/ventes faux</td><td>Aux journal + bill answers.</td></tr><tr><td>Journal correct, ledger faux</td><td>`computeCorrectLedger*` / reconstruction ledger.</td></tr><tr><td>Trial balance fausse</td><td>Account state + `computeCorrectTrialBalance`.</td></tr><tr><td>Income statement / balance sheet faux ensemble</td><td>Account state / closing / regularizations.</td></tr><tr><td>Bank reconciliation fausse</td><td>Bank statement + ledger BR + `computeCorrectBankReconciliation`.</td></tr><tr><td>Row/dropdown manquant</td><td>Builders structure + dynamic dropdowns + bill answers.</td></tr></tbody></table>

## Validation forte

Une Answer Key correcte doit, lorsqu'elle est utilisée comme décisions étudiant, grader à 100 % sur toutes les pages évaluées. `testGeneralFunctionnalities()` exécute exactement ce chemin et doit être utilisé comme test de cohérence globale après une modification importante.

# Chaîne comptable — journaux, ledger et états financiers

## Vue d'ensemble

Le moteur Accounting est construit en chaîne. Les journaux représentent les écritures source, le ledger est dérivé de ces journaux, puis la trial balance et les états financiers consomment les soldes produits.

```mermaid
flowchart LR
B[Bills / Forms] --> J[General + Auxiliary Journals]
J --> L[General Ledger]
L --> TB[Trial Balance]
TB --> IS[Income Statement]
TB --> EQ[Statement of Equity / Retained Earnings]
TB --> BS[Balance Sheet]
J --> BR[Bank Reconciliation]

```

## Synchronisation automatique du ledger

`utilsByModule.shouldSyncLedger()` active la synchronisation lorsque la préférence `AUTO_REPORT_TO_LEDGER` est active et que la page modifiée fait partie des sources reconnues.

Les pages sources incluent notamment :

- `general_journal`;
- `purchases_journal`;
- `sales_journal`;
- `withdrawals_journal`;
- `cashings_journal`;
- `payroll_journal`.

## rebuildLedgerFromJournals()

Le serveur copie d'abord le Module Type Data et évalue les formules, efface uniquement la page dérivée `ledger`, puis rejoue les écritures des journaux en respectant `sectionsIndex` et `rowsIndex`. Les journaux source ne sont pas modifiés.

Les écritures du general journal et des journaux auxiliaires passent par des extracteurs différents avant d'être regroupées dans le ledger.

## Quand le ledger est reconstruit

Les custom commands de sauvegarde de valeur, options, formule, suppression/réordonnancement de row/section vérifient si la page impacte le ledger. Si oui, elles reconstruisent le ledger puis envoient un message RTT spécialisé.

## Answer Key

L'Answer Key utilise la même logique de reconstruction afin que la version correcte du ledger suive les mêmes règles que le ledger de l'étudiant.

## UI Unity

Les contrôles spécialisés incluent notamment `TrialBalanceControl`, `StatementIncomeControl`, `StatementEquityControl`, `BalanceSheetControl` et `BankReconciliationControl`.

## Debug d'une incohérence

1. Vérifier la bill/form source.
2. Vérifier l'écriture générée dans le journal.
3. Vérifier si `AUTO_REPORT_TO_LEDGER` est actif.
4. Inspecter le ledger reconstruit.
5. Comparer la trial balance.
6. Seulement ensuite inspecter l'état financier final.

## Ajouter une nouvelle source comptable dans la chaîne

Une nouvelle opération comptable doit être branchée à l'endroit le plus en amont possible. Si elle produit une écriture, le ledger et les états doivent la recevoir via la chaîne existante plutôt que via un patch direct sur l'état financier.

### Nouvelle écriture dans un journal

1. Définir la source : bill, form, régularisation ou saisie étudiant.
2. Définir debit/credit + comptes.
3. Ajouter la réponse attendue dans l'Answer Key du journal.
4. Ajouter le grading de l'écriture.
5. Si `AUTO_REPORT_TO_LEDGER` doit l'inclure, vérifier `shouldSyncLedger()` et l'extracteur utilisé par `rebuildLedgerFromJournals()`.
6. Vérifier trial balance et états financiers sans code spécial additionnel.

### Ne pas patcher le ledger à deux endroits

Le ledger auto-report est une donnée dérivée. Une custom command modifie le journal puis appelle le rebuild. Ajouter en parallèle une mutation manuelle du ledger crée des doublons et des divergences entre Answer Key et décisions étudiant.

## Diagnostic par invariants

<table id="bkmrk-invariant-cass%C3%A9-zone"><thead><tr><th>Invariant cassé</th><th>Zone à inspecter</th></tr></thead><tbody><tr><td>Débit ≠ crédit dans l'écriture source</td><td>Bill/form/regularization.</td></tr><tr><td>Journal bon, ledger incomplet</td><td>`shouldSyncLedger` / extracteur / rebuild.</td></tr><tr><td>Ledger bon, trial balance fausse</td><td>Account state / signed values.</td></tr><tr><td>Trial balance bonne, Income Statement faux</td><td>Mapping des comptes vers état.</td></tr><tr><td>Income Statement bon, Balance Sheet faux</td><td>closing/equity/retained earnings.</td></tr></tbody></table>

# Grading, reassemble et calcul des scores

## Trois étapes distinctes

La correction By Module n'est pas un seul calcul. Elle sépare : **grading brut**, **réassemblage/pondération**, puis **publication du résultat**.

```mermaid
flowchart LR
DEC[Décisions étudiant] --> GRADE[byModuleGradeModule]
AK[Answer Key] --> GRADE
GRADE --> RAW[scores bruts + errors + expectedValues + ignoredPageUids]
RAW --> RE[byModuleReassembleAnswer]
RE --> FINAL[scores finaux + errorScores]
FINAL --> GEN[GenerateTeamModuleResults]

```

## byModule\_utils\_grade\_module.js

`byModuleGradeModule()` initialise le contexte de grading et compare la donnée étudiante aux valeurs attendues. Le moteur route chaque `modulePageUid` vers son correcteur spécialisé et retourne :

- `scores` bruts;
- `errors` structurées;
- `expectedValues`;
- `ignoredPageUids`.

Si les formules sont actives, leurs valeurs peuvent être pré-calculées avant la comparaison.

## byModule\_utils\_reassemble\_answers.js

`byModuleReassembleAnswer()` transforme les scores bruts selon la configuration de `pages_settings`. Il charge notamment `inputs_ponderation`, `scores_ponderation`, `scores_details` et les colonnes ignorées.

Le moteur supporte des chaînes de pondération et des pénalités. Il recalcule aussi un arbre `errorScores` qui permet au frontend d'associer la perte de points aux cellules/rows concernées.

## Pages ignorées

Une page dans `ignoredPageUids` reçoit un score `-1` au réassemblage. Cela distingue une page non évaluée d'une page évaluée à 0.

## Arrondis

Le réassemblage contient une logique explicite de correction des erreurs d'arrondi autour de 0 afin que la somme des pénalités ne produise pas des scores légèrement négatifs ou des reliquats artificiels.

## Configuration

Les pondérations sont data-driven. Modifier un score n'implique donc pas nécessairement de changer le JavaScript : commencer par vérifier `pages_settings.json` et ses `scores_details`/`scores_ponderation`.

## Tests

Les tests de `grade_module_tests/` couvrent grading générique, balance sheet, income taxes et petty cash. Lorsqu'un changement d'answer key modifie une valeur attendue, exécuter également les tests de grading correspondants.

## Index détaillé du moteur de grading

`byModule_utils_grade_module.js` est encore plus volumineux que l'Answer Key. Il combine routeurs de pages, matching structurel et scoring générique des inputs/options.

### Orchestration

- `byModuleGradeModule()` — entrée publique;
- `gradeModule()` — orchestration;
- `initGradeModuleVariables()` — contexte de correction;
- `trimData()` — normalisation;
- `preComputeFormulaValues()` — calcul des formules avant comparaison;
- `computeModulePageScore()` — routeur de page.

### Correcteurs spécialisés

Le moteur possède des fonctions distinctes pour purchases/sales/withdrawals/cashings/payroll/petty cash/general journal, taxes forms, subledgers, ledger, bank reconciliation, trial balance, statements et accounting journal. Lorsqu'une seule page échoue, commencer par son correcteur spécialisé avant les helpers génériques.

## Matching structurel : pourquoi le grading n'est pas une simple égalité JSON

Le General Journal et plusieurs tableaux doivent accepter des rows dans un ordre ou une structure qui peut différer de l'Answer Key. Le moteur utilise notamment :

- `getMatchRateOfTwoAccountsAndImpactTypes()`;
- `compareAccounts()`;
- `getMatchRateForGeneralJournalSection()`;
- `getTypeAndExplanationOfSection()`;
- `billBelongsInGeneralJournal()`.

Avant de “simplifier” ce code en comparaison directe, vérifier les cas où l'étudiant peut produire une écriture équivalente mais ordonnée différemment.

## Scoring générique des inputs

- `computeInputsGoodAnswers()` prépare les valeurs attendues;
- `inputHasGoodAnswer()` applique valeurs/tolérances;
- `computeRowInputsValueScore()`, section/table/page remontent le score;
- `computeInputsOptionsScore()` corrige les options de format/choix;
- `addError()`, `addExpectedValue()`, `addAnswerScore()` alimentent les sorties.

## Ajouter le grading d'une nouvelle page

1. Déterminer si la page peut utiliser le grading générique table/inputs.
2. Sinon créer `compute<Page>Score()`.
3. Brancher la page dans `computeModulePageScore()`.
4. Réutiliser les helpers de score/tolérance/options.
5. Ajouter les `expectedValues` utiles à la correction visuelle.
6. Ajouter les erreurs avec un chemin suffisamment précis pour retrouver la cellule.
7. Vérifier les `ignored_grading_*` preferences.
8. Ajouter le score dans `pages_settings`/`scores_details` si nécessaire.
9. Tester une bonne réponse = 100 %, puis plusieurs mauvaises réponses indépendantes.

## Account State côté grading

Le grading reconstruit lui aussi l'état des comptes. Certaines fonctions reçoivent `useUserRegularizations` : cela signifie que la page doit parfois être évaluée à partir des régularisations effectivement saisies par l'étudiant plutôt qu'à partir de la bonne réponse. Ce détail est critique pour les pages dépendantes.

## Grading vs Reassemble : frontière à respecter

<table id="bkmrk-probl%C3%A8me-modifier-un"><thead><tr><th>Problème</th><th>Modifier</th></tr></thead><tbody><tr><td>Une réponse est reconnue bonne/mauvaise incorrectement</td><td>`grade_module`.</td></tr><tr><td>La bonne cellule perd le mauvais nombre de points</td><td>grading ou score detail selon le raw score.</td></tr><tr><td>Les raw scores sont bons mais le total final est faux</td><td>`reassemble_answers` / pondérations.</td></tr><tr><td>Une page ne devrait pas être évaluée</td><td>ignored pages/settings.</td></tr></tbody></table>

## Test de régression minimal

Pour chaque bug de grading : ajouter un test qui reproduit exactement la structure étudiante fautive, vérifier le score attendu, les errors et les expectedValues. Ensuite lancer la suite complète du type de page pour éviter de casser les équivalences existantes.

## Matching centralisé et ledger — septembre 2026

`matchUidsToAnswers()` centralise la priorité des identifiants stricts puis les passages fuzzy jusqu’au seuil inclusif de 0.5 pour les journaux auxiliaires, la petite caisse, les subledgers clients/fournisseurs et les écritures admissibles du ledger. Le score de matching sélectionne une réponse attendue; il ne remplace pas le scoring des cellules. Le General Journal conserve son algorithme distinct.

Voir [Fuzzy matching — logique de correspondance du grading Accounting](https://wiki.uniksim.com/books/game/page/fuzzy-matching-logique-de-correspondance-du-grading-accounting) pour les poids, seuils, exemples, limites et le changement du ledger du 30 septembre.

# Résultats, évaluations et correction de module

## Génération des résultats

`byModuleGenerateTeamModuleResults()` orchestre grading + reassemble puis persiste le résultat dans plusieurs documents.

```mermaid
sequenceDiagram
participant G as GenerateResults
participant TM as teamsModules
participant T as teams
participant E as evaluationTeam
participant RTT as RTT
G->>TM: lire moduleTypesData
G->>G: gradeModule()
G->>G: reassembleAnswer()
G->>T: teamDecisionsData.results[actModuleUid][moduleType]
G->>E: actPerf[actModuleUid][moduleType]
G->>RTT: execute_request GetTeamModuleTypeResults
RTT-->>Unity: récupérer le résultat

```

## Document teams

Le résultat complet est enregistré dans `teamDecisionsData.results[actModuleUid][moduleType]`. Il contient notamment `scores`, `errors`, `expectedValues` et `errorScores`.

## evaluationTeam

Les scores sont aussi propagés dans `evaluationTeam.actPerf`, afin d'alimenter l'évaluation globale de l'activité.

## Summative retry

Lorsque la préférence `RETRY_SUMMATIVE` est active, le code compare la nouvelle note globale à `lastSummativeGrade`. La meilleure note est conservée; sinon les anciens scores d'évaluation sont restaurés. Un message `module_graded` est envoyé pour synchroniser l'état.

## Pourquoi les résultats ne sont pas envoyés directement en RTT

Le commentaire serveur indique que le message serait trop gros. Le serveur envoie donc `execute_request` avec `GetTeamModuleTypeResults`; le client fait ensuite une commande dédiée pour récupérer la payload.

## Correction côté Unity

Les résultats alimentent notamment `CorrectedTeamModuleWindowControl`, `ModuleErrorsPopupControl`, `ModuleResultsReportItemControl` et l'évaluation By Module. `ignoreErrorFromTeamModuleResults` permet de modifier l'état de certaines erreurs après correction.

## Debug

Si l'answer key est correcte mais la note est mauvaise, inspecter dans l'ordre : résultat brut de `gradeModule` → `reassembleAnswer` → persistance dans `teams` → `evaluationTeam` → récupération côté Unity.

## Ajouter une nouvelle donnée de résultat ou de correction

1. Déterminer si la donnée appartient au **raw grading**, au **reassemble** ou au **résultat final UI**.
2. Ajouter la valeur au bon objet retourné par grading/reassemble.
3. Persister uniquement ce qui doit survivre au reload dans `teamDecisionsData.results[actModuleUid][moduleType]`.
4. Si la valeur contribue à la note globale, mettre à jour `evaluationTeam.actPerf`.
5. Ajouter la désérialisation Unity dans `ResultsByModule` ou le modèle concerné.
6. Afficher la donnée dans la correction/report sans recalculer une deuxième logique différente côté Unity.
7. Si la payload devient grosse, conserver le pattern `execute_request → GetTeamModuleTypeResults`.

## Retry summative

Toute nouvelle valeur de score doit être compatible avec le mécanisme `RETRY_SUMMATIVE`. Si la nouvelle tentative est moins bonne, le code peut restaurer l'ancienne note. Vérifier que les données auxiliaires de correction restent cohérentes avec la tentative retenue.

## Debug du pipeline résultat

1. inspecter la sortie `byModuleGradeModule()`;
2. inspecter `byModuleReassembleAnswer()`;
3. inspecter l'objet écrit dans `teams`;
4. inspecter `evaluationTeam`;
5. appeler manuellement `GetTeamModuleTypeResults`;
6. inspecter la désérialisation Unity.

# Procédure annuel pour updater les taxes (updated version 2026)

<p class="callout warning">Note: La procédure assume que les liens utilisés resteront les mêmes et que la structure des fichiers PDF ne changera pas. Si les liens sont modifiés ou si la structure des PDF change, le script devra être ajusté en conséquence.</p>

<p class="callout danger">S.V.P Veuillez tout lire avant de faire la procédure</p>

##### La mise à jour annuelle est maintenant automatisée avec le script :

<p class="callout info">node.js/exports/updateAnnualTaxData.js</p>

##### Le script utilise les sources suivantes :

- CRA / ARC T4032 pour les tables fédérales et provinciales ON, NB, PE, QC
- Revenu Québec pour les tables provinciales QC, RRQ, RQAP
- CRA / Revenu Québec pour les taux CPP, EI, GST/HST, QST

##### Les sources et valeurs configurées sont dans :

<p class="callout info">node.js/exports/annualTaxSources.json</p>

##### Comment ça fonctionne:

1. Copier manuellement le Google Sheet <span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">income\_taxes\_&lt;année précédente&gt;</span>.
2. Renommer la copie en changeant l'année précédente par l'année en cours
3. Copier l’ID du nouveau Google Sheet. ( L’ID est la partie dans l’URL entre <span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">/d/</span> et <span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">/edit</span>.)   
    Exemple: https://docs.google.com/spreadsheets/d/**190fSZq-r3K65Fb\_g1D0oTQQZv5ijtIMjXvthD8MlhwM**/edit
4. Lancer le self-test : <p class="callout info">node exports/updateAnnualTaxData.js --year 2026 --templateYear 2025 --mode full --selfTest</p>
    
    Ce test vérifie :
    
    
    - que les PDFs officiels sont accessibles
    - que les tables sont parsées correctement
    - que chaque table commence avec la ligne <span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">min\_salary = 0</span>
    - que les salaires minimums sont en ordre croissant
    - que les tables Québec sont mappées correctement (cas spécial)
    - que les zéros sont exportés au bon format pour Google Sheets
5. Lancer un dry run : <p class="callout info">node exports/updateAnnualTaxData.js --year 2026 --templateYear 2025 --mode full --dryRun --incomeTaxesSpreadsheetId &lt;SPREADSHEET\_ID&gt;</p>
    
    Le dry run ne modifie rien. Il affiche :
    
    
    - les sources utilisées
    - les fichiers qui seraient changés
    - les onglets Google Sheets qui seraient modifiés
    - les PDFs qui seraient uploadés
    - les changements de taux entre l’année précédente et la nouvelle année
6. Lancer l’export réel : <p class="callout info">node exports/updateAnnualTaxData.js --year 2026 --templateYear 2025 --mode full --apply --incomeTaxesSpreadsheetId &lt;SPREADSHEET\_ID&gt;</p>

#### **Utilisation Dans VS Code**

Les configurations mentionnées ci-dessus existent dans `<span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">.vscode/launch.json</span>` :

<p class="callout info">Annual Tax Export - Self Test  
Annual Tax Export - Dry Run  
Annual Tax Export - Run</p>

##### Lors du lancement, VS Code demande :

- `<span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">taxYear</span>` : l’année à générer, ex. <span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">2026</span>
- `<span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">templateTaxYear</span>` : l’année précédente à utiliser comme modèle, ex. <span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">2025</span>
- `<span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">incomeTaxesSpreadsheetId</span>` : l’ID du Google Sheet copié manuellement
- `<span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">annualTaxTargets</span>` : les destinations à mettre à jour
- `setDefaultYear`: si on veut changer l'année en cours ou non

##### Choix Des Destinations

Le script permet de relancer seulement une partie du processus avec <span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">--targets</span>.

**Options disponibles :**

<p class="callout info">all  
incomeTaxes  
byModule  
do  
incomeTaxes,byModule  
incomeTaxes,do  
byModule,do</p>

##### Signification :

##### `incomeTaxes`

Met à jour le Google Sheet copié <span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">income\_taxes\_&lt;année&gt;</span> :

- écrit les onglets de tables d’impôt
- renomme les tabs de l’année précédente vers la nouvelle année
- met à jour <span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">exports/exportAllData.js</span> avec le nouvel ID de sheet et les gids des tabs

##### `byModule`

Met à jour les colonnes de la nouvelle année dans le Google Sheet principal :

- <span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">taxes</span>
- <span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">income\_taxes</span>
- <span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">employer\_income\_taxes</span>
- <span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">interests</span>

##### <span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">`do`  
</span>

Upload les PDFs officiels dans DigitalOcean Spaces :

<div data-theme="dark" id="bkmrk-acc1%2F%3Cann%C3%A9e%3E%2F%3Ctheme"><div><div><div><div data-theme="dark"><div>`acc1/<année>/<theme>_<lang>`</div></div></div></div></div></div>##### Fichiers Modifiés Par Le Script

Selon les targets choisis, le script peut modifier :

<p class="callout info">game/Assets/GameSpecific/byModule/Resources/data\_byModule/income\_tax\_\*\_&lt;année&gt;.json  
game/Assets/GameSpecific/byModule/Resources/data\_byModule/taxes.json  
game/Assets/GameSpecific/byModule/Resources/data\_byModule/income\_taxes.json  
game/Assets/GameSpecific/byModule/Resources/data\_byModule/employer\_income\_taxes.json  
game/Assets/GameSpecific/byModule/Resources/data\_byModule/interests.json  
node.js/exports/exportAllData.js</p>

Si `<span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">--setDefaultYear</span>` est utilisé, il modifie aussi :

<p class="callout info">game/Assets/GameSpecific/byModule/Resources/data\_byModule/act\_settings.json  
uniksimweb/uniksim-back-end/src/node.js/server/backend\_braincloud.js</p>

`<span class="_inlineMarkdown_wo5r6_47 inline-markdown text-size-chat-sm font-mono blend bg-token-text-code-block-background rounded-sm px-1.5 py-0.5 leading-none extension:bg-token-foreground/10 electron:bg-token-list-hover-background/60">--setDefaultYear</span>` change l'année en cours utilisé dans la simulation.

<p class="callout warning">Veuillez vous référer aux étapes ci-dessous si vous décider de ne pas changer l'année tout de suite.</p>

- Modifier le champs year pour l'année par défaut voulu (`act_settings.json`)

<span style="text-decoration: line-through;">![](https://wiki.uniksim.com/uploads/images/gallery/2023-07-Jul/scaled-840-0/image-1689349217892.png)</span>

- Modifier l'année par défaut pour la création de sims démo (`backend_braincloud.js`)  
    <span style="text-decoration: line-through;">![](https://wiki.uniksim.com/uploads/images/gallery/2025-04-Apr/scaled-840-0/image-1743609472785.png)</span><span style="text-decoration: line-through;">  
    </span>
- Exporter les scripts acc1/acc2 et income\_taxes\_\*

##### ![](https://wiki.uniksim.com/uploads/images/gallery/2026-06-Jun/scaled-840-0/image-1781270967797.png)

##### ![](https://wiki.uniksim.com/uploads/images/gallery/2026-06-Jun/scaled-840-0/image-1781270980368.png)  
  


##### Finalement, il faut incrémenter la version dans version.txt et build pour que la nouvelle année en cours prenne effet.

<p class="callout info">Assets\\Resources\\data\\version.txt</p>

#####   
Diagramme qui montre comment le script fonctionne:

[![mermaid-diagram-2026-06-12-101748.png](https://wiki.uniksim.com/uploads/images/gallery/2026-06-Jun/scaled-840-0/mermaid-diagram-2026-06-12-101748.png)](https://wiki.uniksim.com/uploads/images/gallery/2026-06-Jun/mermaid-diagram-2026-06-12-101748.png)

# Contextes Accounting — cycle, héritage et données entre périodes

## Rôle des contextes Accounting

Un **contexte** représente une période/situation comptable simulée avant le `current_module`. Il permet de construire l'état initial du module courant à partir d'une ou plusieurs périodes précédentes : bills, comptes, inventaire, immobilisations, contrats, pages calculées et dynamic dropdowns.

Les contextes ne sont donc pas une simple configuration visuelle. Ils font partie du pipeline serveur de génération du `TeamModuleTypeData`.

```mermaid
flowchart LR
MS[modules_settings.context_list] --> CTX[modules_contexts.json]
CTX --> MERGE[getContextData]
MERGE --> DATA[Copy current ModuleTypeData]
DATA --> BILLS[Create bills for context]
BILLS --> AK[Answer Key du contexte]
AK --> NEXT[getModuleTypeDataFromAnswerKeyModule]
NEXT --> STATE[Accounts / inventory / immobilizations / contracts]
AK --> SAVE[Saved pages + dropdowns]
BILLS --> PREV[previousContextsBills]
STATE --> CURRENT[Generate current_module]
PREV --> CURRENT
SAVE --> TEAM[TeamModuleTypeData]
CURRENT --> TEAM

```

## Les deux fichiers de configuration à ne pas confondre

<table id="bkmrk-fichier-responsabili"><thead><tr><th>Fichier</th><th>Responsabilité</th></tr></thead><tbody><tr><td>`modules_settings.json`</td><td>Choisit **quels contextes** un module utilise, dans quel ordre, avec quelles dates et quels overrides.</td></tr><tr><td>`modules_contexts.json`</td><td>Définit les **templates réutilisables** de contexte : bills à générer, pages disponibles et configuration partagée.</td></tr></tbody></table>

## context\_list dans modules\_settings.json

Chaque module peut avoir une `context_list`. Un élément ressemble conceptuellement à :

```json
{
  "uid": "last_year",
  "context_uid": "module_context_aux_journals_with_regularizations_last_year",
  "start_date": {
    "day": 1,
    "month": 1,
    "year_offset": -1
  },
  "end_date": {
    "day": 31,
    "month": 12,
    "year_offset": -1
  },
  "preferences": {
    "uses_regularizations": false
  },
  "saved_pages": [
    "trial_balance_before_regularizations_and_closure"
  ]
}
```

<table id="bkmrk-champ-r%C3%B4le-uid-ident"><thead><tr><th>Champ</th><th>Rôle</th></tr></thead><tbody><tr><td>`uid`</td><td>Identité de **l'instance de contexte** dans ce module. Sert notamment de clé dans `previousContextsBills` et les snapshots.</td></tr><tr><td>`context_uid`</td><td>Template chargé depuis `modules_contexts.json`.</td></tr><tr><td>`start_date` / `end_date`</td><td>Bornes utilisées pour générer les bills et calculs de cette période. `year_offset` permet de viser l'année précédente.</td></tr><tr><td>`preferences`</td><td>Overrides propres à ce contexte. Ils sont résolus via les helpers de préférence avec override.</td></tr><tr><td>`saved_pages`</td><td>Pages de l'Answer Key de ce contexte à conserver dans le `TeamModuleTypeData` final.</td></tr></tbody></table>

## modules\_contexts.json : templates réutilisables

`modules_contexts.json` contient des définitions comme `module_context_aux_journals_with_regularizations`. Elles peuvent notamment fournir :

- `bills_type_data` — types et quantités de bills à générer;
- `pages_order` — pages comptables utilisées par l'Answer Key du contexte;
- des propriétés réutilisées par le moteur de création.

### Héritage avec base\_context\_uid

`getContextData(moduleContext)` charge le template correspondant à `moduleContext.context_uid`. Si ce template possède `base_context_uid`, le moteur :

1. copie le contexte de base;
2. merge ensuite le contexte enfant par-dessus;
3. applique ensuite les règles/preferences propres à l'instance du module.

Utiliser ce mécanisme lorsqu'un nouveau contexte est une variante d'un contexte existant plutôt que de dupliquer un énorme bloc `bills_type_data`.

## Pipeline réel de création des contextes

Le cœur se trouve dans `byModule_utils_create_module_type_data.js`.

### 1. Initialiser l'état de départ

Le moteur possède un `currentModuleTypeDataBase` qui contient l'état comptable courant : comptes, inventaire, immobilisations, contrats, taxes, services/expenses, etc.

### 2. Parcourir context\_list dans l'ordre

Pour chaque `moduleContext` :

1. `getContextData(moduleContext)` résout le template et ses overrides;
2. les dates absolues sont calculées depuis `actSettings.year`;
3. le moteur fait un `deepCopy` de l'état comptable courant;
4. les bills du contexte sont générés;
5. `BillContext.getBills()` devient le jeu de bills du contexte;
6. un Answer Key complet du contexte est calculé;
7. les pages demandées par `saved_pages` sont snapshotées;
8. le contexte est transformé en nouvel état comptable de base pour le contexte suivant.

### 3. Conserver les bills des contextes précédents

Après chaque contexte :

```javascript
currentModuleTypeDataBase.previousContextsBills[moduleContext.uid] =
    previousModuleTypeData.bills;
```

Les bills ne disparaissent donc pas après leur période. Ils restent disponibles pour les moteurs qui doivent consulter l'historique.

### 4. Générer le current\_module

Une fois tous les contextes calculés, le moteur utilise l'état final comme point de départ du `current_module`. Les bills du module étudiant sont alors générés avec les comptes/inventaire/immobilisations/contracts issus des contextes.

## BillCreationContext vs BillContext vs “module context”

Ces trois termes sont proches mais ne désignent pas la même chose.

<table id="bkmrk-concept-responsabili"><thead><tr><th>Concept</th><th>Responsabilité</th></tr></thead><tbody><tr><td>**Module context**</td><td>Période logique définie dans `context_list` / `modules_contexts.json`.</td></tr><tr><td>`BillCreationContext`</td><td>État temporaire global utilisé pendant la **création** des bills : RNG, dates, partners, inventory, comptes, ordre de calcul, etc.</td></tr><tr><td>`BillContext`</td><td>Vue runtime des bills/comptes/inventory utilisée par les bills, Answer Key et calculs comptables après/pendant génération.</td></tr></tbody></table>

**Ne pas documenter ou coder ces trois concepts comme s'ils étaient interchangeables.**

## previousContextsBills

`TeamModuleTypeData.previousContextsBills` est indexé par `contextUid`. Chaque contexte contient typiquement :

- `billsData`;
- `billsOrder`;
- `partners`;
- `taxes` lorsque présentes.

`BillContext` expose ensuite :

- `getPreviousContextsBills()` — tous les bills historiques;
- `getPreviousContextsBillsByType(...)` — filtre par type;
- `getPreviousContextsBillsByContextUid(contextUid)`;
- `getBillByUid()` — cherche d'abord dans le contexte courant, puis dans les contextes précédents.

### Cas particulier module\_context\_client/provider

`BillContext.getSerializedBillsBase()` exclut actuellement les bills dont le type contient `module_context_client` lors de certaines sérialisations. Le code contient d'ailleurs un TODO explicite sur ce cas. Avant de modifier ces types, inspecter leur lifecycle complet au lieu de supposer qu'ils se comportent comme un bill standard.

## savedPagesDataByContextUids

Si un contexte déclare `saved_pages`, le moteur récupère les pages correspondantes depuis **l'Answer Key de ce contexte** :

```text
savedPagesDataByContextUids
└── <moduleContext.uid>
    ├── trial_balance_before_regularizations_and_closure
    ├── statement_income
    └── ...
```

Côté Unity, `TeamByModuleController.GetContextSavedPageData(actModuleUid, moduleContextUid, modulePageUid)` permet de relire ces snapshots.

### Quand utiliser saved\_pages

Utiliser `saved_pages` lorsqu'une page historique doit être consultable/référencée plus tard. Ne pas l'utiliser pour recopier toutes les pages “au cas où” : chaque snapshot augmente les données persistées dans `teamsModules`.

## savedDynamicDropdownsDataByContextUids

Lorsqu'une saved page dépend d'un dynamic dropdown, le moteur conserve aussi les entrées correspondantes dans :

```
savedDynamicDropdownsDataByContextUids[contextUid]
```

Sinon la page historique pourrait contenir des tables indexées par des UIDs que le client ne saurait plus nommer/afficher.

## Propagation entre contextes

`getModuleTypeDataFromAnswerKeyModule()` transforme la sortie du contexte en état de départ pour le suivant.

<table id="bkmrk-donn%C3%A9e-comportement-"><thead><tr><th>Donnée</th><th>Comportement entre contextes</th></tr></thead><tbody><tr><td>Comptes</td><td>Recalculés depuis l'Answer Key / account state puis propagés.</td></tr><tr><td>Inventaire</td><td>Copié/transformé à partir de l'état final de `BillContext`.</td></tr><tr><td>Immobilisations</td><td>Copiées avec mise à jour de l'usage/dépréciation lorsque les règles du contexte l'exigent.</td></tr><tr><td>Contrats</td><td>Copiés vers le contexte suivant.</td></tr><tr><td>Taxes immobilisations</td><td>Copiées vers le contexte suivant.</td></tr><tr><td>Services / expenses</td><td>Conservés lorsqu'ils existent.</td></tr><tr><td>Bills</td><td>Archivé dans `previousContextsBills`; le contexte suivant obtient ses propres bills.</td></tr></tbody></table>

## Overrides de préférences par contexte

Un contexte peut modifier temporairement des préférences du module. Le code utilise `getPreferenceWithOverride(moduleSettings, moduleContext, preference)` pour plusieurs règles.

Exemples visibles dans `modules_settings.json` :

- `answer_key_until`;
- `uses_accounting_closing`;
- `uses_regularizations`;
- `uses_stock_regularizations`;
- `uses_taxes_payment_form`;
- `has_new_immobilizations`;
- `has_new_contracts`.

Une préférence mise dans le contexte doit affecter **uniquement cette période de génération**. Si le comportement doit aussi changer dans le current module, configurer la preference au bon niveau du module.

## initial\_pages\_data et préfixe previous\_

Après les contextes, `current_module.initial_pages_data` peut construire l'état initial de certaines pages depuis l'état actuel ou précédent.

Le moteur traite notamment :

- `ledger` / `previous_ledger`;
- `bank_statement` / `previous_bank_statement`;
- `clients_subledger`;
- `providers_subledger`;
- `stocks_subledger`;
- fallback vers une page de l'Answer Key précédent.

Le préfixe `previous_` indique au générateur de prendre la donnée du contexte/module précédent puis le préfixe est retiré avant de sauvegarder dans `initialPagesData`.

## Ajouter un nouveau contexte de A à Z

1. **Décider s'il faut vraiment un nouveau template.** Si seule une préférence/date change, réutiliser un `context_uid` existant avec overrides.
2. **Si nécessaire, créer le template** dans `modules_contexts.json`.
3. **Préférer `base_context_uid`** si le nouveau contexte est une variante d'un gros contexte existant.
4. **Ajouter l'instance** dans `modules_settings.context_list` au bon ordre.
5. Donner un `uid` stable à l'instance.
6. Définir `start_date`/`end_date` et `year_offset`.
7. Ajouter les overrides `preferences` nécessaires.
8. Ajouter seulement les `saved_pages` qui doivent réellement survivre.
9. Importer la static data Google Sheets/JSON si la source officielle passe par le pipeline d'export.
10. Générer le Module Type Data avec un seed fixe.
11. Inspecter l'état avant/après ce contexte.
12. Vérifier que le contexte suivant et le current module partent du bon état.

## Ajouter une nouvelle donnée qui doit traverser les contextes

C'est une modification plus profonde. Il faut auditer au minimum :

1. `createInitialModuleTypeData()` — création initiale;
2. `createBills()` / `BillCreationContext` — utilisation pendant la période;
3. `BillContext` — état calculé;
4. `getModuleTypeDataFromAnswerKeyModule()` — propagation vers le contexte suivant;
5. `TeamModuleTypeData.cs` — désérialisation Unity;
6. migration `teamsModules` si vieux documents;
7. Answer Key et Grading si la donnée influence une page comptable.

## Debug d'un contexte

Pour un bug “la bonne valeur existe dans l'année précédente mais disparaît dans le module courant”, inspecter dans cet ordre :

1. **Configuration** — le contexte est-il réellement dans `context_list` ?
2. **Template** — `context_uid` existe-t-il dans `modules_contexts.json` ?
3. **Merge** — `base_context_uid` + overrides produisent-ils le contexte attendu ?
4. **Dates** — vérifier `year_offset` et les timestamps calculés.
5. **Bills du contexte** — inspecter `moduleTypeDataForContext.bills`.
6. **Answer Key du contexte** — la valeur attendue existe-t-elle déjà ici ?
7. **Transformation** — inspecter `getModuleTypeDataFromAnswerKeyModule()`.
8. **previousContextsBills** — le contexte est-il présent sous le bon `uid` ?
9. **saved\_pages** — le snapshot est-il présent ?
10. **current\_module** — la donnée est-elle encore correcte juste avant sa génération ?

## Test de régression recommandé

<table id="bkmrk-cas-v%C3%A9rification-con"><thead><tr><th>Cas</th><th>Vérification</th></tr></thead><tbody><tr><td>Contexte année précédente</td><td>Dates avec `year_offset = -1`, balances correctement reportées.</td></tr><tr><td>Deux contextes successifs</td><td>Le second part de l'état final du premier.</td></tr><tr><td>`saved_pages`</td><td>Page + dynamic dropdowns relisibles côté Unity.</td></tr><tr><td>Inventory</td><td>Stock final du contexte précédent devient l'état de départ suivant.</td></tr><tr><td>Immobilisations</td><td>Usage/dépréciation cumulative correcte.</td></tr><tr><td>Contrats</td><td>Contrat encore disponible et régularisation correcte.</td></tr><tr><td>Bills historiques</td><td>`BillContext.getBillByUid()` retrouve un bill précédent.</td></tr><tr><td>Current module</td><td>Answer Key final et grading à 100 % avec les données propagées.</td></tr></tbody></table>

## Fichiers clés

- `Assets/GameSpecific/byModule/Resources/data_byModule/modules_settings.json`
- `Assets/GameSpecific/byModule/Resources/data_byModule/modules_contexts.json`
- `node.js/backend/braincloud/server_scripts/_utils/by_module/byModule_utils_create_module_type_data.js`
- `.../_utils/by_module/contexts/BillCreationContext.js`
- `.../_utils/by_module/contexts/BillContext.js`
- `Assets/Src/Game/Data/ByModule/TeamModuleTypeData.cs`
- `Assets/Src/UI/SimByModule/Controllers/TeamByModuleController.cs`

# Accounting 1 vs Accounting 2 — différences réelles

## But de cette page

ACC1 et ACC2 partagent le même moteur **By Module / Accounting**, mais ils ne diffèrent pas seulement par le thème visuel. Plusieurs différences sont encodées directement dans les **settings disponibles**, les **types d'entreprise**, la **forme juridique**, les **partenaires**, les **produits/services**, les **modules disponibles** et certaines branches de l'Answer Key.

Cette page répond à deux questions :

1. **Qu'est-ce qui est réellement différent entre ACC1 et ACC2 ?**
2. **Quand une modification doit-elle aller dans le commun, ACC1, ACC2 ou être testée dans les deux ?**

## Résumé fonctionnel

<table id="bkmrk-dimension-acc1-acc2-"><thead><tr><th>Dimension</th><th>ACC1</th><th>ACC2</th></tr></thead><tbody><tr><td>Univers métier</td><td>Entreprise spécialisée dans la vente de piscines.</td><td>Entreprise spécialisée dans la vente de café et de machines à café.</td></tr><tr><td>Objectif pédagogique</td><td>Compléter un cycle comptable.</td><td>Compléter un cycle comptable.</td></tr><tr><td>Framework technique</td><td>By Module + `acc_base`.</td><td>By Module + `acc_base`.</td></tr><tr><td>Type d'activité de l'entreprise</td><td>`our_company_type` disponible : commerciale / services / mixte selon le module.</td><td>Même système.</td></tr><tr><td>**Forme juridique**</td><td>Preference masquée; les defaults des modules sont essentiellement `single_owner`.</td><td>**Preference ACC2-only** : société en nom collectif, société par actions A, société par actions A+B. **PAS DE** `single_owner`.</td></tr><tr><td>Créances / effets à recevoir</td><td>Non exposés dans le wizard.</td><td>**`has_debts` et `has_interests` sont ACC2-only.**</td></tr><tr><td>Petite caisse</td><td>Le moteur commun existe, mais le module n'est pas disponible dans le wizard ACC1.</td><td>**`module_petty_cash` est ACC2-only.**</td></tr><tr><td>Co-propriétaire / actionnaires</td><td>Pas d'entités game-specific correspondantes dans `companies/partners`.</td><td>`our_company_co_owner`, `shareholder_group_a`, `shareholder_group_b`.</td></tr><tr><td>Companies</td><td>66.</td><td>67.</td></tr><tr><td>Partners</td><td>76.</td><td>77.</td></tr><tr><td>Products</td><td>77.</td><td>43.</td></tr><tr><td>Services</td><td>41.</td><td>58.</td></tr><tr><td>Tutoriels game-specific</td><td>Plusieurs scripts/prefabs ACC1 dédiés.</td><td>Pas d'équivalent dans le dossier ACC2 actuel.</td></tr></tbody></table>

## Attention : “Type d'entreprise” et “Forme juridique” sont deux settings différents

C'est important car les deux notions sont faciles à mélanger.

### our\_company\_type — disponible dans le framework des deux jeux

Le setting `our_company_type` décrit l'activité économique de l'entreprise :

- `products_company` — commerciale;
- `services_company` — de services;
- `products_and_services_company` — commerciale et de services.

Ce setting influence notamment :

- stock / inventaire;
- comptes visibles;
- types de bills disponibles;
- pages/relations liées aux ventes et services.

### company\_legal\_form — ACC2-only dans le wizard

Dans `preferences_settings_definitions.json` :

```json
"company_legal_form": {
  "category": "company",
  "type": "dropdown",
  "data_uid": "company_legal_form",
  "acc2_only": true
}
```

`PreferencesWizardPageControl.ShouldSkipPreference()` masque toute preference marquée `acc2Only` lorsque `CurrentGameUid != "acc2"`.

## Formes juridiques supportées par le moteur

Le moteur By Module connaît explicitement :

<table id="bkmrk-uid-libell%C3%A9-cons%C3%A9que"><thead><tr><th>UID</th><th>Libellé</th><th>Conséquence principale</th></tr></thead><tbody><tr><td>`single_owner`</td><td>Propriétaire unique</td><td>Capital et retraits d'un seul propriétaire.</td></tr><tr><td>`partnership`</td><td>Société en nom collectif</td><td>Deux propriétaires; capital, apports/retraits et profits peuvent être répartis.</td></tr><tr><td>`joint_stock_a`</td><td>Société par actions — groupe A</td><td>Capital-actions A + bénéfices non répartis.</td></tr><tr><td>`joint_stock_ab`</td><td>Société par actions — groupes A et B</td><td>Capital-actions A+B + bénéfices non répartis; deux groupes d'actionnaires.</td></tr></tbody></table>

ACC1 contient les textes/structures génériques nécessaires au moteur, mais la preference est explicitement masquée dans son wizard. ACC2 expose réellement cette variation au professeur.

## Impact technique de la forme juridique

Ce n'est pas un simple label. `company_legal_form` traverse plusieurs moteurs.

### 1. BillCreationContext / BillContext

Les deux contextes lisent :

```javascript
this._companyLegalForm =
    utilsByModule.getModulePreference(
        moduleSettings,
        utilsByModule.constants.modulesPreferences.COMPANY_LEGAL_FORM
    );
```

Pour une partnership, `ourCompanyOwnerShares` vaut par défaut 50 si aucune valeur n'est présente.

### 2. Répartition propriétaire / co-propriétaire

`BillContext.getOwnerPaymentAmount()` répartit un montant entre propriétaire/co-propriétaire lorsque la forme est :

- `partnership`;
- `joint_stock_ab` pour certains calculs actionnaires.

### 3. Génération de bills

`BillCreationContext.getPayingOwnerUid()` peut choisir un ou les deux propriétaires pour une partnership. Le `BillFactory` possède aussi des branches spécifiques pour :

- les paiements des propriétaires;
- les retraits;
- la Petite caisse;
- la création de plusieurs bills lorsque plusieurs propriétaires paient.

### 4. Comptes visibles

`accounts.json` possède des conditions de visibilité basées sur la forme juridique. Exemples :

- capital du propriétaire;
- capital du co-propriétaire;
- retraits/apports du co-propriétaire;
- capital-actions;
- bénéfices non répartis.

### 5. État des capitaux propres

L'Answer Key change réellement la structure :

- **single owner** → capital du propriétaire;
- **partnership** → capital propriétaire + co-propriétaire;
- **joint stock** → capital-actions + bénéfices non répartis;
- **joint\_stock\_ab** → deux groupes de capital-actions.

### 6. Clôture

Dans `byModule_utils_answer_key_module.js` :

- une partnership répartit le résultat entre `our_company_capital` et `our_company_co_owner_capital`;
- une société par actions transfère le résultat vers `retained_earnings`;
- un propriétaire unique utilise le capital propriétaire.

Donc toute modification touchant capital, fermeture, equity, dividends, owner bills ou financial statements doit être testée avec **plusieurs formes juridiques ACC2**.

## Différences ACC2-only encodées dans le framework partagé

Un grep de `acc2_only` dans la static data By Module montre actuellement ces différences explicites :

<table id="bkmrk-feature-fichier-effe"><thead><tr><th>Feature</th><th>Fichier</th><th>Effet</th></tr></thead><tbody><tr><td>`company_legal_form`</td><td>`preferences_settings_definitions.json`</td><td>Preference visible seulement dans ACC2.</td></tr><tr><td>`has_debts`</td><td>`preferences_settings_definitions.json`</td><td>Créances activables seulement dans ACC2 via le wizard.</td></tr><tr><td>`has_interests`</td><td>`preferences_settings_definitions.json`</td><td>Effets à recevoir / intérêts activables seulement dans ACC2 via le wizard.</td></tr><tr><td>`module_petty_cash`</td><td>`modules_settings_definitions.json`</td><td>Module proposé uniquement dans ACC2.</td></tr></tbody></table>

### Filtrage du module Petite caisse

`ModuleDataAndNameWizardPageControl` vérifie `moduleDefinition.acc2Only` et ignore le module lorsqu'on n'est pas dans ACC2.

### Filtrage des preferences

`PreferencesWizardPageControl.ShouldSkipPreference()` applique le même principe aux preferences ACC2-only.

## ACC2 — Créances et effets à recevoir

Les preferences :

- `has_debts` — libellé FR actuel : *Créances*;
- `has_interests` — libellé FR actuel : *Effets à recevoir*.

Elles sont référencées dans plusieurs définitions de modules : General Journal, Auxiliary Journals, Regularizations, Balance Sheet, Financial Statements, etc. Elles peuvent donc affecter :

- bills clients;
- radiation de client;
- provisions;
- notes à recevoir;
- intérêts;
- ledger;
- trial balance;
- financial statements;
- Answer Key / Grading.

Une modification de ces chemins doit être testée dans ACC2 avec les toggles ON et OFF.

## ACC2 — Module Petite caisse

`module_petty_cash` est marqué `acc2_only: true` dans `modules_settings_definitions.json`.

### Contenu game-specific ACC2

ACC2 possède notamment :

- `ModulePettyCashScenarioPages.asset`;
- `petty_cash` comme partenaire/contexte;
- fournisseurs Petite caisse : convenience, maintenance, mail, florist, stationery;
- `petty_cash_initial_fund`;
- dépenses : lait, pain, papier hygiénique, timbres, enveloppes, colle, vis, ruban, fleurs, papeterie, etc.;
- retraits personnels;
- textes/références régionaux QC/ON/NB/PE.

### Moteur commun malgré tout

Les classes techniques de Petite caisse sont dans le commun By Module, par exemple :

- `PettyCashDeclarationBill`;
- `PettyCashReplenishmentCheckBill`;
- branches Petty Cash du `BillFactory`;
- journal/Answer Key/Grading correspondants.

Autrement dit : **le moteur est partagé, mais ACC2 est le jeu qui expose officiellement le module.**

## Différences de partenaires / companies

### ACC1 — 66 companies

Les fournisseurs principaux sont liés aux piscines et à l'extérieur :

- `motor_company`;
- `pipe_company`;
- `aluminium_company`;
- `wood_company`;
- `chlore_company` / `chlore_salt_company`;
- `solar_canvas_company` / `winter_canvas_company`;
- `stairs_company`;
- `light_company`;
- `outdoor_company`;
- `toys_company`;
- `barbecue_company`.

### ACC2 — 67 companies

Les fournisseurs principaux sont liés au café :

- `coffee_company`;
- `espresso_company`;
- `beans_company`;
- `brew_company`;
- `blend_company`;
- `grind_company`;
- `roast_company`;
- `barista_company`.

ACC2 ajoute en plus des entités structurelles absentes des companies ACC1 :

- `our_company_co_owner`;
- `shareholder_group_a`;
- `shareholder_group_b`;
- `petty_cash`;
- les fournisseurs de Petite caisse.

## Partner subtypes différents

ACC2 possède des `partner_sub_type` supplémentaires :

- `our_company_co_owner`;
- `shareholder_group_a`;
- `shareholder_group_b`;
- `petty_cash`;
- `petty_cash_convenience_oc`;
- `petty_cash_maintenance_oc`;
- `petty_cash_mail_oc`;
- `petty_cash_florist_oc`;
- `petty_cash_stationery_oc`.

Ces différences ne sont pas décoratives : `BillFactory` et plusieurs bill classes sélectionnent les partenaires selon leur subtype.

## Produits : univers complètement différent

### ACC1 — 77 products

Types et exemples :

- **piscines** : `above_pool_*`, `mid_pool_*`, `in_pool_*`;
- **entretien** : chlore, algicide, robot cleaner;
- **extérieur** : sofa, gazebo, toys, barbecue;
- **composants de piscine** : moteurs, tuyaux, aluminium, bois, chauffe-eau, systèmes chlore, toiles, escaliers, lumières;
- plus les produits techniques communs : deposit/withdrawals, bank, office supplies, loans, etc.

### ACC2 — 43 products

Types et exemples :

- **espresso machines** : regular/highend/deluxe;
- **coffee machines** : regular/highend/deluxe;
- **beans** : raw/roasted/blends;
- **retail/accessoires** : brush, descaler, air blower, grinders, roasters, filters, scales, tampers, cups;
- plus les produits techniques communs.

### Product types propres à ACC2

- `espresso_machine`;
- `coffee_machine`;
- `beans`;
- `our_company_co_owner`;
- `shareholder_group_a`;
- `shareholder_group_b`.

## Services : ACC2 en contient davantage

### ACC1 — 41 services

Une grosse partie est liée aux piscines :

- fermeture de piscine par type/taille;
- nettoyage;
- maintenance;
- installation;
- installation de chauffe-eau;
- services/operating costs communs.

### ACC2 — 58 services

ACC2 ajoute de nombreuses familles :

- **Petite caisse** : fonds initial, consommables, dépenses et retraits;
- **formation barista** : novice / advanced / expert;
- **coffee shop** : installation, cleaning, maintenance;
- **maintenance machines** : repair, cleaning, descaling;
- **maintenance équipements** : grinder sharpening, scale calibration, parts replacing;
- **eau / steam wand**;
- **stocks/actionnaires**.

## Ce qui reste commun dans les datasets

Dans l'état actuel du dépôt, les différences les plus importantes ne viennent pas de :

- `conditions.json`;
- `employees.json`;
- `salaries.json`.

Les différences sont surtout dans `companies`, `partners`, `products`, `services`, les ScenarioPages, les assets et les settings ACC2-only.

## Server scripts ACC1 vs ACC2

Côté serveur, les wrappers restent volontairement presque identiques.

### Commandes présentes dans les deux

- `answer_key_module`;
- `change_module_item_index`;
- `change_module_sort_index`;
- `change_module_visibility`;
- `create_team`;
- `custom_command`;
- `generate_new_good_decisions`;
- `generate_results`;
- `get_new_good_decisions`;
- `reopen_module`;
- `set_correction_mode_off`.

### acc1\_utils.js vs acc2\_utils.js

Ils sont actuellement identiques à part le GameUid et les noms de classes :

```javascript
GameSpecificUtilsAcc1 -> GameSpecificUtilsAccBase("acc1")
GameSpecificUtilsAcc2 -> GameSpecificUtilsAccBase("acc2")
```

Donc une règle comptable commune doit aller dans `acc_base` ou `_utils/by_module`, pas être dupliquée dans les deux fichiers.

### Exception historique ACC1

`acc1_module_sort_name.js` existe uniquement dans ACC1 et ne fait qu'inclure `acc_base_change_module_sort_index`. C'est un alias historique, pas une différence métier.

## UI / scenes / skins

<table id="bkmrk-acc1-acc2-acc1studen"><thead><tr><th>ACC1</th><th>ACC2</th></tr></thead><tbody><tr><td>`Acc1Student.unity`</td><td>`Acc2Student.unity`</td></tr><tr><td>`Acc1Teacher.unity`</td><td>`Acc2Teacher.unity`</td></tr><tr><td>`MainControlStudentAcc1.prefab`</td><td>`MainControlStudentAcc2.prefab`</td></tr><tr><td>`MainControlTeacherAcc1.prefab`</td><td>`MainControlTeacherAcc2.prefab`</td></tr><tr><td>`Acc1Skin.asset`</td><td>`Acc2Skin.asset`</td></tr><tr><td>Doozy graphs ACC1</td><td>Doozy graphs ACC2</td></tr></tbody></table>

## Tutoriels : différence ACC1 importante

ACC1 contient actuellement toute une couche game-specific absente d'ACC2 :

- `AccGeneralTutorials.cs`;
- `GenericAcc1Tutorial.cs`;
- `GenericAcc1TutorialNoModules.cs`;
- `TutorialButtonAcc1Async.cs`;
- `TutorialButtonAcc1Sync.cs`;
- `TutorialPopupControlAcc1Async.cs`;
- `TutorialPopupControlAcc1Sync.cs`;
- prefabs `TutorialPopupControlAcc1*`.

Ils couvrent navigation générale, correction, pratique, fin summative, téléchargement/impression, deuxième fenêtre et report aux grands livres.

Si une UI commune By Module est modifiée, tester que les UIDs ciblés par ces tutoriels ACC1 existent toujours.

## ScenarioPages

Les deux jeux possèdent la majorité des mêmes ScenarioPages Accounting :

- Accounting Cycle;
- Accounting Journal;
- General Journal;
- Auxiliary Journals / Ledgers;
- Taxes;
- Income Taxes / Employer Income Taxes;
- Regularizations;
- Financial Statements;
- Closure;
- Balance Sheet.

ACC2 possède en plus `ModulePettyCashScenarioPages.asset`. Plusieurs assets homologues ont également des différences de contenu/références : ne pas copier un asset ACC1 sur ACC2 ou inversement sans diff.

## Quand modifier où ?

<table id="bkmrk-changement-emplaceme"><thead><tr><th>Changement</th><th>Emplacement attendu</th></tr></thead><tbody><tr><td>Règle comptable valable pour ACC1 + ACC2</td><td>`acc_base` / By Module commun.</td></tr><tr><td>Nouveau bill générique</td><td>Pipeline Bills commun.</td></tr><tr><td>Support d'une nouvelle forme juridique</td><td>Framework commun + settings, puis exposition selon le jeu.</td></tr><tr><td>Modification du comportement partnership/joint stock</td><td>BillContext/BillFactory/Answer Key/Grading communs; tester ACC2.</td></tr><tr><td>Nouvelle feature créances/intérêts</td><td>Framework commun, mais activation ACC2-only tant que le produit reste ainsi.</td></tr><tr><td>Nouveau contenu Petite caisse</td><td>ACC2 data; moteur commun si logique.</td></tr><tr><td>Nouveau fournisseur piscine</td><td>ACC1 static data.</td></tr><tr><td>Nouveau fournisseur café</td><td>ACC2 static data.</td></tr><tr><td>Changement visuel</td><td>GameSpecific concerné.</td></tr><tr><td>Changement tutoriel général ACC1</td><td>GameSpecific ACC1.</td></tr></tbody></table>

## Matrice de test recommandée

Une feature Accounting qui touche une zone commune ne devrait pas être validée sur un seul jeu.

<table id="bkmrk-feature-touch%C3%A9e-test"><thead><tr><th>Feature touchée</th><th>Tests minimum</th></tr></thead><tbody><tr><td>Bills/journaux/ledger généraux</td><td>ACC1 + ACC2.</td></tr><tr><td>Inventory</td><td>ACC1 + ACC2 avec company type/stock type pertinent.</td></tr><tr><td>Capital/equity/closing</td><td>ACC1 default + ACC2 single owner + partnership + joint stock A + joint stock AB.</td></tr><tr><td>Owner bills</td><td>ACC1 owner unique + ACC2 partnership.</td></tr><tr><td>Dividends / retained earnings</td><td>ACC2 joint stock.</td></tr><tr><td>Créances / effets à recevoir</td><td>ACC2 ON/OFF.</td></tr><tr><td>Petite caisse</td><td>ACC2.</td></tr><tr><td>Tutoriel/UI modules</td><td>ACC1 + ACC2; vérifier ACC1 tutorial targets.</td></tr><tr><td>Scenario</td><td>Asset du même module dans les deux jeux.</td></tr></tbody></table>

## Fichiers clés pour comprendre les différences

- `Assets/GameSpecific/acc1/Resources/data_acc1/`
- `Assets/GameSpecific/acc2/Resources/data_acc2/`
- `Assets/GameSpecific/byModule/Resources/data_byModule/preferences_settings_definitions.json`
- `Assets/GameSpecific/byModule/Resources/data_byModule/modules_settings_definitions.json`
- `Assets/Src/UI/SimByModule/UI/Wizards/SaveModule/PreferencesWizardPageControl.cs`
- `Assets/Src/UI/SimByModule/Data/ModuleDataAndNameWizardPageControl.cs`
- `node.js/backend/braincloud/server_scripts/_utils/by_module/contexts/BillCreationContext.js`
- `node.js/backend/braincloud/server_scripts/_utils/by_module/contexts/BillContext.js`
- `node.js/backend/braincloud/server_scripts/_utils/by_module/factories/BillFactory.js`
- `node.js/backend/braincloud/server_scripts/_utils/by_module/byModule_utils_answer_key_module.js`

## Pages liées

- [Architecture By Module / Accounting](https://wiki.uniksim.com/books/game/page/architecture-by-module-accounting)
- [Contextes Accounting](https://wiki.uniksim.com/books/game/page/contextes-accounting-cycle-heritage-et-donnees-entre-periodes)
- [Configuration Accounting — guide des JSON](https://wiki.uniksim.com/books/game/page/configuration-accounting-guide-des-json)
- [Pipeline des Bills](https://wiki.uniksim.com/books/game/page/pipeline-des-bills-architecture-et-ajout-dun-nouveau-type)
- [acc\_base, ACC1 et ACC2 — séparation des responsabilités](https://wiki.uniksim.com/books/game/page/acc-base-acc1-et-acc2-separation-des-responsabilites)

# Fuzzy matching — logique de correspondance du grading Accounting

## À quoi sert le fuzzy matching ?

Le grading doit retrouver quelle facture ou écriture attendue correspond à une ligne ou section étudiante, même si son identifiant manque ou est erroné. Le matching choisit cette correspondance; les correcteurs évaluent ensuite les valeurs, options, dates, références, soldes et l’ordre. Un match trouvé ne rend donc pas automatiquement la réponse correcte et son score de ressemblance n’est pas la note finale.

Cette logique est exécutée dans le script serveur BrainCloud `node.js/backend/braincloud/server_scripts/_utils/by_module/byModule_utils_grade_module.js`, à l’intérieur de `byModuleGradeModule()`. Voir [Grading, reassemble et calcul des scores](https://wiki.uniksim.com/books/game/page/grading-reassemble-et-calcul-des-scores) pour la pondération finale.

## Le helper commun : matchUidsToAnswers

Signature : `matchUidsToAnswers(uids, answersByKey, getStrictAnswerKey, getMatchScore)`. Il reçoit les UID étudiants ordonnés, les réponses candidates indexées par clé, une fonction d’identification stricte et une fonction de ressemblance. Il retourne uniquement `answerKeyByUid`; une entrée sans correspondance est absente de ce résultat.

```mermaid
flowchart TD
A[Entrées étudiantes et réponses candidates] --> B[Passage strict dans l'ordre des UID]
B --> C[Réserver chaque clé stricte une seule fois]
C --> D[Entrées et réponses restantes]
D --> E[Seuil initial 1]
E --> F[Meilleure réponse disponible au-dessus du seuil pour chaque entrée]
F --> G[Consommer la réponse et retirer l'entrée appariée]
G --> H{Au moins un match dans ce passage ?}
H -->|Oui| F
H -->|Non| I{Score restant au moins égal à 0.5 ?}
I -->|Oui| J[Abaisser le seuil au meilleur score observé]
J --> F
I -->|Non| K[Retourner la correspondance partielle]
```

### 1. Priorité stricte

Le passage strict traite toutes les entrées avant tout fuzzy matching. Une clé non nulle est retenue si elle n’a pas déjà été utilisée. Elle est retirée des réponses restantes. Une deuxième entrée demandant la même clé passe dans le lot fuzzy; elle ne peut pas réutiliser cette réponse, mais peut être associée à une autre.

La validation métier de la clé appartient au callback. `getStrictBillUid` lit `bill_id`, retrouve la facture dans `_billsByNumber` et vérifie `getBillAffectedPageUid()`. Un identifiant valide pour la page est prioritaire même si les montants ou la date ressemblent davantage à une autre facture; ces champs seront corrigés ensuite.

### 2. Passages fuzzy

1. Commencer à `currentThreshold = 1`.
2. Pour chaque UID restant, parcourir les réponses disponibles et retenir le plus grand score satisfaisant `score ≥ currentThreshold`.
3. Consommer immédiatement la réponse retenue. Aucun retour arrière ne réaffecte un match déjà choisi.
4. Lorsqu’un passage n’a produit aucun match, abaisser le seuil au plus grand score inférieur au seuil courant mais supérieur ou égal à `0.5`.
5. Arrêter si aucune paire admissible ne reste, ou si l’un des lots est vide.

Le seuil descend vers les scores réellement observés : ce n’est pas une suite fixe 1 → 0,9 → 0,8. Une égalité conserve la première réponse rencontrée, car une nouvelle candidate doit satisfaire `score > bestScore`. Le résultat dépend donc de l’ordre des UID et des clés des réponses.

## Qui utilise le helper commun ?

<table id="bkmrk-correcteur-unit%C3%A9-et-"><thead><tr><th>Correcteur</th><th>Unité et priorité stricte</th><th>Score fuzzy</th></tr></thead><tbody><tr><td>`computeAuxJournalScoreBase` : achats, ventes, retraits, encaissements, paie</td><td>Section; première row contenant bill\_id → facture de cette page</td><td>`calculateMatchScore` : partenaire, mois/année, comptes et montants</td></tr><tr><td>`computePettyCashJournalScore`</td><td>Row; bill\_id → facture de cette page</td><td>`calculateRowMatchScore` : mois/année et comptes/montants</td></tr><tr><td>`computeSubledgersJournalTableScore` : clients/fournisseurs</td><td>Row; clé construite table/partenaire + send/receive + bill\_id, hors upfront\_payment</td><td>`calculateRowAmountMatchScore` : mois/année, sens et total de la facture</td></tr><tr><td>`computeLedgerAccountTableScore`</td><td>Row admissible du compte; bill\_id présent dans ses réponses attendues</td><td>`calculateRowAmountMatchScore` : mois/année, sens et montant de l’écriture de ce compte</td></tr></tbody></table>

Le helper n’est pas branché sur tous les correcteurs du fichier. Les stocks et le General Journal possèdent d’autres règles.

## Comment sont calculés les scores ?

### Journaux auxiliaires : section

`calculateMatchScore` retourne 0 si la facture ne concerne pas cette page. Les poids sont partenaire `0.3`, date `0.3`, comptes `0.4`. Le partenaire correspond à l’expéditeur ou au destinataire de la facture; la date compare le mois et l’année. Les poids disponibles sont additionnés au dénominateur.

Les comptes sont extraits des rows, en excluant `date, partner_uid, bill_id, report_to_account, account_uid`. Les colonnes débit/crédit sont rattachées à `account_uid`, avec un signe négatif pour le crédit. Pour un compte présent dans la facture, le code attribue une contribution de présence ou de sens d’impact, puis une contribution de proximité du montant. La proximité utilise `max(0, 1 - abs(saisi - attendu) / attendu)` lorsque l’attendu est non nul.

**Détails de l’implémentation actuelle :** le test de date utilise `if (sectionMonth && sectionYear)`. Comme janvier vaut 0 dans `Date.getMonth()`, le facteur date est omis en janvier. Le cumul de contributions par compte peut aussi produire un score supérieur à 1 : il n’est pas borné. Il faut conserver ces détails lorsqu’on diagnostique un résultat, plutôt que supposer une probabilité normalisée.

### Petite caisse : row et comptes

`calculateRowMatchScore` utilise date `0.33` et comptes `0.66`. Les comptes renseignés sont comparés à ceux de la facture; `date, partner_uid, bill_id, petty_cash_total` sont exclus. Un compte absent de la facture contribue 0. Un montant attendu nul et saisi nul contribue 1; sinon le code utilise la proximité relative du montant. La contribution moyenne des comptes est pondérée par 0.66.

La date compare uniquement mois/année, avec un test de présence différent de celui des sections. Sans facteur disponible, le résultat est 0. Le partenaire et le total petite caisse ne servent pas au matching de cette row.

### Subledgers et ledger : row et montant

`calculateRowAmountMatchScore` utilise date `0.33` et montant `0.66`. Le sens saisi doit être le sens attendu; si débit et crédit sont tous deux renseignés, le débit est prioritaire. Pour un attendu non nul, la contribution montant est `max(0, 1 - abs(saisi - attendu) / abs(attendu))`. Les facteurs présents sont renormalisés par leur somme; sans facteur, le score vaut 0.

Une date présente mais différente reste dans le dénominateur avec une contribution nulle. Une date absente est exclue. Si seul le mois/année correspond et que le montant est incorrect, le score est environ 1/3 : insuffisant pour le seuil 0.5. Si la date est absente et le sens/montant sont corrects, le score peut atteindre 1.

## Exemple chiffré

Écriture attendue : débit 100 en septembre. L’étudiant saisit débit 90 dans le même mois et la même année, sans identifiant strict valide.

```
proximité montant = 1 - |90 - 100| / 100 = 0.9
score = (0.33 × 1 + 0.66 × 0.9) / (0.33 + 0.66)
      = 0.9333…
```

La row peut donc être appariée lors d’un passage fuzzy, si la réponse reste disponible. Le montant 90 est ensuite comparé à 100 dans le grading des cellules; cet appariement n’accorde pas 93,33 % de la note.

## Ce qui a changé dans le ledger le 30 septembre

Le ledger construit les réponses attendues par compte et numéro de facture ou formulaire, avec sens, montant, page de référence et date. Le nouveau helper remplace le fallback local d’appariement et permet de retrouver une écriture à partir de son sens, montant et mois/année quand le bill\_id ne permet pas de l’identifier.

Seules les rows ayant une `reference` participent à cet appariement. Avec les journaux auxiliaires actifs, la référence doit contenir `general_journal`; sans eux, toutes les rows portant une référence sont candidates. Les reports agrégés des journaux auxiliaires et les réponses de régularisation/clôture conservent leur traitement spécialisé. La recherche ne traverse pas tous les comptes : chaque table possède ses candidates.

Après matching, le correcteur prépare `bill_id`, `reference`, la date, le débit/crédit attendu, puis corrige le solde et l’ordre. Les réponses non rencontrées peuvent produire `table_bill_missing` ou `table_form_missing`. Une référence invalide peut produire `row_reference_missing`.

## Le General Journal garde un algorithme distinct

`computeGeneralJournalScore` ne passe pas par `matchUidsToAnswers`. Il construit `wantedExplanationBySectionUids` pour factures, formulaires et relations de régularisation/clôture, avec ses propres passages de seuil, priorités et traitement des doublons.

`getMatchRateForGeneralJournalSection` pondère la ressemblance des comptes par 4, puis ajoute l’explication sélectionnée et la date exacte lorsqu’elles sont disponibles. Les factures/formulaires comparent aussi montants et sens; les relations peuvent comparer la structure des comptes sans leurs valeurs. Le plancher des passages est `0.4` (le code arrête avant un passage dont le prochain seuil est ≤ 0.4), et une recherche complémentaire de relation utilise `0.8`. Ces seuils ne remplacent pas le 0.5 du helper commun.

## Ambiguïtés et diagnostic

Le helper est glouton : il cherche une bonne paire au seuil courant, pas la meilleure affectation globale de toutes les rows. Exemple : R1→A=0.9, R1→B=0.8, R2→A=0.85, R2→B=0.1. Il peut retenir R1→A puis laisser R2 sans match, alors que R1→B et R2→A permettraient deux correspondances. Les égalités dépendent de l’ordre. Une valeur NaN ne satisfait aucune comparaison de seuil.

1. Identifier le correcteur de page et la liste exacte des candidates.
2. Vérifier si un bill\_id valide a verrouillé une réponse au passage strict.
3. Examiner les champs présents : page, partenaire, date, compte, débit/crédit et montant selon le helper.
4. Recalculer le score avec les poids réellement disponibles et vérifier le seuil.
5. Contrôler l’ordre des UID, les doublons et les réponses consommées par d’autres entrées.
6. Examiner ensuite `errors`, `expectedValues` et les scores bruts; vérifier la pondération dans `pages_settings` seulement après.

# By Year

# Architecture By Year

## Rôle

By Year est le framework historique où une activité progresse année par année. À chaque année, les équipes prennent des décisions game-specific, le serveur génère les résultats, calcule les évaluations et prépare l'année suivante.

```mermaid
flowchart LR
ACT[ActivityByYear] --> Y[Current Year]
Y --> DEC[TeamDecisionsByYear]
DEC --> GAME[Game-specific logic]
GAME --> RES[ResultsByYear]
RES --> EVAL[TeamEvaluation / StudentEvaluation]
RES --> NEXT[Next Year]

```

## Unity

<table id="bkmrk-coucheemplacement-mo"><thead><tr><th>Couche</th><th>Emplacement</th></tr></thead><tbody><tr><td>Modèles</td><td>`Assets/Src/Game/Data/ByYear/`</td></tr><tr><td>Controllers</td><td>`Assets/Src/Game/Controllers/*ByYear*`</td></tr><tr><td>UI commune</td><td>`Assets/Src/UI/SimByYear/`</td></tr><tr><td>Game-specific</td><td>`Assets/GameSpecific/ent1|hr1|mgt1|mk1|mk2/`</td></tr></tbody></table>

## Serveur

- `_utils/utils_by_year.js` : cycle, calendrier, jobs, bots et helpers annuels;
- `_utils/utils_eval.js` : évaluations communes;
- `_utils/utils_rankings.js` : rankings;
- `_utils/factory/utils_factory_generate_results.js` : sélection du moteur de résultats par GameUid;
- `_utils/factory/utils_factory_eval.js` : sélection du moteur d'évaluation par GameUid;
- `<gameUid>/utils/*` : décisions, events, résultats et scoring propres au jeu.

## Game-specific

Contrairement à Accounting, By Year n'a pas une seule logique métier partagée. Le framework fournit le cycle; les jeux fournissent les décisions et les calculs. Il faut donc éviter de mettre les règles MK1, HR1 ou ENT1 dans une page “Common”.

## Ajouter un nouveau jeu By Year de A à Z

Un nouveau jeu annuel n'est pas seulement un nouveau dossier `GameSpecific`. Il doit être branché côté Unity, static data, serveur, génération, évaluation, résultats, messages, UI et factories.

### 1. GameUid et composition Unity

1. Ajouter le GameUid dans `SharedConstants`.
2. Le mapper vers `SimType.ByYear`.
3. Créer `Assets/GameSpecific/<gameUid>/`.
4. Créer les modèles `StaticData`, `Team`, `TeamDecisions`, `Results` et les controllers game-specific nécessaires.
5. Brancher les classes dans `Factory.cs`.

### 2. Static data

Ajouter le jeu dans `node.js/exports/exportAllData.js` avec Spreadsheet/GIDs et fichiers supplémentaires. Créer `Resources/data_<gameUid>`, importer puis vérifier `StaticDataController<Game>`.

### 3. Serveur game-specific

Créer `server_scripts/<gameUid>/` avec :

- `utils/<gameUid>_utils.js` — `GameSpecificUtils*`;
- `utils/<gameUid>_utils_generate_results.js` — `GenerateResults*`;
- `utils/<gameUid>_utils_eval.js` — `Eval*` si l'évaluation est spécifique;
- `utils/..._bots.js` si async/bots;
- wrappers `command/`.

### 4. Brancher les factories serveur

- `utils_factory_game_specific_utils.js`;
- `utils_factory_generate_results.js`;
- `utils_factory_eval.js`;
- `takeBotsDecision()` / factory bots si le jeu supporte les bots.

### 5. Définir le cycle annuel du jeu

Implémenter au minimum `startYearGameSpec(actData)`, `getYearData()`, `generateEvents()`, `gameSpecificInitTeam()`, `startCash()`, `anyDecisionsTaken()` et les helpers métier requis.

### 6. Génération

`GenerateResults<Game>.generateCurrentYearResults(actData)` doit être l'entrée unique du calcul annuel. Il doit lire les équipes/décisions, calculer les résultats, les sauvegarder et préparer l'état de l'année suivante sans dépendre de l'UI.

### 7. Unity UI

Créer les contrôles game-specific dans `GameSpecific/<gameUid>/Src/UI`, plus les prefabs/scenes/Resources. Les composants génériques année/eval/rankings restent sous `Src/UI/SimByYear`.

### 8. Textes, scénario, tutoriels, timelines

Ajouter les textes et assets propres au jeu. Si le jeu possède ses propres tutoriels/timelines, les placer dans ses dossiers GameSpecific et les brancher via les states/UI concernés.

### 9. Validation minimale

1. Créer une simulation.
2. Démarrer année 1.
3. Prendre au moins une décision.
4. Générer résultats.
5. Vérifier résultats + cash + ActPerf.
6. Passer à l'année suivante.
7. Réouvrir une année.
8. Tester async/bot si supporté.
9. Tester rankings/final sequence.

# Modèle de données By Year

## Objets Unity

- `SimulationByYear` : simulation et options propres à la famille;
- `ActivityByYear` : année courante, états, calendrier et données de l'activité;
- `TeamByYear` : équipe et données runtime;
- `TeamDecisionsByYear` : décisions par année;
- `ResultsByYear` : résultats annuels;
- `TeamEvaluationByYear` / `StudentEvaluationByYear` : évaluations;
- `StaticDataByYear` : base de static data des jeux annuels.

## Controllers

Les classes `ActivityByYearController`, `SimulationByYearController`, `TeamByYearController`, `TeamEvaluationByYearController` et `StudentEvaluationByYearController` servent de couche runtime entre ces modèles et l'UI.

## Documents serveur

```mermaid
flowchart TD
SIM[(simulation)] --> ACT[(activity)]
ACT --> TEAM[(teams)]
ACT --> GAME[(actDataGameSpec)]
TEAM --> DEC[team decisions / activities]
ACT --> RES[(results)]
TEAM --> ET[(evaluationTeam)]
SIM --> ES[(evaluation)]
TEAM --> LOG[(teamsLogbooks)]

```

## currentYear / currentYearState

L'activité garde l'année courante et son état. Les transitions serveur utilisent notamment les états “not started”, “year started”, “generating” et “year finished”. Les contrôles Unity affichent l'écran approprié en fonction de cet état.

## years\[\]

Chaque entrée annuelle peut porter les dates de début/fin et le flag de pratique. Le calendrier et le mode de gestion déterminent si les transitions se font manuellement ou automatiquement.

## Ajouter un champ de décision ou de résultat By Year

### Décision étudiante

1. Ajouter le champ dans le modèle Unity `TeamDecisions<Game>` ou `YearData` selon son scope.
2. Ajouter la désérialisation avec une valeur compatible avec les anciennes équipes.
3. Ajouter le contrôle UI qui édite cette décision.
4. Ajouter la custom command serveur ou étendre celle qui possède déjà la décision.
5. Sauvegarder dans le bon `yearData` de l'équipe.
6. Envoyer un RTT game-specific si les coéquipiers doivent voir le changement.
7. Consommer la décision dans `GenerateResults<Game>`.
8. Mettre à jour `anyDecisionsTaken()` si cette décision compte pour déterminer si l'équipe a commencé.
9. Mettre à jour les bots si une équipe bot doit prendre cette décision.

### Résultat annuel

1. Ajouter le champ au modèle serveur de résultat produit par `generateCurrentYearResults()`.
2. Le persister dans `results`.
3. Ajouter le modèle Unity `Results`/`ResultsByYear` et désérialiser.
4. Ajouter report/tooltip/UI.
5. Si le champ participe à ActPerf, mettre à jour `Eval<Game>.computeActPerfScore()`.
6. Si le champ participe au ranking, mettre à jour `updateRankings<Game>()`.
7. Vérifier `deleteResults()`/reopen year.

## Où placer une donnée annuelle

<table id="bkmrk-donn%C3%A9e-emplacement-d"><thead><tr><th>Donnée</th><th>Emplacement</th></tr></thead><tbody><tr><td>Décision qui change chaque année</td><td>`TeamDecisions<Game>.yearData[year]` ou structure équivalente.</td></tr><tr><td>État d'équipe durable entre années</td><td>`Team<Game>` / team data persistante.</td></tr><tr><td>Résultat calculé d'une année</td><td>`results`.</td></tr><tr><td>Configuration immuable</td><td>Static data.</td></tr><tr><td>Donnée commune à toute l'activité</td><td>`actDataGameSpec`.</td></tr></tbody></table>

## Compatibilité anciens documents

Une nouvelle propriété de team/activity doit être tolérante à l'absence. Si sa valeur doit absolument exister sur les vieux documents, utiliser la migration correspondante dans `_utils/version/`.

# Cycle d’une année, calendrier et jobs

## startYear()

`utils_by_year.startYear(actData, updateYearFinished)` incrémente l'année si nécessaire, passe l'état à `ACT_YEAR_STARTED`, enregistre `startTime`, appelle `startYearGameSpec()`, initialise les stubs de présence puis envoie `act_year_started`.

```mermaid
stateDiagram-v2
[*] --> NotStarted
NotStarted --> YearStarted: startYear
YearStarted --> Generating: fin de période / commande
Generating --> YearFinished: génération terminée
YearFinished --> YearStarted: année suivante
YearFinished --> [*]: dernière année

```

## nextGenerate

`updateNextGenerateDate()` calcule quand le prochain traitement automatique doit avoir lieu. En pratique, les années n'ont pas de génération automatique. En mode calendrier, `nextGenerate` pointe vers la fin de l'année active ou le début de la suivante.

## Validation des dates

`validateDates()` corrige les années mal ordonnées ou placées dans le passé et garantit notamment un intervalle minimal de cinq minutes. Si des dates sont déplacées, le professeur peut recevoir `years_date_modified`.

## Job planifié

`gatherActsForJob()` récupère les activités dont `nextGenerate < now`. `runActJob()` traite une activité active : si l'année est commencée, elle passe à generating et appelle `generateResultsJob()`; sinon elle peut démarrer l'année suivante.

## generateResultsJob()

1. envoie `act_generating`;
2. fait jouer les bots si l'activité est async;
3. génère les events game-specific;
4. appelle `generateResultsByGameUid[gameUid].generateCurrentYearResults(actData)`;
5. décide s'il faut démarrer automatiquement l'année suivante;
6. met à jour `nextGenerate`;
7. sauvegarde l'activité;
8. envoie `act_year_finished` lorsque requis.

## Scripts de job

Les wrappers se trouvent sous `server_scripts/_job/`, notamment `generate_results_job.js` et le scheduler commun.

## Modifier le cycle annuel ou le calendrier sans casser les jobs

### Invariants du cycle

- `startYear()` initialise l'année et appelle `startYearGameSpec()`;
- la génération passe par `generateResultsJob()` ou la commande manuelle équivalente;
- `nextGenerate` détermine le prochain traitement automatique;
- les années de pratique ne suivent pas forcément le même calendrier automatique;
- le client est averti via les messages `act_year_started`, `act_generating`, `act_year_finished`.

### Changer la logique de dates

Les fonctions à auditer sont `updateNextGenerateDate()`, `validateDates()`, `dateToUpper()`, `dateToLower()` et `gatherActsForJob()`.

1. Définir le comportement attendu pour sync, async et practice.
2. Vérifier que les dates passées sont corrigées de façon déterministe.
3. Vérifier l'intervalle minimal attendu par `validateDates()`.
4. Mettre à jour les messages professeur si des dates sont déplacées.
5. Tester le job avec une activité dont `nextGenerate` est juste avant/après “now”.

### Modifier startYear()

Toute nouvelle initialisation annuelle doit être faite soit dans le socle commun si elle concerne tous les jeux, soit dans `startYearGameSpec()`. Ne pas ajouter des branches GameUid directement dans `utils_by_year.startYear()`.

## Tester un changement de cycle

<table id="bkmrk-cas-%C3%80-v%C3%A9rifier-ann%C3%A9e"><thead><tr><th>Cas</th><th>À vérifier</th></tr></thead><tbody><tr><td>Année 1 manuelle</td><td>start state, presence, year data, RTT.</td></tr><tr><td>Fin manuelle</td><td>generating → results → finished.</td></tr><tr><td>Calendrier automatique</td><td>`nextGenerate`, scheduler, next year.</td></tr><tr><td>Async</td><td>bots avant génération.</td></tr><tr><td>Dernière année</td><td>pas de démarrage fantôme d'une année suivante.</td></tr><tr><td>Reopen</td><td>état + results + evals + logbook restaurés/cohérents.</td></tr></tbody></table>

## Debug d'un job qui ne part pas

1. Lire `activity.nextGenerate`.
2. Vérifier que l'activité est active.
3. Vérifier `gatherActsForJob()`.
4. Exécuter `runActJob()` avec l'UID.
5. Inspecter l'état annuel avant/après.
6. Vérifier le log de `generateResultsJob()`.

# Commandes serveur, réouverture et transitions By Year

## Entrées communes

Le framework annuel est exposé par des commandes communes et par des wrappers game-specific.

## Commandes communes

- `change_year_state` : change l'état annuel;
- `student_take_decisions` : marque/enregistre la prise de décisions selon le flux annuel;
- `get_activity_current_year` : année courante;
- `ignore_year_from_average` : exclut une année de certaines moyennes;
- `save_market_size_modifier` : modificateur de marché;
- `set_year_sequence_seen` : état de séquence côté utilisateur.

## Wrappers game-specific

Les dossiers `ent1/command`, `hr1/command`, `mgt1/command`, `mk1/command` et `mk2/command` exposent typiquement :

- `<game>_create_team`;
- `<game>_custom_command`;
- `<game>_generate_results`;
- `<game>_start_year`;
- `<game>_start_next_activity`;
- `<game>_reopen_year`;
- `<game>_compute_scenario_score`;
- `<game>_compute_segment_score`.

Certains jeux ajoutent des wrappers propres, par exemple des mises à jour de rankings.

## Réouverture d'année

`_utils/command/utils_reopen_year.js` est la logique partagée. Une réouverture ne se limite pas à décrémenter `currentYear` : il faut supprimer/retourner les résultats, nettoyer les évaluations annuelles, logbook/presence associés et remettre l'activité/équipes dans un état cohérent.

## Transition activité suivante

`utils_start_next_activity.js` gère la transition entre activités. Les modes sync/async et le GameUid influencent les messages et l'activité chargée côté clients.

## Custom commands

`utils_custom_command_by_year.js` est le point commun de dispatch lorsque la commande n'est pas directement implémentée dans le wrapper game-specific. Les règles métier restent dans `utilsByGameUid[gameUid]`.

## Debug

```mermaid
flowchart LR
UNITY[BackEndBrainCloud] --> WRAP[gameUid/command]
WRAP --> COMMON[_utils/command]
COMMON --> SPEC[game-specific utils]
SPEC --> DOC[activity / teams / results]
DOC --> RTT[act_year_* / result messages]

```

## Ajouter une Custom Command By Year

Les commandes communes comme market research, decisions finished ou financial help passent par `utils_custom_command_by_year.js`; les décisions métier propres au jeu doivent rester dans les utils game-specific.

### Procédure

1. Définir `commandUid` et la payload.
2. Brancher le wrapper `<gameUid>_custom_command`.
3. Si commande commune, ajouter le cas dans `executeCommandByYear()`.
4. Sinon déléguer vers `utilsByGameUid[gameUid]` / utils custom du jeu.
5. Modifier `teamDecisionsData`/`teamData`/`actData` au bon niveau.
6. Sauvegarder les documents concernés.
7. Envoyer un RTT avec la payload minimale.
8. Ajouter le modèle/handler Unity.

## Réouverture d'année — système destructif

`utils_reopen_year.reopenYear()` ne doit pas être traité comme un simple changement de state. Il doit remettre en cohérence :

- activity/current year;
- results;
- team decisions/cash si le jeu les reset;
- evaluationTeam de l'année;
- logbook annuel;
- présence;
- events/produits game-specific lorsque le moteur le prévoit;
- rankings si nécessaire.

### Quand une nouvelle donnée annuelle est ajoutée

Auditer immédiatement `reopenYear()`, `GenerateResults<Game>.deleteResults()`, `deleteProducts()` et les helpers de reset du jeu. Si la donnée existe seulement après génération, elle doit généralement être supprimée/restaurée lors d'une réouverture.

## Transition activité suivante

`utils_start_next_activity.js` porte la logique commune. Un nouveau jeu ou nouveau type d'activité doit être testé avec :

- activité précédente terminée;
- création/chargement de la suivante;
- première année de la suivante;
- étudiants/teams copiés ou regroupés;
- async vs sync;
- RTT `next_act_started`/`async_next_act_started`.

# Décisions et génération des résultats By Year

## Principe

Le framework By Year ne connaît pas la structure détaillée des décisions. Il délègue à `utilsByGameUid[gameUid]` et au moteur `generateResultsByGameUid[gameUid]`.

```mermaid
sequenceDiagram
participant U as Unity Decisions UI
participant B as BackEndBrainCloud
participant C as gameUid_custom_command
participant T as teams
participant J as generateResultsJob
participant G as GenerateResultsGame
participant R as results
U->>B: CustomCommand / décision
B->>C: script game-specific
C->>T: sauvegarde TeamDecisions
J->>G: generateCurrentYearResults(actData)
G->>T: lit décisions de toutes les équipes
G->>G: marché / RH / production / features...
G->>R: saveResults
G->>T: met à jour cash/état année suivante

```

## Pattern des moteurs de résultats

Chaque jeu possède un objet `GenerateResults*` avec généralement :

- collecte des décisions de toutes les équipes;
- calcul d'averages/références;
- calcul des scores ou du marché;
- events;
- `finalizeTeamsData()`;
- `saveResults()`;
- reset/préparation de l'année suivante;
- `deleteResults()` pour réouverture/rollback.

## Factories serveur

`utils_factory_generate_results.js` instancie le moteur adapté à `ent1`, `hr1`, `mgt1`, `mk1` ou `mk2`. Ajouter un nouveau jeu By Year exige donc de le brancher dans cette factory.

## UI Unity

Le socle visuel se trouve dans `DecisionsByYearControl`, `DecisionsPanelByYear` et les contrôles `SimByYear*`. Les écrans de décisions réels sont souvent dans `GameSpecific/<gameUid>/Src/UI`.

## Ajouter une nouvelle décision qui influence les résultats

Ce workflow est le cœur d'une feature By Year.

### 1. Static data/config

Ajouter la donnée de référence dans la Sheet/JSON game-specific si la décision dépend d'un coût, seuil, segment, feature, pays, employé, etc.

### 2. Modèle Unity

Ajouter la valeur dans `TeamDecisions<Game>`/`YearData` et l'UI qui l'édite.

### 3. Sauvegarde serveur

Ajouter la custom command game-specific. Valider cash, permissions et contraintes côté serveur, même si l'UI fait déjà la validation.

### 4. Synchronisation équipe

Émettre un message game-specific afin que les autres membres voient la décision. Ajouter la constante dans `GameSpecific/<gameUid>/Src/Messages/MessageType*.cs` si nécessaire.

### 5. Résultats

Consommer la décision dans `GenerateResults<Game>`. Choisir l'étape correcte : gather → averages → scores → sales/hiring/production → finalize/save.

### 6. Préparer l'année suivante

Si la décision crée un état durable, vérifier `finalizeTeamsData()` / reset team pour transporter seulement ce qui doit survivre.

### 7. Évaluation, ranking, report

Si la nouvelle décision change un score, mettre à jour Eval et ranking. Si elle produit un nouveau résultat, ajouter le modèle Unity et le report.

### 8. Bots

Pour les jeux async avec bots, décider explicitement quelle valeur le bot choisit. Sinon les bots peuvent générer des résultats invalides ou avoir un avantage/désavantage artificiel.

## Debug d'un résultat “bizarre”

1. Inspecter la décision persistée avant génération.
2. Inspecter les données agrégées de toutes les équipes.
3. Inspecter averages/références.
4. Inspecter score intermédiaire.
5. Inspecter résultat final.
6. Inspecter cash/team state après `finalizeTeamsData()`.
7. Comparer le modèle Unity désérialisé.

# Bots et simulations asynchrones By Year

## Quand les bots sont utilisés

Dans `generateResultsJob()`, si `actData.isAsync` est vrai, `takeBotsDecision(actData)` est appelé avant la génération des events et résultats.

## Jeux avec bots explicites

Le routeur commun instancie actuellement `BotsEnt1`, `BotsMk1` ou `BotsMk2`. Les équipes marquées `isBot` reçoivent alors des décisions générées avant le calcul des résultats.

```mermaid
flowchart LR
JOB[generateResultsJob] --> ASYNC{isAsync?}
ASYNC -->|oui| B[takeBotsDecision]
B --> BOT[Bot game-specific]
BOT --> TD[Team decisions]
ASYNC -->|non| EV[Events]
TD --> EV[Events]
EV --> RES[Generate Results]

```

## ENT1

`BotsEnt1` gère notamment prêts, capacité de production, construction d'usines, employés, matières premières, choix de pays et création/lancement de produit.

## MK1/MK2

Les bots Marketing construisent des décisions de produit/marché selon un niveau et des heuristiques game-specific.

## Attention

Le code désactive temporairement l'envoi de messages pendant la boucle bots afin d'éviter de spammer les clients avec chaque décision artificielle. Les documents équipes sont ensuite sauvegardés.

## Ajouter ou modifier une décision de bot

Les bots sont exécutés par `takeBotsDecision(actData)` avant la génération de résultats des activités async. Une nouvelle décision métier qui est obligatoire doit être supportée par le bot du jeu.

### Procédure

1. Ajouter la logique dans `Bots<Game>.takeDecisions()` ou un helper spécialisé.
2. Réutiliser les mêmes fonctions de coût/validation que les humains lorsque possible.
3. Éviter de modifier directement les résultats : le bot doit produire des **décisions**, puis passer dans le même moteur de génération.
4. Respecter le `botLevel` si le jeu simule plusieurs niveaux.
5. Vérifier cash/stock/capacité après chaque décision en chaîne.
6. Ajouter une garde si une static data attendue manque.

## Jeux actuellement supportés

Le socle crée explicitement des bots pour ENT1, MK1 et MK2. HR1/MGT1 ne doivent pas être supposés bot-compatible sans implémentation dédiée.

## Messages pendant les bots

Le framework peut désactiver temporairement certains messages pendant la boucle bot. C'est voulu : on ne veut pas envoyer à chaque client un RTT pour chaque micro-décision artificielle. Vérifier la synchronisation finale après sauvegarde/génération.

## Tester un bot

- cash très bas;
- année 1 et année suivante;
- marché/segment déjà utilisé;
- static data manquante ou vide;
- plusieurs bots ensemble;
- mêmes inputs avec seed/config stable;
- génération complète après décisions.

# Évaluations, logbook, scénarios et rankings By Year

## Évaluations

`_utils/utils_eval.js` fournit le cadre commun pour activité, présence, logbook, custom evaluations et stubs. Chaque jeu fournit ensuite son `Eval<Game>` via la factory d'évaluation.

## Scores game-specific

Les classes `EvalEnt1`, `EvalHr1`, `EvalMgt1`, `EvalMk1` et équivalents calculent notamment scenario score, segment score et activity performance à partir des résultats propres au jeu.

## Logbook

Les données de logbook sont persistées dans `teamsLogbooks`. `utils_by_year.deleteYearLogbook()` montre que le logbook et ses évaluations sont liés au cycle annuel et doivent être nettoyés correctement lors d'une réouverture/suppression d'année.

## Présence

`startYear()` crée un nouveau stub de présence pour les étudiants. Les helpers `setYearPresence()` / `deleteYearPresence()` maintiennent ces données lors des changements d'état.

## Scénarios

Le client utilise `StudentPlayScenarioByYearState` et les pages/scénarios propres au jeu. Le serveur peut calculer le scenario score via la factory d'évaluation game-specific.

## Rankings

`_utils/utils_rankings.js` gère les scores et mises à jour de rankings. Chaque jeu peut appliquer son propre calcul avec des fonctions comme `updateRankingsEnt1`, `updateRankingsHr1`, etc.

```mermaid
flowchart LR
RESULTS[Year Results] --> PERF[Act Performance]
SCEN[Scenario] --> EVAL[Student Evaluation]
LOG[Logbook] --> EVAL
PRES[Presence] --> EVAL
PERF --> EVAL
RESULTS --> RANK[Rankings]

```

## UI

`EvaluationByYearSyncControl`, `EvaluationByYearAsyncControl`, lignes étudiant, detailed reports, logbook controls, presence popup et ranking controls composent la couche commune.

## Ajouter un score ActPerf ou une composante de ranking

### ActPerf

1. Ajouter/calculer la métrique dans les résultats du jeu.
2. Modifier `Eval<Game>.computeActPerfScore()`.
3. Ajouter la pondération dans `act_perf_pond` si la métrique est configurable.
4. Vérifier le stub envoyé à `evaluationTeam`.
5. Ajouter le label/report Unity.
6. Tester score d'année en cours vs année terminée.

### Ranking

Chaque jeu peut définir `updateRankings<Game>()`. Une nouvelle métrique de ranking doit être calculable depuis la donnée persistée après génération; éviter un ranking basé sur un état transitoire uniquement présent en mémoire.

## Scénario

Les réponses scénario sont distinctes des décisions annuelles. Si une nouvelle question/segment influence l'évaluation :

1. mettre à jour la static data/scenario;
2. mettre à jour le prefab/page Unity si nouveau type visuel;
3. vérifier `computeScenarioScoreAndSave()` ou `computeSegmentScoreAndSave()` du jeu;
4. vérifier les `Scenario.eScenarioStep` uniquement s'ils contrôlent un flow/tutoriel, pas comme stockage des réponses.

## Logbook et présence lors d'une nouvelle année

`startYear()` initialise la présence; reopen doit la supprimer/restaurer correctement. Le logbook est par activité/année et son évaluation est séparée du texte. Toute nouvelle logique annuelle doit conserver ces index d'année.

## Debug d'une note globale fausse

1. Inspecter le résultat game-specific brut.
2. Appeler `computeActPerfScore()` avec le même year/team.
3. Inspecter `evaluationTeam`.
4. Inspecter les pondérations simulation/static data.
5. Inspecter le stub Unity et les caches.

# UI et prefabs By Year

## Socle commun

`Assets/Src/UI/SimByYear/` contient l'UI réutilisée par les jeux annuels. Le contenu métier propre à ENT1/HR1/MGT1/MK1/MK2 reste dans `Assets/GameSpecific/<gameUid>/Src/UI` et leurs prefabs Resources.

## Contrôles principaux

<table id="bkmrk-domaineclasses-simul"><thead><tr><th>Domaine</th><th>Classes</th></tr></thead><tbody><tr><td>Simulation sync/async</td><td>`SimByYearControlBase`, `SimByYearSyncControl`, `SimByYearAsyncControl`</td></tr><tr><td>Années</td><td>`ActivityYearsControl`, `YearItemControl`, `WaitSimStartPopupControl`</td></tr><tr><td>Décisions</td><td>`DecisionsByYearControl`, `DecisionsPanelByYear`</td></tr><tr><td>Évaluation</td><td>`EvaluationByYearSyncControl`, `EvaluationByYearAsyncControl`, StudentEval lines</td></tr><tr><td>Reports</td><td>`DetailedReport*`, `ActPerfEval*`</td></tr><tr><td>Logbook</td><td>`LogBookControl`, `LogbookEvalPopupControl`, `LogbookRoleControl`</td></tr><tr><td>Rankings</td><td>`RankingsPanelControl`, `HiScoresPopupControl`, `FinalSequenceRankingsControl`</td></tr><tr><td>Events</td><td>`EventHistoryPopupControl` + rows game-specific</td></tr></tbody></table>

## Final sequence

Les contrôles `FinalSequence*` gèrent la fin de simulation, les équipes, classements et trophées. Ils consomment les rankings/résultats générés côté serveur.

## Règle de maintenance

Une UI qui ne connaît ni segment MK, ni employee HR, ni factory ENT, ni feature MGT appartient ici. Dès qu'un écran manipule une structure propre à un jeu, le code et le prefab doivent rester dans `GameSpecific`.

## Ajouter un écran de décision ou un report By Year

### Écran réellement commun

Les contrôles de cycle, années, eval, rankings, logbook et final sequence peuvent vivre sous `Assets/Src/UI/SimByYear`. Une UI qui connaît un type métier game-specific doit rester sous `Assets/GameSpecific/<gameUid>/Src/UI`.

### Nouvel écran de décision game-specific

1. Créer le contrôle sous `GameSpecific/<gameUid>/Src/UI`.
2. Créer/mettre à jour le prefab dans les Resources du jeu.
3. Lire la décision depuis `TeamController<Game>`/`TeamDecisions<Game>`.
4. Envoyer les changements via la custom command game-specific, pas en modifiant seulement le modèle local.
5. Écouter le message game-specific pour les modifications d'un coéquipier.
6. Ajouter les textes.
7. Ajouter un `TutorialUIObject` si l'écran participe à un tutoriel.

### Nouveau report de résultats

1. Ajouter le champ dans le modèle `Results<Game>`.
2. Créer le contrôle/report UI.
3. Brancher le report panel/detailed report du jeu.
4. Gérer année sélectionnée, équipe sélectionnée et teacher/student permissions.
5. Tester résultat absent (année non générée), année courante et années historiques.

## Sync vs async

Les écrans communs ont souvent des variantes `SimByYearSyncControl` et `SimByYearAsyncControl`, ainsi que des contrôles d'évaluation sync/async. Avant d'ajouter un bouton “commun”, vérifier s'il est valide dans les deux modes.

## Checklist prefab/UI By Year

- student + teacher;
- sync + async si applicable;
- année 1 + historique;
- avant résultats + après résultats;
- RTT coéquipier;
- textes FR/EN;
- tutorial target si requis;
- WebGL.

# ENT1 — architecture et logique game-specific

## Positionnement

ENT1 est un jeu By Year centré sur une entreprise qui combine production, usines, matières premières, ressources humaines, financement et lancement de produits sur plusieurs pays. Le framework annuel reste commun; les décisions et calculs ENT1 vivent sous `GameSpecific/ent1` et `server_scripts/ent1`.

## Static data

`exports/exportAllData.js` associe ENT1 aux datasets suivants : scenario, salary, employees, raw\_mat, factories, product\_specs, comms, distrib, medias, events, price\_specs, segments, countries et act\_perf\_pond, plus `constants`.

## Modèles Unity

<table id="bkmrk-domaineclasses-princ"><thead><tr><th>Domaine</th><th>Classes principales</th></tr></thead><tbody><tr><td>Production</td><td>`Factory`, `FactoryData`, `RawMatData`, `ProductionResult`</td></tr><tr><td>Produits/marché</td><td>`ProductSpecs`, `LaunchedProduct`, `Market`, `Country`, `DistribData`, `MediaData`, `CommTool`</td></tr><tr><td>RH</td><td>`EmployeesData`, `SalaryData`, `ResignationResult`</td></tr><tr><td>Résultats</td><td>`Results`, `LaunchProductSales`, `LaunchProductScores`, `Ratio`</td></tr><tr><td>Runtime</td><td>`TeamDecisionsEnt1`, `TeamEnt1`, `YearData`</td></tr></tbody></table>

## Commandes métier serveur

`ent1_utils_custom_command.js` couvre notamment :

- création/suppression/lancement de produit;
- construction et vente d'usine;
- achat de matières premières;
- affectation de production d'usine;
- embauche/licenciement;
- salaire;
- prêt/financement.

## Génération des résultats

```mermaid
flowchart TD
DEC[Décisions équipes] --> PROD[Usines + matières premières]
DEC --> HR[Employés + salaires]
DEC --> LP[Produits lancés]
PROD --> CAP[Capacité / production]
HR --> CAP
LP --> MARKET[Marché par pays/segment]
CAP --> SALES[Ventes]
MARKET --> SALES
SALES --> FIN[Cash / résultats]
FIN --> SCORE[Scores + ratios + rankings]

```

`GenerateResultsEnt1` rassemble produits et usines, calcule moyennes, démissions, production, scores et ventes, puis finalise les équipes et sauvegarde les résultats.

## Bots

`BotsEnt1` peut décider prêts, production souhaitée, construction d'usines, employés, choix de pays, matières premières et création de produits. Il est utilisé dans les simulations async.

## UI

Les écrans game-specific sont sous `Assets/GameSpecific/ent1/Src/UI` : usines, production, raw materials, RH, salaires, finance, pays, portfolio, lancement produit, marché, rapports et checklists.

## Debug

Pour un résultat ENT1 incorrect, suivre : custom command → `TeamDecisionsEnt1` → `GenerateResultsEnt1` → `Results`. Pour une incohérence de production, inspecter ensemble factories, raw mats, employés et la quantité réellement lancée.

## Ajouter une feature ENT1 de A à Z

ENT1 croise plusieurs sous-systèmes; une décision peut affecter production, matières premières, employés, cash, produits et ventes. Avant de coder, identifier laquelle de ces chaînes la feature modifie.

### Carte serveur à connaître

<table id="bkmrk-zone-fonctions-cl%C3%A9s-"><thead><tr><th>Zone</th><th>Fonctions clés</th></tr></thead><tbody><tr><td>Cycle/état</td><td>`startYearGameSpec`, `getYearData`, `generateEvents`, `anyDecisionsTaken`.</td></tr><tr><td>Usines / production</td><td>`getTotalFactoryCost`, `getNeededRawMats`, `SetProductionsForProduct`, `computeFactoriesProduction`.</td></tr><tr><td>RH</td><td>`getNumEmployees`, `computeSalaryAverages`, `computeResignations`.</td></tr><tr><td>Marché</td><td>`gatherProducts`, `computeProductAverages`, `computeScores`, `generateSales`.</td></tr><tr><td>Fin de génération</td><td>`finalizeTeamsData`, `saveResults`, `resetTeam`, `deleteResults`.</td></tr></tbody></table>

### Custom commands ENT1 existantes

Le fichier `ent1_utils_custom_command.js` gère notamment `saveLaunch`, `deleteLaunch`, `buildFactory`, `sellFactory`, `buyRawMats`, `setFactoryProduction`, `hireEmployees`, `dismissEmployees`, `setEmployeeSalary` et `updateLoan`.

### Procédure pour une nouvelle décision

1. Ajouter la static data si la décision dépend d'un coût/capacité/pays/employee.
2. Ajouter le champ au `YearData`/`TeamDecisionsEnt1`.
3. Ajouter l'UI dans le domaine approprié : factory, HR, finance, product/market.
4. Ajouter la command et valider cash/capacité côté serveur.
5. Envoyer un message ENT1 et mettre à jour `TeamControllerEnt1`.
6. Consommer la décision dans la bonne phase de `GenerateResultsEnt1`.
7. Si l'état survit à l'année, mettre à jour `finalizeTeamsData/resetTeam`.
8. Si score/ratio change, mettre à jour Eval/rankings/report.
9. Si bots supportés, ajouter la décision dans `BotsEnt1`.
10. Tester reopen year.

## Piège principal : dépendances physiques

Une décision de lancement produit peut être impossible même si le produit est valide : il faut production disponible, usines, matières premières et employés. Les validations UI ne remplacent jamais celles côté serveur.

## Debug génération ENT1

1. team decisions;
2. factories + raw mats;
3. employees/salaries;
4. production calculée;
5. products gathered;
6. averages/scores;
7. sales;
8. cash/ratios finalisés;
9. Results Unity/report.

# HR1 — architecture et logique game-specific

## Positionnement

HR1 est le jeu By Year orienté gestion des ressources humaines : staffing, rémunération, avantages, postings, contrats, CEO et segments d'employés.

## Static data

Les datasets importés incluent `ceo`, `employees`, `strategies`, `advantages`, `events`, `scenario`, `segments`, `contracts` et `act_perf_pond`, plus `constants`.

## Modèles Unity

- `CeoData`;
- `EmployeeData` / `EmployeesAdvantage` / `AdvantageData`;
- `PostingData`, `LaunchedPosting`, `PostingResult`;
- `ContractData`, `ContractResult`;
- `Segment`, `SegmentResult`, `SegmentScores`;
- `TeamDecisionsHr1`, `TeamHr1`, `YearData`.

## Hooks serveur

`GameSpecificUtilsHr1` gère entre autres le démarrage d'année, génération de contrats, reset des avantages, events, CEOs, coûts d'avantages, coûts de postings/licenciements, coûts employés et rankings.

## Génération

```mermaid
flowchart LR
DEC[Staffing + rémunération + avantages + postings] --> HIRE[Hiring]
DEC --> RET[Retention / belonging]
DEC --> COST[Employee costs]
DEC --> CEO[CEO]
HIRE --> RES[Segment Results]
RET --> RES
COST --> RES
CEO --> RES
RES --> SCORE[Scores / ActPerf]

```

`GenerateResultsHr1` finalise contrats et CEOs, calcule hiring, averages, retention/belonging, scores de postings et segments, puis sauvegarde les résultats.

## Évaluation

`EvalHr1` fournit scenario score, segment score et performance annuelle. Les résultats HR alimentent ensuite le cadre commun d'évaluation By Year.

## UI

Les vues spécifiques comprennent CEO, staffing, employés, avantages, stratégies, postings, contrats, rémunération et rapports. Les classes sont sous `Assets/GameSpecific/hr1/Src/UI`.

## Debug

Pour un problème RH, isoler d'abord la décision (employee/segment/posting/advantage), puis le coût calculé dans `hr1_utils.js`, puis la phase correspondante de `GenerateResultsHr1`.

## Ajouter une feature HR1 de A à Z

### Custom commands réelles

`hr1_custom_command.js` route actuellement :

- `posting` / `delete_posting`;
- `dismiss` / `delete_dismiss`;
- `advantage` / `delete_advantage`;
- `salary`;
- `ceo_bid` / `delete_ceo_bid`;
- `change_wanted_contracts`;
- puis les commandes By Year communes en fallback.

Ces commandes ne font pas que sauvegarder une valeur : elles recalculent souvent un coût, modifient `cash`, sauvegardent `teams`, parfois `actDataGameSpec`, puis envoient des RTT comme `posting_changed`, `salary_changed`, `team_new_bid`.

### Carte résultats

- `finalizeContracts()` — contrats;
- `solveCeos()` — résolution des CEO bids;
- `computeAverages()` — références du marché RH;
- `computeHiring()` — embauche;
- `computeRetentionAndBelonging()` — rétention/appartenance;
- `computeScores()` — scores;
- `gatherSegments()` — collecte des décisions par segment;
- `generateCurrentYearResults()` / `saveResults()`.

## Ajouter une nouvelle décision RH

1. Définir si la décision appartient au segment, à l'employé, aux avantages, aux postings, CEO ou contrats.
2. Ajouter la donnée au bon niveau de `TeamDecisionsHr1`/`YearData`.
3. Ajouter l'UI sous `GameSpecific/hr1/Src/UI`.
4. Ajouter la branche dans `hr1_custom_command.js`.
5. Calculer le coût avec un helper `utilsHr1` partagé; ne pas dupliquer le calcul UI.
6. Appliquer/refund le delta de cash correctement lors d'une édition/suppression.
7. Envoyer le RTT approprié.
8. Consommer la décision dans `GenerateResultsHr1`.
9. Adapter `EvalHr1.computeActPerfScore()` si le score change.
10. Vérifier reopen/delete results.

## CEO bids : exemple d'opération multi-document

Un bid CEO modifie à la fois `teams` et un compteur dans `actDataGameSpec.ceos` via `saveDocumentIncrementData()`. Une nouvelle feature concurrentielle partagée entre équipes peut nécessiter le même pattern; il faut alors tester la concurrence entre plusieurs teams.

## Debug HR1

Quand un coût est faux : comparer static data → helper de coût → delta ancien/nouveau → cash sauvegardé. Quand le résultat annuel est faux : comparer segment decisions → averages → hiring/retention → result segment → ActPerf.

# MGT1 — architecture et logique game-specific (DEPRECATED)

## <s>Positionnement</s>

<s>MGT1 modélise la gestion d'une équipe de développement : employés, temps de travail, features, segments, budget et lancement d'un jeu.</s>

## <s>Static data</s>

<s>Les datasets comprennent `employees`, `events`, `features`, `scenario`, `segments` et `act_perf_pond`, plus `constants`.</s>

## <s>Modèles Unity</s>

- <s>`Employee`, `EmployeesData`;</s>
- <s>`Feature`, `FeatureResults`, `GameFeatureData`;</s>
- <s>`Segment`;</s>
- <s>`LaunchedGame`, `GameResults`;</s>
- <s>`TeamDecisionsMgt1`, `TeamMgt1`, `YearData`.</s>

## <s>Logique serveur</s>

<s>`GameSpecificUtilsMgt1` gère notamment :</s>

- <s>activation/inactivation d'employés;</s>
- <s>création et noms d'employés;</s>
- <s>coûts salaire et coûts additionnels;</s>
- <s>temps de travail;</s>
- <s>affectation d'employés aux features;</s>
- <s>travail requis et complétion de feature;</s>
- <s>events et rankings.</s>

## <s>Résultats</s>

<s>`GenerateResultsMgt1` calcule le travail requis, les bonus énergie/discipline/bonheur, les employés à inactiver, les résultats par équipe et la transition des employés vers l'année suivante.</s>

```mermaid
flowchart LR
EMP[Employés] --> WORK[Temps de travail]
FEAT[Features] --> NEED[Travail requis]
WORK --> PROG[Progression features]
NEED --> PROG
PROG --> COMPLETE[Features complétées]
COMPLETE --> GAME[Résultat du jeu]
EMP --> BONUS[Energy / discipline / happiness]
BONUS --> GAME

```

## <s>UI</s>

<s>L'UI game-specific contient employees list, team leader, producer, president, feature selectors, budgets, work allocation, lancement du jeu et rapports.</s>

## <s>Debug</s>

<s>Pour une feature qui progresse mal, vérifier `setWorkOnFeature()`, le temps/coût de l'employé, `getNeededWork()` et les bonus appliqués dans la génération annuelle.</s>

## <s>Ajouter une feature MGT1 de A à Z</s>

### <s>Custom commands réelles</s>

<s>`mgt1_custom_command.js` gère :</s>

- <s>`launch_game` / `delete_game`;</s>
- <s>`add_feature` / `delete_feature`;</s>
- <s>`hire_employees` / `dismiss_employee`;</s>
- <s>`change_work_time`;</s>
- <s>`save_social_budget` / `save_remote_budget`;</s>
- <s>`save_feature_ratios`;</s>
- <s>`change_employee_active_status`;</s>
- <s>`employee_level_up` / `employee_level_down`.</s>

### <s>Carte du moteur</s>

- <s>`getEmployeeSalaryCost()` / `getEmployeeAdditionnalCost()` — coûts;</s>
- <s>`getEmployeeWorkTimeBonus()` / `getEmployeeWorkTimeCost()` — temps;</s>
- <s>`getFeatureMinimumBudget()`;</s>
- <s>`setWorkOnFeature()`;</s>
- <s>`completeFeature()`;</s>
- <s>`getNeededWork()`;</s>
- <s>`getEnergyBonus()`, `getDisciplinaryBonus()`, `getHappinessBonus()`;</s>
- <s>`computeTeamsResults()` / `generateCurrentYearResults()`.</s>

## <s>Ajouter une nouvelle feature de jeu MGT1</s>

1. <s>Ajouter la static data sous `features` si c'est une feature configurable.</s>
2. <s>Ajouter le modèle Unity `Feature/GameFeatureData` si structure nouvelle.</s>
3. <s>Ajouter le contrôle UI et les ratios/budget si nécessaire.</s>
4. <s>Ajouter la command de création/édition.</s>
5. <s>Vérifier les employés déjà assignés lors d'une suppression : `deleteEmployeeFeature()` doit nettoyer les travaux devenus invalides.</s>
6. <s>Ajouter la contribution au `workNeeded` et à `computeTeamsResults()`.</s>
7. <s>Vérifier la complétion via `completeFeature()`.</s>
8. <s>Ajouter le résultat/report.</s>

## <s>Employés : index et noms</s>

<s>Le code associe certains employés par index dans `yearData.employees` et par nom dans `employeesNames`. Toute opération d'ajout/suppression doit préserver l'alignement attendu; une suppression mal gérée peut faire pointer un nom vers le mauvais employé.</s>

## <s>Work time : validation serveur obligatoire</s>

<s>`changeWorkTime()` valide game, feature, task level et niveau réel de l'employé. Une nouvelle allocation de travail doit conserver ce type de validation côté serveur.</s>

## <s>Debug MGT1</s>

<s>Pour une feature qui ne progresse pas : feature exists → employee works structure → level/workTime → needed work → bonuses → computeTeamsResults → final result.</s>

# MK1 — architecture et logique game-specific

## Positionnement

MK1 est le moteur marketing By Year centré sur création de produit, choix de segment, prix, distribution, communication et lancement sur le marché.

## Static data

Les datasets comprennent `scenario`, `segments`, `product_specs`, `shops`, `price_specs`, `medias`, `comms`, `distrib`, `events` et `act_perf_pond`, plus `constants`.

## Modèles Unity

- `Product`, `ProductSpecs`, `LaunchedProduct`;
- `Segment`, `Market`;
- `MediaData`, `CommTool`, `DistribData`, `PriceSpec`;
- `LaunchProductSales`, `LaunchProductScores`, `Results`;
- `TeamDecisionsMk1`, `TeamMk1`.

## Logique serveur

`GameSpecificUtilsMk1` gère sauvegarde/suppression de produit, lancement, rentabilité, conversion cash↔quantité, sélection de segment, randomisation des décisions bots, événements et création/lancement de produit.

## Résultats

```mermaid
flowchart LR
P[Produits] --> G[gatherProducts]
SEG[Segments] --> G
G --> AVG[Market averages]
AVG --> PS[Product score]
AVG --> CS[Communication score]
AVG --> DS[Distribution score]
PS --> SALES[Sales]
CS --> SALES
DS --> SALES
SALES --> TEAM[Cash / Results]

```

`GenerateResultsMk1` calcule les moyennes de marché, scores produit/communication/distribution, ventes et résultats annuels.

## Bots

`BotsMk1` utilise les heuristiques du moteur MK1 pour générer les décisions d'équipes artificielles en async.

## UI

Les écrans spécifiques couvrent portfolio, nouveaux produits, launched products, market, communications, media, shops, calculator, lancement et rapports. Les tutoriels MK1 couvrent notamment création du premier produit et premier lancement.

## Ajouter une feature MK1 de A à Z

### Entrées de décision principales

`mk1_custom_command.js` route :

- `save_product`;
- `save_launched_product`;
- `delete_product`;
- `delete_launched_product`;
- `delete_calc_product`;
- puis les commandes By Year communes.

### Pipeline de résultats

```mermaid
flowchart LR
DEC[Products + launches] --> G[gatherProducts]
G --> AVG[computeAverages]
AVG --> PS[getProductScore]
AVG --> CS[getCommScore]
AVG --> DS[getDistScore]
PS --> SCORE[computeScores]
CS --> SCORE
DS --> SCORE
SCORE --> SALES[generateSales]
SALES --> SAVE[saveResults / finalizeTeamsData]

```

## Ajouter un nouveau critère marketing

1. Ajouter la static data (segment/product spec/media/distrib/etc.).
2. Ajouter la propriété dans `Product` ou `LaunchedProduct` selon qu'elle est produit ou lancement.
3. Ajouter le champ UI dans `NewProductControl`/`LaunchProductControl` ou un nouveau contrôle.
4. Transmettre la valeur via `save_product` ou `save_launched_product`.
5. Ajouter le coût dans `getProductCost()`/`getLaunchProductCost()` si applicable.
6. Ajouter la valeur au score pertinent : produit, comm ou distribution.
7. Vérifier averages et normalisation si le score est relatif aux concurrents.
8. Vérifier `generateSales()`.
9. Ajouter le report Unity et éventuellement ActPerf/ranking.
10. Adapter `BotsMk1.takeDecisions()` si le critère est requis.

## Calculateur vs décision réelle

MK1 contient des produits de calculateur en plus des produits réellement sauvegardés/lancés. Une nouvelle propriété doit être cohérente dans les deux chemins si le calculateur est censé la simuler. Auditer `saveProduct`, `saveLaunchedProduct` et `deleteCalcProduct`.

## Debug ventes MK1

1. produit/lancement persisted;
2. segment cible;
3. averages marché;
4. product score;
5. comm score;
6. distribution score;
7. sales generated;
8. cash/result saved;
9. report Unity.

# Notifications et présence

## Notifications envoyées depuis Unity

Le Game envoie les notifications au backend Web. `NotificationManager.SaveNotification()` lance la coroutine `BackEndBase.SaveNotification()`, qui effectue `POST /saveNotification` avec `userUid`, `notificationType`, `isImportant` et `extraData`.

`userUid` est le profil jeu du destinataire (`products_code.game_oid`); `extraData.simUid` identifie la simulation. `NotificationController`, protégé par `AuthGuardUser`, délègue à `GameNotificationService.notifyGameProfile()`.

Le serveur vérifie le format, exclut les types réservés au serveur et contrôle que l'expéditeur et le destinataire appartiennent à la simulation BrainCloud. Le profil jeu destinataire est ensuite converti en compte Web pour `NotificationService.notify()`.

La notification est enregistrée dans l'inbox Redis avant son annonce aux connexions du compte. Le push est tenté ensuite; un échec du push n'annule pas l'entrée enregistrée. L'inbox possède une limite de taille et une durée de rétention configurées dans `NOTIFICATION_CONFIG`.

Le Web charge `GET /notifications`, marque une entrée comme lue via `POST /notifications/:notificationUid/read` et la supprime via `DELETE /notifications/:notificationUid`. La réception live utilise `notification.created` sur le socket du compte `/account`.

Voir [Notifications et Push](https://wiki.uniksim.com/books/web/page/notifications-et-push) pour le système Web, et [Panel Web — interface superposée au jeu et intégration Unity](https://wiki.uniksim.com/books/web/page/panel-web-interface-superposee-au-jeu-et-integration-unity) pour son affichage au-dessus du jeu.

## Trois mécanismes de présence

<table id="bkmrk-m%C3%A9canisme-donn%C3%A9e-et-"><thead><tr><th>Mécanisme</th><th>Donnée et usage</th><th>Code</th></tr></thead><tbody><tr><td>Présence du compte Web</td><td>Statut online/away et jeu actif du compte; utilisé par les interfaces Web.</td><td>`AccountSocketService`, socket `/account`, `updateStatus` et `updateGamePresence`.</td></tr><tr><td>Heartbeat du Game</td><td>Session du jeu et derniers heartbeats utilisés par l'UI Unity pour les utilisateurs de la simulation.</td><td>`User.DoHeartbeat()`, endpoint `/heartBeat` et `UsersLastHeartbeats`.</td></tr><tr><td>Présence évaluée By Year</td><td>Participation enregistrée par étudiant, activité et année, utilisée pour calculer un score.</td><td>`_command/set_presence.js` et `evaluation.presence[actUid][yearId]`.</td></tr></tbody></table>

Le nouveau statut Web ne remplace pas automatiquement les deux usages Game : le code local conserve des appels et des consommateurs distincts.

## Présence Web du compte

`AccountSocketService` suit l'activité du navigateur, le focus et l'inactivité pour émettre `online` ou `away`. `PageWebglComponent` appelle `setGamePresence(pid, gameUid)` après validation de l'accès au jeu, puis `clearGamePresence()` à sa destruction.

À la reconnexion du socket, le service réémet le jeu actif conservé en mémoire. Cette information désigne le jeu ouvert; elle ne crée pas d'entrée de présence pédagogique dans BrainCloud.

## Heartbeat utilisé par Unity

`LoadUserState` démarre le heartbeat. `User.DoHeartbeat()` utilise `HEARTBEAT_RATE_SECONDS = 5` : l'intervalle d'attente est de 5 secondes entre les appels. La réponse peut mettre à jour les derniers heartbeats dans le controller de simulation.

`SimulationBaseController.IsUserOnline()` et `ActivityBaseController.IsUserOnline()` considèrent un utilisateur online si le dernier heartbeat date de moins de `5 × 12 = 60` secondes. `ProfileImageControl` consomme cet état pour l'affichage. Le heartbeat a donc encore un consommateur Unity.

## Présence évaluée By Year

`BackEndBrainCloud.SetPresence()` appelle `_command/set_presence`. Le script traite seulement les utilisateurs avec `isTeacher == 0`, charge leur simulation et l'activité courante, puis enregistre la présence uniquement si `currentYearState == 1`.

`setYearPresence()` écrit `true` dans `evaluation.presence[actUid][yearId]` si l'entrée n'existe pas déjà, sauvegarde le document et envoie `user_new_presence_eval` à l'étudiant et au professeur.

`getPresenceScore()` compte les années enregistrées entre 0 et `yearId`, puis divise le cumul de 100 points par le nombre d'années. `StudentState` et `ActivityByYearController` appellent encore `SetPresence()` dans leurs transitions.

`startYear()` envoie le stub de score à chaque étudiant; il ne marque pas tous les étudiants présents. L'enregistrement effectif relève de `set_presence`.

## Réouverture annuelle

`utils_reopen_year.js` appelle `deleteYearPresence(student, actData.currentYear + 1, actData)` après ajustement de l’année courante. Ce flux appartient au cycle des évaluations By Year.

## Usages conservés dans le Game

Le heartbeat alimente l'UI Unity, et la présence By Year alimente les évaluations BrainCloud. La présence du compte Web décrit le compte connecté et son jeu actif; ces deux mécanismes Game conservent leurs consommateurs propres.

## Sources vérifiées

Game : `Assets/Src/Game/Notifications/NotificationManager.cs`, `Game/BackEnd/BackEndBase.cs`, `BackEndBrainCloud.cs`, `Game/User.cs`, `Game/GameStates/StudentState.cs`, `LoadUserState.cs`, les controllers de simulation/activité et `UI/ProfileImageControl.cs`. BrainCloud : `_command/set_presence.js`, `_utils/utils_by_year.js`, `utils_eval.js` et `_utils/command/utils_reopen_year.js`.

Web : `uniksim-back-end/src/app.controller.ts`, `service/game-notification.service.ts`, `notification.service.ts`; frontend : `service/account/account-socket.service.ts` et `Pages/PageWebgl/PageWebgl.component.ts`.

Vérification par lecture du code Game et BrainCloud de la branche `refactor_notifications`, et du code Web de la branche `messagerie_fixes`, le 30 septembre 2026. Aucun envoi de notification ou changement de présence réel n'a été exécuté pour cette vérification.

# MK2 — architecture et logique game-specific

## Positionnement

MK2 reprend le cœur marketing de MK1 mais ajoute notamment la logique pays/segments, relance, stock, loyauté et notoriété. Il doit donc être traité comme un moteur distinct plutôt qu'une simple skin MK1.

## Static data

Les datasets comprennent `product_specs`, `comms`, `distrib`, `medias`, `events`, `price_specs`, `scenario`, `segments`, `countries`, `shops` et `act_perf_pond`, plus `constants`.

## Modèles Unity

Les classes sont proches de MK1 mais ajoutent notamment `Country`. L'UI ajoute `CountryLaunchControl` et des variantes MK2 du marché et des rapports.

## Logique serveur spécifique

- `launchTypeCalculation()`;
- `relaunchSegment()`;
- `launchCountryCalculation()`;
- `analyseRentability()`;
- `redistributeStockAndRefund()`;
- `getLaunchedProductsOfProduct()`;
- création/lancement de produits avec paramètres pays, supports de communication et stock.

## Résultats

```mermaid
flowchart TD
P[Products by country/segment] --> AVG[Market averages]
AVG --> SCORE[Product + Comm + Distrib]
SCORE --> SALES[Sales]
SALES --> LOY[Loyalities]
LOY --> NOT[Notoriety]
NOT --> RES[Team Results]
SALES --> STOCK[Stock / redistribution]
STOCK --> RES

```

`GenerateResultsMk2` ajoute notamment `computeLoyalities()` et persiste la notoriété en plus des ventes et résultats.

## Bots

`BotsMk2` génère les décisions marketing async en tenant compte des règles MK2.

## Debug

Si un résultat diffère de MK1, vérifier d'abord les branches pays/relaunch/loyalty/stock avant de comparer les formules de score communes en apparence.

## Ajouter une feature MK2 sans supposer MK1

MK2 partage des concepts avec MK1, mais son moteur n'est pas un alias. Il possède son propre `GameSpecificUtilsMk2`, son propre générateur de résultats et ses propres modèles Unity.

### Fonctions spécifiques MK2 à connaître

- `launchTypeCalculation()`;
- `relaunchSegment()`;
- `launchCountryCalculation()`;
- `analyseRentability()`;
- `createProduct()` / `launchProduct()`;
- `getLaunchedProductsOfProduct()`;
- `redistributeStockAndRefund()`;
- `computeLoyalities()`;
- notoriété persistée via `saveResults()`.

### Custom commands

Les noms d'entrée sont similaires à MK1 : `save_product`, `save_launched_product`, delete product/launch/calc. Ils appellent toutefois `utilsMk2`, dont les structures incluent notamment country/stock/relaunch.

## Ajouter un nouveau comportement de lancement

1. Définir si le changement concerne produit, lancement initial, relaunch, country ou stock.
2. Ajouter la donnée dans `Product`/`LaunchedProduct`/`Country` côté Unity.
3. Ajouter la saisie UI, notamment `CountryLaunchControl` si géographique.
4. Ajouter la valeur aux save commands.
5. Adapter `launchTypeCalculation`/`relaunchSegment`/`launchCountryCalculation` selon le cas.
6. Vérifier les refunds/redistribution de stock lors d'une modification/suppression.
7. Ajouter l'effet aux scores/ventes.
8. Vérifier loyalty + notoriety sur l'année suivante.
9. Adapter `BotsMk2`.

## Stock MK2

Le stock de lancement MK2 est distinct du système d'inventaire Accounting. Ne pas réutiliser mentalement les règles FIFO/periodic stock de By Module : ici il s'agit de quantités marketing liées aux lancements et à la redistribution/refund.

## Debug différence MK1/MK2

Quand MK2 donne un résultat différent d'une intuition basée sur MK1, vérifier d'abord country, relaunch, stock, loyalities et notoriety. Ensuite seulement comparer product/comm/dist score.

## Test de régression MK2

- premier lancement sans stock historique;
- relaunch même segment;
- lancement nouveau pays;
- modification avec refund de stock;
- loyalty année suivante;
- notoriety sauvegardée/rechargée;
- bot + génération async.