@futdevpro/fsm-dynamo 1.20.97 → 1.20.100
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.
- package/build/_modules/game/_collections/build-reservation.util.d.ts +31 -0
- package/build/_modules/game/_collections/build-reservation.util.d.ts.map +1 -0
- package/build/_modules/game/_collections/build-reservation.util.js +77 -0
- package/build/_modules/game/_collections/build-reservation.util.js.map +1 -0
- package/build/_modules/game/_collections/game-save.util.d.ts +46 -0
- package/build/_modules/game/_collections/game-save.util.d.ts.map +1 -0
- package/build/_modules/game/_collections/game-save.util.js +189 -0
- package/build/_modules/game/_collections/game-save.util.js.map +1 -0
- package/build/_modules/game/_models/build-reservation.interface.d.ts +70 -0
- package/build/_modules/game/_models/build-reservation.interface.d.ts.map +1 -0
- package/build/_modules/game/_models/build-reservation.interface.js +7 -0
- package/build/_modules/game/_models/build-reservation.interface.js.map +1 -0
- package/build/_modules/game/_models/game-save.interface.d.ts +76 -0
- package/build/_modules/game/_models/game-save.interface.d.ts.map +1 -0
- package/build/_modules/game/_models/game-save.interface.js +7 -0
- package/build/_modules/game/_models/game-save.interface.js.map +1 -0
- package/build/_modules/game/_models/viewport.control-model.d.ts +58 -0
- package/build/_modules/game/_models/viewport.control-model.d.ts.map +1 -0
- package/build/_modules/game/_models/viewport.control-model.js +167 -0
- package/build/_modules/game/_models/viewport.control-model.js.map +1 -0
- package/build/_modules/game/_models/viewport.interface.d.ts +35 -0
- package/build/_modules/game/_models/viewport.interface.d.ts.map +1 -0
- package/build/_modules/game/_models/viewport.interface.js +3 -0
- package/build/_modules/game/_models/viewport.interface.js.map +1 -0
- package/build/_modules/game/_models/work-order-scheduler.control-model.d.ts +55 -0
- package/build/_modules/game/_models/work-order-scheduler.control-model.d.ts.map +1 -0
- package/build/_modules/game/_models/work-order-scheduler.control-model.js +201 -0
- package/build/_modules/game/_models/work-order-scheduler.control-model.js.map +1 -0
- package/build/_modules/game/_models/work-order-scheduler.interface.d.ts +43 -0
- package/build/_modules/game/_models/work-order-scheduler.interface.d.ts.map +1 -0
- package/build/_modules/game/_models/work-order-scheduler.interface.js +3 -0
- package/build/_modules/game/_models/work-order-scheduler.interface.js.map +1 -0
- package/build/_modules/game/index.d.ts +8 -0
- package/build/_modules/game/index.d.ts.map +1 -1
- package/build/_modules/game/index.js +8 -0
- package/build/_modules/game/index.js.map +1 -1
- package/build-esm/_modules/game/_collections/build-reservation.util.js +72 -0
- package/build-esm/_modules/game/_collections/game-save.util.js +184 -0
- package/build-esm/_modules/game/_models/build-reservation.interface.js +5 -0
- package/build-esm/_modules/game/_models/game-save.interface.js +5 -0
- package/build-esm/_modules/game/_models/viewport.control-model.js +162 -0
- package/build-esm/_modules/game/_models/viewport.interface.js +1 -0
- package/build-esm/_modules/game/_models/work-order-scheduler.control-model.js +196 -0
- package/build-esm/_modules/game/_models/work-order-scheduler.interface.js +1 -0
- package/build-esm/_modules/game/index.js +8 -0
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { DyFM_BuildReservation, DyFM_PlacementPreview, DyFM_PlacementRule, DyFM_RefundLine, DyFM_RefundPolicy, DyFM_ReservedCostSource } from '../_models/build-reservation.interface';
|
|
2
|
+
/**
|
|
3
|
+
* BFR-WARFACTORY-012 (①–②) — placement preview and escrow reservation, headless and pure (the caller owns the world
|
|
4
|
+
* state and applies the returned refund lines).
|
|
5
|
+
*/
|
|
6
|
+
export declare class DyFM_BuildReservation_Util {
|
|
7
|
+
/** ① Evaluates EVERY rule and returns the preview with all failure reasons (not just the first). */
|
|
8
|
+
static preview<TPosition>(candidate: {
|
|
9
|
+
position: TPosition;
|
|
10
|
+
footprint: {
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
};
|
|
14
|
+
}, rules: DyFM_PlacementRule<TPosition>[]): DyFM_PlacementPreview<TPosition>;
|
|
15
|
+
/**
|
|
16
|
+
* ② Holds the cost in escrow for a VALID preview. An invalid preview or a non-positive amount throws
|
|
17
|
+
* (`DYFM-RESERVATION-INVALID`) — reserving an invalid placement is how resources silently disappear.
|
|
18
|
+
*/
|
|
19
|
+
static reserve<TPosition>(reservationId: string, preview: DyFM_PlacementPreview<TPosition>, sources: DyFM_ReservedCostSource[], policy?: DyFM_RefundPolicy): DyFM_BuildReservation<TPosition>;
|
|
20
|
+
/** ② The build started / finished: the escrow is consumed. Idempotent on `committed`; a released one cannot commit. */
|
|
21
|
+
static commit<TPosition>(reservation: DyFM_BuildReservation<TPosition>): DyFM_BuildReservation<TPosition>;
|
|
22
|
+
/**
|
|
23
|
+
* ② Cancel: returns the refund lines BACK TO THEIR ORIGINAL SOURCES — `cancelRefundRatio × (1 − progressRatio)`
|
|
24
|
+
* of each line (floored to whole units). A second release returns nothing (no double refund).
|
|
25
|
+
*/
|
|
26
|
+
static release<TPosition>(reservation: DyFM_BuildReservation<TPosition>, progressRatio?: number): {
|
|
27
|
+
reservation: DyFM_BuildReservation<TPosition>;
|
|
28
|
+
refunds: DyFM_RefundLine[];
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=build-reservation.util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-reservation.util.d.ts","sourceRoot":"","sources":["../../../../src/_modules/game/_collections/build-reservation.util.ts"],"names":[],"mappings":"AACA,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EAErB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,uBAAuB,EACxB,MAAM,wCAAwC,CAAC;AAEhD;;;GAGG;AACH,qBAAa,0BAA0B;IAErC,oGAAoG;IACpG,MAAM,CAAC,OAAO,CAAC,SAAS,EACtB,SAAS,EAAE;QAAE,QAAQ,EAAE,SAAS,CAAC;QAAC,SAAS,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,EAChF,KAAK,EAAE,kBAAkB,CAAC,SAAS,CAAC,EAAE,GACrC,qBAAqB,CAAC,SAAS,CAAC;IAiBnC;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,SAAS,EACtB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,qBAAqB,CAAC,SAAS,CAAC,EACzC,OAAO,EAAE,uBAAuB,EAAE,EAClC,MAAM,GAAE,iBAAsB,GAC7B,qBAAqB,CAAC,SAAS,CAAC;IAwBnC,uHAAuH;IACvH,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,EAAE,qBAAqB,CAAC,SAAS,CAAC,GAAG,qBAAqB,CAAC,SAAS,CAAC;IAYzG;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,SAAS,EACtB,WAAW,EAAE,qBAAqB,CAAC,SAAS,CAAC,EAC7C,aAAa,GAAE,MAAU,GACxB;QAAE,WAAW,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAC;QAAC,OAAO,EAAE,eAAe,EAAE,CAAA;KAAE;CAcjF"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DyFM_BuildReservation_Util = void 0;
|
|
4
|
+
const error_control_model_1 = require("../../../_models/control-models/error.control-model");
|
|
5
|
+
/**
|
|
6
|
+
* BFR-WARFACTORY-012 (①–②) — placement preview and escrow reservation, headless and pure (the caller owns the world
|
|
7
|
+
* state and applies the returned refund lines).
|
|
8
|
+
*/
|
|
9
|
+
class DyFM_BuildReservation_Util {
|
|
10
|
+
/** ① Evaluates EVERY rule and returns the preview with all failure reasons (not just the first). */
|
|
11
|
+
static preview(candidate, rules) {
|
|
12
|
+
const reasons = [];
|
|
13
|
+
if (!(candidate.footprint?.width > 0) || !(candidate.footprint?.height > 0)) {
|
|
14
|
+
reasons.push({ code: 'invalid-footprint', message: 'the footprint must be at least 1 × 1' });
|
|
15
|
+
}
|
|
16
|
+
for (const rule of rules) {
|
|
17
|
+
const reason = rule(candidate);
|
|
18
|
+
if (reason) {
|
|
19
|
+
reasons.push(reason);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { position: candidate.position, footprint: { ...candidate.footprint }, valid: !reasons.length, reasons: reasons };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* ② Holds the cost in escrow for a VALID preview. An invalid preview or a non-positive amount throws
|
|
26
|
+
* (`DYFM-RESERVATION-INVALID`) — reserving an invalid placement is how resources silently disappear.
|
|
27
|
+
*/
|
|
28
|
+
static reserve(reservationId, preview, sources, policy = {}) {
|
|
29
|
+
const ratio = policy.cancelRefundRatio ?? 1;
|
|
30
|
+
if (!reservationId || !preview.valid
|
|
31
|
+
|| sources.some((s) => !s.resource || !s.sourceId || !(s.amount > 0))
|
|
32
|
+
|| !(ratio >= 0 && ratio <= 1)) {
|
|
33
|
+
throw new error_control_model_1.DyFM_Error({
|
|
34
|
+
message: 'DyFM_BuildReservation: reserve needs an id, a VALID preview, sources with resource + sourceId + '
|
|
35
|
+
+ `amount > 0 and a refund ratio in 0..1 (reasons: ${preview.reasons.map((r) => r.code).join(', ') || '-'})`,
|
|
36
|
+
errorCode: 'DYFM-RESERVATION-INVALID',
|
|
37
|
+
issuerService: 'DyFM_BuildReservation_Util',
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
reservationId: reservationId,
|
|
42
|
+
position: preview.position,
|
|
43
|
+
footprint: { ...preview.footprint },
|
|
44
|
+
reservedCostSources: sources.map((s) => ({ ...s })),
|
|
45
|
+
refundPolicy: { cancelRefundRatio: ratio },
|
|
46
|
+
status: 'held',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** ② The build started / finished: the escrow is consumed. Idempotent on `committed`; a released one cannot commit. */
|
|
50
|
+
static commit(reservation) {
|
|
51
|
+
if (reservation.status === 'released') {
|
|
52
|
+
throw new error_control_model_1.DyFM_Error({
|
|
53
|
+
message: `DyFM_BuildReservation: ${reservation.reservationId} was already released — it cannot be committed`,
|
|
54
|
+
errorCode: 'DYFM-RESERVATION-STATE',
|
|
55
|
+
issuerService: 'DyFM_BuildReservation_Util',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return { ...reservation, status: 'committed' };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* ② Cancel: returns the refund lines BACK TO THEIR ORIGINAL SOURCES — `cancelRefundRatio × (1 − progressRatio)`
|
|
62
|
+
* of each line (floored to whole units). A second release returns nothing (no double refund).
|
|
63
|
+
*/
|
|
64
|
+
static release(reservation, progressRatio = 0) {
|
|
65
|
+
if (reservation.status === 'released') {
|
|
66
|
+
return { reservation: reservation, refunds: [] };
|
|
67
|
+
}
|
|
68
|
+
const progress = Math.min(1, Math.max(0, Number.isFinite(progressRatio) ? progressRatio : 0));
|
|
69
|
+
const share = reservation.refundPolicy.cancelRefundRatio * (1 - progress);
|
|
70
|
+
const refunds = reservation.reservedCostSources
|
|
71
|
+
.map((s) => ({ resource: s.resource, sourceId: s.sourceId, amount: Math.floor(s.amount * share + 1e-9) }))
|
|
72
|
+
.filter((line) => line.amount > 0);
|
|
73
|
+
return { reservation: { ...reservation, status: 'released' }, refunds: refunds };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.DyFM_BuildReservation_Util = DyFM_BuildReservation_Util;
|
|
77
|
+
//# sourceMappingURL=build-reservation.util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-reservation.util.js","sourceRoot":"","sources":["../../../../src/_modules/game/_collections/build-reservation.util.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { DyFM_GameSave_ConflictResult, DyFM_GameSave_Envelope, DyFM_GameSave_Metadata, DyFM_GameSave_MigrationStep, DyFM_GameSave_Result, DyFM_GameSave_SealInput } from '../_models/game-save.interface';
|
|
2
|
+
/** The only envelope format this version reads and writes. */
|
|
3
|
+
export declare const DYFM_GAME_SAVE_FORMAT: 'dyfm-game-save/1';
|
|
4
|
+
/**
|
|
5
|
+
* BFR-WARFACTORY-011 — deterministic, headless game-save operations: seal (checksum), parse (corruption + format
|
|
6
|
+
* check), migrate (ordered replay with a typed failure) and conflict resolution (lineage first, then a time window).
|
|
7
|
+
* Nothing here reads a clock, a platform or a credential.
|
|
8
|
+
*/
|
|
9
|
+
export declare class DyFM_GameSave_Util {
|
|
10
|
+
/**
|
|
11
|
+
* FNV-1a (32-bit, hex) over the CANONICAL JSON of the payload (object keys sorted) — the same payload always gives
|
|
12
|
+
* the same checksum, whatever the key order. A corruption / identity check, NOT a security signature.
|
|
13
|
+
*/
|
|
14
|
+
static checksum(payload: unknown): string;
|
|
15
|
+
/** Builds an envelope with the format and the computed checksum. Invalid input throws (`DYFM-GAME-SAVE-SEAL`). */
|
|
16
|
+
static seal<TPayload>(input: DyFM_GameSave_SealInput<TPayload>): DyFM_GameSave_Envelope<TPayload>;
|
|
17
|
+
/** The metadata of an envelope (what `list` returns — no payload). */
|
|
18
|
+
static metadataOf(envelope: DyFM_GameSave_Envelope): DyFM_GameSave_Metadata;
|
|
19
|
+
/**
|
|
20
|
+
* Validates a RAW read (anything an adapter returned): shape, format, field types and the checksum. A corrupt or
|
|
21
|
+
* foreign save is a typed failure — never a half-loaded game.
|
|
22
|
+
*/
|
|
23
|
+
static parse<TPayload = unknown>(raw: unknown): DyFM_GameSave_Result<TPayload>;
|
|
24
|
+
/**
|
|
25
|
+
* Replays the migration steps from the envelope's `schemaVersion` up to `targetVersion`, step by step, on a COPY of
|
|
26
|
+
* the payload; the result is re-sealed (same slot, time and lineage). A save NEWER than the target, a gap in the
|
|
27
|
+
* chain or a throwing step is a typed failure and the input is untouched.
|
|
28
|
+
*/
|
|
29
|
+
static migrate<TPayload = unknown>(envelope: DyFM_GameSave_Envelope, steps: DyFM_GameSave_MigrationStep[], targetVersion: number): DyFM_GameSave_Result<TPayload>;
|
|
30
|
+
/**
|
|
31
|
+
* Deterministic local ↔ remote decision for ONE slot:
|
|
32
|
+
* none / one side missing → the existing one · same checksum → `in-sync` · proven descent (one side's
|
|
33
|
+
* `parentChecksum` is the other's checksum) → the descendant · both carry lineage but neither descends from the
|
|
34
|
+
* other → `ask-user` (a true divergence: picking one silently LOSES progress) · no lineage (legacy saves) → within
|
|
35
|
+
* `ambiguityWindowMs` `ask-user`, otherwise the newer one.
|
|
36
|
+
*/
|
|
37
|
+
static resolveConflict(local: DyFM_GameSave_Metadata | null | undefined, remote: DyFM_GameSave_Metadata | null | undefined, options?: {
|
|
38
|
+
ambiguityWindowMs?: number;
|
|
39
|
+
}): DyFM_GameSave_ConflictResult;
|
|
40
|
+
private static isVersion;
|
|
41
|
+
/** JSON with sorted object keys (arrays keep their order) — the checksum input. */
|
|
42
|
+
private static canonicalJson;
|
|
43
|
+
/** A deep copy for the migration replay (a step must not be able to change the caller's envelope). */
|
|
44
|
+
private static clone;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=game-save.util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game-save.util.d.ts","sourceRoot":"","sources":["../../../../src/_modules/game/_collections/game-save.util.ts"],"names":[],"mappings":"AACA,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,sBAAsB,EACtB,2BAA2B,EAC3B,oBAAoB,EACpB,uBAAuB,EACxB,MAAM,gCAAgC,CAAC;AAExC,8DAA8D;AAC9D,eAAO,MAAM,qBAAqB,EAAE,kBAAuC,CAAC;AAE5E;;;;GAIG;AACH,qBAAa,kBAAkB;IAE7B;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM;IAYzC,kHAAkH;IAClH,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,uBAAuB,CAAC,QAAQ,CAAC,GAAG,sBAAsB,CAAC,QAAQ,CAAC;IA8BjG,sEAAsE;IACtE,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,sBAAsB,GAAG,sBAAsB;IAM3E;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,oBAAoB,CAAC,QAAQ,CAAC;IA0B9E;;;;OAIG;IACH,MAAM,CAAC,OAAO,CAAC,QAAQ,GAAG,OAAO,EAC/B,QAAQ,EAAE,sBAAsB,EAChC,KAAK,EAAE,2BAA2B,EAAE,EACpC,aAAa,EAAE,MAAM,GACpB,oBAAoB,CAAC,QAAQ,CAAC;IA4CjC;;;;;;OAMG;IACH,MAAM,CAAC,eAAe,CACpB,KAAK,EAAE,sBAAsB,GAAG,IAAI,GAAG,SAAS,EAChD,MAAM,EAAE,sBAAsB,GAAG,IAAI,GAAG,SAAS,EACjD,OAAO,GAAE;QAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAAO,GAC3C,4BAA4B;IA2C/B,OAAO,CAAC,MAAM,CAAC,SAAS;IAIxB,mFAAmF;IACnF,OAAO,CAAC,MAAM,CAAC,aAAa;IAe5B,sGAAsG;IACtG,OAAO,CAAC,MAAM,CAAC,KAAK;CAGrB"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DyFM_GameSave_Util = exports.DYFM_GAME_SAVE_FORMAT = void 0;
|
|
4
|
+
const error_control_model_1 = require("../../../_models/control-models/error.control-model");
|
|
5
|
+
/** The only envelope format this version reads and writes. */
|
|
6
|
+
exports.DYFM_GAME_SAVE_FORMAT = 'dyfm-game-save/1';
|
|
7
|
+
/**
|
|
8
|
+
* BFR-WARFACTORY-011 — deterministic, headless game-save operations: seal (checksum), parse (corruption + format
|
|
9
|
+
* check), migrate (ordered replay with a typed failure) and conflict resolution (lineage first, then a time window).
|
|
10
|
+
* Nothing here reads a clock, a platform or a credential.
|
|
11
|
+
*/
|
|
12
|
+
class DyFM_GameSave_Util {
|
|
13
|
+
/**
|
|
14
|
+
* FNV-1a (32-bit, hex) over the CANONICAL JSON of the payload (object keys sorted) — the same payload always gives
|
|
15
|
+
* the same checksum, whatever the key order. A corruption / identity check, NOT a security signature.
|
|
16
|
+
*/
|
|
17
|
+
static checksum(payload) {
|
|
18
|
+
const text = DyFM_GameSave_Util.canonicalJson(payload);
|
|
19
|
+
let hash = 0x811c9dc5;
|
|
20
|
+
for (let i = 0; i < text.length; i++) {
|
|
21
|
+
hash ^= text.charCodeAt(i);
|
|
22
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
23
|
+
}
|
|
24
|
+
return hash.toString(16).padStart(8, '0');
|
|
25
|
+
}
|
|
26
|
+
/** Builds an envelope with the format and the computed checksum. Invalid input throws (`DYFM-GAME-SAVE-SEAL`). */
|
|
27
|
+
static seal(input) {
|
|
28
|
+
if (!input?.gameId?.trim() || !input.slotId?.trim() || !DyFM_GameSave_Util.isVersion(input.schemaVersion)
|
|
29
|
+
|| !Number.isFinite(input.savedAtMs) || input.payload === undefined) {
|
|
30
|
+
throw new error_control_model_1.DyFM_Error({
|
|
31
|
+
message: 'DyFM_GameSave: seal needs gameId, slotId, an integer schemaVersion ≥ 1, a finite savedAtMs and a payload',
|
|
32
|
+
errorCode: 'DYFM-GAME-SAVE-SEAL',
|
|
33
|
+
issuerService: 'DyFM_GameSave_Util',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const envelope = {
|
|
37
|
+
format: exports.DYFM_GAME_SAVE_FORMAT,
|
|
38
|
+
gameId: input.gameId,
|
|
39
|
+
slotId: input.slotId,
|
|
40
|
+
schemaVersion: input.schemaVersion,
|
|
41
|
+
savedAtMs: input.savedAtMs,
|
|
42
|
+
checksum: DyFM_GameSave_Util.checksum(input.payload),
|
|
43
|
+
payload: input.payload,
|
|
44
|
+
};
|
|
45
|
+
if (input.parentChecksum) {
|
|
46
|
+
envelope.parentChecksum = input.parentChecksum;
|
|
47
|
+
}
|
|
48
|
+
if (input.label) {
|
|
49
|
+
envelope.label = input.label;
|
|
50
|
+
}
|
|
51
|
+
return envelope;
|
|
52
|
+
}
|
|
53
|
+
/** The metadata of an envelope (what `list` returns — no payload). */
|
|
54
|
+
static metadataOf(envelope) {
|
|
55
|
+
const { payload: _payload, ...metadata } = envelope;
|
|
56
|
+
return metadata;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Validates a RAW read (anything an adapter returned): shape, format, field types and the checksum. A corrupt or
|
|
60
|
+
* foreign save is a typed failure — never a half-loaded game.
|
|
61
|
+
*/
|
|
62
|
+
static parse(raw) {
|
|
63
|
+
const fail = (code, message) => ({ ok: false, code: code, message: message, appliedSteps: [] });
|
|
64
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
65
|
+
return fail('not-an-object', 'the save is not an object');
|
|
66
|
+
}
|
|
67
|
+
const envelope = raw;
|
|
68
|
+
if (envelope.format !== exports.DYFM_GAME_SAVE_FORMAT) {
|
|
69
|
+
return fail('unknown-format', `unknown save format: ${String(envelope.format)}`);
|
|
70
|
+
}
|
|
71
|
+
if (typeof envelope.gameId !== 'string' || typeof envelope.slotId !== 'string'
|
|
72
|
+
|| !DyFM_GameSave_Util.isVersion(envelope.schemaVersion) || !Number.isFinite(envelope.savedAtMs)
|
|
73
|
+
|| typeof envelope.checksum !== 'string' || !('payload' in envelope)) {
|
|
74
|
+
return fail('invalid-field', 'the save is missing or has an invalid gameId / slotId / schemaVersion / savedAtMs / '
|
|
75
|
+
+ 'checksum / payload');
|
|
76
|
+
}
|
|
77
|
+
if (DyFM_GameSave_Util.checksum(envelope.payload) !== envelope.checksum) {
|
|
78
|
+
return fail('checksum-mismatch', `the payload does not match its checksum (${envelope.checksum}) — corrupt save`);
|
|
79
|
+
}
|
|
80
|
+
return { ok: true, envelope: envelope, appliedSteps: [] };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Replays the migration steps from the envelope's `schemaVersion` up to `targetVersion`, step by step, on a COPY of
|
|
84
|
+
* the payload; the result is re-sealed (same slot, time and lineage). A save NEWER than the target, a gap in the
|
|
85
|
+
* chain or a throwing step is a typed failure and the input is untouched.
|
|
86
|
+
*/
|
|
87
|
+
static migrate(envelope, steps, targetVersion) {
|
|
88
|
+
const applied = [];
|
|
89
|
+
const fail = (code, message) => ({ ok: false, code: code, message: message, appliedSteps: [...applied] });
|
|
90
|
+
if (steps.some((step) => !(step.to > step.from))) {
|
|
91
|
+
return fail('invalid-step', 'every migration step must go forward (to > from)');
|
|
92
|
+
}
|
|
93
|
+
if (envelope.schemaVersion > targetVersion) {
|
|
94
|
+
return fail('future-version', `save schema ${envelope.schemaVersion} is newer than this game (${targetVersion}) — `
|
|
95
|
+
+ 'an older build must not load it');
|
|
96
|
+
}
|
|
97
|
+
let version = envelope.schemaVersion;
|
|
98
|
+
let payload = DyFM_GameSave_Util.clone(envelope.payload);
|
|
99
|
+
while (version < targetVersion) {
|
|
100
|
+
const step = steps.find((candidate) => candidate.from === version);
|
|
101
|
+
if (!step || step.to > targetVersion) {
|
|
102
|
+
return fail('missing-step', `no migration step from schema ${version} toward ${targetVersion}`);
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
payload = step.migrate(payload);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
return fail('step-failed', `migration ${step.from}→${step.to} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
109
|
+
}
|
|
110
|
+
applied.push(`${step.from}→${step.to}`);
|
|
111
|
+
version = step.to;
|
|
112
|
+
}
|
|
113
|
+
const migrated = {
|
|
114
|
+
...envelope,
|
|
115
|
+
schemaVersion: version,
|
|
116
|
+
payload: payload,
|
|
117
|
+
checksum: DyFM_GameSave_Util.checksum(payload),
|
|
118
|
+
};
|
|
119
|
+
return { ok: true, envelope: migrated, appliedSteps: applied };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Deterministic local ↔ remote decision for ONE slot:
|
|
123
|
+
* none / one side missing → the existing one · same checksum → `in-sync` · proven descent (one side's
|
|
124
|
+
* `parentChecksum` is the other's checksum) → the descendant · both carry lineage but neither descends from the
|
|
125
|
+
* other → `ask-user` (a true divergence: picking one silently LOSES progress) · no lineage (legacy saves) → within
|
|
126
|
+
* `ambiguityWindowMs` `ask-user`, otherwise the newer one.
|
|
127
|
+
*/
|
|
128
|
+
static resolveConflict(local, remote, options = {}) {
|
|
129
|
+
if (!local && !remote) {
|
|
130
|
+
return { decision: 'none', reason: 'no save on either side' };
|
|
131
|
+
}
|
|
132
|
+
if (!remote) {
|
|
133
|
+
return { decision: 'use-local', reason: 'only the local save exists' };
|
|
134
|
+
}
|
|
135
|
+
if (!local) {
|
|
136
|
+
return { decision: 'use-remote', reason: 'only the remote save exists' };
|
|
137
|
+
}
|
|
138
|
+
if (local.gameId !== remote.gameId || local.slotId !== remote.slotId) {
|
|
139
|
+
throw new error_control_model_1.DyFM_Error({
|
|
140
|
+
message: `DyFM_GameSave: resolveConflict compares ONE slot — got ${local.gameId}/${local.slotId} vs `
|
|
141
|
+
+ `${remote.gameId}/${remote.slotId}`,
|
|
142
|
+
errorCode: 'DYFM-GAME-SAVE-SLOT-MISMATCH',
|
|
143
|
+
issuerService: 'DyFM_GameSave_Util',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (local.checksum === remote.checksum) {
|
|
147
|
+
return { decision: 'in-sync', reason: 'identical payloads' };
|
|
148
|
+
}
|
|
149
|
+
if (local.parentChecksum === remote.checksum) {
|
|
150
|
+
return { decision: 'use-local', reason: 'the local save was made from the remote one' };
|
|
151
|
+
}
|
|
152
|
+
if (remote.parentChecksum === local.checksum) {
|
|
153
|
+
return { decision: 'use-remote', reason: 'the remote save was made from the local one' };
|
|
154
|
+
}
|
|
155
|
+
if (local.parentChecksum && remote.parentChecksum) {
|
|
156
|
+
return { decision: 'ask-user', reason: 'both sides progressed separately (diverged) — either choice loses progress' };
|
|
157
|
+
}
|
|
158
|
+
const windowMs = options.ambiguityWindowMs ?? 30_000;
|
|
159
|
+
const gap = local.savedAtMs - remote.savedAtMs;
|
|
160
|
+
if (Math.abs(gap) <= windowMs) {
|
|
161
|
+
return { decision: 'ask-user', reason: `no lineage and saved within ${windowMs} ms of each other` };
|
|
162
|
+
}
|
|
163
|
+
return gap > 0
|
|
164
|
+
? { decision: 'use-local', reason: 'no lineage; the local save is newer' }
|
|
165
|
+
: { decision: 'use-remote', reason: 'no lineage; the remote save is newer' };
|
|
166
|
+
}
|
|
167
|
+
static isVersion(value) {
|
|
168
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 1;
|
|
169
|
+
}
|
|
170
|
+
/** JSON with sorted object keys (arrays keep their order) — the checksum input. */
|
|
171
|
+
static canonicalJson(value) {
|
|
172
|
+
return JSON.stringify(value, (_key, item) => {
|
|
173
|
+
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
174
|
+
return Object.keys(item).sort()
|
|
175
|
+
.reduce((sorted, key) => {
|
|
176
|
+
sorted[key] = item[key];
|
|
177
|
+
return sorted;
|
|
178
|
+
}, {});
|
|
179
|
+
}
|
|
180
|
+
return item;
|
|
181
|
+
}) ?? 'undefined';
|
|
182
|
+
}
|
|
183
|
+
/** A deep copy for the migration replay (a step must not be able to change the caller's envelope). */
|
|
184
|
+
static clone(value) {
|
|
185
|
+
return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
exports.DyFM_GameSave_Util = DyFM_GameSave_Util;
|
|
189
|
+
//# sourceMappingURL=game-save.util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game-save.util.js","sourceRoot":"","sources":["../../../../src/_modules/game/_collections/game-save.util.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BFR-WARFACTORY-012 (scope extension ①–②) — placement preview + reservation with escrow. Game-specific resources
|
|
3
|
+
* (drones, a hammer, the player's position) stay OUT: they are the game's extension, not these models.
|
|
4
|
+
*/
|
|
5
|
+
/** ① Why a placement is invalid — a reason, never a bare `false`. */
|
|
6
|
+
export interface DyFM_PlacementReason {
|
|
7
|
+
/** Stable code (e.g. `blocked`, `out-of-bounds`, `insufficient-resources`). */
|
|
8
|
+
code: string;
|
|
9
|
+
/** Human text for the UI. */
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
/** ① A placement preview: where, how big, is it valid, and why not. */
|
|
13
|
+
export interface DyFM_PlacementPreview<TPosition = {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
}> {
|
|
17
|
+
position: TPosition;
|
|
18
|
+
footprint: {
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
};
|
|
22
|
+
valid: boolean;
|
|
23
|
+
/** Empty when valid; every failed rule otherwise (all of them, not just the first). */
|
|
24
|
+
reasons: DyFM_PlacementReason[];
|
|
25
|
+
}
|
|
26
|
+
/** ① One placement rule: `null` = fine, a reason = why not. */
|
|
27
|
+
export type DyFM_PlacementRule<TPosition = {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
}> = (candidate: {
|
|
31
|
+
position: TPosition;
|
|
32
|
+
footprint: {
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
};
|
|
36
|
+
}) => DyFM_PlacementReason | null;
|
|
37
|
+
/** ② One held cost line: how much of which resource, taken FROM WHERE (the key to restoring the original state). */
|
|
38
|
+
export interface DyFM_ReservedCostSource {
|
|
39
|
+
resource: string;
|
|
40
|
+
amount: number;
|
|
41
|
+
/** Where it was taken from (a storage, a building, the player's inventory …). */
|
|
42
|
+
sourceId: string;
|
|
43
|
+
}
|
|
44
|
+
/** ② What happens to the held cost when the reservation is cancelled. */
|
|
45
|
+
export interface DyFM_RefundPolicy {
|
|
46
|
+
/** Share returned on cancel before any progress (0..1). Default: `1`. */
|
|
47
|
+
cancelRefundRatio?: number;
|
|
48
|
+
}
|
|
49
|
+
/** ② A reservation: the reserved location + the cost in escrow, per source. */
|
|
50
|
+
export interface DyFM_BuildReservation<TPosition = {
|
|
51
|
+
x: number;
|
|
52
|
+
y: number;
|
|
53
|
+
}> {
|
|
54
|
+
reservationId: string;
|
|
55
|
+
position: TPosition;
|
|
56
|
+
footprint: {
|
|
57
|
+
width: number;
|
|
58
|
+
height: number;
|
|
59
|
+
};
|
|
60
|
+
reservedCostSources: DyFM_ReservedCostSource[];
|
|
61
|
+
refundPolicy: Required<DyFM_RefundPolicy>;
|
|
62
|
+
status: 'held' | 'committed' | 'released';
|
|
63
|
+
}
|
|
64
|
+
/** ② A refund line, back to its ORIGINAL source. */
|
|
65
|
+
export interface DyFM_RefundLine {
|
|
66
|
+
resource: string;
|
|
67
|
+
amount: number;
|
|
68
|
+
sourceId: string;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=build-reservation.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-reservation.interface.d.ts","sourceRoot":"","sources":["../../../../src/_modules/game/_models/build-reservation.interface.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,qEAAqE;AACrE,MAAM,WAAW,oBAAoB;IACnC,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,uEAAuE;AACvE,MAAM,WAAW,qBAAqB,CAAC,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE;IACzE,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,KAAK,EAAE,OAAO,CAAC;IACf,uFAAuF;IACvF,OAAO,EAAE,oBAAoB,EAAE,CAAC;CACjC;AAED,+DAA+D;AAC/D,MAAM,MAAM,kBAAkB,CAAC,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,IACjE,CAAC,SAAS,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,KAAK,oBAAoB,GAAG,IAAI,CAAC;AAEpH,oHAAoH;AACpH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,yEAAyE;AACzE,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB,CAAC,SAAS,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE;IACzE,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,mBAAmB,EAAE,uBAAuB,EAAE,CAAC;IAC/C,YAAY,EAAE,QAAQ,CAAC,iBAAiB,CAAC,CAAC;IAC1C,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;CAC3C;AAED,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* BFR-WARFACTORY-012 (scope extension ①–②) — placement preview + reservation with escrow. Game-specific resources
|
|
4
|
+
* (drones, a hammer, the player's position) stay OUT: they are the game's extension, not these models.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
//# sourceMappingURL=build-reservation.interface.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-reservation.interface.js","sourceRoot":"","sources":["../../../../src/_modules/game/_models/build-reservation.interface.ts"],"names":[],"mappings":";AAAA;;;GAGG"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BFR-WARFACTORY-011 — headless game-save contracts. No provider credential, platform binding or game payload shape
|
|
3
|
+
* lives here: the payload is opaque (`TPayload`, typically the serialized game state), adapters are the game's.
|
|
4
|
+
*/
|
|
5
|
+
/** The envelope format marker — an unknown one is rejected, never guessed. */
|
|
6
|
+
export type DyFM_GameSave_Format_Type = 'dyfm-game-save/1';
|
|
7
|
+
/** The list / conflict view of a save: everything except the payload. */
|
|
8
|
+
export interface DyFM_GameSave_Metadata {
|
|
9
|
+
format: DyFM_GameSave_Format_Type;
|
|
10
|
+
/** The game (one envelope format serves several games). */
|
|
11
|
+
gameId: string;
|
|
12
|
+
/** The save slot (the adapter-level key). */
|
|
13
|
+
slotId: string;
|
|
14
|
+
/** The GAME's payload schema version (integer ≥ 1) — what migrations step through. */
|
|
15
|
+
schemaVersion: number;
|
|
16
|
+
/** When it was saved (Unix ms, from the caller's clock). */
|
|
17
|
+
savedAtMs: number;
|
|
18
|
+
/** Deterministic checksum of the payload (corruption + identity). */
|
|
19
|
+
checksum: string;
|
|
20
|
+
/** The checksum of the save this one was made FROM (lineage) — lets conflict resolution prove descent. */
|
|
21
|
+
parentChecksum?: string;
|
|
22
|
+
/** Display label (optional). */
|
|
23
|
+
label?: string;
|
|
24
|
+
}
|
|
25
|
+
/** A full save: metadata + the opaque payload. */
|
|
26
|
+
export interface DyFM_GameSave_Envelope<TPayload = unknown> extends DyFM_GameSave_Metadata {
|
|
27
|
+
payload: TPayload;
|
|
28
|
+
}
|
|
29
|
+
/** What a game passes to `seal` — the checksum and the format are computed, never hand-written. */
|
|
30
|
+
export interface DyFM_GameSave_SealInput<TPayload = unknown> {
|
|
31
|
+
gameId: string;
|
|
32
|
+
slotId: string;
|
|
33
|
+
schemaVersion: number;
|
|
34
|
+
savedAtMs: number;
|
|
35
|
+
payload: TPayload;
|
|
36
|
+
parentChecksum?: string;
|
|
37
|
+
label?: string;
|
|
38
|
+
}
|
|
39
|
+
/** A storage adapter (localStorage, file system, Steam, cloud …) — implemented by the game, not here. */
|
|
40
|
+
export interface DyFM_GameSave_Adapter_Interface<TPayload = unknown> {
|
|
41
|
+
/** Writes (overwrites) the slot. */
|
|
42
|
+
write(envelope: DyFM_GameSave_Envelope<TPayload>): Promise<void>;
|
|
43
|
+
/** Reads a slot; `null` when it does not exist. Callers should pass the raw result through `parse`. */
|
|
44
|
+
read(slotId: string): Promise<unknown>;
|
|
45
|
+
/** Lists the slots' metadata (no payloads — the list UI must not load every save into memory). */
|
|
46
|
+
list(): Promise<DyFM_GameSave_Metadata[]>;
|
|
47
|
+
/** Deletes a slot; idempotent. */
|
|
48
|
+
delete(slotId: string): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
/** One migration step: the payload of `from` → the payload of `to` (`to` > `from`). Pure; must not mutate its input. */
|
|
51
|
+
export interface DyFM_GameSave_MigrationStep {
|
|
52
|
+
from: number;
|
|
53
|
+
to: number;
|
|
54
|
+
migrate: (payload: unknown) => unknown;
|
|
55
|
+
}
|
|
56
|
+
/** Why a parse / migration failed — a stable code for the game's UI and telemetry. */
|
|
57
|
+
export type DyFM_GameSave_Failure_Type = 'not-an-object' | 'unknown-format' | 'invalid-field' | 'checksum-mismatch' | 'future-version' | 'missing-step' | 'invalid-step' | 'step-failed';
|
|
58
|
+
/** The result of `parse` / `migrate`. */
|
|
59
|
+
export type DyFM_GameSave_Result<TPayload = unknown> = {
|
|
60
|
+
ok: true;
|
|
61
|
+
envelope: DyFM_GameSave_Envelope<TPayload>;
|
|
62
|
+
appliedSteps: string[];
|
|
63
|
+
} | {
|
|
64
|
+
ok: false;
|
|
65
|
+
code: DyFM_GameSave_Failure_Type;
|
|
66
|
+
message: string;
|
|
67
|
+
appliedSteps: string[];
|
|
68
|
+
};
|
|
69
|
+
/** The deterministic conflict decision (the game maps `ask-user` to its conflict dialog). */
|
|
70
|
+
export type DyFM_GameSave_ConflictDecision_Type = 'in-sync' | 'use-local' | 'use-remote' | 'ask-user' | 'none';
|
|
71
|
+
/** The decision + why (for logs and the dialog). */
|
|
72
|
+
export interface DyFM_GameSave_ConflictResult {
|
|
73
|
+
decision: DyFM_GameSave_ConflictDecision_Type;
|
|
74
|
+
reason: string;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=game-save.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game-save.interface.d.ts","sourceRoot":"","sources":["../../../../src/_modules/game/_models/game-save.interface.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,8EAA8E;AAC9E,MAAM,MAAM,yBAAyB,GAAG,kBAAkB,CAAC;AAE3D,yEAAyE;AACzE,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,yBAAyB,CAAC;IAClC,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,aAAa,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,0GAA0G;IAC1G,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,kDAAkD;AAClD,MAAM,WAAW,sBAAsB,CAAC,QAAQ,GAAG,OAAO,CAAE,SAAQ,sBAAsB;IACxF,OAAO,EAAE,QAAQ,CAAC;CACnB;AAED,mGAAmG;AACnG,MAAM,WAAW,uBAAuB,CAAC,QAAQ,GAAG,OAAO;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,QAAQ,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,yGAAyG;AACzG,MAAM,WAAW,+BAA+B,CAAC,QAAQ,GAAG,OAAO;IACjE,oCAAoC;IACpC,KAAK,CAAC,QAAQ,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,uGAAuG;IACvG,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,kGAAkG;IAClG,IAAI,IAAI,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC;IAC1C,kCAAkC;IAClC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,wHAAwH;AACxH,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC;CACxC;AAED,sFAAsF;AACtF,MAAM,MAAM,0BAA0B,GAClC,eAAe,GAAG,gBAAgB,GAAG,eAAe,GAAG,mBAAmB,GAC1E,gBAAgB,GAAG,cAAc,GAAG,cAAc,GAAG,aAAa,CAAC;AAEvE,yCAAyC;AACzC,MAAM,MAAM,oBAAoB,CAAC,QAAQ,GAAG,OAAO,IAC/C;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,sBAAsB,CAAC,QAAQ,CAAC,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,GAChF;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,0BAA0B,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE7F,6FAA6F;AAC7F,MAAM,MAAM,mCAAmC,GAAG,SAAS,GAAG,WAAW,GAAG,YAAY,GAAG,UAAU,GAAG,MAAM,CAAC;AAE/G,oDAAoD;AACpD,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,mCAAmC,CAAC;IAC9C,MAAM,EAAE,MAAM,CAAC;CAChB"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* BFR-WARFACTORY-011 — headless game-save contracts. No provider credential, platform binding or game payload shape
|
|
4
|
+
* lives here: the payload is opaque (`TPayload`, typically the serialized game state), adapters are the game's.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
//# sourceMappingURL=game-save.interface.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"game-save.interface.js","sourceRoot":"","sources":["../../../../src/_modules/game/_models/game-save.interface.ts"],"names":[],"mappings":";AAAA;;;GAGG"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { DyFM_Viewport_Config, DyFM_Viewport_Point, DyFM_Viewport_State } from './viewport.interface';
|
|
2
|
+
/**
|
|
3
|
+
* BFR-WARFACTORY-013 — framework-independent zoom / pan viewport. It is deliberately NOT "the camera": the same model
|
|
4
|
+
* drives a world canvas and a DOM panel (e.g. a zoomable tech tree). Input handling (pointer, wheel, keys) belongs to
|
|
5
|
+
* a UI adapter that calls these methods.
|
|
6
|
+
*
|
|
7
|
+
* - `worldToScreen` / `screenToWorld` — the only transform; the screen centre shows `center`.
|
|
8
|
+
* - `zoomAt(screenPoint, factor)` — the world point UNDER the pointer stays under the pointer (wheel / pinch feel).
|
|
9
|
+
* - `panByScreen(dx, dy)` — drag: the content follows the pointer at any zoom.
|
|
10
|
+
* - `move(direction, speed, elapsedSeconds)` — elapsed-time keyboard panning; each step is capped
|
|
11
|
+
* (`maxMoveStepSeconds`), so a hitch never makes the camera jump; speed is in SCREEN px/s (same feel at any zoom).
|
|
12
|
+
* - Zoom is clamped to [minZoom, maxZoom], the centre to `worldBounds`; non-finite input is ignored, never stored.
|
|
13
|
+
*/
|
|
14
|
+
export declare class DyFM_Viewport_ControlModel {
|
|
15
|
+
private screenWidth;
|
|
16
|
+
private screenHeight;
|
|
17
|
+
private readonly minZoom;
|
|
18
|
+
private readonly maxZoom;
|
|
19
|
+
private readonly bounds;
|
|
20
|
+
private readonly stepFactor;
|
|
21
|
+
private readonly maxMoveStep;
|
|
22
|
+
private centerPoint;
|
|
23
|
+
private zoomValue;
|
|
24
|
+
/** Creates the viewport; an invalid configuration throws `DYFM-VIEWPORT-CONFIG`. */
|
|
25
|
+
constructor(config: DyFM_Viewport_Config);
|
|
26
|
+
/** The world point shown at the screen centre. */
|
|
27
|
+
get center(): DyFM_Viewport_Point;
|
|
28
|
+
/** Screen pixels per world unit. */
|
|
29
|
+
get zoom(): number;
|
|
30
|
+
/** World → screen pixels. */
|
|
31
|
+
worldToScreen(world: DyFM_Viewport_Point): DyFM_Viewport_Point;
|
|
32
|
+
/** Screen pixels → world. */
|
|
33
|
+
screenToWorld(screen: DyFM_Viewport_Point): DyFM_Viewport_Point;
|
|
34
|
+
/** Sets the zoom (clamped), keeping the centre. */
|
|
35
|
+
setZoom(zoom: number): void;
|
|
36
|
+
/** Zooms by `factor` keeping the world point under `screenPoint` fixed on screen (then clamps). */
|
|
37
|
+
zoomAt(screenPoint: DyFM_Viewport_Point, factor: number): void;
|
|
38
|
+
/** `steps` notches (+ in, − out) at `screenPoint` (default: the screen centre). */
|
|
39
|
+
zoomStep(steps: number, screenPoint?: DyFM_Viewport_Point): void;
|
|
40
|
+
/** Drag by a screen delta: the content moves WITH the pointer. */
|
|
41
|
+
panByScreen(dx: number, dy: number): void;
|
|
42
|
+
/**
|
|
43
|
+
* Elapsed-time panning (keyboard): `direction` is normalised, `speedScreenPxPerSecond` is screen speed, the elapsed
|
|
44
|
+
* time is capped at `maxMoveStepSeconds` per call. Returns the world distance actually moved.
|
|
45
|
+
*/
|
|
46
|
+
move(direction: DyFM_Viewport_Point, speedScreenPxPerSecond: number, elapsedSeconds: number): number;
|
|
47
|
+
/** Centres on a world point (clamped). */
|
|
48
|
+
centerOn(world: DyFM_Viewport_Point): void;
|
|
49
|
+
/** The screen changed size (e.g. a window resize); the centre stays. */
|
|
50
|
+
resize(screenWidth: number, screenHeight: number): void;
|
|
51
|
+
/** Save state. */
|
|
52
|
+
exportState(): DyFM_Viewport_State;
|
|
53
|
+
/** Restore state (clamped to THIS viewport's limits); `false` and no change when unreadable. */
|
|
54
|
+
importState(state: unknown): boolean;
|
|
55
|
+
private clampZoom;
|
|
56
|
+
private clampCenter;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=viewport.control-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewport.control-model.d.ts","sourceRoot":"","sources":["../../../../src/_modules/game/_models/viewport.control-model.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEtG;;;;;;;;;;;GAWG;AACH,qBAAa,0BAA0B;IACrC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsC;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,SAAS,CAAa;IAE9B,oFAAoF;gBACxE,MAAM,EAAE,oBAAoB;IAyBxC,kDAAkD;IAClD,IAAI,MAAM,IAAI,mBAAmB,CAEhC;IAED,oCAAoC;IACpC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,6BAA6B;IAC7B,aAAa,CAAC,KAAK,EAAE,mBAAmB,GAAG,mBAAmB;IAO9D,6BAA6B;IAC7B,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,mBAAmB;IAO/D,mDAAmD;IACnD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM3B,mGAAmG;IACnG,MAAM,CAAC,WAAW,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAe9D,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,mBAAmB,GAAG,IAAI;IAMhE,kEAAkE;IAClE,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IASzC;;;OAGG;IACH,IAAI,CAAC,SAAS,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM;IAoBpG,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAM1C,wEAAwE;IACxE,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAOvD,kBAAkB;IAClB,WAAW,IAAI,mBAAmB;IAIlC,gGAAgG;IAChG,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IAapC,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,WAAW;CAUpB"}
|