@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.
Files changed (72) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +4 -3
  3. package/README.zh.md +4 -3
  4. package/icon.svg +3 -0
  5. package/lib/client.js +522 -194
  6. package/lib/client.js.map +1 -1
  7. package/lib/index.js +706 -422
  8. package/lib/live2d-vendor.js +96 -96
  9. package/lib/live2d-vendor.js.map +1 -1
  10. package/lib/types/client/PetDockEntry.d.ts.map +1 -1
  11. package/lib/types/client/PetDockEntry.js +1 -1
  12. package/lib/types/client/PetSettingsCard.d.ts +20 -9
  13. package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
  14. package/lib/types/client/PetSettingsCard.js +116 -5
  15. package/lib/types/client/PetSprite.d.ts +6 -0
  16. package/lib/types/client/PetSprite.d.ts.map +1 -1
  17. package/lib/types/client/PetSprite.js +95 -0
  18. package/lib/types/client/PluginSettingsCard.d.ts +4 -0
  19. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  20. package/lib/types/client/PluginSettingsCard.js +3 -2
  21. package/lib/types/client/gameplay-hud.d.ts +10 -1
  22. package/lib/types/client/gameplay-hud.d.ts.map +1 -1
  23. package/lib/types/client/gameplay-hud.js +137 -23
  24. package/lib/types/client/index.d.ts +8 -5
  25. package/lib/types/client/index.d.ts.map +1 -1
  26. package/lib/types/client/index.js +61 -27
  27. package/lib/types/client/main-session.d.ts +33 -0
  28. package/lib/types/client/main-session.d.ts.map +1 -0
  29. package/lib/types/client/main-session.js +34 -0
  30. package/lib/types/client/settings-form.d.ts +17 -17
  31. package/lib/types/client/settings-form.d.ts.map +1 -1
  32. package/lib/types/client/settings-form.js +30 -27
  33. package/lib/types/event-projection.d.ts.map +1 -1
  34. package/lib/types/event-projection.js +5 -3
  35. package/lib/types/gameplay.d.ts +79 -5
  36. package/lib/types/gameplay.d.ts.map +1 -1
  37. package/lib/types/gameplay.js +212 -17
  38. package/lib/types/http.d.ts +18 -1
  39. package/lib/types/http.d.ts.map +1 -1
  40. package/lib/types/http.js +22 -1
  41. package/lib/types/index.d.ts +100 -29
  42. package/lib/types/index.d.ts.map +1 -1
  43. package/lib/types/index.js +113 -64
  44. package/lib/types/persist.d.ts.map +1 -1
  45. package/lib/types/persist.js +3 -1
  46. package/lib/types/routes.d.ts.map +1 -1
  47. package/lib/types/routes.js +3 -1
  48. package/lib/types/service.d.ts +8 -3
  49. package/lib/types/service.d.ts.map +1 -1
  50. package/lib/types/service.js +26 -4
  51. package/package.json +46 -26
  52. package/src/client/PetDockEntry.tsx +1 -0
  53. package/src/client/PetSettingsCard.test.tsx +103 -0
  54. package/src/client/PetSettingsCard.tsx +129 -26
  55. package/src/client/PetSprite.tsx +96 -0
  56. package/src/client/PluginSettingsCard.tsx +7 -2
  57. package/src/client/gameplay-hud.test.tsx +106 -8
  58. package/src/client/gameplay-hud.tsx +143 -28
  59. package/src/client/index.test.tsx +37 -4
  60. package/src/client/index.ts +74 -34
  61. package/src/client/main-session.ts +43 -0
  62. package/src/client/settings-form.ts +34 -31
  63. package/src/event-projection.ts +5 -3
  64. package/src/gameplay.test.ts +138 -1
  65. package/src/gameplay.ts +253 -16
  66. package/src/http.ts +23 -1
  67. package/src/index.test.ts +107 -11
  68. package/src/index.ts +162 -73
  69. package/src/persist.ts +3 -1
  70. package/src/registry.test.ts +3 -2
  71. package/src/routes.ts +3 -1
  72. 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 scope subscription installed in the constructor; released by dispose(). */
95
- disposeScope;
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 settings scope for this card's namespace. */
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.disposeScope = scope.subscribe(() => { this.publish(); });
104
+ this.disposeForm = scope.subscribe(() => { this.publish(); });
105
105
  }
106
106
  /**
107
- * Release the scope subscription and every bound store listener. The card
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.disposeScope();
114
+ this.disposeForm();
115
115
  this.listeners.clear();
116
116
  }
117
- /** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
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 scope mutation, then re-seed from
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 0.1.2 scope contract never rejects a refused
177
- * mutation — the scope recovers with a fresh Host view and resolves — so
178
- * resolution alone proves nothing: the outcome is judged by reading the
179
- * settled snapshot back, one planned write at a time, and one missed write
180
- * fails the whole save. A scope that still rejects on refusal (the dsh-web
181
- * bridge scope) reports through the same failure path with its rejection
182
- * message. A save that did not land keeps its drafts, so the user can
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 0.1.2 scope contract takes ordered
202
- // path operations, so the whole staged batch is validated, persisted, and
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
- // The 0.1.2 scope resolves even a refused mutation (it recovers with a
215
- // fresh view instead of throwing), so resolution alone proves nothing:
216
- // judge every planned write against the settled snapshot. The mutation is
217
- // atomic, so one missed write fails the whole save and keeps the drafts.
218
- const landed = failedReason === undefined && valid.every(item => item.judge());
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 read-back failure carries no server reason: the card surfaces its
226
- // generic failure copy; a rejecting scope (the bridge) adds its message.
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 scope
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,CAiGnC;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,iBAAiB,EAC1B,KAAK,GAAE,MAAmB,GACzB,qBAAqB,GAAG,SAAS,CAkBnC"}
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
- const block = event.data.message.content[0];
84
- const callId = String(event.data.message.source.callId);
85
- const failed = event.data.error !== undefined || block.isError === true;
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;
@@ -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: 'work' | 'sleep' | null;
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 sleep restore tick. */
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 sleep restore for the
164
- * elapsed wall time since the last settle. Mirrors the treats.ts discipline
165
- * (no host timers; read paths settle). Returns whether anything changed.
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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"}
@@ -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 restoreStat = typeof s.restore.stat === 'string' && stats[s.restore.stat] !== undefined ? s.restore.stat : undefined;
365
- if (restoreStat === undefined)
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: { stat: restoreStat, amount: s.restore.amount, intervalMs: s.restore.intervalMs },
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 sleep restore for the
531
- * elapsed wall time since the last settle. Mirrors the treats.ts discipline
532
- * (no host timers; read paths settle). Returns whether anything changed.
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
- if (state.mode === 'sleep' && manifest.sleep !== undefined) {
763
+ const restore = modeRestoreOf(manifest, state.mode);
764
+ if (restore !== undefined) {
569
765
  const restoreElapsed = elapsedMs + (state.restoreCarryMs ?? 0);
570
- const interval = manifest.sleep.restore.intervalMs;
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
- const stat = manifest.sleep.restore.stat;
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
  }
@@ -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, and one JSON writer. Previously these were copy-pasted across the
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
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;GAaG;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;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,mBAAwB,GAChC,IAAI,CAIN"}
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"}