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. 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 Couche Emplacement Modèles Assets/Src/Game/Data/ByYear/ Controllers Assets/Src/Game/Controllers/*ByYear* UI commune Assets/Src/UI/SimByYear/ Game-specific Assets/GameSpecific/ent1|hr1|mgt1|mk1|mk2/ 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; /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 Ajouter le GameUid dans SharedConstants. Le mapper vers SimType.ByYear. Créer Assets/GameSpecific//. Créer les modèles StaticData, Team, TeamDecisions, Results et les controllers game-specific nécessaires. 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_, importer puis vérifier StaticDataController. 3. Serveur game-specific Créer server_scripts// avec : utils/_utils.js — GameSpecificUtils*; utils/_utils_generate_results.js — GenerateResults*; utils/_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.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//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 Créer une simulation. Démarrer année 1. Prendre au moins une décision. Générer résultats. Vérifier résultats + cash + ActPerf. Passer à l'année suivante. Réouvrir une année. Tester async/bot si supporté. 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 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 Ajouter le champ dans le modèle Unity TeamDecisions ou YearData selon son scope. Ajouter la désérialisation avec une valeur compatible avec les anciennes équipes. Ajouter le contrôle UI qui édite cette décision. Ajouter la custom command serveur ou étendre celle qui possède déjà la décision. Sauvegarder dans le bon yearData de l'équipe. Envoyer un RTT game-specific si les coéquipiers doivent voir le changement. Consommer la décision dans GenerateResults. Mettre à jour anyDecisionsTaken() si cette décision compte pour déterminer si l'équipe a commencé. Mettre à jour les bots si une équipe bot doit prendre cette décision. Résultat annuel Ajouter le champ au modèle serveur de résultat produit par generateCurrentYearResults(). Le persister dans results. Ajouter le modèle Unity Results/ ResultsByYear et désérialiser. Ajouter report/tooltip/UI. Si le champ participe à ActPerf, mettre à jour Eval.computeActPerfScore(). Si le champ participe au ranking, mettre à jour updateRankings(). Vérifier deleteResults()/reopen year. Où placer une donnée annuelle Donnée Emplacement Décision qui change chaque année TeamDecisions.yearData[year] ou structure équivalente. État d'équipe durable entre années Team / team data persistante. Résultat calculé d'une année results. Configuration immuable Static data. Donnée commune à toute l'activité actDataGameSpec. 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. 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() envoie act_generating; fait jouer les bots si l'activité est async; génère les events game-specific; appelle generateResultsByGameUid[gameUid].generateCurrentYearResults(actData); décide s'il faut démarrer automatiquement l'année suivante; met à jour nextGenerate; sauvegarde l'activité; 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(). Définir le comportement attendu pour sync, async et practice. Vérifier que les dates passées sont corrigées de façon déterministe. Vérifier l'intervalle minimal attendu par validateDates(). Mettre à jour les messages professeur si des dates sont déplacées. 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 Cas À vérifier Année 1 manuelle start state, presence, year data, RTT. Fin manuelle generating → results → finished. Calendrier automatique nextGenerate, scheduler, next year. Async bots avant génération. Dernière année pas de démarrage fantôme d'une année suivante. Reopen état + results + evals + logbook restaurés/cohérents. Debug d'un job qui ne part pas Lire activity.nextGenerate. Vérifier que l'activité est active. Vérifier gatherActsForJob(). Exécuter runActJob() avec l'UID. Inspecter l'état annuel avant/après. 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 : _create_team; _custom_command; _generate_results; _start_year; _start_next_activity; _reopen_year; _compute_scenario_score; _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 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 Définir commandUid et la payload. Brancher le wrapper _custom_command. Si commande commune, ajouter le cas dans executeCommandByYear(). Sinon déléguer vers utilsByGameUid[gameUid] / utils custom du jeu. Modifier teamDecisionsData/ teamData/ actData au bon niveau. Sauvegarder les documents concernés. Envoyer un RTT avec la payload minimale. 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.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]. 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//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/ 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//Src/Messages/MessageType*.cs si nécessaire. 5. Résultats Consommer la décision dans GenerateResults. 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” Inspecter la décision persistée avant génération. Inspecter les données agrégées de toutes les équipes. Inspecter averages/références. Inspecter score intermédiaire. Inspecter résultat final. Inspecter cash/team state après finalizeTeamsData(). 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. 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 Ajouter la logique dans Bots.takeDecisions() ou un helper spécialisé. Réutiliser les mêmes fonctions de coût/validation que les humains lorsque possible. É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. Respecter le botLevel si le jeu simule plusieurs niveaux. Vérifier cash/stock/capacité après chaque décision en chaîne. 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 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. 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 Ajouter/calculer la métrique dans les résultats du jeu. Modifier Eval.computeActPerfScore(). Ajouter la pondération dans act_perf_pond si la métrique est configurable. Vérifier le stub envoyé à evaluationTeam. Ajouter le label/report Unity. Tester score d'année en cours vs année terminée. Ranking Chaque jeu peut définir updateRankings(). 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 : mettre à jour la static data/scenario; mettre à jour le prefab/page Unity si nouveau type visuel; vérifier computeScenarioScoreAndSave() ou computeSegmentScoreAndSave() du jeu; 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 Inspecter le résultat game-specific brut. Appeler computeActPerfScore() avec le même year/team. Inspecter evaluationTeam. Inspecter les pondérations simulation/static data. 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//Src/UI et leurs prefabs Resources. Contrôles principaux Domaine Classes Simulation sync/async SimByYearControlBase, SimByYearSyncControl, SimByYearAsyncControl Années ActivityYearsControl, YearItemControl, WaitSimStartPopupControl Décisions DecisionsByYearControl, DecisionsPanelByYear Évaluation EvaluationByYearSyncControl, EvaluationByYearAsyncControl, StudentEval lines Reports DetailedReport*, ActPerfEval* Logbook LogBookControl, LogbookEvalPopupControl, LogbookRoleControl Rankings RankingsPanelControl, HiScoresPopupControl, FinalSequenceRankingsControl Events EventHistoryPopupControl + rows game-specific 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//Src/UI. Nouvel écran de décision game-specific Créer le contrôle sous GameSpecific//Src/UI. Créer/mettre à jour le prefab dans les Resources du jeu. Lire la décision depuis TeamController/ TeamDecisions. Envoyer les changements via la custom command game-specific, pas en modifiant seulement le modèle local. Écouter le message game-specific pour les modifications d'un coéquipier. Ajouter les textes. Ajouter un TutorialUIObject si l'écran participe à un tutoriel. Nouveau report de résultats Ajouter le champ dans le modèle Results. Créer le contrôle/report UI. Brancher le report panel/detailed report du jeu. Gérer année sélectionnée, équipe sélectionnée et teacher/student permissions. 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 Domaine Classes principales Production Factory, FactoryData, RawMatData, ProductionResult Produits/marché ProductSpecs, LaunchedProduct, Market, Country, DistribData, MediaData, CommTool RH EmployeesData, SalaryData, ResignationResult Résultats Results, LaunchProductSales, LaunchProductScores, Ratio Runtime TeamDecisionsEnt1, TeamEnt1, YearData 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 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 Zone Fonctions clés Cycle/état startYearGameSpec, getYearData, generateEvents, anyDecisionsTaken. Usines / production getTotalFactoryCost, getNeededRawMats, SetProductionsForProduct, computeFactoriesProduction. RH getNumEmployees, computeSalaryAverages, computeResignations. Marché gatherProducts, computeProductAverages, computeScores, generateSales. Fin de génération finalizeTeamsData, saveResults, resetTeam, deleteResults. 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 Ajouter la static data si la décision dépend d'un coût/capacité/pays/employee. Ajouter le champ au YearData/ TeamDecisionsEnt1. Ajouter l'UI dans le domaine approprié : factory, HR, finance, product/market. Ajouter la command et valider cash/capacité côté serveur. Envoyer un message ENT1 et mettre à jour TeamControllerEnt1. Consommer la décision dans la bonne phase de GenerateResultsEnt1. Si l'état survit à l'année, mettre à jour finalizeTeamsData/resetTeam. Si score/ratio change, mettre à jour Eval/rankings/report. Si bots supportés, ajouter la décision dans BotsEnt1. 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 team decisions; factories + raw mats; employees/salaries; production calculée; products gathered; averages/scores; sales; cash/ratios finalisés; 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 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 Définir si la décision appartient au segment, à l'employé, aux avantages, aux postings, CEO ou contrats. Ajouter la donnée au bon niveau de TeamDecisionsHr1/ YearData. Ajouter l'UI sous GameSpecific/hr1/Src/UI. Ajouter la branche dans hr1_custom_command.js. Calculer le coût avec un helper utilsHr1 partagé; ne pas dupliquer le calcul UI. Appliquer/refund le delta de cash correctement lors d'une édition/suppression. Envoyer le RTT approprié. Consommer la décision dans GenerateResultsHr1. Adapter EvalHr1.computeActPerfScore() si le score change. 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) Positionnement MGT1 modélise la gestion d'une équipe de développement : employés, temps de travail, features, segments, budget et lancement d'un jeu. Static data Les datasets comprennent employees, events, features, scenario, segments et act_perf_pond, plus constants. Modèles Unity Employee, EmployeesData; Feature, FeatureResults, GameFeatureData; Segment; LaunchedGame, GameResults; TeamDecisionsMgt1, TeamMgt1, YearData. Logique serveur GameSpecificUtilsMgt1 gère notamment : activation/inactivation d'employés; création et noms d'employés; coûts salaire et coûts additionnels; temps de travail; affectation d'employés aux features; travail requis et complétion de feature; events et rankings. Résultats 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. 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 UI L'UI game-specific contient employees list, team leader, producer, president, feature selectors, budgets, work allocation, lancement du jeu et rapports. Debug 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. Ajouter une feature MGT1 de A à Z Custom commands réelles mgt1_custom_command.js gère : launch_game / delete_game; add_feature / delete_feature; hire_employees / dismiss_employee; change_work_time; save_social_budget / save_remote_budget; save_feature_ratios; change_employee_active_status; employee_level_up / employee_level_down. Carte du moteur getEmployeeSalaryCost() / getEmployeeAdditionnalCost() — coûts; getEmployeeWorkTimeBonus() / getEmployeeWorkTimeCost() — temps; getFeatureMinimumBudget(); setWorkOnFeature(); completeFeature(); getNeededWork(); getEnergyBonus(), getDisciplinaryBonus(), getHappinessBonus(); computeTeamsResults() / generateCurrentYearResults(). Ajouter une nouvelle feature de jeu MGT1 Ajouter la static data sous features si c'est une feature configurable. Ajouter le modèle Unity Feature/GameFeatureData si structure nouvelle. Ajouter le contrôle UI et les ratios/budget si nécessaire. Ajouter la command de création/édition. Vérifier les employés déjà assignés lors d'une suppression : deleteEmployeeFeature() doit nettoyer les travaux devenus invalides. Ajouter la contribution au workNeeded et à computeTeamsResults(). Vérifier la complétion via completeFeature(). Ajouter le résultat/report. Employés : index et noms 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é. Work time : validation serveur obligatoire 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. Debug MGT1 Pour une feature qui ne progresse pas : feature exists → employee works structure → level/workTime → needed work → bonuses → computeTeamsResults → final result. 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 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 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 Ajouter la static data (segment/product spec/media/distrib/etc.). Ajouter la propriété dans Product ou LaunchedProduct selon qu'elle est produit ou lancement. Ajouter le champ UI dans NewProductControl/ LaunchProductControl ou un nouveau contrôle. Transmettre la valeur via save_product ou save_launched_product. Ajouter le coût dans getProductCost()/ getLaunchProductCost() si applicable. Ajouter la valeur au score pertinent : produit, comm ou distribution. Vérifier averages et normalisation si le score est relatif aux concurrents. Vérifier generateSales(). Ajouter le report Unity et éventuellement ActPerf/ranking. 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 produit/lancement persisted; segment cible; averages marché; product score; comm score; distribution score; sales generated; cash/result saved; 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 pour le système Web, et Panel Web — interface superposée au jeu et intégration Unity pour son affichage au-dessus du jeu. Trois mécanismes de présence Mécanisme Donnée et usage Code Présence du compte Web Statut online/away et jeu actif du compte; utilisé par les interfaces Web. AccountSocketService, socket /account, updateStatus et updateGamePresence. Heartbeat du Game Session du jeu et derniers heartbeats utilisés par l'UI Unity pour les utilisateurs de la simulation. User.DoHeartbeat(), endpoint /heartBeat et UsersLastHeartbeats. Présence évaluée By Year Participation enregistrée par étudiant, activité et année, utilisée pour calculer un score. _command/set_presence.js et evaluation.presence[actUid][yearId]. 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 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 Définir si le changement concerne produit, lancement initial, relaunch, country ou stock. Ajouter la donnée dans Product/ LaunchedProduct/ Country côté Unity. Ajouter la saisie UI, notamment CountryLaunchControl si géographique. Ajouter la valeur aux save commands. Adapter launchTypeCalculation/ relaunchSegment/ launchCountryCalculation selon le cas. Vérifier les refunds/redistribution de stock lors d'une modification/suppression. Ajouter l'effet aux scores/ventes. Vérifier loyalty + notoriety sur l'année suivante. 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.