@linxin666/dsh-pet 0.3.24 → 0.4.2
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/README.i18n.yaml +2 -2
- package/README.md +4 -3
- package/README.zh.md +4 -3
- package/icon.svg +3 -0
- package/lib/client.js +522 -194
- package/lib/client.js.map +1 -1
- package/lib/index.js +706 -422
- package/lib/live2d-vendor.js +96 -96
- package/lib/live2d-vendor.js.map +1 -1
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +1 -1
- package/lib/types/client/PetSettingsCard.d.ts +20 -9
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSettingsCard.js +116 -5
- package/lib/types/client/PetSprite.d.ts +6 -0
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +95 -0
- package/lib/types/client/PluginSettingsCard.d.ts +4 -0
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +3 -2
- package/lib/types/client/gameplay-hud.d.ts +10 -1
- package/lib/types/client/gameplay-hud.d.ts.map +1 -1
- package/lib/types/client/gameplay-hud.js +137 -23
- package/lib/types/client/index.d.ts +8 -5
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +61 -27
- package/lib/types/client/main-session.d.ts +33 -0
- package/lib/types/client/main-session.d.ts.map +1 -0
- package/lib/types/client/main-session.js +34 -0
- package/lib/types/client/settings-form.d.ts +17 -17
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +30 -27
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +5 -3
- package/lib/types/gameplay.d.ts +79 -5
- package/lib/types/gameplay.d.ts.map +1 -1
- package/lib/types/gameplay.js +212 -17
- package/lib/types/http.d.ts +18 -1
- package/lib/types/http.d.ts.map +1 -1
- package/lib/types/http.js +22 -1
- package/lib/types/index.d.ts +100 -29
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +113 -64
- package/lib/types/persist.d.ts.map +1 -1
- package/lib/types/persist.js +3 -1
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +3 -1
- package/lib/types/service.d.ts +8 -3
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +26 -4
- package/package.json +46 -26
- package/src/client/PetDockEntry.tsx +1 -0
- package/src/client/PetSettingsCard.test.tsx +103 -0
- package/src/client/PetSettingsCard.tsx +129 -26
- package/src/client/PetSprite.tsx +96 -0
- package/src/client/PluginSettingsCard.tsx +7 -2
- package/src/client/gameplay-hud.test.tsx +106 -8
- package/src/client/gameplay-hud.tsx +143 -28
- package/src/client/index.test.tsx +37 -4
- package/src/client/index.ts +74 -34
- package/src/client/main-session.ts +43 -0
- package/src/client/settings-form.ts +34 -31
- package/src/event-projection.ts +5 -3
- package/src/gameplay.test.ts +138 -1
- package/src/gameplay.ts +253 -16
- package/src/http.ts +23 -1
- package/src/index.test.ts +107 -11
- package/src/index.ts +162 -73
- package/src/persist.ts +3 -1
- package/src/registry.test.ts +3 -2
- package/src/routes.ts +3 -1
- package/src/service.ts +27 -5
|
@@ -91,30 +91,30 @@ export class CardForm {
|
|
|
91
91
|
specs;
|
|
92
92
|
staged = new Map();
|
|
93
93
|
listeners = new Set();
|
|
94
|
-
/** The
|
|
95
|
-
|
|
94
|
+
/** The form subscription installed in the constructor; released by dispose(). */
|
|
95
|
+
disposeForm;
|
|
96
96
|
disposed = false;
|
|
97
97
|
saving = false;
|
|
98
98
|
failed = false;
|
|
99
99
|
failedReason;
|
|
100
|
-
/** @param scope - the bound
|
|
100
|
+
/** @param scope - the bound configuration form for this card's namespace. */
|
|
101
101
|
constructor(scope, specs) {
|
|
102
102
|
this.scope = scope;
|
|
103
103
|
this.specs = new Map(specs.map(spec => [spec.field, spec]));
|
|
104
|
-
this.
|
|
104
|
+
this.disposeForm = scope.subscribe(() => { this.publish(); });
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
|
-
* Release the
|
|
107
|
+
* Release the form subscription and every bound store listener. The card
|
|
108
108
|
* must call this on teardown; later calls are no-ops.
|
|
109
109
|
*/
|
|
110
110
|
dispose() {
|
|
111
111
|
if (this.disposed)
|
|
112
112
|
return;
|
|
113
113
|
this.disposed = true;
|
|
114
|
-
this.
|
|
114
|
+
this.disposeForm();
|
|
115
115
|
this.listeners.clear();
|
|
116
116
|
}
|
|
117
|
-
/** Publish a projection of this form, rebuilt whenever the
|
|
117
|
+
/** Publish a projection of this form, rebuilt whenever the form or a draft changes. */
|
|
118
118
|
bind(project) {
|
|
119
119
|
const store = createSnapshotStore(project());
|
|
120
120
|
this.listeners.add(() => { store.set(project()); });
|
|
@@ -168,19 +168,19 @@ export class CardForm {
|
|
|
168
168
|
};
|
|
169
169
|
}
|
|
170
170
|
/**
|
|
171
|
-
* Write every staged edit in one atomic
|
|
171
|
+
* Write every staged edit in one atomic form mutation, then re-seed from
|
|
172
172
|
* what the Host accepted.
|
|
173
173
|
*
|
|
174
174
|
* The whole batch rides one mutate, so cross-field validate hooks
|
|
175
175
|
* (baseURL+model) judge it as a unit: the Host either applies every write
|
|
176
|
-
* or refuses the batch. The
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
* settled snapshot back
|
|
180
|
-
* fails the whole save. A
|
|
181
|
-
*
|
|
182
|
-
* message. A save that did not land keeps its drafts, so
|
|
183
|
-
* correct them instead of retyping.
|
|
176
|
+
* or refuses the batch. The form contract answers a refusal or a skipped
|
|
177
|
+
* write with `false` (it recovers with a fresh Host view instead of
|
|
178
|
+
* throwing), so the outcome is judged twice: the answer itself, and then the
|
|
179
|
+
* settled snapshot read back one planned write at a time. One missed write
|
|
180
|
+
* fails the whole save. A transport that rejects instead (the dsh-web bridge
|
|
181
|
+
* controller on a dead connection) reports through the same failure path
|
|
182
|
+
* with its rejection message. A save that did not land keeps its drafts, so
|
|
183
|
+
* the user can correct them instead of retyping.
|
|
184
184
|
* @returns settlement after the mutation and the read-back.
|
|
185
185
|
*/
|
|
186
186
|
async save() {
|
|
@@ -198,32 +198,35 @@ export class CardForm {
|
|
|
198
198
|
this.failed = false;
|
|
199
199
|
this.failedReason = undefined;
|
|
200
200
|
this.publish();
|
|
201
|
-
// One atomic namespace mutation: the
|
|
202
|
-
//
|
|
201
|
+
// One atomic namespace mutation: the form contract takes ordered path
|
|
202
|
+
// operations, so the whole staged batch is validated, persisted, and
|
|
203
203
|
// recovered together — either every write lands or none does.
|
|
204
204
|
const ops = valid.map(item => item.op.op === 'set'
|
|
205
205
|
? { op: 'set', path: [item.field], value: item.op.value }
|
|
206
206
|
: { op: 'unset', path: [item.field] });
|
|
207
207
|
let failedReason;
|
|
208
|
+
let accepted = false;
|
|
208
209
|
try {
|
|
209
|
-
await this.scope.mutate(ops);
|
|
210
|
+
accepted = await this.scope.mutate(ops);
|
|
210
211
|
}
|
|
211
212
|
catch (error) {
|
|
212
213
|
failedReason = error instanceof Error ? error.message : String(error);
|
|
213
214
|
}
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
// judge every planned write against the settled
|
|
217
|
-
// atomic, so one missed write fails the
|
|
218
|
-
|
|
215
|
+
// `false` is the contract's refusal/skip answer, and the form still
|
|
216
|
+
// resolves after a refusal (it recovers with a fresh view), so the answer
|
|
217
|
+
// alone is not enough: judge every planned write against the settled
|
|
218
|
+
// snapshot as well. The mutation is atomic, so one missed write fails the
|
|
219
|
+
// whole save and keeps the drafts.
|
|
220
|
+
const landed = accepted && failedReason === undefined && valid.every(item => item.judge());
|
|
219
221
|
for (const [field, before] of pending) {
|
|
220
222
|
if (landed && this.staged.get(field) === before)
|
|
221
223
|
this.staged.delete(field);
|
|
222
224
|
}
|
|
223
225
|
this.saving = false;
|
|
224
226
|
this.failed = !landed;
|
|
225
|
-
// A
|
|
226
|
-
// generic failure copy; a rejecting
|
|
227
|
+
// A refused or unlanded batch carries no server reason: the card surfaces
|
|
228
|
+
// its generic failure copy; a rejecting transport (the bridge) adds its
|
|
229
|
+
// message.
|
|
227
230
|
this.failedReason = failedReason;
|
|
228
231
|
this.publish();
|
|
229
232
|
}
|
|
@@ -261,7 +264,7 @@ export class CardForm {
|
|
|
261
264
|
*/
|
|
262
265
|
landedSet(field, value) {
|
|
263
266
|
// A redacted secret never appears in any wire view layer: the Host strips
|
|
264
|
-
// role('secret') fields and reports them through a sidecar the
|
|
267
|
+
// role('secret') fields and reports them through a sidecar the form
|
|
265
268
|
// snapshot does not expose, so there is nothing to compare the draft
|
|
266
269
|
// against. Settling is the only signal the form has; the rest of the
|
|
267
270
|
// batch, when one exists, still carries the atomic verdict by read-back.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-projection.d.ts","sourceRoot":"","sources":["../../src/event-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EACL,WAAW,EAGX,aAAa,EAGb,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAA;AAErB,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,+EAA+E;IAC/E,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACtB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,cAAc,EAAE,OAAO,CAAA;IACvB,qEAAqE;IACrE,KAAK,EAAE,WAAW,CAAA;IAClB,2EAA2E;IAC3E,QAAQ,EAAE,aAAa,CAAA;CACxB;AAED,kFAAkF;AAClF,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,aAAa,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,CAAC,EAAE,kBAAkB,GAAG,iBAAiB,CASpF;AAQD,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAAC,OAAO,CAAC,CAE9E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,iBAAiB,EAC1B,KAAK,GAAE,MAAmB,GACzB,qBAAqB,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"event-projection.d.ts","sourceRoot":"","sources":["../../src/event-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EACL,WAAW,EAGX,aAAa,EAGb,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAA;AAErB,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,+EAA+E;IAC/E,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACtB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,cAAc,EAAE,OAAO,CAAA;IACvB,qEAAqE;IACrE,KAAK,EAAE,WAAW,CAAA;IAClB,2EAA2E;IAC3E,QAAQ,EAAE,aAAa,CAAA;CACxB;AAED,kFAAkF;AAClF,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,aAAa,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,CAAC,EAAE,kBAAkB,GAAG,iBAAiB,CASpF;AAQD,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAAC,OAAO,CAAC,CAE9E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,iBAAiB,EAC1B,KAAK,GAAE,MAAmB,GACzB,qBAAqB,GAAG,SAAS,CAmGnC;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,iBAAiB,EAC1B,KAAK,GAAE,MAAmB,GACzB,qBAAqB,GAAG,SAAS,CAkBnC"}
|
|
@@ -80,9 +80,11 @@ export function projectOfficialEvent(event, runtime, nowMs = Date.now()) {
|
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
case 'tool/result': {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
83
|
+
// Session format V4 moved the call identity and the outcome onto the
|
|
84
|
+
// tool-role message root; the content blocks no longer carry either.
|
|
85
|
+
const { message } = event.data;
|
|
86
|
+
const callId = String(message.toolCallId);
|
|
87
|
+
const failed = event.data.error !== undefined || message.isError === true;
|
|
86
88
|
const wasTest = runtime.testCalls.delete(callId);
|
|
87
89
|
runtime.activeTools.delete(callId);
|
|
88
90
|
runtime.stepHadFailure ||= failed;
|
package/lib/types/gameplay.d.ts
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
* discipline): the host runs no timers for gameplay.
|
|
12
12
|
* @module @linxin666/dsh-pet/gameplay
|
|
13
13
|
*/
|
|
14
|
+
/** One roam direction the pet may walk in. */
|
|
15
|
+
export type PetRoamDirection = 'up' | 'down' | 'left' | 'right';
|
|
16
|
+
/** Every roam direction, in roll order (equal chance each unless restricted). */
|
|
17
|
+
export declare const PET_ROAM_DIRECTIONS: readonly PetRoamDirection[];
|
|
14
18
|
/** One gameplay effect: add amount to a declared stat or a currency. */
|
|
15
19
|
export interface PetGameplayEffect {
|
|
16
20
|
stat?: string;
|
|
@@ -62,6 +66,26 @@ export interface PetGameplayShopItem {
|
|
|
62
66
|
}[];
|
|
63
67
|
};
|
|
64
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* One extra named gameplay mode beyond work/sleep (a bath, a play session…).
|
|
71
|
+
* The host holds `state` while the mode is active and may restore a stat on
|
|
72
|
+
* a fixed cadence — the same lazy-settle rule the sleep mode uses. A mode is
|
|
73
|
+
* pure data: adding one to a manifest needs no host change.
|
|
74
|
+
*/
|
|
75
|
+
export interface PetGameplayModeDef {
|
|
76
|
+
/** frames2d track held for as long as the mode is active. */
|
|
77
|
+
state: string;
|
|
78
|
+
/** Action-button label (plain text); falls back to the i18n key pet.gameplay.<name>. */
|
|
79
|
+
label?: string;
|
|
80
|
+
/** Label while the mode is active (plain text); falls back to `label`. */
|
|
81
|
+
activeLabel?: string;
|
|
82
|
+
/** Periodic stat restore while the mode is active (omitted = no restore). */
|
|
83
|
+
restore?: {
|
|
84
|
+
stat: string;
|
|
85
|
+
amount: number;
|
|
86
|
+
intervalMs: number;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
65
89
|
/** The validated manifest 'gameplay' block. */
|
|
66
90
|
export interface PetGameplayManifest {
|
|
67
91
|
idleDirector?: {
|
|
@@ -118,6 +142,38 @@ export interface PetGameplayManifest {
|
|
|
118
142
|
intervalMs: number;
|
|
119
143
|
};
|
|
120
144
|
};
|
|
145
|
+
/**
|
|
146
|
+
* Extra named modes beyond work/sleep, keyed by a kebab mode id. Each one
|
|
147
|
+
* gets its own menu button, holds its track while active and may restore a
|
|
148
|
+
* stat on a cadence. 'work' and 'sleep' are reserved keys.
|
|
149
|
+
*/
|
|
150
|
+
modes?: Record<string, PetGameplayModeDef>;
|
|
151
|
+
/**
|
|
152
|
+
* Random roaming: on a slow interval the pet rolls whether to wander and
|
|
153
|
+
* walks to a new spot on screen with `state` held for the travel time. The
|
|
154
|
+
* chrome owns the motion (clamped to the viewport) and persists the resting
|
|
155
|
+
* spot the way a drag does; the roll only runs while the pet is idle and no
|
|
156
|
+
* mode, drag or touch animation owns it.
|
|
157
|
+
*/
|
|
158
|
+
roam?: {
|
|
159
|
+
/** frames2d track held while walking. */
|
|
160
|
+
state: string;
|
|
161
|
+
/** Ms between roam decisions. */
|
|
162
|
+
intervalMs: number;
|
|
163
|
+
/** Chance one decision actually walks (0, 1]. */
|
|
164
|
+
probability: number;
|
|
165
|
+
/** Travel per roam in px (distanceMin <= distanceMax). */
|
|
166
|
+
distanceMin: number;
|
|
167
|
+
distanceMax: number;
|
|
168
|
+
/** Walking speed in px per second. */
|
|
169
|
+
speed: number;
|
|
170
|
+
/**
|
|
171
|
+
* Directions the pet may walk in, rolled uniformly (equal chance each).
|
|
172
|
+
* Omitted = all four. A side-view crawl reads best walking left/right, so
|
|
173
|
+
* a pet may restrict the list to those two.
|
|
174
|
+
*/
|
|
175
|
+
directions?: PetRoamDirection[];
|
|
176
|
+
};
|
|
121
177
|
passiveIncome?: {
|
|
122
178
|
currency: string;
|
|
123
179
|
amount: number;
|
|
@@ -147,22 +203,40 @@ export declare function parseGameplayManifest(raw: unknown, hooks: GameplayParse
|
|
|
147
203
|
export interface PetGameplayState {
|
|
148
204
|
stats: Record<string, number>;
|
|
149
205
|
currencies: Record<string, number>;
|
|
150
|
-
mode
|
|
206
|
+
/** Active gameplay mode id ('work' | 'sleep' | a declared extra mode) or null. */
|
|
207
|
+
mode: string | null;
|
|
151
208
|
/** Epoch ms of the last lazy settle. */
|
|
152
209
|
settledAt: number;
|
|
153
210
|
/** Accumulated remainder ms towards the next passive income tick. */
|
|
154
211
|
incomeCarryMs?: number;
|
|
155
|
-
/** Accumulated remainder ms towards the next
|
|
212
|
+
/** Accumulated remainder ms towards the next mode restore tick. */
|
|
156
213
|
restoreCarryMs?: number;
|
|
157
214
|
}
|
|
215
|
+
/** Every mode the menu offers, in manifest order (sleep first, then extras). */
|
|
216
|
+
export declare function declaredModes(manifest: PetGameplayManifest): string[];
|
|
217
|
+
/**
|
|
218
|
+
* The frames2d track one active mode holds. 'work' is owned by the work loop
|
|
219
|
+
* (its state, result and fallback are its own), so it resolves to undefined.
|
|
220
|
+
*/
|
|
221
|
+
export declare function modeStateOf(manifest: PetGameplayManifest, mode: string): string | undefined;
|
|
222
|
+
/** The restore rule of one active mode, when it declares one. */
|
|
223
|
+
export declare function modeRestoreOf(manifest: PetGameplayManifest, mode: string | null): {
|
|
224
|
+
stat: string;
|
|
225
|
+
amount: number;
|
|
226
|
+
intervalMs: number;
|
|
227
|
+
} | undefined;
|
|
228
|
+
/** Whether the manifest still declares this gameplay mode id. */
|
|
229
|
+
export declare function isDeclaredMode(manifest: PetGameplayManifest, mode: string): boolean;
|
|
158
230
|
/** Fresh state for one pet: stats at their initial (default max), no currency. */
|
|
159
231
|
export declare function initialGameplayState(manifest: PetGameplayManifest, now: number): PetGameplayState;
|
|
160
232
|
/** Clamp one stat value into [0, max]; currencies into [0, CURRENCY_MAX]. */
|
|
161
233
|
export declare function clampGameplay(state: PetGameplayState, manifest: PetGameplayManifest): void;
|
|
162
234
|
/**
|
|
163
|
-
* Lazy settle: apply stat decay, passive income and
|
|
164
|
-
*
|
|
165
|
-
*
|
|
235
|
+
* Lazy settle: apply stat decay, passive income and the active mode's restore
|
|
236
|
+
* rule (sleep restores energy, a declared extra mode restores whatever it
|
|
237
|
+
* declares) for the elapsed wall time since the last settle. Mirrors the
|
|
238
|
+
* treats.ts discipline (no host timers; read paths settle). Returns whether
|
|
239
|
+
* anything changed.
|
|
166
240
|
*/
|
|
167
241
|
export declare function settleGameplay(state: PetGameplayState, manifest: PetGameplayManifest, now: number, options: {
|
|
168
242
|
sessionActive: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gameplay.d.ts","sourceRoot":"","sources":["../../src/gameplay.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"gameplay.d.ts","sourceRoot":"","sources":["../../src/gameplay.ts"],"names":[],"mappings":"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"}
|
package/lib/types/gameplay.js
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* discipline): the host runs no timers for gameplay.
|
|
12
12
|
* @module @linxin666/dsh-pet/gameplay
|
|
13
13
|
*/
|
|
14
|
+
/** Every roam direction, in roll order (equal chance each unless restricted). */
|
|
15
|
+
export const PET_ROAM_DIRECTIONS = ['up', 'down', 'left', 'right'];
|
|
14
16
|
/* ------------------------------------------------------------------ *
|
|
15
17
|
* Manifest parsing (fail-closed structure)
|
|
16
18
|
* ------------------------------------------------------------------ */
|
|
@@ -23,9 +25,11 @@ const MAX_SHOP_ITEMS = 32;
|
|
|
23
25
|
const MAX_LOTTERY_TIERS = 16;
|
|
24
26
|
const MAX_PHRASES = 64;
|
|
25
27
|
const PHRASE_MAX_LENGTH = 120;
|
|
28
|
+
const MAX_MODES = 8;
|
|
29
|
+
const MODE_LABEL_MAX_LENGTH = 40;
|
|
26
30
|
const STAT_VALUE_MAX = 1_000_000;
|
|
27
31
|
const CURRENCY_MAX = 9_999_999;
|
|
28
|
-
const KNOWN_GAMEPLAY = new Set(['idleDirector', 'stats', 'hitBox', 'touch', 'work', 'sleep', 'passiveIncome', 'shop', 'dragState', 'dragEndState']);
|
|
32
|
+
const KNOWN_GAMEPLAY = new Set(['idleDirector', 'stats', 'hitBox', 'touch', 'work', 'sleep', 'modes', 'roam', 'passiveIncome', 'shop', 'dragState', 'dragEndState']);
|
|
29
33
|
const KNOWN_STAT = new Set(['max', 'initial', 'decayPerMinute', 'workingDecayPerMinute', 'idleDecayPerMinute']);
|
|
30
34
|
const KNOWN_ZONE = new Set(['name', 'y0', 'y1', 'branches']);
|
|
31
35
|
const KNOWN_TOUCH = new Set(['zones', 'clickBoost']);
|
|
@@ -33,6 +37,9 @@ const KNOWN_BRANCH = new Set(['probability', 'effects', 'state', 'stateMs', 'phr
|
|
|
33
37
|
const KNOWN_EFFECT = new Set(['stat', 'currency', 'amount']);
|
|
34
38
|
const KNOWN_WORK = new Set(['state', 'successState', 'failState', 'tickMs', 'resultMs', 'successProbability', 'success', 'fail']);
|
|
35
39
|
const KNOWN_SLEEP = new Set(['state', 'wakeState', 'restore']);
|
|
40
|
+
const KNOWN_MODE = new Set(['state', 'label', 'activeLabel', 'restore']);
|
|
41
|
+
const KNOWN_ROAM = new Set(['state', 'intervalMs', 'probability', 'distanceMin', 'distanceMax', 'speed', 'directions']);
|
|
42
|
+
const KNOWN_RESTORE = new Set(['stat', 'amount', 'intervalMs']);
|
|
36
43
|
const KNOWN_SHOP_ITEM = new Set(['id', 'label', 'image', 'price', 'currency', 'effects', 'lottery']);
|
|
37
44
|
const KNOWN_LOTTERY = new Set(['effects', 'currency', 'tiers']);
|
|
38
45
|
const KNOWN_IDLE_DIRECTOR = new Set(['intervalMs', 'maxMiss', 'idleWeight', 'acts']);
|
|
@@ -113,6 +120,26 @@ function parseStateRef(raw, field, hooks) {
|
|
|
113
120
|
}
|
|
114
121
|
return raw;
|
|
115
122
|
}
|
|
123
|
+
/** Parse one mode restore rule "{ stat (declared), amount, intervalMs }". */
|
|
124
|
+
function parseModeRestore(raw, field, stats, fail) {
|
|
125
|
+
if (!isRecord(raw)) {
|
|
126
|
+
fail(field + ' must be an object { stat, amount, intervalMs }');
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const extra = unknownKeys(raw, KNOWN_RESTORE);
|
|
130
|
+
if (extra.length > 0)
|
|
131
|
+
fail(field + ': unknown field(s) ' + extra.map(k => JSON.stringify(k)).join(', '));
|
|
132
|
+
const stat = typeof raw.stat === 'string' && stats[raw.stat] !== undefined ? raw.stat : undefined;
|
|
133
|
+
if (stat === undefined)
|
|
134
|
+
fail(field + '.stat must reference a declared stat');
|
|
135
|
+
if (!intIn(raw.amount, 1, 1000))
|
|
136
|
+
fail(field + '.amount must be an integer in [1, 1000]');
|
|
137
|
+
if (!intIn(raw.intervalMs, 1000, 600_000))
|
|
138
|
+
fail(field + '.intervalMs must be an integer in [1000, 600000]');
|
|
139
|
+
if (stat === undefined || !intIn(raw.amount, 1, 1000) || !intIn(raw.intervalMs, 1000, 600_000))
|
|
140
|
+
return undefined;
|
|
141
|
+
return { stat, amount: raw.amount, intervalMs: raw.intervalMs };
|
|
142
|
+
}
|
|
116
143
|
/**
|
|
117
144
|
* Validate the manifest 'gameplay' block (fail-closed). Only frames2d pets
|
|
118
145
|
* may declare gameplay today: every state reference checks against the
|
|
@@ -361,18 +388,137 @@ export function parseGameplayManifest(raw, hooks) {
|
|
|
361
388
|
fail('gameplay.sleep: unknown field(s) ' + sExtra.map(k => JSON.stringify(k)).join(', '));
|
|
362
389
|
const state = parseStateRef(s.state, 'gameplay.sleep.state', hooks);
|
|
363
390
|
const wakeState = parseStateRef(s.wakeState, 'gameplay.sleep.wakeState', hooks);
|
|
364
|
-
const
|
|
365
|
-
if (
|
|
366
|
-
fail('gameplay.sleep.restore.stat must reference a declared stat');
|
|
367
|
-
if (!intIn(s.restore.amount, 1, 1000))
|
|
368
|
-
fail('gameplay.sleep.restore.amount must be an integer in [1, 1000]');
|
|
369
|
-
if (!intIn(s.restore.intervalMs, 1000, 600_000))
|
|
370
|
-
fail('gameplay.sleep.restore.intervalMs must be an integer in [1000, 600000]');
|
|
371
|
-
if (state !== undefined && restoreStat !== undefined && intIn(s.restore.amount, 1, 1000) && intIn(s.restore.intervalMs, 1000, 600_000)) {
|
|
391
|
+
const restore = parseModeRestore(s.restore, 'gameplay.sleep.restore', stats, fail);
|
|
392
|
+
if (state !== undefined && restore !== undefined) {
|
|
372
393
|
block.sleep = {
|
|
373
394
|
state,
|
|
374
395
|
...(wakeState === undefined ? {} : { wakeState }),
|
|
375
|
-
restore
|
|
396
|
+
restore,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// --- extra modes (bath and friends) ---
|
|
402
|
+
if (raw.modes !== undefined) {
|
|
403
|
+
if (!isRecord(raw.modes))
|
|
404
|
+
fail('gameplay.modes must be an object keyed by mode id');
|
|
405
|
+
else {
|
|
406
|
+
const entries = Object.entries(raw.modes);
|
|
407
|
+
if (entries.length > MAX_MODES)
|
|
408
|
+
fail('gameplay.modes declares too many modes (max ' + MAX_MODES + ')');
|
|
409
|
+
const modes = {};
|
|
410
|
+
for (const [name, value] of entries) {
|
|
411
|
+
if (!validName(name, 24) || name === 'work' || name === 'sleep') {
|
|
412
|
+
fail('gameplay.modes: invalid mode id ' + JSON.stringify(name) + ' (kebab, and not the reserved work/sleep)');
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (!isRecord(value)) {
|
|
416
|
+
fail('gameplay.modes.' + name + ' must be an object');
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const mExtra = unknownKeys(value, KNOWN_MODE);
|
|
420
|
+
if (mExtra.length > 0)
|
|
421
|
+
fail('gameplay.modes.' + name + ': unknown field(s) ' + mExtra.map(k => JSON.stringify(k)).join(', '));
|
|
422
|
+
const state = parseStateRef(value.state, 'gameplay.modes.' + name + '.state', hooks);
|
|
423
|
+
let usable = state !== undefined;
|
|
424
|
+
let label;
|
|
425
|
+
if (value.label !== undefined) {
|
|
426
|
+
if (typeof value.label !== 'string' || value.label.trim() === '' || value.label.length > MODE_LABEL_MAX_LENGTH) {
|
|
427
|
+
fail('gameplay.modes.' + name + '.label must be a non-empty string of at most ' + MODE_LABEL_MAX_LENGTH + ' chars');
|
|
428
|
+
usable = false;
|
|
429
|
+
}
|
|
430
|
+
else
|
|
431
|
+
label = value.label.trim();
|
|
432
|
+
}
|
|
433
|
+
let activeLabel;
|
|
434
|
+
if (value.activeLabel !== undefined) {
|
|
435
|
+
if (typeof value.activeLabel !== 'string' || value.activeLabel.trim() === '' || value.activeLabel.length > MODE_LABEL_MAX_LENGTH) {
|
|
436
|
+
fail('gameplay.modes.' + name + '.activeLabel must be a non-empty string of at most ' + MODE_LABEL_MAX_LENGTH + ' chars');
|
|
437
|
+
usable = false;
|
|
438
|
+
}
|
|
439
|
+
else
|
|
440
|
+
activeLabel = value.activeLabel.trim();
|
|
441
|
+
}
|
|
442
|
+
const restore = value.restore === undefined ? undefined : parseModeRestore(value.restore, 'gameplay.modes.' + name + '.restore', stats, fail);
|
|
443
|
+
if (value.restore !== undefined && restore === undefined)
|
|
444
|
+
usable = false;
|
|
445
|
+
if (usable && state !== undefined) {
|
|
446
|
+
modes[name] = {
|
|
447
|
+
state,
|
|
448
|
+
...(label === undefined ? {} : { label }),
|
|
449
|
+
...(activeLabel === undefined ? {} : { activeLabel }),
|
|
450
|
+
...(restore === undefined ? {} : { restore }),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (Object.keys(modes).length > 0)
|
|
455
|
+
block.modes = modes;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// --- random roaming ---
|
|
459
|
+
if (raw.roam !== undefined) {
|
|
460
|
+
const rm = raw.roam;
|
|
461
|
+
if (!isRecord(rm))
|
|
462
|
+
fail('gameplay.roam must be an object');
|
|
463
|
+
else {
|
|
464
|
+
const rExtra = unknownKeys(rm, KNOWN_ROAM);
|
|
465
|
+
if (rExtra.length > 0)
|
|
466
|
+
fail('gameplay.roam: unknown field(s) ' + rExtra.map(k => JSON.stringify(k)).join(', '));
|
|
467
|
+
const state = parseStateRef(rm.state, 'gameplay.roam.state', hooks);
|
|
468
|
+
if (!intIn(rm.intervalMs, 1000, 3_600_000))
|
|
469
|
+
fail('gameplay.roam.intervalMs must be an integer in [1000, 3600000]');
|
|
470
|
+
if (!numIn(rm.probability, 0, 1) || rm.probability === 0)
|
|
471
|
+
fail('gameplay.roam.probability must be a number in (0, 1]');
|
|
472
|
+
if (!intIn(rm.distanceMin, 1, 2000))
|
|
473
|
+
fail('gameplay.roam.distanceMin must be an integer in [1, 2000]');
|
|
474
|
+
if (!intIn(rm.distanceMax, 1, 2000))
|
|
475
|
+
fail('gameplay.roam.distanceMax must be an integer in [1, 2000]');
|
|
476
|
+
if (intIn(rm.distanceMin, 1, 2000) && intIn(rm.distanceMax, 1, 2000) && rm.distanceMin > rm.distanceMax) {
|
|
477
|
+
fail('gameplay.roam.distanceMin must not exceed distanceMax');
|
|
478
|
+
}
|
|
479
|
+
if (!intIn(rm.speed, 1, 2000))
|
|
480
|
+
fail('gameplay.roam.speed must be an integer in [1, 2000] (px per second)');
|
|
481
|
+
let directions;
|
|
482
|
+
if (rm.directions !== undefined) {
|
|
483
|
+
if (!Array.isArray(rm.directions) || rm.directions.length === 0) {
|
|
484
|
+
fail('gameplay.roam.directions must be a non-empty array of up, down, left, right');
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
const picked = [];
|
|
488
|
+
for (const entry of rm.directions) {
|
|
489
|
+
if (typeof entry !== 'string' || !PET_ROAM_DIRECTIONS.includes(entry)) {
|
|
490
|
+
fail('gameplay.roam.directions entries must be up, down, left or right');
|
|
491
|
+
picked.length = 0;
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
if (picked.includes(entry)) {
|
|
495
|
+
fail('gameplay.roam.directions must not repeat ' + entry);
|
|
496
|
+
picked.length = 0;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
picked.push(entry);
|
|
500
|
+
}
|
|
501
|
+
if (picked.length > 0)
|
|
502
|
+
directions = picked;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
// The guards are repeated inline: tsc only narrows the unknown fields
|
|
506
|
+
// through a call in the condition itself.
|
|
507
|
+
if (state !== undefined
|
|
508
|
+
&& intIn(rm.intervalMs, 1000, 3_600_000)
|
|
509
|
+
&& numIn(rm.probability, 0, 1) && rm.probability !== 0
|
|
510
|
+
&& intIn(rm.distanceMin, 1, 2000)
|
|
511
|
+
&& intIn(rm.distanceMax, 1, 2000)
|
|
512
|
+
&& rm.distanceMin <= rm.distanceMax
|
|
513
|
+
&& intIn(rm.speed, 1, 2000)) {
|
|
514
|
+
block.roam = {
|
|
515
|
+
state,
|
|
516
|
+
intervalMs: rm.intervalMs,
|
|
517
|
+
probability: rm.probability,
|
|
518
|
+
distanceMin: rm.distanceMin,
|
|
519
|
+
distanceMax: rm.distanceMax,
|
|
520
|
+
speed: rm.speed,
|
|
521
|
+
...(directions === undefined ? {} : { directions }),
|
|
376
522
|
};
|
|
377
523
|
}
|
|
378
524
|
}
|
|
@@ -505,6 +651,53 @@ export function parseGameplayManifest(raw, hooks) {
|
|
|
505
651
|
}
|
|
506
652
|
return failed ? undefined : block;
|
|
507
653
|
}
|
|
654
|
+
/**
|
|
655
|
+
* One declared extra mode, by OWN key only. A plain `modes[id]` lookup also
|
|
656
|
+
* answers for Object.prototype members ('constructor', 'toString', …), which
|
|
657
|
+
* would let a crafted mode id pass the "is it declared?" test.
|
|
658
|
+
*/
|
|
659
|
+
function declaredModeOf(manifest, mode) {
|
|
660
|
+
const modes = manifest.modes;
|
|
661
|
+
if (modes === undefined || !Object.prototype.hasOwnProperty.call(modes, mode))
|
|
662
|
+
return undefined;
|
|
663
|
+
return modes[mode];
|
|
664
|
+
}
|
|
665
|
+
/** Every mode the menu offers, in manifest order (sleep first, then extras). */
|
|
666
|
+
export function declaredModes(manifest) {
|
|
667
|
+
return [
|
|
668
|
+
...(manifest.sleep === undefined ? [] : ['sleep']),
|
|
669
|
+
...Object.keys(manifest.modes ?? {}),
|
|
670
|
+
];
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* The frames2d track one active mode holds. 'work' is owned by the work loop
|
|
674
|
+
* (its state, result and fallback are its own), so it resolves to undefined.
|
|
675
|
+
*/
|
|
676
|
+
export function modeStateOf(manifest, mode) {
|
|
677
|
+
if (mode === 'sleep')
|
|
678
|
+
return manifest.sleep?.state;
|
|
679
|
+
if (mode === 'work')
|
|
680
|
+
return manifest.work?.state;
|
|
681
|
+
return declaredModeOf(manifest, mode)?.state;
|
|
682
|
+
}
|
|
683
|
+
/** The restore rule of one active mode, when it declares one. */
|
|
684
|
+
export function modeRestoreOf(manifest, mode) {
|
|
685
|
+
if (mode === null)
|
|
686
|
+
return undefined;
|
|
687
|
+
if (mode === 'sleep')
|
|
688
|
+
return manifest.sleep?.restore;
|
|
689
|
+
if (mode === 'work')
|
|
690
|
+
return undefined;
|
|
691
|
+
return declaredModeOf(manifest, mode)?.restore;
|
|
692
|
+
}
|
|
693
|
+
/** Whether the manifest still declares this gameplay mode id. */
|
|
694
|
+
export function isDeclaredMode(manifest, mode) {
|
|
695
|
+
if (mode === 'work')
|
|
696
|
+
return manifest.work !== undefined;
|
|
697
|
+
if (mode === 'sleep')
|
|
698
|
+
return manifest.sleep !== undefined;
|
|
699
|
+
return declaredModeOf(manifest, mode) !== undefined;
|
|
700
|
+
}
|
|
508
701
|
/** Fresh state for one pet: stats at their initial (default max), no currency. */
|
|
509
702
|
export function initialGameplayState(manifest, now) {
|
|
510
703
|
const stats = {};
|
|
@@ -527,9 +720,11 @@ export function clampGameplay(state, manifest) {
|
|
|
527
720
|
}
|
|
528
721
|
}
|
|
529
722
|
/**
|
|
530
|
-
* Lazy settle: apply stat decay, passive income and
|
|
531
|
-
*
|
|
532
|
-
*
|
|
723
|
+
* Lazy settle: apply stat decay, passive income and the active mode's restore
|
|
724
|
+
* rule (sleep restores energy, a declared extra mode restores whatever it
|
|
725
|
+
* declares) for the elapsed wall time since the last settle. Mirrors the
|
|
726
|
+
* treats.ts discipline (no host timers; read paths settle). Returns whether
|
|
727
|
+
* anything changed.
|
|
533
728
|
*/
|
|
534
729
|
export function settleGameplay(state, manifest, now, options) {
|
|
535
730
|
const elapsedMs = now - state.settledAt;
|
|
@@ -565,14 +760,14 @@ export function settleGameplay(state, manifest, now, options) {
|
|
|
565
760
|
changed = true;
|
|
566
761
|
}
|
|
567
762
|
}
|
|
568
|
-
|
|
763
|
+
const restore = modeRestoreOf(manifest, state.mode);
|
|
764
|
+
if (restore !== undefined) {
|
|
569
765
|
const restoreElapsed = elapsedMs + (state.restoreCarryMs ?? 0);
|
|
570
|
-
const interval =
|
|
766
|
+
const interval = restore.intervalMs;
|
|
571
767
|
const ticks = Math.floor(restoreElapsed / interval);
|
|
572
768
|
state.restoreCarryMs = restoreElapsed % interval;
|
|
573
769
|
if (ticks > 0) {
|
|
574
|
-
|
|
575
|
-
state.stats[stat] = (state.stats[stat] ?? 0) + ticks * manifest.sleep.restore.amount;
|
|
770
|
+
state.stats[restore.stat] = (state.stats[restore.stat] ?? 0) + ticks * restore.amount;
|
|
576
771
|
changed = true;
|
|
577
772
|
}
|
|
578
773
|
}
|
package/lib/types/http.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared JSON body/response helpers for the host route families: one strict
|
|
3
3
|
* bounded body reader, one lenient bounded body reader, one JSON object
|
|
4
|
-
* narrow,
|
|
4
|
+
* narrow, one JSON writer, and the outbound request-init helper that keeps a
|
|
5
|
+
* host fetch body decodable (withIdentityEncoding). Previously the readers
|
|
6
|
+
* were copy-pasted across the
|
|
5
7
|
* package route files (routes.ts, update-routes.ts, mobile-api.ts, and each
|
|
6
8
|
* family's route module) with drifting contracts: body caps ranging 4 KiB to
|
|
7
9
|
* 1 MiB and four distinct overflow behaviors (reject, undefined, null, throw).
|
|
@@ -34,6 +36,21 @@ export declare function readJsonBody(req: IncomingMessage, opts?: {
|
|
|
34
36
|
}): Promise<unknown | null>;
|
|
35
37
|
/** Narrow a value to a JSON object, or undefined when it is not one. */
|
|
36
38
|
export declare function asJsonObject(value: unknown): Record<string, unknown> | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Request init that asks a remote origin for an uncompressed body.
|
|
41
|
+
*
|
|
42
|
+
* The DSH host boots `@deepseek-ai/dsh-http-proxy`, whose top-level import of
|
|
43
|
+
* the npm `undici` copy replaces the legacy `undici.globalDispatcher.1` slot
|
|
44
|
+
* Node's built-in `fetch()` reads. That cross-major wrapper drops the
|
|
45
|
+
* `content-encoding` header and automatic decompression, so a plain fetch
|
|
46
|
+
* resolves to raw gzip/brotli/zstd bytes — compressed noise no JSON or text
|
|
47
|
+
* consumer can read. Every host fetch that parses a remote body must request
|
|
48
|
+
* identity encoding. Caller headers survive; `accept-encoding` is forced to
|
|
49
|
+
* `identity` because a caller value has no decoder behind it in this host.
|
|
50
|
+
* @param init - the caller's request init (signal, headers, method, ...).
|
|
51
|
+
* @returns a copy with `accept-encoding: identity` merged in.
|
|
52
|
+
*/
|
|
53
|
+
export declare function withIdentityEncoding(init?: RequestInit): RequestInit;
|
|
37
54
|
/**
|
|
38
55
|
* Write one JSON response. Default headers are the family defaults
|
|
39
56
|
* (content-type and referrer-policy); caller headers are appended or
|
package/lib/types/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"AACA
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAWrF;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAU9F;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,eAAe,EACpB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GACrD,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAsBzB;AAOD,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAEhF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,WAAgB,GAAG,WAAW,CAIxE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,mBAAwB,GAChC,IAAI,CAIN"}
|