@linxin666/dsh-pet 0.1.18 → 0.1.19

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 (66) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +12 -5
  3. package/README.zh.md +12 -5
  4. package/lib/client.js +48 -11
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +100 -30
  7. package/lib/invariant.js +2 -2
  8. package/lib/state-Diiq8vjk.js +394 -0
  9. package/lib/types/affinity.d.ts +31 -3
  10. package/lib/types/affinity.d.ts.map +1 -1
  11. package/lib/types/affinity.js +21 -7
  12. package/lib/types/client/PetSettingsCard.d.ts +5 -0
  13. package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
  14. package/lib/types/client/PetSettingsCard.js +7 -0
  15. package/lib/types/client/PetSprite.d.ts.map +1 -1
  16. package/lib/types/client/PetSprite.js +8 -2
  17. package/lib/types/client/index.d.ts.map +1 -1
  18. package/lib/types/client/index.js +14 -8
  19. package/lib/types/client/settings-form.d.ts +8 -0
  20. package/lib/types/client/settings-form.d.ts.map +1 -1
  21. package/lib/types/client/settings-form.js +15 -1
  22. package/lib/types/event-projection.d.ts.map +1 -1
  23. package/lib/types/event-projection.js +3 -1
  24. package/lib/types/index.d.ts +4 -2
  25. package/lib/types/index.d.ts.map +1 -1
  26. package/lib/types/index.js +2 -1
  27. package/lib/types/ledger.d.ts +16 -1
  28. package/lib/types/ledger.d.ts.map +1 -1
  29. package/lib/types/ledger.js +29 -4
  30. package/lib/types/registry.d.ts +10 -0
  31. package/lib/types/registry.d.ts.map +1 -1
  32. package/lib/types/registry.js +3 -0
  33. package/lib/types/remarks.d.ts +56 -0
  34. package/lib/types/remarks.d.ts.map +1 -0
  35. package/lib/types/remarks.js +157 -0
  36. package/lib/types/service.d.ts +40 -3
  37. package/lib/types/service.d.ts.map +1 -1
  38. package/lib/types/service.js +90 -18
  39. package/lib/types/state.js +5 -5
  40. package/lib/types/treats.d.ts +3 -0
  41. package/lib/types/treats.d.ts.map +1 -1
  42. package/lib/types/treats.js +5 -2
  43. package/package.json +1 -1
  44. package/src/affinity.test.ts +19 -0
  45. package/src/affinity.ts +22 -7
  46. package/src/client/PetSettingsCard.tsx +8 -0
  47. package/src/client/PetSprite.test.tsx +33 -0
  48. package/src/client/PetSprite.tsx +16 -1
  49. package/src/client/index.ts +14 -8
  50. package/src/client/pet.module.css +19 -0
  51. package/src/client/settings-form.ts +15 -1
  52. package/src/event-projection.ts +3 -1
  53. package/src/index.ts +12 -1
  54. package/src/ledger.test.ts +40 -9
  55. package/src/ledger.ts +33 -4
  56. package/src/persist.test.ts +1 -1
  57. package/src/registry.test.ts +27 -0
  58. package/src/registry.ts +12 -0
  59. package/src/remarks.test.ts +89 -0
  60. package/src/remarks.ts +177 -0
  61. package/src/service.ts +116 -18
  62. package/src/state.test.ts +9 -0
  63. package/src/state.ts +5 -5
  64. package/src/treats.test.ts +13 -11
  65. package/src/treats.ts +5 -2
  66. package/lib/state-CFyJv0sQ.js +0 -213
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as AFFINITY_MAX, c as applyInteraction, d as emptyAffinity, f as rankOf, i as rowOf, l as applyTurnReward, n as animationForPhase, o as AFFINITY_RANKS, r as defaultPetStateConfig, s as affinityViewOf, t as PetStateMachine, u as defaultAffinityConfig } from "./state-CFyJv0sQ.js";
1
+ import { _ as RemarkPicker, a as AFFINITY_MAX, c as applyInteraction, d as emptyAffinity, f as rankOf, g as REMARK_LINE_MAX, h as REMARK_LINES_MAX, i as rowOf, l as applyTurnReward, m as REMARK_KINDS, n as animationForPhase, o as AFFINITY_RANKS, p as BUILTIN_REMARKS, r as defaultPetStateConfig, s as affinityViewOf, t as PetStateMachine, u as defaultAffinityConfig, v as builtinRemark, y as normalizePetRemarks } from "./state-Diiq8vjk.js";
2
2
  import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
3
3
  import z from "schemastery";
4
4
  import { Service } from "@deepseek-ai/cordis";
@@ -117,10 +117,7 @@ function projectOfficialEvent(event, runtime) {
117
117
  phase: "waiting",
118
118
  line: "等待继续"
119
119
  } };
120
- case "aborted": return { input: {
121
- phase: "idle",
122
- line: "已停止"
123
- } };
120
+ case "aborted": return { input: { phase: "idle" } };
124
121
  default: return { input: { phase: "idle" } };
125
122
  }
126
123
  default: return;
@@ -129,8 +126,8 @@ function projectOfficialEvent(event, runtime) {
129
126
  //#endregion
130
127
  //#region src/treats.ts
131
128
  const defaultTreatConfig = {
132
- turnsPerTreat: 3,
133
- timeTreatMs: 30 * 6e4,
129
+ turnsPerTreat: 30,
130
+ timeTreatMs: 300 * 6e4,
134
131
  maxTreats: 20
135
132
  };
136
133
  function emptyTreatLedger() {
@@ -217,6 +214,8 @@ function consumeTreat(ledger) {
217
214
  var PetLedger = class {
218
215
  affinityConfig;
219
216
  treatConfig;
217
+ /** Round-robin reaction picker; rebuilt when the selected pet changes. */
218
+ picker;
220
219
  current;
221
220
  /** Completed turns already rewarded, per session (turn numbers are per-session). */
222
221
  rewardedTurns = /* @__PURE__ */ new Map();
@@ -231,6 +230,7 @@ var PetLedger = class {
231
230
  ...defaultTreatConfig,
232
231
  ...config.treats ?? {}
233
232
  };
233
+ this.picker = new RemarkPicker(config.remarks);
234
234
  this.current = persist;
235
235
  }
236
236
  /** Affinity cooldown/rank tuning (read-only). */
@@ -251,6 +251,13 @@ var PetLedger = class {
251
251
  this.dirty = false;
252
252
  return was;
253
253
  }
254
+ /**
255
+ * Drop a session's rewarded-turn bookkeeping once that session is disposed,
256
+ * so the per-session map does not grow without bound.
257
+ */
258
+ forgetSession(sessionId) {
259
+ this.rewardedTurns.delete(sessionId);
260
+ }
254
261
  /** Replace the display block (clamping stays a caller concern). */
255
262
  setDisplay(display) {
256
263
  this.current = {
@@ -280,9 +287,16 @@ var PetLedger = class {
280
287
  this.dirty = true;
281
288
  }
282
289
  /**
290
+ * Swap the reaction pools to another pet's custom remarks (called on pet
291
+ * selection). Slots the pet does not declare fall back to built-ins.
292
+ */
293
+ setRemarks(remarks) {
294
+ this.picker = new RemarkPicker(remarks);
295
+ }
296
+ /**
283
297
  * Settle the treat economy (work + time output since the last settlement).
284
298
  * A zero-gain first settlement still starts the time clock (anchor write),
285
- * which is how the 30-minute time output can ever accrue. Returns true when
299
+ * which is how the time output can ever accrue. Returns true when
286
300
  * the in-memory ledger changed and should be persisted.
287
301
  */
288
302
  settleTreats(nowMs) {
@@ -332,14 +346,14 @@ var PetLedger = class {
332
346
  if (kind === "feed") this.settleTreats(nowMs);
333
347
  const outcome = applyInteraction(this.current.affinity, kind, nowMs, this.affinityConfig);
334
348
  if (kind === "feed" && !outcome.accepted) return {
335
- reaction: outcome.reaction,
349
+ reaction: this.picker.pick("feedCooldown"),
336
350
  delta: 0,
337
351
  affinity: this.affinityView(nowMs)
338
352
  };
339
353
  if (kind === "feed") {
340
354
  const consume = consumeTreat(this.current.treats);
341
355
  if (!consume.ok) return {
342
- reaction: "没有小鱼干了,多陪我工作一会儿吧~",
356
+ reaction: this.picker.pick("noTreats"),
343
357
  delta: 0,
344
358
  affinity: this.affinityView(nowMs)
345
359
  };
@@ -357,7 +371,7 @@ var PetLedger = class {
357
371
  this.dirty = true;
358
372
  }
359
373
  return {
360
- reaction: outcome.reaction,
374
+ reaction: this.picker.pick(outcome.accepted ? kind : "petCooldown"),
361
375
  delta: outcome.delta,
362
376
  affinity: this.affinityView(nowMs)
363
377
  };
@@ -464,7 +478,7 @@ function loadPetPersist(dir = petHomeDir()) {
464
478
  const base = emptyPersist();
465
479
  const rawAffinity = parsed.affinity ?? {};
466
480
  const affinity = {
467
- points: clamp(finiteNum(rawAffinity.points, 0), 100),
481
+ points: clamp(finiteNum(rawAffinity.points, 0), AFFINITY_MAX),
468
482
  lastPetAt: clamp(finiteNum(rawAffinity.lastPetAt, 0), Number.MAX_SAFE_INTEGER),
469
483
  lastFeedAt: clamp(finiteNum(rawAffinity.lastFeedAt, 0), Number.MAX_SAFE_INTEGER),
470
484
  pets: clamp(finiteNum(rawAffinity.pets, 0), Number.MAX_SAFE_INTEGER),
@@ -728,6 +742,7 @@ function resolvePetManifest(raw, dir, options = {}) {
728
742
  const rows = DEFAULT_FRAME_COUNTS.map((fallback, index) => {
729
743
  return finiteInt(Array.isArray(source.frames) ? source.frames[index] : void 0, fallback, columns);
730
744
  });
745
+ const remarks = normalizePetRemarks(source.remarks, (message) => warn("manifest " + id + ": " + message));
731
746
  const trackOverrides = typeof source.tracks === "object" && source.tracks !== null ? source.tracks : {};
732
747
  const tracks = {};
733
748
  for (const [row, animation] of PET_ROW_ORDER.entries()) {
@@ -758,7 +773,8 @@ function resolvePetManifest(raw, dir, options = {}) {
758
773
  atlasUrl: assetUrl(assetPrefix, id, spritesheet),
759
774
  manifestUrl: assetUrl(assetPrefix, id, "pet.json"),
760
775
  dir,
761
- spritesheetPath: spritesheetPath.join("/")
776
+ spritesheetPath: spritesheetPath.join("/"),
777
+ ...remarks === void 0 ? {} : { remarks }
762
778
  };
763
779
  }
764
780
  /** Scan one directory of pet folders; entries come back in name order. */
@@ -852,6 +868,8 @@ function petEntryView(entry) {
852
868
  manifestUrl: entry.manifestUrl
853
869
  };
854
870
  }
871
+ /** Hard cap on simultaneously displayed session bubbles (most recent first). */
872
+ const MAX_SESSION_BUBBLES = 12;
855
873
  /**
856
874
  * Cordis service exposing the pet RPC domain. Lazy: nothing is scanned or
857
875
  * written until an economic event or interaction arrives; event listeners
@@ -861,6 +879,7 @@ function petEntryView(entry) {
861
879
  var PetService = class extends Service {
862
880
  static inject = [];
863
881
  machine;
882
+ stateConfig;
864
883
  ledger;
865
884
  registry;
866
885
  persistDir;
@@ -868,7 +887,12 @@ var PetService = class extends Service {
868
887
  disposeActivity;
869
888
  /** Session whose most recent meaningful event currently drives the global pet. */
870
889
  displaySession;
871
- sessionActivity = /* @__PURE__ */ new WeakMap();
890
+ /**
891
+ * Per-session activity, most recent last (Map insertion order). Bounded by
892
+ * MAX_SESSION_BUBBLES so a burst of sessions cannot grow it without bound;
893
+ * disposed sessions are removed by the 'session/disposed' listener.
894
+ */
895
+ sessionActivity = /* @__PURE__ */ new Map();
872
896
  constructor(ctx, config = {}) {
873
897
  super(ctx, "pet");
874
898
  this.persistDir = config.persistDir ?? petHomeDir();
@@ -882,15 +906,18 @@ var PetService = class extends Service {
882
906
  ...persist,
883
907
  petId: this.registry.defaultEntry().id
884
908
  };
909
+ const selected = this.registry.byId(persist.petId) ?? this.registry.defaultEntry();
885
910
  const ledgerConfig = {
886
911
  affinity: config.affinity,
887
- treats: config.treats
912
+ treats: config.treats,
913
+ remarks: selected.remarks
888
914
  };
889
915
  this.ledger = new PetLedger(persist, ledgerConfig);
890
- this.machine = new PetStateMachine({
916
+ this.stateConfig = {
891
917
  ...defaultPetStateConfig,
892
918
  ...config.state ?? {}
893
- });
919
+ };
920
+ this.machine = new PetStateMachine(this.stateConfig);
894
921
  this.enabled = config.enabled ?? true;
895
922
  this.syncActivity();
896
923
  }
@@ -936,6 +963,7 @@ var PetService = class extends Service {
936
963
  error: "unknown-pet"
937
964
  };
938
965
  this.ledger.setPetId(entry.id);
966
+ this.ledger.setRemarks(entry.remarks);
939
967
  this.flush();
940
968
  this.syncSettingsFromPet();
941
969
  return {
@@ -956,7 +984,7 @@ var PetService = class extends Service {
956
984
  if (!this.enabled) return;
957
985
  this.disposeActivity = (() => {
958
986
  const disposers = [this.ctx.on("session/event", (session, event) => {
959
- const runtime = this.activityRuntime(session);
987
+ const runtime = this.activityOf(session).runtime;
960
988
  if (event.type === "activity/status") {
961
989
  const payload = event.data ?? {};
962
990
  if (typeof payload.phase !== "string" || !isActivityPhase(payload.phase)) return;
@@ -974,26 +1002,52 @@ var PetService = class extends Service {
974
1002
  this.applyActivity(session, transition.input);
975
1003
  if (transition.completedTurn !== void 0) this.rewardTurn(String(session.id), transition.completedTurn);
976
1004
  }), this.ctx.on("session/disposed", (session) => {
1005
+ this.ledger.forgetSession(String(session.id));
1006
+ this.sessionActivity.delete(session);
977
1007
  if (session !== this.displaySession) return;
978
1008
  this.displaySession = void 0;
979
- this.machine.onSessionDisposed();
1009
+ const remaining = [...this.sessionActivity.entries()].at(-1);
1010
+ if (remaining !== void 0) {
1011
+ const [nextSession, activity] = remaining;
1012
+ this.displaySession = nextSession;
1013
+ if (activity.lastInput !== void 0) this.machine.onActivityStatus(activity.lastInput);
1014
+ this.machine.onSessionActive();
1015
+ } else this.machine.onSessionDisposed();
980
1016
  })];
981
1017
  return () => {
982
1018
  for (const dispose of disposers) dispose();
983
1019
  };
984
1020
  })();
985
1021
  }
986
- /** Return the projection state associated with one live session. */
987
- activityRuntime(session) {
988
- let runtime = this.sessionActivity.get(session);
989
- if (runtime === void 0) {
990
- runtime = emptyProjectionRuntime();
991
- this.sessionActivity.set(session, runtime);
1022
+ /** Return the per-session activity record, creating it on first sight. */
1023
+ activityOf(session) {
1024
+ let activity = this.sessionActivity.get(session);
1025
+ if (activity === void 0) {
1026
+ activity = {
1027
+ runtime: emptyProjectionRuntime(),
1028
+ machine: new PetStateMachine(this.stateConfig)
1029
+ };
1030
+ this.sessionActivity.set(session, activity);
992
1031
  }
993
- return runtime;
1032
+ return activity;
994
1033
  }
995
- /** Commit one activity as the host-global pet's most recent display state. */
1034
+ /**
1035
+ * Commit one activity: the session's own machine renders its bubble, and
1036
+ * the session becomes the host-global display session (most recent
1037
+ * meaningful event wins the sprite animation).
1038
+ */
996
1039
  applyActivity(session, input) {
1040
+ const activity = this.activityOf(session);
1041
+ activity.lastInput = input;
1042
+ activity.machine.onActivityStatus(input);
1043
+ activity.machine.onSessionActive();
1044
+ this.sessionActivity.delete(session);
1045
+ this.sessionActivity.set(session, activity);
1046
+ while (this.sessionActivity.size > 12) {
1047
+ const oldest = this.sessionActivity.keys().next().value;
1048
+ if (oldest === void 0) break;
1049
+ this.sessionActivity.delete(oldest);
1050
+ }
997
1051
  this.displaySession = session;
998
1052
  this.machine.onActivityStatus(input);
999
1053
  this.machine.onSessionActive();
@@ -1060,8 +1114,11 @@ var PetService = class extends Service {
1060
1114
  * @param section - the resolved settings section.
1061
1115
  */
1062
1116
  applySettingsSection(section) {
1063
- if (typeof section.petId === "string" && this.registry.byId(section.petId) !== void 0) this.ledger.setPetId(section.petId);
1064
- else if (section.petId !== void 0) this.syncSettingsFromPet();
1117
+ const selected = typeof section.petId === "string" ? this.registry.byId(section.petId) : void 0;
1118
+ if (selected !== void 0) {
1119
+ this.ledger.setPetId(selected.id);
1120
+ this.ledger.setRemarks(selected.remarks);
1121
+ } else if (section.petId !== void 0) this.syncSettingsFromPet();
1065
1122
  const next = { ...this.ledger.snapshot.display };
1066
1123
  next.visible = section.visible && (section.enabled ?? true);
1067
1124
  next.size = Math.round(Math.min(512, Math.max(32, section.size)));
@@ -1094,11 +1151,24 @@ var PetService = class extends Service {
1094
1151
  view() {
1095
1152
  const snapshot = this.machine.render();
1096
1153
  const entry = this.activeEntry();
1154
+ const sessions = [];
1155
+ for (const [session, activity] of [...this.sessionActivity.entries()].reverse()) {
1156
+ if (sessions.length >= 12) break;
1157
+ const perSession = activity.machine.render();
1158
+ if (perSession.bubble === void 0) continue;
1159
+ sessions.push({
1160
+ sessionId: String(session.id),
1161
+ animation: perSession.animation,
1162
+ bubble: perSession.bubble,
1163
+ phase: perSession.phase
1164
+ });
1165
+ }
1097
1166
  return {
1098
1167
  animation: snapshot.animation,
1099
1168
  ...snapshot.bubble === void 0 ? {} : { bubble: snapshot.bubble },
1100
1169
  phase: snapshot.phase,
1101
1170
  sessionActive: snapshot.sessionActive,
1171
+ sessions,
1102
1172
  affinity: this.ledger.affinityView(Date.now()),
1103
1173
  display: { ...this.ledger.snapshot.display },
1104
1174
  pet: {
@@ -1457,4 +1527,4 @@ function apply(ctx, config = {}) {
1457
1527
  syncRoutes();
1458
1528
  }
1459
1529
  //#endregion
1460
- export { AFFINITY_MAX, AFFINITY_RANKS, DEFAULT_FRAME_COUNTS, DEFAULT_PET_CELL, DEFAULT_PET_COLUMNS, DEFAULT_PET_ID, DEFAULT_PET_NAME, DEFAULT_PET_ROW_COUNT, DEFAULT_TRACK_PATTERNS, PET_API_PREFIX, PET_ASSET_PREFIX, PET_NAME_MAX_LENGTH, PET_ROW_ORDER, PetService, PetStateMachine, animationForPhase, apply, applyInteraction, applyTurnReward, codexPetsDir, consumeTreat, defaultDisplayConfig, defaultTreatConfig, emptyAffinity, emptyPersist, emptyTreatLedger, inject, loadPetPersist, loadPetRegistry, makePetRoutes, makePetSettingsSchema, name, petEntryView, petHomeDir, petPackageRoot, rankOf, resolvePetManifest, rowOf, savePetPersist, settleTreatGrants };
1530
+ export { AFFINITY_MAX, AFFINITY_RANKS, BUILTIN_REMARKS, DEFAULT_FRAME_COUNTS, DEFAULT_PET_CELL, DEFAULT_PET_COLUMNS, DEFAULT_PET_ID, DEFAULT_PET_NAME, DEFAULT_PET_ROW_COUNT, DEFAULT_TRACK_PATTERNS, MAX_SESSION_BUBBLES, PET_API_PREFIX, PET_ASSET_PREFIX, PET_NAME_MAX_LENGTH, PET_ROW_ORDER, PetService, PetStateMachine, REMARK_KINDS, REMARK_LINES_MAX, REMARK_LINE_MAX, RemarkPicker, animationForPhase, apply, applyInteraction, applyTurnReward, builtinRemark, codexPetsDir, consumeTreat, defaultDisplayConfig, defaultTreatConfig, emptyAffinity, emptyPersist, emptyTreatLedger, inject, loadPetPersist, loadPetRegistry, makePetRoutes, makePetSettingsSchema, name, normalizePetRemarks, petEntryView, petHomeDir, petPackageRoot, rankOf, resolvePetManifest, rowOf, savePetPersist, settleTreatGrants };
package/lib/invariant.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as animationForPhase, o as AFFINITY_RANKS, u as defaultAffinityConfig } from "./state-CFyJv0sQ.js";
1
+ import { a as AFFINITY_MAX, n as animationForPhase, o as AFFINITY_RANKS, u as defaultAffinityConfig } from "./state-Diiq8vjk.js";
2
2
  //#region src/invariant.ts
3
3
  /**
4
4
  * Package invariants — cheap structural checks run at import time on the
@@ -11,7 +11,7 @@ function invariant(condition, message) {
11
11
  }
12
12
  /** Run every package invariant once; throws on the first violation. */
13
13
  function runPetInvariants() {
14
- invariant(true, "AFFINITY_MAX must be positive");
14
+ invariant(AFFINITY_MAX > 0, "AFFINITY_MAX must be positive");
15
15
  invariant(AFFINITY_RANKS.length > 0 && AFFINITY_RANKS[0].min === 0, "AFFINITY_RANKS must start at 0");
16
16
  invariant(defaultAffinityConfig.turnReward > 0, "turnReward must be positive");
17
17
  invariant(defaultAffinityConfig.feedCooldownMs > defaultAffinityConfig.petCooldownMs, "feed cooldown must exceed pet cooldown");