@micheeeeel/yinyangapp 2.5.1 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/bundle.js +1 -1
  2. package/dist/types/adapters/yasuniCauseAdapter.d.ts +31 -5
  3. package/dist/types/api.d.ts +5 -1
  4. package/dist/types/components/controller/YinYangController.d.ts +63 -679
  5. package/dist/types/components/controller/action-stack/ActionStackController.d.ts +35 -0
  6. package/dist/types/components/controller/action-stack/ActionStackManager.d.ts +9 -0
  7. package/dist/types/components/controller/action-stack/StackFocusController.d.ts +105 -0
  8. package/dist/types/components/controller/actions/ActionProjectionService.d.ts +32 -3
  9. package/dist/types/components/controller/actions/ChildActionMenuController.d.ts +8 -4
  10. package/dist/types/components/controller/actions/CoreEventMenuController.d.ts +13 -13
  11. package/dist/types/components/controller/actions/KeyEventController.d.ts +192 -0
  12. package/dist/types/components/controller/actions/radialItemMenu.d.ts +0 -9
  13. package/dist/types/components/controller/children/ChildrenBuilder.d.ts +95 -0
  14. package/dist/types/components/controller/children/ComponentSwitchCoordinator.d.ts +80 -0
  15. package/dist/types/components/controller/debug/ControllerDebugTools.d.ts +81 -0
  16. package/dist/types/components/controller/force-field/ForceFieldComputationService.d.ts +3 -15
  17. package/dist/types/components/controller/force-field/ForceFieldDetailsPanel.d.ts +120 -66
  18. package/dist/types/components/controller/force-field/ForceFieldDetailsPresenter.d.ts +139 -0
  19. package/dist/types/components/controller/force-field/ForceFieldRenderer.d.ts +0 -8
  20. package/dist/types/components/controller/force-field/IncomingForceFieldService.d.ts +53 -0
  21. package/dist/types/components/controller/force-field/PropagationAnimator.d.ts +56 -0
  22. package/dist/types/components/controller/force-field/edgeIdentity.d.ts +28 -0
  23. package/dist/types/components/controller/force-field/incomingContinuation.d.ts +22 -0
  24. package/dist/types/components/controller/force-field/propagation-viz/PropagationCometPlayer.d.ts +60 -0
  25. package/dist/types/components/controller/force-field/propagation-viz/propagationVizLists.d.ts +51 -0
  26. package/dist/types/components/controller/force-field/propagation-viz/propagationVizModel.d.ts +60 -0
  27. package/dist/types/components/controller/force-field/propagationTree.d.ts +32 -0
  28. package/dist/types/components/controller/force-field/shieldViewModel.d.ts +79 -0
  29. package/dist/types/components/controller/force-field/types.d.ts +37 -10
  30. package/dist/types/components/controller/labels/ComponentTitlePresenter.d.ts +99 -0
  31. package/dist/types/components/controller/navigation/DiagramNavigator.d.ts +64 -0
  32. package/dist/types/components/controller/odd/OddViewManager.d.ts +1 -1
  33. package/dist/types/components/controller/search/SearchPanelSynchronizer.d.ts +9 -0
  34. package/dist/types/components/controller/state/ControllerSession.d.ts +107 -0
  35. package/dist/types/components/controller/state/ControllerUiCallbacks.d.ts +60 -0
  36. package/dist/types/components/controller/state/YinYangControllerState.d.ts +4 -5
  37. package/dist/types/components/controller/types.d.ts +103 -0
  38. package/dist/types/components/controller/utils/actionCatalog.d.ts +15 -0
  39. package/dist/types/components/controller/utils/forceFieldPresentation.d.ts +14 -8
  40. package/dist/types/components/search/GlobalSearchController.d.ts +68 -9
  41. package/dist/types/components/view/BorderDrawer.d.ts +5 -0
  42. package/dist/types/components/workspace/workspaceTemplate.d.ts +1 -1
  43. package/dist/types/config/propagationCometConfig.d.ts +9 -36
  44. package/dist/types/config/propagationCometVizConfig.d.ts +34 -0
  45. package/dist/types/data/activeDiagram.d.ts +4 -6
  46. package/dist/types/data/adverse-action-planner.d.ts +22 -0
  47. package/dist/types/data/catalogue-coverage.d.ts +26 -0
  48. package/dist/types/data/combo-engine.d.ts +76 -21
  49. package/dist/types/data/contacts/buildContactSphere.d.ts +23 -4
  50. package/dist/types/data/contacts/index.d.ts +6 -3
  51. package/dist/types/data/propagation-graph.d.ts +16 -0
  52. package/dist/types/data/stability-shield.d.ts +21 -0
  53. package/dist/types/data/yasuni_actions.d.ts +38 -5
  54. package/dist/types/data/yasuni_cause.d.ts +7 -0
  55. package/dist/types/data/yasuni_key_events.d.ts +3 -2
  56. package/dist/types/interfaces/IPropagationGraph.d.ts +39 -0
  57. package/dist/types/interfaces/IYinYangController.d.ts +2 -2
  58. package/dist/types/utils/actionPropagation.d.ts +104 -32
  59. package/dist/types/utils/adverseMode.d.ts +14 -0
  60. package/package.json +4 -2
  61. package/dist/types/components/controller/utils/forceFieldMetadata.d.ts +0 -4
  62. package/dist/types/data/yasuni_graph.d.ts +0 -22
  63. package/dist/types/data/yasuni_propagation_layers.d.ts +0 -3
  64. package/dist/types/interfaces/ICauseGraph.d.ts +0 -9
  65. package/dist/types/interfaces/IPropagationLayers.d.ts +0 -27
  66. package/dist/types/utils/causeGraph.d.ts +0 -6
@@ -47,11 +47,46 @@ export declare class ActionStackController {
47
47
  maxDeltaMultiplier?: number;
48
48
  direction?: "forward" | "backward";
49
49
  }): void;
50
+ /**
51
+ * Guide de placement d'un prérequis : un clic sur un prérequis absent de la
52
+ * pile (carte combo en mode STACK) ouvre RECHERCHE sur cette action et
53
+ * mémorise ici le contexte du combo. Dès que l'utilisateur glisse cette
54
+ * action vers la pile, les barres de position du combo réapparaissent —
55
+ * sans avoir à déposer puis recliquer sur la carte combo.
56
+ */
57
+ private _prereqDragGuide;
58
+ /** Vrai pendant un glisser guidé : les re-rendus du panneau (bascule
59
+ * RECHERCHE → STACK) ne doivent pas effacer les barres en plein geste. */
60
+ private _prereqDragGuideActive;
61
+ setPrereqDragGuide(actionId: string, context: Parameters<ActionStackController["highlightComboContext"]>[0]): void;
62
+ /** Début du glisser de `actionId` vers la pile : affiche les barres du
63
+ * combo mémorisé si cette action en est le prérequis. Retourne l'item de
64
+ * pile portant le combo (pour réafficher son bloc), ou null. */
65
+ beginPrereqDragGuide(actionId: string): string | null;
66
+ /** Guide en cours de glisser : action glissée et item portant le combo. */
67
+ getActivePrereqDragGuide(): {
68
+ actionId: string;
69
+ sourceItemId: string;
70
+ } | null;
71
+ /**
72
+ * Historique slot-paddé (cf. getSlotPaddedActionIds) que verrait l'item
73
+ * `anchorItemId` si `insertedActionId` était déposé à l'emplacement
74
+ * actuellement survolé — même arithmétique d'insertion avec décalage que
75
+ * ActionStackState.addItem, sans muter la pile. null hors zone de dépôt.
76
+ */
77
+ getPriorIdsIfInsertedAtPreview(anchorItemId: string, insertedActionId: string): string[] | null;
78
+ /** Fin du glisser (dépôt ou abandon). Les barres restent affichées ; un
79
+ * dépôt consomme le guide, un abandon le garde pour un nouvel essai. */
80
+ endPrereqDragGuide(dropped: boolean): void;
50
81
  /** Efface le surlignage combo et restaure l'apparence normale du stack. */
51
82
  clearComboHighlight(): void;
52
83
  setBlockHighlightCallback(cb: (id: string | null) => void): void;
53
84
  setSatelliteHighlightCallback(cb: (id: string | null) => void): void;
54
85
  getItemById(id: string): ActionStackItem | undefined;
86
+ /** Même effet qu'un clic sur le libellé de l'item (selectSatelliteForItem) :
87
+ * sélection de son satellite ou de sa Facette, focus — ou défocus s'il
88
+ * était déjà en focus. Sert aux bulles du diagramme en mode STACK. */
89
+ selectItemAsFromLabel(stackItemId: string): void;
55
90
  highlightByStackItemId(id: string | null): void;
56
91
  /** Focus persistant (miroir du bloc en focus dans le panneau) — survit aux
57
92
  * re-rendus, updateVisual relisant _focusedItemId. */
@@ -92,6 +92,15 @@ export declare class ActionStackManager {
92
92
  getWeightDeltaByComponentOddCutting(): Map<string, number>;
93
93
  clearWeightPreview(): void;
94
94
  resetLastComboResults(): void;
95
+ /**
96
+ * Résultats combo d'une action. En mode STACK, ils tiennent compte de la
97
+ * pile (résultats de l'item s'il est empilé, sinon évaluation en haut de
98
+ * pile). En mode RECHERCHE, l'action est montrée BRUTE : la pile est
99
+ * ignorée (historique vide), ses prérequis apparaissent donc non joués,
100
+ * même s'ils sont empilés — seul le mode STACK fait jouer la pile.
101
+ * Exception : une sécurisation y est évaluée protecteurs supposés joués
102
+ * (×1), sinon elle serait toujours à ×0 et masquerait la propagation.
103
+ */
95
104
  computeComboResultsForAction(actionId: string): ComboResult[] | undefined;
96
105
  validateActionSelection(childController: IYinYangController, actionId: string, label: string, insertAtVisibleIndex?: number | null, entryOrigin?: {
97
106
  x: number;
@@ -0,0 +1,105 @@
1
+ import { IYinYangController } from "../../../interfaces/IYinYangController";
2
+ import { YinYangInternalModel } from "../../../interfaces/IYinYangInternalModel";
3
+ import { ChildrenOrbitEntry } from "../utils/childrenOrbit";
4
+ import { ChildActionMenuController } from "../actions/ChildActionMenuController";
5
+ import { ActionStackController, ActionStackItem } from "./ActionStackController";
6
+ import { ForceFieldDetailsPanel } from "../force-field/ForceFieldDetailsPanel";
7
+ import { YinYangControllerState } from "../state/YinYangControllerState";
8
+ import { PropagationAnimator } from "../force-field/PropagationAnimator";
9
+ import { OddViewManager } from "../odd/OddViewManager";
10
+ export type StackFocusControllerDeps = {
11
+ actionStackController: ActionStackController;
12
+ buildChildrenOrbitData: () => ChildrenOrbitEntry[];
13
+ buildForceFieldSourceIndicesFromStackItems: (items: ActionStackItem[]) => number[];
14
+ childActionMenuController: ChildActionMenuController;
15
+ getChildControllers: () => IYinYangController[];
16
+ childOddMap: Map<string, {
17
+ oddId: string;
18
+ score: number;
19
+ cuttingIndex: number;
20
+ }>;
21
+ forceFieldDetailsPanel: ForceFieldDetailsPanel;
22
+ getAllComponents: () => YinYangInternalModel["components"];
23
+ getCurrentComponentOrUndefined: () => YinYangInternalModel["components"][number] | undefined;
24
+ oddViewManager: OddViewManager;
25
+ propagationAnimator: PropagationAnimator;
26
+ state: YinYangControllerState;
27
+ updateChildActionMenu: (orbitData: ChildrenOrbitEntry[]) => void;
28
+ };
29
+ /** Focus et emphase d'un élément de pile : satellite source mis en valeur,
30
+ * identité de la source pour l'atténuation du champ, bulle d'action en focus. */
31
+ export declare class StackFocusController {
32
+ private deps;
33
+ constructor(deps: StackFocusControllerDeps);
34
+ /** Focus persistant — null pour l'effacer. */
35
+ setFocusedStackItem(stackItemId: string | null): void;
36
+ /**
37
+ * Clic sur le libellé d'un item déjà en focus, alors que son bloc est
38
+ * visible dans le panneau (satellite porteur sélectionné) : désactive le
39
+ * focus et retourne true — l'appelant n'a alors ni navigation ni scroll à
40
+ * faire. Si le bloc n'est pas visible (l'utilisateur a navigué ailleurs
41
+ * entre-temps), le clic redevient une sélection normale : false.
42
+ */
43
+ toggleStackItemFocusFromStack(stackItemId: string): boolean;
44
+ /** Aperçu par survol — null à la sortie, qui retombe sur le focus. */
45
+ setStackItemEmphasisPreview(stackItemId: string | null): void;
46
+ /**
47
+ * Point d'accroche unique de la mise en valeur du satellite source de
48
+ * l'action portant l'emphase (aperçu ?? focus). Réutilise pour l'instant
49
+ * le glow de highlightOddByStackItemId (ou le segment de bordure si la
50
+ * source est dans une autre facette) — l'anneau animé viendra ici.
51
+ * `clearIfNone` : sans emphase, efface la mise en valeur (sorties de
52
+ * survol, défocus) ; false pour les simples re-applications (satellites
53
+ * recréés, fin d'un survol de satellite) afin de ne pas écraser d'autres
54
+ * highlights en cours.
55
+ */
56
+ applyFocusedSourceHighlight(clearIfNone?: boolean): void;
57
+ /** Memo à une entrée de resolveEmphasisSourceChildIndex (lu chaque frame). */
58
+ private _emphasisSourceMemo;
59
+ /**
60
+ * Index orbital du satellite source de l'action portant l'emphase, ou null
61
+ * si rien ne doit être atténué : hors mode STACK (un aperçu RECHERCHE ne
62
+ * doit pas hériter d'un focus STACK résiduel), item absent, Événement Clé
63
+ * (pas de champ de force), ou source dans une autre facette (tout reste
64
+ * plein, la bordure source est surlignée par highlightOddByStackItemId).
65
+ * Résolution par oddId + cuttingIndex (buildForceFieldSourceIndicesFromStackItems),
66
+ * robuste au rebuild de childOddMap ; la clé du memo suit la convention de
67
+ * ForceFieldLifecycleController._buildPropCacheKey.
68
+ */
69
+ resolveEmphasisSourceChildIndex(): number | null;
70
+ /** Identité stable (sourceOriginKey) du satellite source d'un item de la
71
+ * pile — null pour un Événement Clé ou une facette introuvable. */
72
+ stackItemSourceOriginKey(item: ActionStackItem): string | null;
73
+ private _emphasisKeyMemo;
74
+ /** Clé de la source de l'action portant l'emphase (aperçu ?? focus), quelle
75
+ * que soit sa facette ; null hors STACK ou sans emphase. Lue à chaque frame. */
76
+ resolveEmphasisSourceOriginKey(): string | null;
77
+ /** Memo à une entrée de resolveFocusedActionSource (lu à chaque update du menu). */
78
+ private _focusSourceMemo;
79
+ /**
80
+ * Action en focus (focus persistant seulement, pas l'aperçu par survol —
81
+ * une bulle qui apparaît/disparaît au survol serait du bruit), résolue en
82
+ * (actionId, clé et index orbital du satellite source) dans la facette
83
+ * courante. null hors mode STACK, sans focus, pour un Événement Clé, ou si
84
+ * la source vit dans une autre facette.
85
+ */
86
+ resolveFocusedActionSource(): {
87
+ actionId: string;
88
+ sourceKey: string;
89
+ sourceIndex: number;
90
+ } | null;
91
+ /**
92
+ * Point de départ « bulle » du champ de force de l'action en focus : la
93
+ * bulle de son action dans le menu radial si le satellite source est celui
94
+ * sélectionné, sinon la bulle détachée posée sur la source. null si
95
+ * aucune bulle n'est visible (le champ démarre alors au satellite).
96
+ */
97
+ resolveFocusBubbleStub(): {
98
+ sourceChildIndex: number;
99
+ center: {
100
+ x: number;
101
+ y: number;
102
+ };
103
+ } | null;
104
+ highlightOddByStackItemId(stackItemId: string | null): void;
105
+ }
@@ -1,5 +1,5 @@
1
1
  import { YinYangInternalModel } from "../../../interfaces/IYinYangInternalModel";
2
- import { type PropagatedActionEdge } from "../../../utils/actionPropagation";
2
+ import { type PropagatedActionEdge, type PropagationTruncation } from "../../../utils/actionPropagation";
3
3
  import { ForceFieldLink } from "../force-field/types";
4
4
  export type ProjectedCuttingState = {
5
5
  cuttingId: number;
@@ -17,6 +17,12 @@ export type GhostProjectionResult = {
17
17
  export type SequentialActionProjectionInput = {
18
18
  sourceOddId: string;
19
19
  sourceCuttingIndex: number;
20
+ /** Bouclier de stabilité (points d'état) que cette action crée sur son
21
+ * satellite source une fois résolue — issu de ses slots de stabilisation
22
+ * déclenchés (voir stabilityShieldFromResults). Actif pour toutes les
23
+ * résolutions suivantes de la séquence, jamais consommé ; le plus fort
24
+ * remplace le précédent sur un même satellite. */
25
+ shield?: number;
20
26
  };
21
27
  export type EmptyGhostProjectionState = {
22
28
  projectedGhostTensionByOddId: null;
@@ -30,10 +36,31 @@ export declare function projectGhostStateForIncomingLinks(params: {
30
36
  incomingLinks: ForceFieldLink[];
31
37
  }): GhostProjectionResult | null;
32
38
  export type ActionSequenceStateSnapshot = Map<string, Map<number, number>>;
39
+ /** Réduction appliquée par un bouclier de stabilité lors d'une étape :
40
+ * le satellite recevait `aggregated` (total négatif direct + propagé),
41
+ * il ne reçoit finalement que `applied`. */
42
+ export type ShieldAbsorption = {
43
+ oddId: string;
44
+ cuttingId: number;
45
+ shield: number;
46
+ aggregated: number;
47
+ applied: number;
48
+ };
49
+ /**
50
+ * Bouclier de stabilité sur UNE résolution : pour chaque satellite protégé,
51
+ * la variation de toute l'étape (état projeté − état de départ) agrège déjà
52
+ * le delta direct et tous les chemins propagés ; le bouclier réduit ce
53
+ * total une seule fois — jamais chaque contribution séparément, jamais
54
+ * au-delà de 0. Mute `projectedStates` et rend les réductions appliquées.
55
+ */
56
+ export declare function applyStabilityShieldsToResolution(startStates: Record<string, Record<number, number>>, projectedStates: Record<string, Record<number, number>>, activeShields: Map<string, Map<number, number>>): ShieldAbsorption[];
33
57
  export type ActionSequenceStepInfo = {
34
58
  actionIndex: number;
35
59
  action: SequentialActionProjectionInput;
36
60
  effectiveDelta: number;
61
+ /** Boucliers ayant réduit une contribution négative de cette étape — vide
62
+ * si aucun satellite protégé n'a été touché négativement. */
63
+ shieldAbsorptions: ShieldAbsorption[];
37
64
  /** Clone of the fractional accumulator just before this step is applied. */
38
65
  statesBefore: ActionSequenceStateSnapshot;
39
66
  /** Clone of the fractional accumulator just after this step is applied. */
@@ -44,13 +71,16 @@ export type ActionSequenceStepInfo = {
44
71
  * callers build a descriptive breakdown (e.g. UI badges) that's guaranteed
45
72
  * consistent with the state diff, without re-running propagation. */
46
73
  edges: PropagatedActionEdge[];
74
+ /** Passes locales interrompues par la garde technique pendant cette étape
75
+ * (PROP-01) — vide si la résolution est complète. Un appelant qui affiche
76
+ * l'étape doit signaler un résultat partiel plutôt que le taire. */
77
+ truncations: PropagationTruncation[];
47
78
  };
48
79
  export declare function projectGhostStateForActionSequence(params: {
49
80
  component: YinYangInternalModel["components"][number] | undefined;
50
81
  allComponents: YinYangInternalModel["components"] | undefined;
51
82
  actions: SequentialActionProjectionInput[];
52
83
  actionDelta: number;
53
- actionPropagationFactor: number;
54
84
  /** Delta effectif par action (index-aligné sur `actions`). Quand fourni,
55
85
  * remplace `actionDelta` pour chaque action individuellement. */
56
86
  actionDeltas?: number[];
@@ -67,7 +97,6 @@ export declare function projectGhostStateForComponent(params: {
67
97
  sourceOddId: string | undefined;
68
98
  sourceCuttingIndex: number;
69
99
  actionDelta: number;
70
- actionPropagationFactor: number;
71
100
  }): GhostProjectionResult | null;
72
101
  export declare function getOriginalComponentTension(component: YinYangInternalModel["components"][number] | undefined): number | null;
73
102
  export declare function getOriginalOddTension(component: YinYangInternalModel["components"][number] | undefined, oddId: string): number | null;
@@ -23,14 +23,18 @@ type ChildActionMenuContext = {
23
23
  childActionIconFontScale: number;
24
24
  /** Actions filtrées pour le cutting actif (depuis yasuniCause + actionMap). */
25
25
  actionsForActiveChild: ActionDefinition[];
26
+ /** Mode STACK : la seule bulle déployée est celle de l'item en focus, et
27
+ * son clic vaut clic sur le libellé de cet item dans la pile (ni aperçu,
28
+ * ni glisser). null hors STACK. */
29
+ onStackBubbleClick: (() => void) | null;
26
30
  };
27
31
  export declare class ChildActionMenuController {
28
32
  private readonly view;
29
33
  private readonly getChildRenderedCenter;
30
34
  private readonly refreshActionStackVisual;
31
- /** Aperçu — 1er clic sur une action : affiche sa carte, ne joue rien. */
35
+ /** Aperçu — clic sur une action : affiche sa carte, ne joue rien. */
32
36
  private readonly activateActionFromChild;
33
- /** Validation — 2e clic sur l'action déjà en aperçu : l'ajoute à la pile. */
37
+ /** Validation — dépôt d'une bulle glissée sur la pile : l'y ajoute. */
34
38
  private readonly validateActionSelection;
35
39
  /** Glisser une bulle vers une position précise de la pile — cœur partagé
36
40
  * avec le glisser-déposer depuis la hiérarchie RECHERCHE et depuis le
@@ -68,9 +72,9 @@ export declare class ChildActionMenuController {
68
72
  /** Centre de la bulle détachée si elle porte `actionId`, sinon null. */
69
73
  getDetachedCenter(actionId: string): Point | null;
70
74
  constructor(view: IYinYangView, getChildRenderedCenter: (childController: IYinYangController) => Point | null, refreshActionStackVisual: () => void,
71
- /** Aperçu — 1er clic sur une action : affiche sa carte, ne joue rien. */
75
+ /** Aperçu — clic sur une action : affiche sa carte, ne joue rien. */
72
76
  activateActionFromChild: (childController: IYinYangController, actionId: string, label?: string) => void,
73
- /** Validation — 2e clic sur l'action déjà en aperçu : l'ajoute à la pile. */
77
+ /** Validation — dépôt d'une bulle glissée sur la pile : l'y ajoute. */
74
78
  validateActionSelection: (childController: IYinYangController, actionId: string, label: string, insertAtVisibleIndex?: number | null, entryOrigin?: Point) => void,
75
79
  /** Glisser une bulle vers une position précise de la pile — cœur partagé
76
80
  * avec le glisser-déposer depuis la hiérarchie RECHERCHE et depuis le
@@ -1,5 +1,5 @@
1
1
  import { IYinYangView } from "../../../interfaces/IYinYangView";
2
- import type { KeyEventDefinition } from "../../../data/yasuni_key_events";
2
+ import { type KeyEventDefinition } from "../../../data/yasuni_key_events";
3
3
  import { type Point } from "./radialItemMenu";
4
4
  type CoreEventMenuContext = {
5
5
  /** null/false ferme le menu (même logique que activeActionMenuChildKey côté satellite). */
@@ -30,18 +30,18 @@ type CoreEventMenuContext = {
30
30
  * Menu radial des Événements Clés, déclenché par un clic sur le cercle
31
31
  * central (yin-yang) de la facette active — même langage visuel et même
32
32
  * sémantique de clic que ChildActionMenuController (bulles + icône,
33
- * chorégraphie D3 en éventail, 1er clic = aperçu / 2e clic sur l'item déjà
34
- * actif = validation — factorisées dans radialItemMenu.ts), mais ancré sur
33
+ * chorégraphie D3 en éventail factorisée dans radialItemMenu.ts ; clic =
34
+ * aperçu, glisser vers la pile = validation), mais ancré sur
35
35
  * le centre fixe de la facette plutôt que sur un satellite, et déployé à
36
36
  * angle égal sur 360° plutôt que sur un axe source.
37
37
  */
38
38
  export declare class CoreEventMenuController {
39
39
  private readonly view;
40
- /** Aperçu — 1er clic sur un événement : affiche sa carte, ne joue rien. */
41
- private readonly previewKeyEvent;
42
- /** Validation — 2e clic sur l'événement déjà en aperçu : l'ajoute à la pile
43
- * (au sommet par défaut, ou à insertAtVisibleIndex si fourni par un
44
- * glisser-déposer — voir startKeyEventDrag). */
40
+ /** Clic sur une bulle — aperçu, ou focus de son item de pile en STACK
41
+ * (voir KeyEventController.onKeyEventBubbleClick). */
42
+ private readonly onKeyEventClick;
43
+ /** Validation — dépôt d'une bulle glissée sur la pile : l'y ajoute à
44
+ * insertAtVisibleIndex (voir startKeyEventDrag). */
45
45
  private readonly validateKeyEventSelection;
46
46
  /** Glisser une bulle vers une position précise de la pile — même cœur
47
47
  * partagé que ChildActionMenuController.startActionDrag (voir
@@ -66,11 +66,11 @@ export declare class CoreEventMenuController {
66
66
  * impulsion ne doit pas relancer toute la chorégraphie. */
67
67
  private linkStyleGetter;
68
68
  constructor(view: IYinYangView,
69
- /** Aperçu — 1er clic sur un événement : affiche sa carte, ne joue rien. */
70
- previewKeyEvent: (keyEvent: KeyEventDefinition) => void,
71
- /** Validation — 2e clic sur l'événement déjà en aperçu : l'ajoute à la pile
72
- * (au sommet par défaut, ou à insertAtVisibleIndex si fourni par un
73
- * glisser-déposer — voir startKeyEventDrag). */
69
+ /** Clic sur une bulle — aperçu, ou focus de son item de pile en STACK
70
+ * (voir KeyEventController.onKeyEventBubbleClick). */
71
+ onKeyEventClick: (keyEvent: KeyEventDefinition) => void,
72
+ /** Validation — dépôt d'une bulle glissée sur la pile : l'y ajoute à
73
+ * insertAtVisibleIndex (voir startKeyEventDrag). */
74
74
  validateKeyEventSelection: (keyEvent: KeyEventDefinition, insertAtVisibleIndex?: number | null, entryOrigin?: Point) => void,
75
75
  /** Glisser une bulle vers une position précise de la pile — même cœur
76
76
  * partagé que ChildActionMenuController.startActionDrag (voir
@@ -0,0 +1,192 @@
1
+ import { IYinYangView } from "../../../interfaces/IYinYangView";
2
+ import { YinYangInternalModel } from "../../../interfaces/IYinYangInternalModel";
3
+ import { type KeyEventDefinition } from "../../../data/yasuni_key_events";
4
+ import { ChildrenOrbitEntry } from "../utils/childrenOrbit";
5
+ import { CoreEventMenuController } from "./CoreEventMenuController";
6
+ import { ActionStackController } from "../action-stack/ActionStackController";
7
+ import { ForceFieldDetailsPanel } from "../force-field/ForceFieldDetailsPanel";
8
+ import { EventFieldRenderer } from "../force-field/EventFieldRenderer";
9
+ import { YinYangControllerState } from "../state/YinYangControllerState";
10
+ import { ComponentTitlePresenter } from "../labels/ComponentTitlePresenter";
11
+ import { ControllerSession } from "../state/ControllerSession";
12
+ import type { ControllerUiCallbacks } from "../state/ControllerUiCallbacks";
13
+ import { EventFieldLifecycleController } from "../force-field/EventFieldLifecycleController";
14
+ import { ActionStackManager } from "../action-stack/ActionStackManager";
15
+ export type KeyEventControllerDeps = {
16
+ actionStackController: ActionStackController;
17
+ actionStackManager: ActionStackManager;
18
+ animateChildrenHide: () => void;
19
+ buildChildrenOrbitData: () => ChildrenOrbitEntry[];
20
+ closeChildActionMenu: () => void;
21
+ coreEventMenuController: CoreEventMenuController;
22
+ eventFieldLifecycleController: EventFieldLifecycleController;
23
+ eventFieldRenderer: EventFieldRenderer;
24
+ forceFieldDetailsPanel: ForceFieldDetailsPanel;
25
+ getComponentBorderTargetPoint: (componentId: string) => {
26
+ x: number;
27
+ y: number;
28
+ } | null;
29
+ getCurrentComponentOrUndefined: () => YinYangInternalModel["components"][number] | undefined;
30
+ navigateToPrereqAction: (actionId: string) => void;
31
+ prepareForComponentSwitch: (targetComponentId: string) => void;
32
+ session: ControllerSession;
33
+ state: YinYangControllerState;
34
+ titlePresenter: ComponentTitlePresenter;
35
+ ui: ControllerUiCallbacks;
36
+ view: IYinYangView;
37
+ };
38
+ /** Événements Clés : menu radial du cœur, aperçu de carte, champ
39
+ * d'événement et ajout à la pile. */
40
+ export declare class KeyEventController {
41
+ private deps;
42
+ constructor(deps: KeyEventControllerDeps);
43
+ /**
44
+ * Appelé par YinYangDrawer quand le petit cercle "œil" du lobe dominant
45
+ * (yin ou yang, selon Composante.orientation) est cliqué — jamais l'autre
46
+ * lobe, jamais un satellite (garde déjà appliquée côté YinYangDrawer, même
47
+ * principe que onDiagramOddArcClicked pour les arcs ODD). Ouvre/ferme le
48
+ * menu radial des Événements Clés de la facette active.
49
+ */
50
+ onCoreEventNucleusClicked(): void;
51
+ notifyKeyEventMenuState(open: boolean): void;
52
+ private toggleCoreEventMenu;
53
+ /**
54
+ * Fermeture du Champ d'événement : l'Événement Clé en aperçu et les bulles
55
+ * du menu cœur — chemin unique partagé par la fermeture de facette
56
+ * (closeCurrentComposante) et par l'exclusivité action/événement
57
+ * (openActionMenuFromChild). No-op si rien n'est ouvert.
58
+ *
59
+ * hide() d'abord pour la rétraction immédiate du souffle (le geste
60
+ * d'ouverture joué à l'envers — cf. prepareForComponentSwitch),
61
+ * updateCoreEventMenu() ensuite pour replier les bulles et laisser
62
+ * refreshTargets constater qu'il n'y a plus rien à dessiner. La carte du
63
+ * panneau RECHERCHE part avec : elle décrit l'Événement Clé qu'on vient de
64
+ * retirer du diagramme, et rien ne garantit qu'un autre bloc prenne sa
65
+ * place (ouvrir un satellite n'affiche qu'un descriptif, pas un bloc).
66
+ */
67
+ closeKeyEventField(): void;
68
+ updateCoreEventMenu(): void;
69
+ /**
70
+ * Événements Clés qui pilotent le Champ d'événement en ce moment — source
71
+ * unique de vérité pour EventFieldLifecycleController (cibles) comme pour
72
+ * getEventFieldSource (point d'émission).
73
+ *
74
+ * Deux régimes, mêmes règles de priorité que le champ de force d'action :
75
+ * un APERÇU (clic sur une bulle du menu cœur ou sur l'entrée de l'arbre
76
+ * RECHERCHE) prend toujours la main — c'est le geste que l'utilisateur
77
+ * vient de faire, quel que soit l'onglet ; sinon, en mode STACK, ce sont
78
+ * les Événements Clés COCHÉS de la pile qui décident, exactement comme les
79
+ * actions cochées y décident des champs de force. En mode RECHERCHE sans
80
+ * aperçu, rien : la pile n'a pas voix au chapitre.
81
+ *
82
+ * Le renderer n'a qu'une source (le souffle part d'UN point, voir
83
+ * EventFieldRenderer), donc on ne renvoie jamais qu'un jeu d'événements
84
+ * partageant la même : ceux nés de la facette courante partent tous de son
85
+ * cœur et se cumulent ; à défaut, on retombe sur le plus récent des
86
+ * événements distants qui atteignent cette facette (mode entrant, source =
87
+ * l'arc de sa facette d'origine).
88
+ */
89
+ getEventFieldKeyEvents(): KeyEventDefinition[];
90
+ /**
91
+ * Point d'émission du Champ d'événement : le CŒUR du yin/yang, c'est-à-dire
92
+ * le disque central du lobe correspondant à l'orientation de la facette
93
+ * courante — le cœur noir du yang sur une facette Yang, le cœur blanc du
94
+ * yin sur une facette Yin. Ce n'est PAS le centre géométrique du symbole :
95
+ * les deux lobes ont des rayons qui varient avec le score, donc le cœur se
96
+ * déplace le long de l'axe vertical au fil de la partie, d'où la
97
+ * relecture de la géométrie à chaque frame. Injecté dans
98
+ * EventFieldLifecycleController (getEventFieldSource), rappelé à chaque
99
+ * frame de son animation.
100
+ *
101
+ * `geometry.yin/yang.centerY` est la position CANONIQUE (pré-rotation) :
102
+ * pendant une rotation Yin/Yang (AnimationsMngr), le cœur visible se
103
+ * déplace via un `transform` écrit à même `graphElementGroup` à chaque
104
+ * frame, sans jamais toucher `geometry`. On lit donc la position réelle
105
+ * via getLiveLobeCoreCenter plutôt que la géométrie statique, pour que les
106
+ * anneaux du champ d'événement suivent le cœur qui tourne au lieu de
107
+ * naître à une origine figée.
108
+ */
109
+ getEventFieldSource(): {
110
+ center: {
111
+ x: number;
112
+ y: number;
113
+ } | null;
114
+ radius: number;
115
+ };
116
+ /**
117
+ * Résout un point porté par `lobeGroup` (coordonnées locales, ici le
118
+ * centre canonique du cœur) dans le repère de globalGroup — celui où
119
+ * EventFieldRenderer dessine (voir EventFieldLifecycleController,
120
+ * getGlobalGroupNode). Passe par les CTM SVG plutôt que de reparser
121
+ * l'attribut `transform` (comme getChildRenderedCenter le fait pour une
122
+ * simple translation) : le transform posé sur graphElementGroup pendant
123
+ * une rotation combine rotate ET translate, et cette composition suit
124
+ * fidèlement AnimationsMngr sans dépendre de sa formule exacte — un
125
+ * changement futur de celle-ci n'a pas besoin d'être répercuté ici.
126
+ */
127
+ private getLiveLobeCoreCenter;
128
+ /**
129
+ * Rejoue l'aperçu d'un Événement Clé depuis une navigation Recherche
130
+ * (historique précédent/suivant) — pendant minimal de searchNavigateToAction,
131
+ * sans mise au point du diagramme (pas de satellite/facette à recentrer,
132
+ * l'entrée d'arbre dédiée est hors scope pour l'instant).
133
+ */
134
+ searchNavigateToKeyEvent(keyEventId: string): void;
135
+ /**
136
+ * Rétablit le Champ d'événement d'un Événement Clé DÉJÀ affiché à droite —
137
+ * pendant de restoreActionDiagramState pour les événements, utilisé au
138
+ * retour en mode RECHERCHE (voir PanelManager.resyncDiagramToMasterPanel).
139
+ *
140
+ * Volontairement plus étroit que previewKeyEvent : ni carte redessinée, ni
141
+ * notification de navigation (donc pas d'entrée d'historique en double) —
142
+ * le panneau montre déjà cet événement, c'est le diagramme qui le rattrape.
143
+ * On garde en revanche l'exclusivité action/événement : les deux champs ne
144
+ * cohabitent jamais.
145
+ *
146
+ * Si la facette courante n'est plus celle de l'événement (l'utilisateur a
147
+ * navigué ailleurs pendant le mode STACK), on revient d'abord sur sa facette
148
+ * d'origine — mêmes gestes que searchSetKeyEventMenuOpen : l'aperçu survit
149
+ * au switch (prepareForComponentSwitch le garde vivant, destination =
150
+ * origine) et le menu cœur se redéploie à l'arrivée avec sa bulle active.
151
+ */
152
+ restoreKeyEventDiagramState(keyEventId: string): void;
153
+ /**
154
+ * Dépli/repli du groupe "Événements Clés" depuis la hiérarchie RECHERCHE —
155
+ * pendant exact du clic sur le cœur du yin/yang (onCoreEventNucleusClicked),
156
+ * pour que l'arbre et le diagramme montrent le même menu déployé : ouvrir le
157
+ * groupe déploie les bulles de la facette, le refermer les rétracte (même
158
+ * règle que le dépli d'un Satellite, qui déploie ses boutons d'Action).
159
+ *
160
+ * Le repli passe par closeKeyEventField plutôt que par le toggle : il doit
161
+ * aussi emporter l'Événement Clé en aperçu (et sa carte), sinon une bulle
162
+ * validée resterait allumée sous un groupe replié.
163
+ */
164
+ searchSetKeyEventMenuOpen(componentIndex: number, open: boolean): void;
165
+ /** Ouverture idempotente du menu cœur — no-op s'il est déjà déployé (le
166
+ * toggle le refermerait). */
167
+ openCoreEventMenu(): void;
168
+ /** Seul point de mutation de activeKeyEventId — resynchronise la ligne
169
+ * Événement Clé du titre (voir getCurrentSatelliteTitle) à chaque
170
+ * changement, exactement comme setActiveChildKey le fait pour le
171
+ * satellite. */
172
+ setActiveKeyEventId(keyEventId: string | null): void;
173
+ /**
174
+ * Clic sur une bulle du menu cœur. En STACK : clic sur le libellé de son
175
+ * item de pile — celui en focus s'il s'agit de cet événement, sinon le
176
+ * plus récent ; rien s'il n'est pas dans la pile. Ailleurs : aperçu.
177
+ */
178
+ onKeyEventBubbleClick(keyEvent: KeyEventDefinition): void;
179
+ /** Aperçu — 1er clic sur un Événement Clé : affiche sa carte descriptive
180
+ * dans le panneau Recherche, ne joue rien (même principe que
181
+ * activateActionFromChild côté satellite). */
182
+ previewKeyEvent(keyEvent: KeyEventDefinition): void;
183
+ /** Validation — glisser une bulle (menu radial ou arbre RECHERCHE)
184
+ * jusqu'à une position précise de la pile (voir startKeyEventDrag, même
185
+ * mécanisme que validateActionSelection côté satellite). Sans
186
+ * insertAtVisibleIndex, l'item est ajouté au sommet — le menu radial
187
+ * reste ouvert, pour enchaîner d'autres choix. */
188
+ validateKeyEventSelection(keyEvent: KeyEventDefinition, insertAtVisibleIndex?: number | null, entryOrigin?: {
189
+ x: number;
190
+ y: number;
191
+ }): void;
192
+ }
@@ -106,12 +106,3 @@ export declare function renderRadialItemLinks<T extends {
106
106
  startRadius?: number;
107
107
  getStyle?: (d: RadialMenuItemPosition<T>) => RadialItemLinkStyle;
108
108
  }): void;
109
- /**
110
- * Sémantique de clic partagée par les deux menus radiaux : un premier
111
- * clic sur un item prévisualise (affiche sa carte, ne joue rien) ; un
112
- * second clic sur ce même item déjà actif valide (l'ajoute à la
113
- * pile). Cliquer un autre item pendant qu'un aperçu est actif bascule
114
- * l'aperçu sans valider — c'est simplement le cas "isAlreadyActive
115
- * === false" pour ce nouvel item.
116
- */
117
- export declare function handleRadialItemClick(isAlreadyActive: boolean, onPreview: () => void, onValidate: () => void): void;
@@ -0,0 +1,95 @@
1
+ import { IYinYangView } from "../../../interfaces/IYinYangView";
2
+ import { IYinYangController } from "../../../interfaces/IYinYangController";
3
+ import { IYinYangAppFactory } from "../../../interfaces/IYinYangAppFactory";
4
+ import { YinYangInternalModel } from "../../../interfaces/IYinYangInternalModel";
5
+ import { type ActionDefinition } from "../../../data/yasuni_actions";
6
+ import { ChildrenOrbitEntry } from "../utils/childrenOrbit";
7
+ import { YinYangControllerState } from "../state/YinYangControllerState";
8
+ import { StackFocusController } from "../action-stack/StackFocusController";
9
+ import { ComponentTitlePresenter } from "../labels/ComponentTitlePresenter";
10
+ import type { ControllerUiCallbacks } from "../state/ControllerUiCallbacks";
11
+ import { ChildrenOrbitManager } from "../odd/ChildrenOrbitManager";
12
+ export type ChildrenBuilderDeps = {
13
+ animateChildren: (show: boolean) => Promise<void>;
14
+ applyChildrenOrbitTransforms: (angleOffset: number, orbitData?: ChildrenOrbitEntry[]) => void;
15
+ attachBackgroundClickHandler: () => void;
16
+ attachChildHoverHandler: (childController: IYinYangController) => void;
17
+ attachChildToggleHandler: (childController: IYinYangController) => void;
18
+ getChildControllers: () => IYinYangController[];
19
+ childOddMap: Map<string, {
20
+ oddId: string;
21
+ score: number;
22
+ cuttingIndex: number;
23
+ }>;
24
+ childrenOrbitManager: ChildrenOrbitManager;
25
+ clearChildren: () => void;
26
+ factory: IYinYangAppFactory;
27
+ getAllComponents: () => YinYangInternalModel["components"];
28
+ getCurrentComponent: () => YinYangInternalModel["components"][number] | null;
29
+ getCurrentComponentOrUndefined: () => YinYangInternalModel["components"][number] | undefined;
30
+ getKey: () => string;
31
+ renderOppositeChildrenForceField: (orbitData?: ChildrenOrbitEntry[], angleOffset?: number) => void;
32
+ getRotationState: () => {
33
+ clockWise: boolean;
34
+ durationMs: number;
35
+ rampMs: number;
36
+ } | null;
37
+ setChildrenHiddenState: (hidden: boolean) => void;
38
+ stackFocus: StackFocusController;
39
+ startChildrenOrbit: (durationMs: number) => void;
40
+ state: YinYangControllerState;
41
+ titlePresenter: ComponentTitlePresenter;
42
+ ui: ControllerUiCallbacks;
43
+ view: IYinYangView;
44
+ };
45
+ /** Création des satellites (contrôleurs enfants) d'une facette, mise à
46
+ * l'échelle pour tenir dans le cercle, et actions proposées par satellite. */
47
+ export declare class ChildrenBuilder {
48
+ private deps;
49
+ constructor(deps: ChildrenBuilderDeps);
50
+ /**
51
+ * Creates child instances of the YinYang pattern using the model's data.
52
+ * Each child is generated based on the small circle data.
53
+ */
54
+ createYinYangChildren(): void;
55
+ /** Both distance and size are fixed relative to the facette's uniform
56
+ * baseline radius, independent of any satellite's own weight-scaled
57
+ * radius — mirrors ChildActionMenuController's action cluster geometry. */
58
+ private getChildActionClusterReach;
59
+ /** Décalage constant appliqué au poids ET à sa moyenne avant de calculer le
60
+ * ratio de taille d'un satellite (voir weightToSizeRatio) : resserre la
61
+ * fourchette de tailles (poids min moins minuscule, poids max un peu
62
+ * moins dominant) sans changer la taille d'un satellite au poids moyen
63
+ * exact (ratio toujours 1 dans ce cas), et sans dépendre du poids moyen
64
+ * de la fratrie pour l'ampleur du resserrement. */
65
+ private static readonly WEIGHT_SIZE_FLOOR_OFFSET;
66
+ /** Ratio de taille d'un satellite (rayon relatif à la baseline uniforme de
67
+ * sa facette) à partir de son poids et du poids moyen de sa fratrie —
68
+ * voir WEIGHT_SIZE_FLOOR_OFFSET pour le resserrement. */
69
+ private static weightToSizeRatio;
70
+ /** Ratio of a component's heaviest satellite radius to its own uniform
71
+ * baseline radius. Mirrors the per-cutting weight-based scaling applied
72
+ * in createYinYangChildren(). */
73
+ private getMaxWeightRatio;
74
+ /** The container-fit shrink ratio only depends on borderOuterRadius/
75
+ * smallCircleRadius/distanceFromChild, which are fixed for the whole
76
+ * session once this top-level view is first initialized (they don't get
77
+ * recomputed on facette switch) — so this is computed once (on the worst
78
+ * case weight ratio across every facette) and cached. That's what keeps
79
+ * the whole diagram's size from visibly changing every time the user
80
+ * switches facette. */
81
+ private cachedGlobalFitShrinkRatio?;
82
+ private computeGlobalFitShrinkRatio;
83
+ applyChildrenFitScale(): void;
84
+ /**
85
+ * Retourne les actions applicables au cutting associé à `childKey`,
86
+ * en consultant `activeComposantes` via `actionMap` (lookup O(1)).
87
+ *
88
+ * Les actions adverses vivent sous le même satellite (l'invariant "une
89
+ * action vit sous un satellite" tient) mais ne sont proposées au joueur
90
+ * qu'en mode adverse (outil de debug, voir adverseMode.ts), où elles
91
+ * remplacent les actions ordinaires — d'où le filtre "mode" par défaut.
92
+ * Hors de ce mode, c'est le système qui les place lui-même dans la pile.
93
+ */
94
+ getActionsForChild(childKey: string, filter?: "mode" | "playable" | "adverse" | "all"): ActionDefinition[];
95
+ }