@futdevpro/fsm-dynamo 1.20.100 → 1.20.102

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.
@@ -0,0 +1,58 @@
1
+ import { DyFM_InputAction_Definition, DyFM_InputApplyReport, DyFM_InputChord, DyFM_InputConflict, DyFM_InputContext, DyFM_InputFlatMap, DyFM_InputKeyEvent, DyFM_InputResolution, DyFM_InputSlotInfo } from './input-action.interface';
2
+ /**
3
+ * BFR-WARFACTORY-017 — typed input-action registry: default bindings, per-binding `code` / `key` matching, three
4
+ * orthogonal guards, ONE resolution per event, conflict detection, reset, and a flat `slotId → chord` map for disk.
5
+ *
6
+ * Slot ids are `<actionId>#<index>` (the index in the definition's `bindings`) — stable across remaps; a remap only
7
+ * changes a slot's chord, never its payload.
8
+ */
9
+ export declare class DyFM_InputActionRegistry_ControlModel {
10
+ private readonly actions;
11
+ private readonly slots;
12
+ /** Registers the actions; duplicate ids or an action without bindings throw `DYFM-INPUT-DEFINITION`. */
13
+ constructor(actions: DyFM_InputAction_Definition[]);
14
+ /** The chord string form: `ctrl+shift+code:KeyS` / `key:?` (modifiers in a fixed order). */
15
+ static formatChord(chord: DyFM_InputChord): string;
16
+ /** Parses a chord string; `null` when it is not one. */
17
+ static parseChord(text: string): DyFM_InputChord | null;
18
+ /**
19
+ * Resolves a key event to AT MOST ONE action: the matching slots whose guard is live in `context`, highest
20
+ * `priority` first, then definition order.
21
+ */
22
+ resolve(event: DyFM_InputKeyEvent, context: DyFM_InputContext): DyFM_InputResolution | null;
23
+ /** Every slot, in definition order (for a remap screen). */
24
+ listSlots(): DyFM_InputSlotInfo[];
25
+ /** The current chord of a slot. */
26
+ chordOf(slotId: string): DyFM_InputChord | undefined;
27
+ /** Slots whose chord equals `chord` and whose guards can be live together with `slotId`'s action. */
28
+ findConflicts(slotId: string, chord: DyFM_InputChord): DyFM_InputConflict[];
29
+ /**
30
+ * Remaps a slot. A conflicting chord is REFUSED (the conflicts are returned) unless `force` — then the conflicting
31
+ * slots SWAP: they take this slot's previous chord (resetting them to their defaults would not help when a default
32
+ * is the contested chord itself).
33
+ */
34
+ rebind(slotId: string, chord: DyFM_InputChord, force?: boolean): {
35
+ ok: boolean;
36
+ conflicts: DyFM_InputConflict[];
37
+ };
38
+ /** Resets one slot, one action's slots, or (no argument) everything to the defaults. */
39
+ resetToDefault(slotOrActionId?: string): void;
40
+ /** ③ The flat on-disk form — ONLY the slots that differ from their defaults. */
41
+ toFlatMap(): DyFM_InputFlatMap;
42
+ /**
43
+ * ③ Applies a stored flat map on top of the defaults: unknown slots (an action the game removed) and unparsable
44
+ * chords are skipped and reported; a stored chord that now conflicts is NOT applied (reported) — a stale save
45
+ * cannot silently steal a key.
46
+ */
47
+ applyFlatMap(map: DyFM_InputFlatMap): DyFM_InputApplyReport;
48
+ private action;
49
+ private slot;
50
+ private static validChord;
51
+ /** ① code vs key, and the modifiers must match exactly (an unset modifier must not be held). */
52
+ private static matches;
53
+ /** ② The three predicates. */
54
+ private static guardAllows;
55
+ /** Can two actions be live in the same context? (phases intersect AND overlay conditions can hold together) */
56
+ private static guardsOverlap;
57
+ }
58
+ //# sourceMappingURL=input-action-registry.control-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-action-registry.control-model.d.ts","sourceRoot":"","sources":["../../../../src/_modules/game/_models/input-action-registry.control-model.ts"],"names":[],"mappings":"AACA,OAAO,EACL,2BAA2B,EAE3B,qBAAqB,EACrB,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAElB,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAWlC;;;;;;GAMG;AACH,qBAAa,qCAAqC;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IAEtD,wGAAwG;gBAC5F,OAAO,EAAE,2BAA2B,EAAE;IA4BlD,4FAA4F;IAC5F,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM;IAOlD,wDAAwD;IACxD,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;IAqBvD;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,iBAAiB,GAAG,oBAAoB,GAAG,IAAI;IA4B3F,4DAA4D;IAC5D,SAAS,IAAI,kBAAkB,EAAE;IAajC,mCAAmC;IACnC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAMpD,qGAAqG;IACrG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,kBAAkB,EAAE;IAiB3E;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,GAAE,OAAe,GAAG;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,kBAAkB,EAAE,CAAA;KAAE;IAwBxH,wFAAwF;IACxF,cAAc,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI;IAS7C,gFAAgF;IAChF,SAAS,IAAI,iBAAiB;IAc9B;;;;OAIG;IACH,YAAY,CAAC,GAAG,EAAE,iBAAiB,GAAG,qBAAqB;IAyB3D,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,MAAM,CAAC,UAAU;IAIzB,gGAAgG;IAChG,OAAO,CAAC,MAAM,CAAC,OAAO;IAQtB,8BAA8B;IAC9B,OAAO,CAAC,MAAM,CAAC,WAAW;IAoB1B,+GAA+G;IAC/G,OAAO,CAAC,MAAM,CAAC,aAAa;CAoB7B"}
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DyFM_InputActionRegistry_ControlModel = void 0;
4
+ const error_control_model_1 = require("../../../_models/control-models/error.control-model");
5
+ /**
6
+ * BFR-WARFACTORY-017 — typed input-action registry: default bindings, per-binding `code` / `key` matching, three
7
+ * orthogonal guards, ONE resolution per event, conflict detection, reset, and a flat `slotId → chord` map for disk.
8
+ *
9
+ * Slot ids are `<actionId>#<index>` (the index in the definition's `bindings`) — stable across remaps; a remap only
10
+ * changes a slot's chord, never its payload.
11
+ */
12
+ class DyFM_InputActionRegistry_ControlModel {
13
+ actions;
14
+ slots = [];
15
+ /** Registers the actions; duplicate ids or an action without bindings throw `DYFM-INPUT-DEFINITION`. */
16
+ constructor(actions) {
17
+ const seen = new Set();
18
+ for (const action of actions) {
19
+ if (!action?.actionId || seen.has(action.actionId) || !action.bindings?.length
20
+ || action.bindings.some((slot) => !DyFM_InputActionRegistry_ControlModel.validChord(slot?.chord))) {
21
+ throw new error_control_model_1.DyFM_Error({
22
+ message: `DyFM_InputActionRegistry: action ids must be unique, with ≥ 1 valid binding (${action?.actionId})`,
23
+ errorCode: 'DYFM-INPUT-DEFINITION',
24
+ issuerService: 'DyFM_InputActionRegistry_ControlModel',
25
+ });
26
+ }
27
+ seen.add(action.actionId);
28
+ }
29
+ this.actions = actions.map((a) => ({ ...a }));
30
+ for (const action of this.actions) {
31
+ action.bindings.forEach((slot, index) => {
32
+ this.slots.push({
33
+ slotId: `${action.actionId}#${index}`,
34
+ actionId: action.actionId,
35
+ defaultChord: { ...slot.chord },
36
+ chord: { ...slot.chord },
37
+ payload: slot.payload,
38
+ });
39
+ });
40
+ }
41
+ }
42
+ /** The chord string form: `ctrl+shift+code:KeyS` / `key:?` (modifiers in a fixed order). */
43
+ static formatChord(chord) {
44
+ const modifiers = ['ctrl', 'alt', 'shift', 'meta']
45
+ .filter((m) => chord[m] === true);
46
+ return [...modifiers, `${chord.match}:${chord.value}`].join('+');
47
+ }
48
+ /** Parses a chord string; `null` when it is not one. */
49
+ static parseChord(text) {
50
+ if (typeof text !== 'string' || !text) {
51
+ return null;
52
+ }
53
+ const parts = text.split('+');
54
+ const last = parts.pop();
55
+ const colon = last.indexOf(':');
56
+ const match = last.slice(0, colon);
57
+ const chord = { match: match, value: last.slice(colon + 1) };
58
+ for (const modifier of parts) {
59
+ if (!['ctrl', 'alt', 'shift', 'meta'].includes(modifier)) {
60
+ return null;
61
+ }
62
+ chord[modifier] = true;
63
+ }
64
+ return colon > 0 && DyFM_InputActionRegistry_ControlModel.validChord(chord) ? chord : null;
65
+ }
66
+ /**
67
+ * Resolves a key event to AT MOST ONE action: the matching slots whose guard is live in `context`, highest
68
+ * `priority` first, then definition order.
69
+ */
70
+ resolve(event, context) {
71
+ let best = null;
72
+ this.slots.forEach((slot) => {
73
+ const action = this.action(slot.actionId);
74
+ if (!DyFM_InputActionRegistry_ControlModel.matches(slot.chord, event)
75
+ || !DyFM_InputActionRegistry_ControlModel.guardAllows(action.guard, context)) {
76
+ return;
77
+ }
78
+ const priority = action.priority ?? 0;
79
+ const order = this.actions.indexOf(action);
80
+ if (!best || priority > best.priority || (priority === best.priority && order < best.order)) {
81
+ best = { slot: slot, priority: priority, order: order };
82
+ }
83
+ });
84
+ if (!best) {
85
+ return null;
86
+ }
87
+ const chosen = best.slot;
88
+ return { actionId: chosen.actionId, slotId: chosen.slotId, payload: chosen.payload };
89
+ }
90
+ /** Every slot, in definition order (for a remap screen). */
91
+ listSlots() {
92
+ return this.slots.map((slot) => ({
93
+ slotId: slot.slotId,
94
+ actionId: slot.actionId,
95
+ label: this.action(slot.actionId).label,
96
+ chord: { ...slot.chord },
97
+ defaultChord: { ...slot.defaultChord },
98
+ isDefault: DyFM_InputActionRegistry_ControlModel.formatChord(slot.chord)
99
+ === DyFM_InputActionRegistry_ControlModel.formatChord(slot.defaultChord),
100
+ payload: slot.payload,
101
+ }));
102
+ }
103
+ /** The current chord of a slot. */
104
+ chordOf(slotId) {
105
+ const slot = this.slot(slotId);
106
+ return slot ? { ...slot.chord } : undefined;
107
+ }
108
+ /** Slots whose chord equals `chord` and whose guards can be live together with `slotId`'s action. */
109
+ findConflicts(slotId, chord) {
110
+ const own = this.slot(slotId);
111
+ if (!own) {
112
+ return [];
113
+ }
114
+ const text = DyFM_InputActionRegistry_ControlModel.formatChord(chord);
115
+ const ownGuard = this.action(own.actionId).guard;
116
+ return this.slots
117
+ .filter((other) => other.slotId !== slotId
118
+ && DyFM_InputActionRegistry_ControlModel.formatChord(other.chord) === text
119
+ && DyFM_InputActionRegistry_ControlModel.guardsOverlap(ownGuard, this.action(other.actionId).guard))
120
+ .map((other) => ({ slotId: slotId, otherSlotId: other.slotId, chord: text }));
121
+ }
122
+ /**
123
+ * Remaps a slot. A conflicting chord is REFUSED (the conflicts are returned) unless `force` — then the conflicting
124
+ * slots SWAP: they take this slot's previous chord (resetting them to their defaults would not help when a default
125
+ * is the contested chord itself).
126
+ */
127
+ rebind(slotId, chord, force = false) {
128
+ const slot = this.slot(slotId);
129
+ if (!slot || !DyFM_InputActionRegistry_ControlModel.validChord(chord)) {
130
+ return { ok: false, conflicts: [] };
131
+ }
132
+ const conflicts = this.findConflicts(slotId, chord);
133
+ if (conflicts.length && !force) {
134
+ return { ok: false, conflicts: conflicts };
135
+ }
136
+ const previous = { ...slot.chord };
137
+ for (const conflict of conflicts) {
138
+ const other = this.slot(conflict.otherSlotId);
139
+ other.chord = { ...previous };
140
+ }
141
+ slot.chord = { ...chord };
142
+ return { ok: true, conflicts: conflicts };
143
+ }
144
+ /** Resets one slot, one action's slots, or (no argument) everything to the defaults. */
145
+ resetToDefault(slotOrActionId) {
146
+ this.slots
147
+ .filter((slot) => !slotOrActionId || slot.slotId === slotOrActionId || slot.actionId === slotOrActionId)
148
+ .forEach((slot) => {
149
+ slot.chord = { ...slot.defaultChord };
150
+ });
151
+ }
152
+ /** ③ The flat on-disk form — ONLY the slots that differ from their defaults. */
153
+ toFlatMap() {
154
+ const map = {};
155
+ for (const slot of this.slots) {
156
+ const current = DyFM_InputActionRegistry_ControlModel.formatChord(slot.chord);
157
+ if (current !== DyFM_InputActionRegistry_ControlModel.formatChord(slot.defaultChord)) {
158
+ map[slot.slotId] = current;
159
+ }
160
+ }
161
+ return map;
162
+ }
163
+ /**
164
+ * ③ Applies a stored flat map on top of the defaults: unknown slots (an action the game removed) and unparsable
165
+ * chords are skipped and reported; a stored chord that now conflicts is NOT applied (reported) — a stale save
166
+ * cannot silently steal a key.
167
+ */
168
+ applyFlatMap(map) {
169
+ const report = { applied: [], unknownSlots: [], invalidChords: [], conflicts: [] };
170
+ this.resetToDefault();
171
+ for (const [slotId, text] of Object.entries(map ?? {})) {
172
+ const chord = DyFM_InputActionRegistry_ControlModel.parseChord(text);
173
+ if (!this.slot(slotId)) {
174
+ report.unknownSlots.push(slotId);
175
+ }
176
+ else if (!chord) {
177
+ report.invalidChords.push(slotId);
178
+ }
179
+ else {
180
+ const result = this.rebind(slotId, chord);
181
+ if (result.ok) {
182
+ report.applied.push(slotId);
183
+ }
184
+ else {
185
+ report.conflicts.push(...result.conflicts);
186
+ }
187
+ }
188
+ }
189
+ return report;
190
+ }
191
+ action(actionId) {
192
+ return this.actions.find((a) => a.actionId === actionId);
193
+ }
194
+ slot(slotId) {
195
+ return this.slots.find((s) => s.slotId === slotId);
196
+ }
197
+ static validChord(chord) {
198
+ return !!chord && (chord.match === 'code' || chord.match === 'key') && typeof chord.value === 'string' && chord.value.length > 0;
199
+ }
200
+ /** ① code vs key, and the modifiers must match exactly (an unset modifier must not be held). */
201
+ static matches(chord, event) {
202
+ const value = chord.match === 'code' ? event.code : event.key;
203
+ return value === chord.value
204
+ && !!chord.ctrl === !!event.ctrlKey && !!chord.alt === !!event.altKey
205
+ && !!chord.shift === !!event.shiftKey && !!chord.meta === !!event.metaKey;
206
+ }
207
+ /** ② The three predicates. */
208
+ static guardAllows(guard, context) {
209
+ if (context.interactiveTarget && !guard?.allowInInteractiveTarget) {
210
+ return false;
211
+ }
212
+ const overlays = guard?.overlays ?? 'none';
213
+ if (overlays === 'none' && context.openOverlay !== null) {
214
+ return false;
215
+ }
216
+ if (overlays === 'open' && context.openOverlay === null) {
217
+ return false;
218
+ }
219
+ if (Array.isArray(overlays) && (context.openOverlay === null || !overlays.includes(context.openOverlay))) {
220
+ return false;
221
+ }
222
+ return !guard?.phases || guard.phases.includes(context.phase);
223
+ }
224
+ /** Can two actions be live in the same context? (phases intersect AND overlay conditions can hold together) */
225
+ static guardsOverlap(a, b) {
226
+ const phasesOverlap = !a?.phases || !b?.phases || a.phases.some((p) => b.phases.includes(p));
227
+ const overlayA = a?.overlays ?? 'none';
228
+ const overlayB = b?.overlays ?? 'none';
229
+ let overlaysOverlap;
230
+ if (overlayA === 'any' || overlayB === 'any') {
231
+ overlaysOverlap = true;
232
+ }
233
+ else if (overlayA === 'none' || overlayB === 'none') {
234
+ // 'none' only overlaps 'none' (an open overlay excludes it)
235
+ overlaysOverlap = overlayA === overlayB;
236
+ }
237
+ else if (overlayA === 'open' || overlayB === 'open') {
238
+ // 'open' overlaps 'open' and any overlay list (both need an overlay on top)
239
+ overlaysOverlap = true;
240
+ }
241
+ else {
242
+ overlaysOverlap = overlayA.some((o) => overlayB.includes(o));
243
+ }
244
+ return phasesOverlap && overlaysOverlap;
245
+ }
246
+ }
247
+ exports.DyFM_InputActionRegistry_ControlModel = DyFM_InputActionRegistry_ControlModel;
248
+ //# sourceMappingURL=input-action-registry.control-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-action-registry.control-model.js","sourceRoot":"","sources":["../../../../src/_modules/game/_models/input-action-registry.control-model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * BFR-WARFACTORY-017 — game input actions. The common layer owns the MECHANICS (bindings, guards, conflicts, flat
3
+ * persistence); the action ids and labels are the game's.
4
+ */
5
+ /** ① What a binding matches on: the PHYSICAL key position (`code`, e.g. `KeyW` — layout-independent) or the produced
6
+ * character (`key`, e.g. `?`). Declared per binding. */
7
+ export type DyFM_InputMatch_Type = 'code' | 'key';
8
+ /** One physical input: the matched value + the required modifiers (unset = must NOT be held). */
9
+ export interface DyFM_InputChord {
10
+ match: DyFM_InputMatch_Type;
11
+ value: string;
12
+ ctrl?: boolean;
13
+ alt?: boolean;
14
+ shift?: boolean;
15
+ meta?: boolean;
16
+ }
17
+ /** ④ A binding slot of an action: the default chord + an optional PAYLOAD (e.g. speed hotkeys: `Digit3` → 3). */
18
+ export interface DyFM_InputBindingSlot {
19
+ chord: DyFM_InputChord;
20
+ payload?: unknown;
21
+ }
22
+ /** ② The overlay condition of a guard. */
23
+ export type DyFM_InputOverlayCondition_Type = 'none' | 'open' | 'any' | string[];
24
+ /** ② When an action may fire — three ORTHOGONAL predicates instead of one `enabled` flag. */
25
+ export interface DyFM_InputActionGuard {
26
+ /** Fire while an interactive element (input / textarea / button …) has focus? Default: `false` (typing wins). */
27
+ allowInInteractiveTarget?: boolean;
28
+ /**
29
+ * Overlay condition: `'none'` (no overlay open — default) · `'open'` (SOME overlay is open — e.g. close-overlay) ·
30
+ * `'any'` (open or not) · a list of overlay ids that must be on top.
31
+ */
32
+ overlays?: DyFM_InputOverlayCondition_Type;
33
+ /** Game phases the action is live in; unset = every phase. */
34
+ phases?: string[];
35
+ }
36
+ /** One action definition. */
37
+ export interface DyFM_InputAction_Definition {
38
+ actionId: string;
39
+ label: string;
40
+ bindings: DyFM_InputBindingSlot[];
41
+ guard?: DyFM_InputActionGuard;
42
+ /** Higher wins when several actions match one event (tie → definition order). Default: `0`. */
43
+ priority?: number;
44
+ }
45
+ /** The runtime context a key event is resolved in. */
46
+ export interface DyFM_InputContext {
47
+ /** An interactive element has focus. */
48
+ interactiveTarget: boolean;
49
+ /** The TOP open overlay id (`null` = none). */
50
+ openOverlay: string | null;
51
+ /** The current game phase. */
52
+ phase: string;
53
+ }
54
+ /** The subset of a `KeyboardEvent` the registry reads. */
55
+ export interface DyFM_InputKeyEvent {
56
+ code: string;
57
+ key: string;
58
+ ctrlKey?: boolean;
59
+ altKey?: boolean;
60
+ shiftKey?: boolean;
61
+ metaKey?: boolean;
62
+ }
63
+ /** A resolved action — ONE per event (Escape never closes two layers). */
64
+ export interface DyFM_InputResolution {
65
+ actionId: string;
66
+ slotId: string;
67
+ payload?: unknown;
68
+ }
69
+ /** A binding conflict: another slot has the same chord and their guards can be live at the same time. */
70
+ export interface DyFM_InputConflict {
71
+ slotId: string;
72
+ otherSlotId: string;
73
+ chord: string;
74
+ }
75
+ /** A slot as a remap screen lists it (copies — changing them does not change the registry). */
76
+ export interface DyFM_InputSlotInfo {
77
+ slotId: string;
78
+ actionId: string;
79
+ /** The action's label (for the UI). */
80
+ label: string;
81
+ chord: DyFM_InputChord;
82
+ defaultChord: DyFM_InputChord;
83
+ /** `true` when the slot still has its default chord. */
84
+ isDefault: boolean;
85
+ payload?: unknown;
86
+ }
87
+ /** ③ The flat, on-disk form: `slotId → chord string` (only the remapped chords; payloads stay in the definitions). */
88
+ export type DyFM_InputFlatMap = Record<string, string>;
89
+ /** What `applyFlatMap` did. */
90
+ export interface DyFM_InputApplyReport {
91
+ applied: string[];
92
+ unknownSlots: string[];
93
+ invalidChords: string[];
94
+ conflicts: DyFM_InputConflict[];
95
+ }
96
+ //# sourceMappingURL=input-action.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-action.interface.d.ts","sourceRoot":"","sources":["../../../../src/_modules/game/_models/input-action.interface.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;wDACwD;AACxD,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,KAAK,CAAC;AAElD,iGAAiG;AACjG,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,oBAAoB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,iHAAiH;AACjH,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,0CAA0C;AAC1C,MAAM,MAAM,+BAA+B,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,EAAE,CAAC;AAEjF,6FAA6F;AAC7F,MAAM,WAAW,qBAAqB;IACpC,iHAAiH;IACjH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,EAAE,+BAA+B,CAAC;IAC3C,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,6BAA6B;AAC7B,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,qBAAqB,EAAE,CAAC;IAClC,KAAK,CAAC,EAAE,qBAAqB,CAAC;IAC9B,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,sDAAsD;AACtD,MAAM,WAAW,iBAAiB;IAChC,wCAAwC;IACxC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,+CAA+C;IAC/C,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,0DAA0D;AAC1D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,0EAA0E;AAC1E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,yGAAyG;AACzG,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,+FAA+F;AAC/F,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,eAAe,CAAC;IACvB,YAAY,EAAE,eAAe,CAAC;IAC9B,wDAAwD;IACxD,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,sHAAsH;AACtH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEvD,+BAA+B;AAC/B,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,EAAE,kBAAkB,EAAE,CAAC;CACjC"}
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * BFR-WARFACTORY-017 — game input actions. The common layer owns the MECHANICS (bindings, guards, conflicts, flat
4
+ * persistence); the action ids and labels are the game's.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ //# sourceMappingURL=input-action.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-action.interface.js","sourceRoot":"","sources":["../../../../src/_modules/game/_models/input-action.interface.ts"],"names":[],"mappings":";AAAA;;;GAGG"}
@@ -18,6 +18,8 @@ export * from './_models/work-order-scheduler.control-model';
18
18
  export * from './_models/build-reservation.interface';
19
19
  export * from './_models/viewport.interface';
20
20
  export * from './_models/viewport.control-model';
21
+ export * from './_models/input-action.interface';
22
+ export * from './_models/input-action-registry.control-model';
21
23
  export * from './_collections/audio-mixer.util';
22
24
  export * from './_collections/audio-scale.util';
23
25
  export * from './_collections/game-save.util';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/_modules/game/index.ts"],"names":[],"mappings":"AACA,cAAc,6BAA6B,CAAC;AAG5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,0CAA0C,CAAC;AACzD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,uCAAuC,CAAC;AACtD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AACtD,cAAc,sCAAsC,CAAC;AACrD,cAAc,0CAA0C,CAAC;AACzD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0CAA0C,CAAC;AACzD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,uCAAuC,CAAC;AACtD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AAGjD,cAAc,iCAAiC,CAAC;AAChD,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uCAAuC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/_modules/game/index.ts"],"names":[],"mappings":"AACA,cAAc,6BAA6B,CAAC;AAG5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,0CAA0C,CAAC;AACzD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qCAAqC,CAAC;AACpD,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,uCAAuC,CAAC;AACtD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AACtD,cAAc,sCAAsC,CAAC;AACrD,cAAc,0CAA0C,CAAC;AACzD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0CAA0C,CAAC;AACzD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,uCAAuC,CAAC;AACtD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,kCAAkC,CAAC;AACjD,cAAc,+CAA+C,CAAC;AAG9D,cAAc,iCAAiC,CAAC;AAChD,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,uCAAuC,CAAC"}
@@ -23,6 +23,8 @@ tslib_1.__exportStar(require("./_models/work-order-scheduler.control-model"), ex
23
23
  tslib_1.__exportStar(require("./_models/build-reservation.interface"), exports);
24
24
  tslib_1.__exportStar(require("./_models/viewport.interface"), exports);
25
25
  tslib_1.__exportStar(require("./_models/viewport.control-model"), exports);
26
+ tslib_1.__exportStar(require("./_models/input-action.interface"), exports);
27
+ tslib_1.__exportStar(require("./_models/input-action-registry.control-model"), exports);
26
28
  // COLLECTIONS
27
29
  tslib_1.__exportStar(require("./_collections/audio-mixer.util"), exports);
28
30
  tslib_1.__exportStar(require("./_collections/audio-scale.util"), exports);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/_modules/game/index.ts"],"names":[],"mappings":";;;AAAA,QAAQ;AACR,sEAA4C;AAE5C,SAAS;AACT,iFAAuD;AACvD,mFAAyD;AACzD,gFAAsD;AACtD,8EAAoD;AACpD,0EAAgD;AAChD,kFAAwD;AACxD,gFAAsD;AACtD,qFAA2D;AAC3D,yFAA+D;AAC/D,4EAAkD;AAClD,gFAAsD;AACtD,+EAAqD;AACrD,mFAAyD;AACzD,wEAA8C;AAC9C,mFAAyD;AACzD,uFAA6D;AAC7D,gFAAsD;AACtD,uEAA6C;AAC7C,2EAAiD;AAEjD,cAAc;AACd,0EAAgD;AAChD,0EAAgD;AAChD,wEAA8C;AAC9C,gFAAsD"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/_modules/game/index.ts"],"names":[],"mappings":";;;AAAA,QAAQ;AACR,sEAA4C;AAE5C,SAAS;AACT,iFAAuD;AACvD,mFAAyD;AACzD,gFAAsD;AACtD,8EAAoD;AACpD,0EAAgD;AAChD,kFAAwD;AACxD,gFAAsD;AACtD,qFAA2D;AAC3D,yFAA+D;AAC/D,4EAAkD;AAClD,gFAAsD;AACtD,+EAAqD;AACrD,mFAAyD;AACzD,wEAA8C;AAC9C,mFAAyD;AACzD,uFAA6D;AAC7D,gFAAsD;AACtD,uEAA6C;AAC7C,2EAAiD;AACjD,2EAAiD;AACjD,wFAA8D;AAE9D,cAAc;AACd,0EAAgD;AAChD,0EAAgD;AAChD,wEAA8C;AAC9C,gFAAsD"}
@@ -0,0 +1,243 @@
1
+ import { DyFM_Error } from '../../../_models/control-models/error.control-model';
2
+ /**
3
+ * BFR-WARFACTORY-017 — typed input-action registry: default bindings, per-binding `code` / `key` matching, three
4
+ * orthogonal guards, ONE resolution per event, conflict detection, reset, and a flat `slotId → chord` map for disk.
5
+ *
6
+ * Slot ids are `<actionId>#<index>` (the index in the definition's `bindings`) — stable across remaps; a remap only
7
+ * changes a slot's chord, never its payload.
8
+ */
9
+ export class DyFM_InputActionRegistry_ControlModel {
10
+ actions;
11
+ slots = [];
12
+ /** Registers the actions; duplicate ids or an action without bindings throw `DYFM-INPUT-DEFINITION`. */
13
+ constructor(actions) {
14
+ const seen = new Set();
15
+ for (const action of actions) {
16
+ if (!action?.actionId || seen.has(action.actionId) || !action.bindings?.length
17
+ || action.bindings.some((slot) => !DyFM_InputActionRegistry_ControlModel.validChord(slot?.chord))) {
18
+ throw new DyFM_Error({
19
+ message: `DyFM_InputActionRegistry: action ids must be unique, with ≥ 1 valid binding (${action?.actionId})`,
20
+ errorCode: 'DYFM-INPUT-DEFINITION',
21
+ issuerService: 'DyFM_InputActionRegistry_ControlModel',
22
+ });
23
+ }
24
+ seen.add(action.actionId);
25
+ }
26
+ this.actions = actions.map((a) => ({ ...a }));
27
+ for (const action of this.actions) {
28
+ action.bindings.forEach((slot, index) => {
29
+ this.slots.push({
30
+ slotId: `${action.actionId}#${index}`,
31
+ actionId: action.actionId,
32
+ defaultChord: { ...slot.chord },
33
+ chord: { ...slot.chord },
34
+ payload: slot.payload,
35
+ });
36
+ });
37
+ }
38
+ }
39
+ /** The chord string form: `ctrl+shift+code:KeyS` / `key:?` (modifiers in a fixed order). */
40
+ static formatChord(chord) {
41
+ const modifiers = ['ctrl', 'alt', 'shift', 'meta']
42
+ .filter((m) => chord[m] === true);
43
+ return [...modifiers, `${chord.match}:${chord.value}`].join('+');
44
+ }
45
+ /** Parses a chord string; `null` when it is not one. */
46
+ static parseChord(text) {
47
+ if (typeof text !== 'string' || !text) {
48
+ return null;
49
+ }
50
+ const parts = text.split('+');
51
+ const last = parts.pop();
52
+ const colon = last.indexOf(':');
53
+ const match = last.slice(0, colon);
54
+ const chord = { match: match, value: last.slice(colon + 1) };
55
+ for (const modifier of parts) {
56
+ if (!['ctrl', 'alt', 'shift', 'meta'].includes(modifier)) {
57
+ return null;
58
+ }
59
+ chord[modifier] = true;
60
+ }
61
+ return colon > 0 && DyFM_InputActionRegistry_ControlModel.validChord(chord) ? chord : null;
62
+ }
63
+ /**
64
+ * Resolves a key event to AT MOST ONE action: the matching slots whose guard is live in `context`, highest
65
+ * `priority` first, then definition order.
66
+ */
67
+ resolve(event, context) {
68
+ let best = null;
69
+ this.slots.forEach((slot) => {
70
+ const action = this.action(slot.actionId);
71
+ if (!DyFM_InputActionRegistry_ControlModel.matches(slot.chord, event)
72
+ || !DyFM_InputActionRegistry_ControlModel.guardAllows(action.guard, context)) {
73
+ return;
74
+ }
75
+ const priority = action.priority ?? 0;
76
+ const order = this.actions.indexOf(action);
77
+ if (!best || priority > best.priority || (priority === best.priority && order < best.order)) {
78
+ best = { slot: slot, priority: priority, order: order };
79
+ }
80
+ });
81
+ if (!best) {
82
+ return null;
83
+ }
84
+ const chosen = best.slot;
85
+ return { actionId: chosen.actionId, slotId: chosen.slotId, payload: chosen.payload };
86
+ }
87
+ /** Every slot, in definition order (for a remap screen). */
88
+ listSlots() {
89
+ return this.slots.map((slot) => ({
90
+ slotId: slot.slotId,
91
+ actionId: slot.actionId,
92
+ label: this.action(slot.actionId).label,
93
+ chord: { ...slot.chord },
94
+ defaultChord: { ...slot.defaultChord },
95
+ isDefault: DyFM_InputActionRegistry_ControlModel.formatChord(slot.chord)
96
+ === DyFM_InputActionRegistry_ControlModel.formatChord(slot.defaultChord),
97
+ payload: slot.payload,
98
+ }));
99
+ }
100
+ /** The current chord of a slot. */
101
+ chordOf(slotId) {
102
+ const slot = this.slot(slotId);
103
+ return slot ? { ...slot.chord } : undefined;
104
+ }
105
+ /** Slots whose chord equals `chord` and whose guards can be live together with `slotId`'s action. */
106
+ findConflicts(slotId, chord) {
107
+ const own = this.slot(slotId);
108
+ if (!own) {
109
+ return [];
110
+ }
111
+ const text = DyFM_InputActionRegistry_ControlModel.formatChord(chord);
112
+ const ownGuard = this.action(own.actionId).guard;
113
+ return this.slots
114
+ .filter((other) => other.slotId !== slotId
115
+ && DyFM_InputActionRegistry_ControlModel.formatChord(other.chord) === text
116
+ && DyFM_InputActionRegistry_ControlModel.guardsOverlap(ownGuard, this.action(other.actionId).guard))
117
+ .map((other) => ({ slotId: slotId, otherSlotId: other.slotId, chord: text }));
118
+ }
119
+ /**
120
+ * Remaps a slot. A conflicting chord is REFUSED (the conflicts are returned) unless `force` — then the conflicting
121
+ * slots SWAP: they take this slot's previous chord (resetting them to their defaults would not help when a default
122
+ * is the contested chord itself).
123
+ */
124
+ rebind(slotId, chord, force = false) {
125
+ const slot = this.slot(slotId);
126
+ if (!slot || !DyFM_InputActionRegistry_ControlModel.validChord(chord)) {
127
+ return { ok: false, conflicts: [] };
128
+ }
129
+ const conflicts = this.findConflicts(slotId, chord);
130
+ if (conflicts.length && !force) {
131
+ return { ok: false, conflicts: conflicts };
132
+ }
133
+ const previous = { ...slot.chord };
134
+ for (const conflict of conflicts) {
135
+ const other = this.slot(conflict.otherSlotId);
136
+ other.chord = { ...previous };
137
+ }
138
+ slot.chord = { ...chord };
139
+ return { ok: true, conflicts: conflicts };
140
+ }
141
+ /** Resets one slot, one action's slots, or (no argument) everything to the defaults. */
142
+ resetToDefault(slotOrActionId) {
143
+ this.slots
144
+ .filter((slot) => !slotOrActionId || slot.slotId === slotOrActionId || slot.actionId === slotOrActionId)
145
+ .forEach((slot) => {
146
+ slot.chord = { ...slot.defaultChord };
147
+ });
148
+ }
149
+ /** ③ The flat on-disk form — ONLY the slots that differ from their defaults. */
150
+ toFlatMap() {
151
+ const map = {};
152
+ for (const slot of this.slots) {
153
+ const current = DyFM_InputActionRegistry_ControlModel.formatChord(slot.chord);
154
+ if (current !== DyFM_InputActionRegistry_ControlModel.formatChord(slot.defaultChord)) {
155
+ map[slot.slotId] = current;
156
+ }
157
+ }
158
+ return map;
159
+ }
160
+ /**
161
+ * ③ Applies a stored flat map on top of the defaults: unknown slots (an action the game removed) and unparsable
162
+ * chords are skipped and reported; a stored chord that now conflicts is NOT applied (reported) — a stale save
163
+ * cannot silently steal a key.
164
+ */
165
+ applyFlatMap(map) {
166
+ const report = { applied: [], unknownSlots: [], invalidChords: [], conflicts: [] };
167
+ this.resetToDefault();
168
+ for (const [slotId, text] of Object.entries(map ?? {})) {
169
+ const chord = DyFM_InputActionRegistry_ControlModel.parseChord(text);
170
+ if (!this.slot(slotId)) {
171
+ report.unknownSlots.push(slotId);
172
+ }
173
+ else if (!chord) {
174
+ report.invalidChords.push(slotId);
175
+ }
176
+ else {
177
+ const result = this.rebind(slotId, chord);
178
+ if (result.ok) {
179
+ report.applied.push(slotId);
180
+ }
181
+ else {
182
+ report.conflicts.push(...result.conflicts);
183
+ }
184
+ }
185
+ }
186
+ return report;
187
+ }
188
+ action(actionId) {
189
+ return this.actions.find((a) => a.actionId === actionId);
190
+ }
191
+ slot(slotId) {
192
+ return this.slots.find((s) => s.slotId === slotId);
193
+ }
194
+ static validChord(chord) {
195
+ return !!chord && (chord.match === 'code' || chord.match === 'key') && typeof chord.value === 'string' && chord.value.length > 0;
196
+ }
197
+ /** ① code vs key, and the modifiers must match exactly (an unset modifier must not be held). */
198
+ static matches(chord, event) {
199
+ const value = chord.match === 'code' ? event.code : event.key;
200
+ return value === chord.value
201
+ && !!chord.ctrl === !!event.ctrlKey && !!chord.alt === !!event.altKey
202
+ && !!chord.shift === !!event.shiftKey && !!chord.meta === !!event.metaKey;
203
+ }
204
+ /** ② The three predicates. */
205
+ static guardAllows(guard, context) {
206
+ if (context.interactiveTarget && !guard?.allowInInteractiveTarget) {
207
+ return false;
208
+ }
209
+ const overlays = guard?.overlays ?? 'none';
210
+ if (overlays === 'none' && context.openOverlay !== null) {
211
+ return false;
212
+ }
213
+ if (overlays === 'open' && context.openOverlay === null) {
214
+ return false;
215
+ }
216
+ if (Array.isArray(overlays) && (context.openOverlay === null || !overlays.includes(context.openOverlay))) {
217
+ return false;
218
+ }
219
+ return !guard?.phases || guard.phases.includes(context.phase);
220
+ }
221
+ /** Can two actions be live in the same context? (phases intersect AND overlay conditions can hold together) */
222
+ static guardsOverlap(a, b) {
223
+ const phasesOverlap = !a?.phases || !b?.phases || a.phases.some((p) => b.phases.includes(p));
224
+ const overlayA = a?.overlays ?? 'none';
225
+ const overlayB = b?.overlays ?? 'none';
226
+ let overlaysOverlap;
227
+ if (overlayA === 'any' || overlayB === 'any') {
228
+ overlaysOverlap = true;
229
+ }
230
+ else if (overlayA === 'none' || overlayB === 'none') {
231
+ // 'none' only overlaps 'none' (an open overlay excludes it)
232
+ overlaysOverlap = overlayA === overlayB;
233
+ }
234
+ else if (overlayA === 'open' || overlayB === 'open') {
235
+ // 'open' overlaps 'open' and any overlay list (both need an overlay on top)
236
+ overlaysOverlap = true;
237
+ }
238
+ else {
239
+ overlaysOverlap = overlayA.some((o) => overlayB.includes(o));
240
+ }
241
+ return phasesOverlap && overlaysOverlap;
242
+ }
243
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * BFR-WARFACTORY-017 — game input actions. The common layer owns the MECHANICS (bindings, guards, conflicts, flat
3
+ * persistence); the action ids and labels are the game's.
4
+ */
5
+ export {};
@@ -20,6 +20,8 @@ export * from './_models/work-order-scheduler.control-model';
20
20
  export * from './_models/build-reservation.interface';
21
21
  export * from './_models/viewport.interface';
22
22
  export * from './_models/viewport.control-model';
23
+ export * from './_models/input-action.interface';
24
+ export * from './_models/input-action-registry.control-model';
23
25
  // COLLECTIONS
24
26
  export * from './_collections/audio-mixer.util';
25
27
  export * from './_collections/audio-scale.util';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@futdevpro/fsm-dynamo",
3
- "version": "1.20.100",
3
+ "version": "1.20.102",
4
4
  "description": "Full Stack Model Collection for Dynamic (NodeJS-Typescript) Framework called Dynamo, by Future Development Ltd.",
5
5
  "DyBu_settings": {
6
6
  "packageType": "full-stack-package",