# 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.