@linxin666/dsh-pet 0.3.20 → 0.3.21

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 (50) hide show
  1. package/lib/client.js +127 -26
  2. package/lib/client.js.map +1 -1
  3. package/lib/index.js +99 -4
  4. package/lib/types/client/gameplay-hud.d.ts +15 -0
  5. package/lib/types/client/gameplay-hud.d.ts.map +1 -1
  6. package/lib/types/client/gameplay-hud.js +48 -6
  7. package/lib/types/client/index.d.ts.map +1 -1
  8. package/lib/types/client/index.js +21 -13
  9. package/lib/types/client/renderers/Frames2dVisualMount.d.ts.map +1 -1
  10. package/lib/types/client/renderers/Frames2dVisualMount.js +5 -0
  11. package/lib/types/client/renderers/frames2d.d.ts +5 -4
  12. package/lib/types/client/renderers/frames2d.d.ts.map +1 -1
  13. package/lib/types/client/renderers/frames2d.js +27 -18
  14. package/lib/types/client/work-tick-gate.d.ts +40 -0
  15. package/lib/types/client/work-tick-gate.d.ts.map +1 -0
  16. package/lib/types/client/work-tick-gate.js +49 -0
  17. package/lib/types/gameplay.d.ts +4 -0
  18. package/lib/types/gameplay.d.ts.map +1 -1
  19. package/lib/types/gameplay.js +11 -2
  20. package/lib/types/ledger.d.ts +8 -0
  21. package/lib/types/ledger.d.ts.map +1 -1
  22. package/lib/types/ledger.js +25 -0
  23. package/lib/types/persist.d.ts +6 -0
  24. package/lib/types/persist.d.ts.map +1 -1
  25. package/lib/types/persist.js +25 -1
  26. package/lib/types/routes.d.ts.map +1 -1
  27. package/lib/types/routes.js +6 -0
  28. package/lib/types/service.d.ts +24 -0
  29. package/lib/types/service.d.ts.map +1 -1
  30. package/lib/types/service.js +32 -0
  31. package/package.json +1 -1
  32. package/src/client/PetDockEntry.test.tsx +1 -0
  33. package/src/client/gameplay-hud.test.tsx +165 -2
  34. package/src/client/gameplay-hud.tsx +62 -6
  35. package/src/client/index.ts +23 -13
  36. package/src/client/pet.module.css +1 -1
  37. package/src/client/renderers/Frames2dVisualMount.test.tsx +73 -0
  38. package/src/client/renderers/Frames2dVisualMount.tsx +4 -0
  39. package/src/client/renderers/frames2d.test.ts +38 -6
  40. package/src/client/renderers/frames2d.ts +27 -19
  41. package/src/client/work-tick-gate.test.ts +53 -0
  42. package/src/client/work-tick-gate.ts +63 -0
  43. package/src/gameplay.test.ts +36 -0
  44. package/src/gameplay.ts +14 -2
  45. package/src/ledger.test.ts +19 -0
  46. package/src/ledger.ts +24 -0
  47. package/src/persist.test.ts +13 -0
  48. package/src/persist.ts +29 -1
  49. package/src/routes.ts +5 -0
  50. package/src/service.ts +38 -0
@@ -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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"}
@@ -555,7 +555,10 @@ export function settleGameplay(state, manifest, now, options) {
555
555
  }
556
556
  }
557
557
  if (manifest.passiveIncome !== undefined) {
558
- const ticks = Math.floor(elapsedMs / manifest.passiveIncome.intervalMs);
558
+ const incomeElapsed = elapsedMs + (state.incomeCarryMs ?? 0);
559
+ const interval = manifest.passiveIncome.intervalMs;
560
+ const ticks = Math.floor(incomeElapsed / interval);
561
+ state.incomeCarryMs = incomeElapsed % interval;
559
562
  if (ticks > 0) {
560
563
  const currency = manifest.passiveIncome.currency;
561
564
  state.currencies[currency] = (state.currencies[currency] ?? 0) + ticks * manifest.passiveIncome.amount;
@@ -563,13 +566,19 @@ export function settleGameplay(state, manifest, now, options) {
563
566
  }
564
567
  }
565
568
  if (state.mode === 'sleep' && manifest.sleep !== undefined) {
566
- const ticks = Math.floor(elapsedMs / manifest.sleep.restore.intervalMs);
569
+ const restoreElapsed = elapsedMs + (state.restoreCarryMs ?? 0);
570
+ const interval = manifest.sleep.restore.intervalMs;
571
+ const ticks = Math.floor(restoreElapsed / interval);
572
+ state.restoreCarryMs = restoreElapsed % interval;
567
573
  if (ticks > 0) {
568
574
  const stat = manifest.sleep.restore.stat;
569
575
  state.stats[stat] = (state.stats[stat] ?? 0) + ticks * manifest.sleep.restore.amount;
570
576
  changed = true;
571
577
  }
572
578
  }
579
+ else {
580
+ state.restoreCarryMs = 0;
581
+ }
573
582
  state.settledAt = now;
574
583
  clampGameplay(state, manifest);
575
584
  return changed;
@@ -66,6 +66,14 @@ export declare class PetLedger {
66
66
  setGameplay(petId: string, gameplay: PetGameplayState): void;
67
67
  /** Replace one pet's display name (validation stays a caller concern). */
68
68
  setPetName(petId: string, name: string): void;
69
+ /**
70
+ * Select one pet's frames2d skin; `undefined` clears the choice back to the
71
+ * pet's default look. Manifest validation stays a caller concern, exactly
72
+ * like setPetName's length check.
73
+ */
74
+ setPetSkin(petId: string, skinId: string | undefined): void;
75
+ /** The persisted skin id for one pet (undefined = the pet's default look). */
76
+ petSkin(petId: string): string | undefined;
69
77
  /**
70
78
  * Swap the reaction pools to another pet's custom remarks (called on pet
71
79
  * selection). Slots the pet does not declare fall back to voice packs or built-ins.
@@ -1 +1 @@
1
- {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../src/ledger.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../src/ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,eAAe,CAAA;AACtB,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAErD,iDAAiD;AACjD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;IAC7B,mFAAmF;IACnF,OAAO,CAAC,EAAE,UAAU,CAAA;IACpB,mEAAmE;IACnE,YAAY,CAAC,EAAE,UAAU,CAAA;CAC1B;AAED,wEAAwE;AACxE,MAAM,WAAW,uBAAuB;IACtC,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,QAAQ,EAAE,eAAe,CAAA;CAC1B;AAED;;;;GAIG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAa;IACzC,0EAA0E;IAC1E,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,OAAO,CAAY;IAC3B,oFAAoF;IACpF,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,sBAAsB,CAAI;IAClC,OAAO,CAAC,KAAK,CAAQ;gBAET,OAAO,EAAE,UAAU,EAAE,MAAM,GAAE,YAAiB;IAO1D,iDAAiD;IACjD,IAAI,QAAQ,IAAI,cAAc,CAE7B;IAED,qEAAqE;IACrE,IAAI,QAAQ,IAAI,UAAU,CAEzB;IAED,qCAAqC;IACrC,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,+DAA+D;IAC/D,SAAS,IAAI,OAAO;IAMpB;;;OAGG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAItC,mEAAmE;IACnE,UAAU,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAK3C,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAM7B,qFAAqF;IACrF,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAK5D,0EAA0E;IAC1E,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAK7C;;;;OAIG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAc3D,8EAA8E;IAC9E,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI1C;;;OAGG;IACH,UAAU,CAAC,OAAO,CAAC,EAAE,UAAU,EAAE,YAAY,CAAC,EAAE,UAAU,GAAG,IAAI;IAIjE;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAapC;;;;;OAKG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IASpC,8EAA8E;IAC9E,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE;IAQ5C;;;;OAIG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IASnE,8EAA8E;IAC9E,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IASxC,OAAO,CAAC,eAAe;IASvB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,uBAAuB;IAwCtE,kDAAkD;IAClD,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe;CAG7C"}
@@ -77,6 +77,31 @@ export class PetLedger {
77
77
  this.current = { ...this.current, names: { ...this.current.names, [petId]: name } };
78
78
  this.dirty = true;
79
79
  }
80
+ /**
81
+ * Select one pet's frames2d skin; `undefined` clears the choice back to the
82
+ * pet's default look. Manifest validation stays a caller concern, exactly
83
+ * like setPetName's length check.
84
+ */
85
+ setPetSkin(petId, skinId) {
86
+ const skins = this.current.skins;
87
+ if (skinId === undefined) {
88
+ if (skins[petId] === undefined)
89
+ return;
90
+ const next = { ...skins };
91
+ delete next[petId];
92
+ this.current = { ...this.current, skins: next };
93
+ }
94
+ else {
95
+ if (skins[petId] === skinId)
96
+ return;
97
+ this.current = { ...this.current, skins: { ...skins, [petId]: skinId } };
98
+ }
99
+ this.dirty = true;
100
+ }
101
+ /** The persisted skin id for one pet (undefined = the pet's default look). */
102
+ petSkin(petId) {
103
+ return this.current.skins[petId];
104
+ }
80
105
  /**
81
106
  * Swap the reaction pools to another pet's custom remarks (called on pet
82
107
  * selection). Slots the pet does not declare fall back to voice packs or built-ins.
@@ -33,6 +33,12 @@ export interface PetPersist {
33
33
  * to its manifest displayName, so only user renames are stored here.
34
34
  */
35
35
  names: Record<string, string>;
36
+ /**
37
+ * Per-pet selected frames2d skin id (keyed by pet id). Skin ids are manifest
38
+ * data, so a stale entry (skin renamed or removed, pet swapped) is ignored
39
+ * when the state view is built instead of pinning an unresolvable track.
40
+ */
41
+ skins: Record<string, string>;
36
42
  affinity: AffinityState;
37
43
  /** Treat (小鱼干) stock ledger. */
38
44
  treats: TreatLedger;
@@ -1 +1 @@
1
- {"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/persist.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAA+B,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAC/E,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAEpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAErD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEhE,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAA;CACf;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAKlC,CAAA;AAED,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,gBAAgB,OAAO,CAAA;AACpC,eAAO,MAAM,iBAAiB,QAAS,CAAA;AAEvC,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,QAAQ,EAAE,aAAa,CAAA;IACvB,gCAAgC;IAChC,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,gBAAgB,CAAA;IACzB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CAC3C;AAED,wBAAwB;AACxB,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,wBAAgB,YAAY,IAAI,UAAU,CASzC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AA+DD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAqB,GAAG,UAAU,CAoDrE;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,GAAE,MAAqB,GAAG,IAAI,CAMjF"}
1
+ {"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/persist.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAA+B,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAC/E,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAEpF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAErD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAEhE,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAC/B,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAA;CACf;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAKlC,CAAA;AAED,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAClC,eAAO,MAAM,gBAAgB,OAAO,CAAA;AACpC,eAAO,MAAM,iBAAiB,QAAS,CAAA;AAEvC,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,QAAQ,EAAE,aAAa,CAAA;IACvB,gCAAgC;IAChC,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,gBAAgB,CAAA;IACzB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CAC3C;AAED,wBAAwB;AACxB,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,wBAAgB,YAAY,IAAI,UAAU,CAUzC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAmFD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAqB,GAAG,UAAU,CAqDrE;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,GAAE,MAAqB,GAAG,IAAI,CAMjF"}
@@ -27,6 +27,7 @@ export function emptyPersist() {
27
27
  return {
28
28
  petId: DEFAULT_PET_ID,
29
29
  names: {},
30
+ skins: {},
30
31
  affinity: emptyAffinity(),
31
32
  treats: emptyTreatLedger(),
32
33
  display: { ...defaultDisplayConfig },
@@ -60,6 +61,21 @@ function loadPetNames(parsed) {
60
61
  }
61
62
  return names;
62
63
  }
64
+ /** Sanitize the per-pet skin selection map (string keys, non-empty trimmed values). */
65
+ function loadPetSkins(parsed) {
66
+ const skins = {};
67
+ if (typeof parsed.skins !== 'object' || parsed.skins === null)
68
+ return skins;
69
+ for (const [id, value] of Object.entries(parsed.skins)) {
70
+ if (id === '' || typeof value !== 'string')
71
+ continue;
72
+ const skin = value.trim();
73
+ if (skin === '')
74
+ continue;
75
+ skins[id] = skin;
76
+ }
77
+ return skins;
78
+ }
63
79
  /** Clamp one count/score into [0, max]. */
64
80
  function clamp(value, max) {
65
81
  return Math.min(max, Math.max(0, value));
@@ -92,12 +108,19 @@ function loadGameplay(parsed) {
92
108
  currencies[key] = Math.min(GAMEPLAY_LOAD_CURRENCY_CAP, Math.max(0, Math.floor(value)));
93
109
  }
94
110
  }
95
- result[petId] = {
111
+ const item = {
96
112
  stats,
97
113
  currencies,
98
114
  mode: record.mode === 'work' || record.mode === 'sleep' ? record.mode : null,
99
115
  settledAt: clamp(finiteNum(record.settledAt, 0), Number.MAX_SAFE_INTEGER),
100
116
  };
117
+ if (typeof record.incomeCarryMs === 'number' && Number.isFinite(record.incomeCarryMs)) {
118
+ item.incomeCarryMs = Math.max(0, record.incomeCarryMs);
119
+ }
120
+ if (typeof record.restoreCarryMs === 'number' && Number.isFinite(record.restoreCarryMs)) {
121
+ item.restoreCarryMs = Math.max(0, record.restoreCarryMs);
122
+ }
123
+ result[petId] = item;
101
124
  }
102
125
  return result;
103
126
  }
@@ -146,6 +169,7 @@ export function loadPetPersist(dir = petHomeDir()) {
146
169
  return {
147
170
  petId,
148
171
  names,
172
+ skins: loadPetSkins(parsed),
149
173
  affinity,
150
174
  treats,
151
175
  display,
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAO9C,+CAA+C;AAC/C,eAAO,MAAM,cAAc,aAAa,CAAA;AAExC,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,SAAS,CAAA;AAMtC;;;;GAIG;AACH,eAAO,MAAM,cAAc;IACzB,yBAAyB;;IAEzB,iDAAiD;;IAEjD,8EAA8E;;CAEtE,CAAA;AAEV,6DAA6D;AAC7D,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAsBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAcrF;AAqPD,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,QAA8B,CAAA;AAe7D,4EAA4E;AAC5E,eAAO,MAAM,eAAe,QAAmB,CAAA;AAE/C,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AA4MD,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,YAAY,CAAA;CAAE,GAAG,eAAe,GAAG,QAAQ,EAAE,CAgFjI;AAGD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAO9C,+CAA+C;AAC/C,eAAO,MAAM,cAAc,aAAa,CAAA;AAExC,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,SAAS,CAAA;AAMtC;;;;GAIG;AACH,eAAO,MAAM,cAAc;IACzB,yBAAyB;;IAEzB,iDAAiD;;IAEjD,8EAA8E;;CAEtE,CAAA;AAEV,6DAA6D;AAC7D,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAsBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAcrF;AAqPD,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,QAA8B,CAAA;AAe7D,4EAA4E;AAC5E,eAAO,MAAM,eAAe,QAAmB,CAAA;AAE/C,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AA4MD,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,YAAY,CAAA;CAAE,GAAG,eAAe,GAAG,QAAQ,EAAE,CAqFjI;AAGD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA"}
@@ -586,6 +586,12 @@ export function makePetRoutes(deps) {
586
586
  return Promise.reject(new Error('invalid-name'));
587
587
  return service.setName(name);
588
588
  }),
589
+ postRoute(ctx, PET_API_PREFIX + '/set-skin', (body) => {
590
+ const skin = body.skin;
591
+ if (skin !== undefined && typeof skin !== 'string')
592
+ return Promise.reject(new Error('invalid-skin'));
593
+ return service.setSkin(skin === undefined || skin === '' ? undefined : skin);
594
+ }),
589
595
  postRoute(ctx, PET_API_PREFIX + '/set-pet', (body) => {
590
596
  const petId = body.petId;
591
597
  if (typeof petId !== 'string')
@@ -117,6 +117,12 @@ export interface PetStateView {
117
117
  };
118
118
  /** The selected pet's display name (user rename or manifest default). */
119
119
  name: string;
120
+ /**
121
+ * The selected frames2d skin id for this pet (absent = the pet's default
122
+ * look). Persisted host-side, so a page reload or client restart restores
123
+ * the user's last choice instead of falling back to the default look.
124
+ */
125
+ skin?: string;
120
126
  /** Treat (小鱼干) stock snapshot. */
121
127
  treats: {
122
128
  /** Stocked treats now. */
@@ -308,6 +314,24 @@ export declare class PetService extends Service {
308
314
  ok: true;
309
315
  display: PetDisplayConfig;
310
316
  }>;
317
+ /**
318
+ * The persisted skin for one entry, when the manifest still declares it: a
319
+ * stale id (skin removed from the manifest, pet swapped) reads as "default"
320
+ * rather than pinning a track the browser half cannot resolve.
321
+ */
322
+ private persistedSkin;
323
+ /**
324
+ * RPC: select the current pet's frames2d skin (`undefined` restores the
325
+ * pet's default look). The choice is stored per pet, so every later state
326
+ * view (reload, client restart, pet re-selection) serves it back.
327
+ */
328
+ setSkin(skin: string | undefined): Promise<{
329
+ ok: true;
330
+ skin?: string;
331
+ } | {
332
+ ok: false;
333
+ error: string;
334
+ }>;
311
335
  /** RPC: update display config (size / position). Values are clamped to whole pixels. */
312
336
  setConfig(patch: Partial<PetDisplayConfig>): Promise<{
313
337
  ok: true;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"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"}
@@ -502,6 +502,36 @@ export class PetService extends Service {
502
502
  this.syncSettingsFromPet();
503
503
  return { ok: true, display: this.ledger.snapshot.display };
504
504
  }
505
+ /**
506
+ * The persisted skin for one entry, when the manifest still declares it: a
507
+ * stale id (skin removed from the manifest, pet swapped) reads as "default"
508
+ * rather than pinning a track the browser half cannot resolve.
509
+ */
510
+ persistedSkin(entry) {
511
+ const stored = this.ledger.petSkin(entry.id);
512
+ if (stored === undefined)
513
+ return undefined;
514
+ return entry.frames2d?.skins?.some(skin => skin.id === stored) === true ? stored : undefined;
515
+ }
516
+ /**
517
+ * RPC: select the current pet's frames2d skin (`undefined` restores the
518
+ * pet's default look). The choice is stored per pet, so every later state
519
+ * view (reload, client restart, pet re-selection) serves it back.
520
+ */
521
+ async setSkin(skin) {
522
+ const entry = this.activeEntry();
523
+ const declared = entry.frames2d?.skins ?? [];
524
+ if (skin === undefined) {
525
+ this.ledger.setPetSkin(entry.id, undefined);
526
+ this.flush();
527
+ return { ok: true };
528
+ }
529
+ if (!declared.some(candidate => candidate.id === skin))
530
+ return { ok: false, error: 'unknown-skin' };
531
+ this.ledger.setPetSkin(entry.id, skin);
532
+ this.flush();
533
+ return { ok: true, skin };
534
+ }
505
535
  /** RPC: update display config (size / position). Values are clamped to whole pixels. */
506
536
  async setConfig(patch) {
507
537
  const next = { ...this.ledger.snapshot.display, ...patch };
@@ -636,6 +666,7 @@ export class PetService extends Service {
636
666
  const announcement = this.announcement !== undefined && announcementFresh(this.announcement, Date.now())
637
667
  ? this.announcement
638
668
  : undefined;
669
+ const skin = this.persistedSkin(entry);
639
670
  return {
640
671
  animation: snapshot.animation,
641
672
  ...(snapshot.bubble === undefined ? {} : { bubble: snapshot.bubble }),
@@ -652,6 +683,7 @@ export class PetService extends Service {
652
683
  description: entry.description,
653
684
  },
654
685
  name: this.petName(),
686
+ ...(skin === undefined ? {} : { skin }),
655
687
  treats: {
656
688
  stocked: this.ledger.snapshot.treats.treats,
657
689
  max: this.ledger.treatMax,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-pet",
3
3
  "description": "Multi-pet companion plugin for the dsh web GUI: a registry-driven floating pet that reacts to model activity, with per-pet naming, petting/feeding interactions and an affinity score",
4
- "version": "0.3.20",
4
+ "version": "0.3.21",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -84,6 +84,7 @@ function injected(): PetInjected {
84
84
  setMode: vi.fn(),
85
85
  workTick: vi.fn(),
86
86
  buy: vi.fn(),
87
+ setSkin: vi.fn(async () => ({ ok: true })),
87
88
  },
88
89
  }
89
90
  }
@@ -110,7 +110,7 @@ function petDefinition(): PetDefinition {
110
110
  }
111
111
  }
112
112
 
113
- function snapshot(view: PetGameplayStateView): PetStateView {
113
+ function snapshot(view: PetGameplayStateView, skin?: string): PetStateView {
114
114
  return {
115
115
  animation: 'idle',
116
116
  phase: 'idle',
@@ -121,6 +121,7 @@ function snapshot(view: PetGameplayStateView): PetStateView {
121
121
  pet: { id: 'miku', displayName: 'Miku', description: '' },
122
122
  name: 'Miku',
123
123
  treats: { stocked: 0, max: 5 },
124
+ ...(skin === undefined ? {} : { skin }),
124
125
  gameplay: view,
125
126
  }
126
127
  }
@@ -128,7 +129,7 @@ function snapshot(view: PetGameplayStateView): PetStateView {
128
129
  interface Harness {
129
130
  store: PetStoreInstance
130
131
  bus: GameplayBus
131
- api: GameplayApi & { touch: ReturnType<typeof vi.fn>; setMode: ReturnType<typeof vi.fn>; workTick: ReturnType<typeof vi.fn>; buy: ReturnType<typeof vi.fn> }
132
+ api: GameplayApi & { touch: ReturnType<typeof vi.fn>; setMode: ReturnType<typeof vi.fn>; workTick: ReturnType<typeof vi.fn>; buy: ReturnType<typeof vi.fn>; setSkin: ReturnType<typeof vi.fn> }
132
133
  setTrack: ReturnType<typeof vi.fn>
133
134
  drag: ReturnType<typeof createDragStream>
134
135
  setView: (view: PetGameplayStateView) => void
@@ -147,6 +148,7 @@ function harness(view: PetGameplayStateView = gameplayView()): Harness {
147
148
  setMode: vi.fn(async (mode: 'work' | 'sleep' | null) => ok({ view: gameplayView({ mode }) })),
148
149
  workTick: vi.fn(async () => ok({ outcome: 'success' as const, view: gameplayView({ mode: 'work' }) })),
149
150
  buy: vi.fn(async () => ok({ view: gameplayView() })),
151
+ setSkin: vi.fn(async () => ({ ok: true })),
150
152
  }
151
153
  const setView = (next: PetGameplayStateView): void => store.actions.setSnapshot(snapshot(next))
152
154
  render(<GameplayHud definition={petDefinition()} store={store} api={api} bus={bus} drag={drag} t={t} />)
@@ -284,6 +286,7 @@ describe('GameplayHud', () => {
284
286
  const api = {
285
287
  touch: vi.fn(async () => ({ ok: true, view: gameplayView() })),
286
288
  setMode: vi.fn(), workTick: vi.fn(), buy: vi.fn(),
289
+ setSkin: vi.fn(async () => ({ ok: true })),
287
290
  } as unknown as Harness['api']
288
291
  const setTrack = vi.fn()
289
292
  const bus: GameplayBus = { setTrack }
@@ -316,6 +319,7 @@ describe('GameplayHud', () => {
316
319
  const api = {
317
320
  touch: vi.fn(async () => ({ ok: true, view: gameplayView() })),
318
321
  setMode: vi.fn(), workTick: vi.fn(), buy: vi.fn(),
322
+ setSkin: vi.fn(async () => ({ ok: true })),
319
323
  } as unknown as Harness['api']
320
324
  const setTrack = vi.fn()
321
325
  const bus: GameplayBus = { setTrack }
@@ -335,6 +339,101 @@ describe('GameplayHud', () => {
335
339
  expect(api.touch).not.toHaveBeenCalledWith('head')
336
340
  })
337
341
 
342
+ it('restores the host-persisted skin and pushes its idle track on mount', () => {
343
+ // Reload / client restart: the state view carries the last choice, so the
344
+ // renderer repaints the selected skin without any interaction.
345
+ const def = petDefinition()
346
+ def.frames2d!.skins = [
347
+ { id: 'lanhainishang', label: '蓝海霓裳', idleTrack: 'lanhainishang-idle' },
348
+ ]
349
+ const store = createPetStore().create()
350
+ store.actions.setSnapshot(snapshot(gameplayView(), 'lanhainishang'))
351
+ const setSkin = vi.fn(async () => ({ ok: true }))
352
+ const api = { touch: vi.fn(), setMode: vi.fn(), workTick: vi.fn(), buy: vi.fn(), setSkin } as unknown as Harness['api']
353
+ const setIdleTrack = vi.fn()
354
+ const bus: GameplayBus = { setTrack: vi.fn(), setIdleTrack }
355
+ render(<GameplayHud definition={def} store={store} api={api} bus={bus} drag={createDragStream()} t={t} />)
356
+ expect(setIdleTrack).toHaveBeenLastCalledWith('lanhainishang-idle')
357
+ // Latched on the bus too, so a later (re)mount re-applies it.
358
+ expect(bus.idleTrack).toBe('lanhainishang-idle')
359
+ // Restoring is read-only: mounting never writes the choice back.
360
+ expect(setSkin).not.toHaveBeenCalled()
361
+ })
362
+
363
+ it('persists the skin selection through the host API', async () => {
364
+ const def = petDefinition()
365
+ def.frames2d!.skins = [
366
+ { id: 'lanhainishang', label: '蓝海霓裳', idleTrack: 'lanhainishang-idle' },
367
+ ]
368
+ const store = createPetStore().create()
369
+ store.actions.setSnapshot(snapshot(gameplayView()))
370
+ const setSkin = vi.fn(async () => ({ ok: true }))
371
+ const api = { touch: vi.fn(), setMode: vi.fn(), workTick: vi.fn(), buy: vi.fn(), setSkin } as unknown as Harness['api']
372
+ const setIdleTrack = vi.fn()
373
+ const bus: GameplayBus = { setTrack: vi.fn(), setIdleTrack }
374
+ render(<GameplayHud definition={def} store={store} api={api} bus={bus} drag={createDragStream()} t={t} />)
375
+ await act(async () => {
376
+ bus.openCard?.()
377
+ })
378
+ fireEvent.click(screen.getByText('皮肤'))
379
+ await act(async () => {
380
+ fireEvent.click(screen.getByText('蓝海霓裳'))
381
+ })
382
+ expect(setIdleTrack).toHaveBeenLastCalledWith('lanhainishang-idle')
383
+ expect(setSkin).toHaveBeenCalledWith('lanhainishang')
384
+ })
385
+
386
+ it('clears the persisted skin when the default look is picked', async () => {
387
+ const def = petDefinition()
388
+ def.frames2d!.skins = [
389
+ { id: 'lanhainishang', label: '蓝海霓裳', idleTrack: 'lanhainishang-idle' },
390
+ ]
391
+ const store = createPetStore().create()
392
+ store.actions.setSnapshot(snapshot(gameplayView(), 'lanhainishang'))
393
+ const setSkin = vi.fn(async () => ({ ok: true }))
394
+ const api = { touch: vi.fn(), setMode: vi.fn(), workTick: vi.fn(), buy: vi.fn(), setSkin } as unknown as Harness['api']
395
+ const setIdleTrack = vi.fn()
396
+ const bus: GameplayBus = { setTrack: vi.fn(), setIdleTrack }
397
+ render(<GameplayHud definition={def} store={store} api={api} bus={bus} drag={createDragStream()} t={t} />)
398
+ await act(async () => {
399
+ bus.openCard?.()
400
+ })
401
+ fireEvent.click(screen.getByText('皮肤'))
402
+ await act(async () => {
403
+ fireEvent.click(screen.getByText('默认'))
404
+ })
405
+ expect(setSkin).toHaveBeenCalledWith(undefined)
406
+ expect(setIdleTrack).toHaveBeenLastCalledWith(undefined)
407
+ })
408
+
409
+ it('falls back to the served skin when the host rejects the choice', async () => {
410
+ const def = petDefinition()
411
+ def.frames2d!.skins = [
412
+ { id: 'lanhainishang', label: '蓝海霓裳', idleTrack: 'lanhainishang-idle' },
413
+ ]
414
+ const store = createPetStore().create()
415
+ store.actions.setSnapshot(snapshot(gameplayView()))
416
+ const setSkin = vi.fn(async () => ({ ok: false, error: 'unknown-skin' }))
417
+ const api = { touch: vi.fn(), setMode: vi.fn(), workTick: vi.fn(), buy: vi.fn(), setSkin } as unknown as Harness['api']
418
+ const setIdleTrack = vi.fn()
419
+ const bus: GameplayBus = { setTrack: vi.fn(), setIdleTrack }
420
+ render(<GameplayHud definition={def} store={store} api={api} bus={bus} drag={createDragStream()} t={t} />)
421
+ await act(async () => {
422
+ bus.openCard?.()
423
+ })
424
+ fireEvent.click(screen.getByText('皮肤'))
425
+ await act(async () => {
426
+ fireEvent.click(screen.getByText('蓝海霓裳'))
427
+ // The rejected write settles on a later microtask; flush it so the
428
+ // rollback render reaches the bus before the assertion.
429
+ await Promise.resolve()
430
+ await Promise.resolve()
431
+ })
432
+ expect(setSkin).toHaveBeenCalledWith('lanhainishang')
433
+ // The rejected optimistic swap rolls back to what the host still serves.
434
+ expect(setIdleTrack).toHaveBeenLastCalledWith(undefined)
435
+ })
436
+
338
437
  it('runs the idle director: weighted act rolls on the interval', async () => {
339
438
  const h = harness()
340
439
  await act(async () => {
@@ -372,6 +471,70 @@ describe('GameplayHud', () => {
372
471
  expect(h.store.getSnapshot().snapshot?.gameplay?.mode).toBe('work')
373
472
  })
374
473
 
474
+ it('drops a late work adjudication once the mode has been left', async () => {
475
+ // #1495: the tick RPC can still be in flight when the user exits work mode;
476
+ // its result must not write the work view back or play the result track.
477
+ const h = harness(gameplayView({ mode: 'work' }))
478
+ let resolveTick: ((result: PetGameplayVerbResult) => void) | undefined
479
+ h.api.workTick.mockImplementationOnce(() => new Promise<PetGameplayVerbResult>((resolve) => { resolveTick = resolve }))
480
+ await act(async () => {
481
+ vi.advanceTimersByTime(10_000)
482
+ })
483
+ expect(h.api.workTick).toHaveBeenCalledTimes(1)
484
+ await act(async () => {
485
+ h.setView(gameplayView({ mode: null }))
486
+ })
487
+ await act(async () => {
488
+ resolveTick?.({ ok: true, outcome: 'success', view: gameplayView({ mode: 'work' }) })
489
+ await Promise.resolve()
490
+ })
491
+ expect(h.store.getSnapshot().snapshot?.gameplay?.mode).toBeNull()
492
+ expect(h.setTrack).not.toHaveBeenCalledWith('success')
493
+ })
494
+
495
+ it('drives the skin work loop and its skin result animation on the shared 10s rule', async () => {
496
+ // A skin supplies its own work / result art, but the adjudication rule
497
+ // (10s tick, 50% roll, result hold) stays the pet-level gameplay.work rule.
498
+ const def = petDefinition()
499
+ def.frames2d!.tracks['skin-work'] = { frames: ['/pet/miku/skin-work_1.webp'], durations: [200], loop: true }
500
+ def.frames2d!.tracks['skin-success'] = { frames: ['/pet/miku/skin-ok_1.webp'], durations: [200], loop: false, fallback: 'skin-work' }
501
+ def.frames2d!.skins = [
502
+ {
503
+ id: 'skin', label: 'Skin', idleTrack: 'idle',
504
+ // The override key is the manifest's successState ('success' in this fixture).
505
+ gameplayTracks: { work: 'skin-work', success: 'skin-success' },
506
+ },
507
+ ]
508
+ const store = createPetStore().create()
509
+ store.actions.setSnapshot(snapshot(gameplayView({ mode: 'work' }), 'skin'))
510
+ const setTrack = vi.fn()
511
+ const bus: GameplayBus = { setTrack }
512
+ const api = {
513
+ touch: vi.fn(), setMode: vi.fn(),
514
+ workTick: vi.fn(async () => ({ ok: true, outcome: 'success' as const, view: gameplayView({ mode: 'work' }) })),
515
+ buy: vi.fn(), setSkin: vi.fn(async () => ({ ok: true })),
516
+ } as unknown as Harness['api']
517
+ render(<GameplayHud definition={def} store={store} api={api} bus={bus} drag={createDragStream()} t={t} />)
518
+ // The mode hold uses the skin's own work loop, not the manifest track.
519
+ expect(setTrack).toHaveBeenCalledWith('skin-work')
520
+ await act(async () => {
521
+ vi.advanceTimersByTime(10_000)
522
+ })
523
+ // One adjudication per 10s window, then the skin's own result animation.
524
+ expect(api.workTick).toHaveBeenCalledTimes(1)
525
+ expect(setTrack).toHaveBeenCalledWith('skin-success')
526
+ await act(async () => {
527
+ vi.advanceTimersByTime(1300)
528
+ })
529
+ // The result holds for resultMs, then the skin work loop resumes ...
530
+ expect(setTrack).toHaveBeenLastCalledWith('skin-work')
531
+ await act(async () => {
532
+ vi.advanceTimersByTime(10_000)
533
+ })
534
+ // ... and the next 10s round adjudicates again.
535
+ expect(api.workTick).toHaveBeenCalledTimes(2)
536
+ })
537
+
375
538
  it('holds the sleep track and wakes on drag', async () => {
376
539
  const h = harness(gameplayView({ mode: 'sleep' }))
377
540
  expect(h.setTrack).toHaveBeenCalledWith('sleep')