@linxin666/dsh-pet 0.1.18 → 0.1.20
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 +12 -5
- package/README.zh.md +12 -5
- package/lib/client.js +95 -26
- package/lib/client.js.map +1 -1
- package/lib/index.js +144 -31
- package/lib/invariant.js +2 -2
- package/lib/state-Diiq8vjk.js +394 -0
- package/lib/types/affinity.d.ts +39 -11
- package/lib/types/affinity.d.ts.map +1 -1
- package/lib/types/affinity.js +21 -7
- package/lib/types/client/PetDockEntry.d.ts +2 -0
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +2 -2
- package/lib/types/client/PetSettingsCard.d.ts +5 -0
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSettingsCard.js +9 -2
- package/lib/types/client/PetSprite.d.ts +2 -0
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +22 -15
- package/lib/types/client/PluginSettingsCard.d.ts +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +45 -14
- package/lib/types/client/locales.d.ts +82 -80
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +2 -0
- package/lib/types/client/settings-form.d.ts +8 -0
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +15 -1
- package/lib/types/client/spritesheet.d.ts +1 -1
- package/lib/types/client/spritesheet.d.ts.map +1 -1
- package/lib/types/client/spritesheet.js +3 -12
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +3 -1
- package/lib/types/index.d.ts +6 -3
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +15 -12
- package/lib/types/invariant.js +2 -2
- package/lib/types/ledger.d.ts +16 -1
- package/lib/types/ledger.d.ts.map +1 -1
- package/lib/types/ledger.js +31 -6
- package/lib/types/mount-once.d.ts +25 -0
- package/lib/types/mount-once.d.ts.map +1 -0
- package/lib/types/mount-once.js +42 -0
- package/lib/types/persist.js +3 -3
- package/lib/types/registry.d.ts +10 -0
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +3 -0
- package/lib/types/remarks.d.ts +56 -0
- package/lib/types/remarks.d.ts.map +1 -0
- package/lib/types/remarks.js +157 -0
- package/lib/types/routes.js +2 -2
- package/lib/types/service.d.ts +44 -3
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +100 -23
- package/lib/types/state.d.ts.map +1 -1
- package/lib/types/state.js +5 -5
- package/lib/types/treats.d.ts +3 -0
- package/lib/types/treats.d.ts.map +1 -1
- package/lib/types/treats.js +5 -2
- package/package.json +1 -1
- package/src/affinity.test.ts +19 -0
- package/src/affinity.ts +22 -7
- package/src/client/PetDockEntry.tsx +3 -0
- package/src/client/PetSettingsCard.tsx +8 -0
- package/src/client/PetSprite.test.tsx +89 -3
- package/src/client/PetSprite.tsx +36 -12
- package/src/client/index.ts +40 -11
- package/src/client/locales.ts +2 -0
- package/src/client/pet.module.css +52 -10
- package/src/client/settings-form.ts +15 -1
- package/src/client/spritesheet.ts +3 -13
- package/src/event-projection.ts +3 -1
- package/src/index.ts +16 -2
- package/src/ledger.test.ts +40 -9
- package/src/ledger.ts +33 -4
- package/src/mount-once.ts +48 -0
- package/src/persist.test.ts +1 -1
- package/src/registry.test.ts +27 -0
- package/src/registry.ts +12 -0
- package/src/remarks.test.ts +89 -0
- package/src/remarks.ts +177 -0
- package/src/service.ts +124 -18
- package/src/state.test.ts +9 -0
- package/src/state.ts +5 -5
- package/src/treats.test.ts +13 -11
- package/src/treats.ts +5 -2
- package/lib/state-CFyJv0sQ.js +0 -213
package/lib/types/index.js
CHANGED
|
@@ -12,17 +12,19 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
14
14
|
import z from 'schemastery';
|
|
15
|
-
import { PetService, PET_SETTINGS_NAMESPACE } from
|
|
16
|
-
import { makePetRoutes } from
|
|
17
|
-
import { loadPetRegistry, petPackageRoot } from
|
|
18
|
-
import { DISPLAY_INSET_MAX, DISPLAY_SIZE_MAX, DISPLAY_SIZE_MIN } from
|
|
19
|
-
|
|
20
|
-
export {
|
|
21
|
-
export {
|
|
22
|
-
export {
|
|
23
|
-
export {
|
|
24
|
-
export {
|
|
25
|
-
export {
|
|
15
|
+
import { PetService, PET_SETTINGS_NAMESPACE } from './service.js';
|
|
16
|
+
import { makePetRoutes } from './routes.js';
|
|
17
|
+
import { loadPetRegistry, petPackageRoot } from './registry.js';
|
|
18
|
+
import { DISPLAY_INSET_MAX, DISPLAY_SIZE_MAX, DISPLAY_SIZE_MIN } from './persist.js';
|
|
19
|
+
import { mountOnce } from './mount-once.js';
|
|
20
|
+
export { PetService, MAX_SESSION_BUBBLES } from './service.js';
|
|
21
|
+
export { AFFINITY_MAX, AFFINITY_RANKS, applyInteraction, applyTurnReward, emptyAffinity, rankOf, } from './affinity.js';
|
|
22
|
+
export { animationForPhase, PetStateMachine, rowOf, } from './state.js';
|
|
23
|
+
export { consumeTreat, defaultTreatConfig, emptyTreatLedger, settleTreatGrants, } from './treats.js';
|
|
24
|
+
export { BUILTIN_REMARKS, REMARK_KINDS, REMARK_LINE_MAX, REMARK_LINES_MAX, RemarkPicker, builtinRemark, normalizePetRemarks, } from './remarks.js';
|
|
25
|
+
export { DEFAULT_PET_ID, DEFAULT_PET_NAME, PET_NAME_MAX_LENGTH, defaultDisplayConfig, emptyPersist, loadPetPersist, petHomeDir, savePetPersist, } from './persist.js';
|
|
26
|
+
export { DEFAULT_FRAME_COUNTS, DEFAULT_PET_CELL, DEFAULT_PET_COLUMNS, DEFAULT_PET_ROW_COUNT, DEFAULT_TRACK_PATTERNS, PET_ROW_ORDER, codexPetsDir, loadPetRegistry, petEntryView, petPackageRoot, resolvePetManifest, } from './registry.js';
|
|
27
|
+
export { makePetRoutes, PET_API_PREFIX, PET_ASSET_PREFIX, } from './routes.js';
|
|
26
28
|
/** Stable cordis plugin name (matches cordis.patch.yml insert id). */
|
|
27
29
|
export const name = 'pet';
|
|
28
30
|
/** Services required before the pet can mount its surfaces. */
|
|
@@ -46,7 +48,8 @@ export function makePetSettingsSchema(fallbackPetId) {
|
|
|
46
48
|
});
|
|
47
49
|
}
|
|
48
50
|
/** Register the pet service and its API + asset routes on the context. */
|
|
49
|
-
export
|
|
51
|
+
export const apply = mountOnce('@linxin666/dsh-pet', applyImpl);
|
|
52
|
+
function applyImpl(ctx, config = {}) {
|
|
50
53
|
const registry = config.registry
|
|
51
54
|
?? loadPetRegistry({
|
|
52
55
|
packageRoot: petPackageRoot(import.meta.url),
|
package/lib/types/invariant.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* host side. Mirrors the pattern used by other dsh plugin packages.
|
|
4
4
|
* @module @linxin666/dsh-pet/invariant
|
|
5
5
|
*/
|
|
6
|
-
import { AFFINITY_MAX, AFFINITY_RANKS, defaultAffinityConfig } from
|
|
7
|
-
import { animationForPhase } from
|
|
6
|
+
import { AFFINITY_MAX, AFFINITY_RANKS, defaultAffinityConfig } from './affinity.js';
|
|
7
|
+
import { animationForPhase } from './state.js';
|
|
8
8
|
/** Assert a condition; throws a descriptive Error when violated. */
|
|
9
9
|
export function invariant(condition, message) {
|
|
10
10
|
if (!condition) {
|
package/lib/types/ledger.d.ts
CHANGED
|
@@ -8,11 +8,14 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { type AffinityConfig, type PetAffinityView, type PetInteraction } from './affinity.ts';
|
|
10
10
|
import { type TreatConfig } from './treats.ts';
|
|
11
|
+
import { type PetRemarks } from './remarks.ts';
|
|
11
12
|
import type { PetDisplayConfig, PetPersist } from './persist.ts';
|
|
12
13
|
/** Tuning overrides for the affinity economy. */
|
|
13
14
|
export interface LedgerConfig {
|
|
14
15
|
affinity?: Partial<AffinityConfig>;
|
|
15
16
|
treats?: Partial<TreatConfig>;
|
|
17
|
+
/** Per-pet remark pools for the selected pet (custom slots override built-ins). */
|
|
18
|
+
remarks?: PetRemarks;
|
|
16
19
|
}
|
|
17
20
|
/** Result of one ledger interaction (the shape the pet RPC returns). */
|
|
18
21
|
export interface LedgerInteractionResult {
|
|
@@ -31,6 +34,8 @@ export interface LedgerInteractionResult {
|
|
|
31
34
|
export declare class PetLedger {
|
|
32
35
|
private readonly affinityConfig;
|
|
33
36
|
private readonly treatConfig;
|
|
37
|
+
/** Round-robin reaction picker; rebuilt when the selected pet changes. */
|
|
38
|
+
private picker;
|
|
34
39
|
private current;
|
|
35
40
|
/** Completed turns already rewarded, per session (turn numbers are per-session). */
|
|
36
41
|
private rewardedTurns;
|
|
@@ -45,16 +50,26 @@ export declare class PetLedger {
|
|
|
45
50
|
get treatMax(): number;
|
|
46
51
|
/** Consume the pending-write flag if any mutation occurred. */
|
|
47
52
|
takeDirty(): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Drop a session's rewarded-turn bookkeeping once that session is disposed,
|
|
55
|
+
* so the per-session map does not grow without bound.
|
|
56
|
+
*/
|
|
57
|
+
forgetSession(sessionId: string): void;
|
|
48
58
|
/** Replace the display block (clamping stays a caller concern). */
|
|
49
59
|
setDisplay(display: PetDisplayConfig): void;
|
|
50
60
|
/** Replace the selected pet id (validation stays a caller concern). */
|
|
51
61
|
setPetId(petId: string): void;
|
|
52
62
|
/** Replace one pet's display name (validation stays a caller concern). */
|
|
53
63
|
setPetName(petId: string, name: string): void;
|
|
64
|
+
/**
|
|
65
|
+
* Swap the reaction pools to another pet's custom remarks (called on pet
|
|
66
|
+
* selection). Slots the pet does not declare fall back to built-ins.
|
|
67
|
+
*/
|
|
68
|
+
setRemarks(remarks?: PetRemarks): void;
|
|
54
69
|
/**
|
|
55
70
|
* Settle the treat economy (work + time output since the last settlement).
|
|
56
71
|
* A zero-gain first settlement still starts the time clock (anchor write),
|
|
57
|
-
* which is how the
|
|
72
|
+
* which is how the time output can ever accrue. Returns true when
|
|
58
73
|
* the in-memory ledger changed and should be persisted.
|
|
59
74
|
*/
|
|
60
75
|
settleTreats(nowMs: number): boolean;
|
|
@@ -1 +1 @@
|
|
|
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,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEhE,iDAAiD;AACjD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;
|
|
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;AAEhE,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;CACrB;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;IAErB,YAAY,OAAO,EAAE,UAAU,EAAE,MAAM,GAAE,YAAiB,EAKzD;IAED,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,CAInB;IAED;;;OAGG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAErC;IAED,mEAAmE;IACnE,UAAU,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAG1C;IAED,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAI5B;IAED,0EAA0E;IAC1E,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAG5C;IAED;;;OAGG;IACH,UAAU,CAAC,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAErC;IAED;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAWnC;IAED;;;;OAIG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAOlE;IAED,8EAA8E;IAC9E,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOvC;IAED,OAAO,CAAC,eAAe;IASvB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,uBAAuB,CA8BrE;IAED,kDAAkD;IAClD,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,CAE3C;CACF"}
|
package/lib/types/ledger.js
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* economic events: completed-turn rewards (official or legacy) and feeds.
|
|
7
7
|
* @module @linxin666/dsh-pet/ledger
|
|
8
8
|
*/
|
|
9
|
-
import { applyInteraction, affinityViewOf, applyTurnReward, defaultAffinityConfig, } from
|
|
10
|
-
import { consumeTreat, defaultTreatConfig, settleTreatGrants, } from
|
|
9
|
+
import { applyInteraction, affinityViewOf, applyTurnReward, defaultAffinityConfig, } from './affinity.js';
|
|
10
|
+
import { consumeTreat, defaultTreatConfig, settleTreatGrants, } from './treats.js';
|
|
11
|
+
import { RemarkPicker } from './remarks.js';
|
|
11
12
|
/**
|
|
12
13
|
* Holds the current persistence snapshot and all economy bookkeeping. Every
|
|
13
14
|
* mutating call flags takeDirty so the facade persists exactly once per
|
|
@@ -16,6 +17,8 @@ import { consumeTreat, defaultTreatConfig, settleTreatGrants, } from "./treats.j
|
|
|
16
17
|
export class PetLedger {
|
|
17
18
|
affinityConfig;
|
|
18
19
|
treatConfig;
|
|
20
|
+
/** Round-robin reaction picker; rebuilt when the selected pet changes. */
|
|
21
|
+
picker;
|
|
19
22
|
current;
|
|
20
23
|
/** Completed turns already rewarded, per session (turn numbers are per-session). */
|
|
21
24
|
rewardedTurns = new Map();
|
|
@@ -24,6 +27,7 @@ export class PetLedger {
|
|
|
24
27
|
constructor(persist, config = {}) {
|
|
25
28
|
this.affinityConfig = { ...defaultAffinityConfig, ...(config.affinity ?? {}) };
|
|
26
29
|
this.treatConfig = { ...defaultTreatConfig, ...(config.treats ?? {}) };
|
|
30
|
+
this.picker = new RemarkPicker(config.remarks);
|
|
27
31
|
this.current = persist;
|
|
28
32
|
}
|
|
29
33
|
/** Affinity cooldown/rank tuning (read-only). */
|
|
@@ -44,6 +48,13 @@ export class PetLedger {
|
|
|
44
48
|
this.dirty = false;
|
|
45
49
|
return was;
|
|
46
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Drop a session's rewarded-turn bookkeeping once that session is disposed,
|
|
53
|
+
* so the per-session map does not grow without bound.
|
|
54
|
+
*/
|
|
55
|
+
forgetSession(sessionId) {
|
|
56
|
+
this.rewardedTurns.delete(sessionId);
|
|
57
|
+
}
|
|
47
58
|
/** Replace the display block (clamping stays a caller concern). */
|
|
48
59
|
setDisplay(display) {
|
|
49
60
|
this.current = { ...this.current, display };
|
|
@@ -61,10 +72,17 @@ export class PetLedger {
|
|
|
61
72
|
this.current = { ...this.current, names: { ...this.current.names, [petId]: name } };
|
|
62
73
|
this.dirty = true;
|
|
63
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Swap the reaction pools to another pet's custom remarks (called on pet
|
|
77
|
+
* selection). Slots the pet does not declare fall back to built-ins.
|
|
78
|
+
*/
|
|
79
|
+
setRemarks(remarks) {
|
|
80
|
+
this.picker = new RemarkPicker(remarks);
|
|
81
|
+
}
|
|
64
82
|
/**
|
|
65
83
|
* Settle the treat economy (work + time output since the last settlement).
|
|
66
84
|
* A zero-gain first settlement still starts the time clock (anchor write),
|
|
67
|
-
* which is how the
|
|
85
|
+
* which is how the time output can ever accrue. Returns true when
|
|
68
86
|
* the in-memory ledger changed and should be persisted.
|
|
69
87
|
*/
|
|
70
88
|
settleTreats(nowMs) {
|
|
@@ -118,14 +136,17 @@ export class PetLedger {
|
|
|
118
136
|
if (kind === 'feed')
|
|
119
137
|
this.settleTreats(nowMs);
|
|
120
138
|
const outcome = applyInteraction(this.current.affinity, kind, nowMs, this.affinityConfig);
|
|
139
|
+
// Reactions come from the picker pools (custom per-pet slots override
|
|
140
|
+
// the built-in library per slot); outcome.reaction stays the fallback
|
|
141
|
+
// copy for direct applyInteraction callers.
|
|
121
142
|
if (kind === 'feed' && !outcome.accepted) {
|
|
122
|
-
return { reaction:
|
|
143
|
+
return { reaction: this.picker.pick('feedCooldown'), delta: 0, affinity: this.affinityView(nowMs) };
|
|
123
144
|
}
|
|
124
145
|
if (kind === 'feed') {
|
|
125
146
|
const consume = consumeTreat(this.current.treats);
|
|
126
147
|
if (!consume.ok) {
|
|
127
148
|
return {
|
|
128
|
-
reaction: '
|
|
149
|
+
reaction: this.picker.pick('noTreats'),
|
|
129
150
|
delta: 0,
|
|
130
151
|
affinity: this.affinityView(nowMs),
|
|
131
152
|
};
|
|
@@ -137,7 +158,11 @@ export class PetLedger {
|
|
|
137
158
|
this.current = { ...this.current, affinity: outcome.affinity };
|
|
138
159
|
this.dirty = true;
|
|
139
160
|
}
|
|
140
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
reaction: this.picker.pick(outcome.accepted ? kind : 'petCooldown'),
|
|
163
|
+
delta: outcome.delta,
|
|
164
|
+
affinity: this.affinityView(nowMs),
|
|
165
|
+
};
|
|
141
166
|
}
|
|
142
167
|
/** Current affinity view for the RPC snapshot. */
|
|
143
168
|
affinityView(nowMs) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host single-instance guard shared by the plugin family. The family bundle
|
|
3
|
+
* (dsh-web-ui-all / dsh-skins) namespaces every child row id (web-ui-*), so
|
|
4
|
+
* the loader accepts a standalone install of the same package side by side;
|
|
5
|
+
* without this guard the second instance would still re-register the same
|
|
6
|
+
* webserver routes, tools, settings namespaces, and system-prompt sections
|
|
7
|
+
* and fail the boot. mountOnce makes the second host apply a no-op for the
|
|
8
|
+
* lifetime of the first instance (the browser half is already deduped by
|
|
9
|
+
* package name in the client module host).
|
|
10
|
+
*
|
|
11
|
+
* The registry rides a global symbol so two module instances of the same
|
|
12
|
+
* package (npm copy vs repository link) still share one verdict. cordis
|
|
13
|
+
* `ctx.effect` runs its callback immediately and treats the callback's
|
|
14
|
+
* return value as the fiber disposer, so the unmarker is returned, not run.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Wrap a cordis plugin apply so the package runs at most once per process.
|
|
18
|
+
* The first mount registers normally and unmarks when its fiber disposes;
|
|
19
|
+
* any later mount of the same package name is a no-op.
|
|
20
|
+
* @param packageName - npm package identity shared by every install source.
|
|
21
|
+
* @param fn - the original plugin apply.
|
|
22
|
+
* @returns an apply of the same shape.
|
|
23
|
+
*/
|
|
24
|
+
export declare function mountOnce<T extends (...args: any[]) => unknown>(packageName: string, fn: T): T;
|
|
25
|
+
//# sourceMappingURL=mount-once.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount-once.d.ts","sourceRoot":"","sources":["../../src/mount-once.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;GAcG;AAaH;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,CAW9F"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Generated by scripts/sync-shared.mjs from shared/host/mount-once.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
|
|
2
|
+
/**
|
|
3
|
+
* Host single-instance guard shared by the plugin family. The family bundle
|
|
4
|
+
* (dsh-web-ui-all / dsh-skins) namespaces every child row id (web-ui-*), so
|
|
5
|
+
* the loader accepts a standalone install of the same package side by side;
|
|
6
|
+
* without this guard the second instance would still re-register the same
|
|
7
|
+
* webserver routes, tools, settings namespaces, and system-prompt sections
|
|
8
|
+
* and fail the boot. mountOnce makes the second host apply a no-op for the
|
|
9
|
+
* lifetime of the first instance (the browser half is already deduped by
|
|
10
|
+
* package name in the client module host).
|
|
11
|
+
*
|
|
12
|
+
* The registry rides a global symbol so two module instances of the same
|
|
13
|
+
* package (npm copy vs repository link) still share one verdict. cordis
|
|
14
|
+
* `ctx.effect` runs its callback immediately and treats the callback's
|
|
15
|
+
* return value as the fiber disposer, so the unmarker is returned, not run.
|
|
16
|
+
*/
|
|
17
|
+
const MOUNTED = Symbol.for('dsh-web-ui.mounted-plugins');
|
|
18
|
+
function mountedSet() {
|
|
19
|
+
const registry = globalThis;
|
|
20
|
+
return (registry[MOUNTED] ??= new Set());
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Wrap a cordis plugin apply so the package runs at most once per process.
|
|
24
|
+
* The first mount registers normally and unmarks when its fiber disposes;
|
|
25
|
+
* any later mount of the same package name is a no-op.
|
|
26
|
+
* @param packageName - npm package identity shared by every install source.
|
|
27
|
+
* @param fn - the original plugin apply.
|
|
28
|
+
* @returns an apply of the same shape.
|
|
29
|
+
*/
|
|
30
|
+
export function mountOnce(packageName, fn) {
|
|
31
|
+
return ((...args) => {
|
|
32
|
+
const mounted = mountedSet();
|
|
33
|
+
if (mounted.has(packageName))
|
|
34
|
+
return;
|
|
35
|
+
mounted.add(packageName);
|
|
36
|
+
const ctx = args[0];
|
|
37
|
+
ctx?.effect?.(() => () => {
|
|
38
|
+
mounted.delete(packageName);
|
|
39
|
+
});
|
|
40
|
+
return fn(...args);
|
|
41
|
+
});
|
|
42
|
+
}
|
package/lib/types/persist.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
|
-
import { dshHome } from
|
|
10
|
-
import { AFFINITY_MAX, emptyAffinity } from
|
|
11
|
-
import { defaultTreatConfig, emptyTreatLedger } from
|
|
9
|
+
import { dshHome } from './dsh-home.js';
|
|
10
|
+
import { AFFINITY_MAX, emptyAffinity } from './affinity.js';
|
|
11
|
+
import { defaultTreatConfig, emptyTreatLedger } from './treats.js';
|
|
12
12
|
export const defaultDisplayConfig = {
|
|
13
13
|
visible: true,
|
|
14
14
|
size: 160,
|
package/lib/types/registry.d.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* @module @linxin666/dsh-pet/registry
|
|
18
18
|
*/
|
|
19
19
|
import type { PetAnimation } from './state.ts';
|
|
20
|
+
import { type PetRemarks, type PetRemarksManifest } from './remarks.ts';
|
|
20
21
|
/** Fixed row order of the 9-state animation contract. */
|
|
21
22
|
export declare const PET_ROW_ORDER: readonly PetAnimation[];
|
|
22
23
|
/** Atlas cell size in px. */
|
|
@@ -80,6 +81,13 @@ export interface PetManifest {
|
|
|
80
81
|
frames?: number[];
|
|
81
82
|
/** Optional per-track rhythm overrides; omitted tracks use the defaults. */
|
|
82
83
|
tracks?: Partial<Record<PetAnimation, PetTrackOverride>>;
|
|
84
|
+
/**
|
|
85
|
+
* Optional witty-remark overrides the pet speaks on interactions
|
|
86
|
+
* (community contributions use this to give their pet its own voice).
|
|
87
|
+
* Each slot accepts one line or a pool of lines; a slot replaces the
|
|
88
|
+
* built-in default pool for that slot only.
|
|
89
|
+
*/
|
|
90
|
+
remarks?: PetRemarksManifest;
|
|
83
91
|
}
|
|
84
92
|
/** Per-track rhythm overrides a manifest may carry. */
|
|
85
93
|
export interface PetTrackOverride {
|
|
@@ -114,6 +122,8 @@ export interface PetEntry extends PetDefinition {
|
|
|
114
122
|
dir: string;
|
|
115
123
|
/** Atlas path relative to 'dir' (declared by the manifest). */
|
|
116
124
|
spritesheetPath: string;
|
|
125
|
+
/** Normalized per-pet remark pools (manifest 'remarks'), when declared. */
|
|
126
|
+
remarks?: PetRemarks;
|
|
117
127
|
}
|
|
118
128
|
/** Registry load result: resolved entries plus load warnings. */
|
|
119
129
|
export interface PetRegistry {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry.ts"],"names":[],"mappings":"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"}
|
package/lib/types/registry.js
CHANGED
|
@@ -20,6 +20,7 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
|
20
20
|
import { homedir } from 'node:os';
|
|
21
21
|
import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import { normalizePetRemarks } from './remarks.js';
|
|
23
24
|
/** Fixed row order of the 9-state animation contract. */
|
|
24
25
|
export const PET_ROW_ORDER = [
|
|
25
26
|
'idle',
|
|
@@ -129,6 +130,7 @@ export function resolvePetManifest(raw, dir, options = {}) {
|
|
|
129
130
|
const value = Array.isArray(source.frames) ? source.frames[index] : undefined;
|
|
130
131
|
return finiteInt(value, fallback, columns);
|
|
131
132
|
});
|
|
133
|
+
const remarks = normalizePetRemarks(source.remarks, message => warn('manifest ' + id + ': ' + message));
|
|
132
134
|
const trackOverrides = (typeof source.tracks === 'object' && source.tracks !== null ? source.tracks : {});
|
|
133
135
|
const tracks = {};
|
|
134
136
|
for (const [row, animation] of PET_ROW_ORDER.entries()) {
|
|
@@ -168,6 +170,7 @@ export function resolvePetManifest(raw, dir, options = {}) {
|
|
|
168
170
|
manifestUrl: assetUrl(assetPrefix, id, 'pet.json'),
|
|
169
171
|
dir,
|
|
170
172
|
spritesheetPath: spritesheetPath.join('/'),
|
|
173
|
+
...(remarks === undefined ? {} : { remarks }),
|
|
171
174
|
};
|
|
172
175
|
}
|
|
173
176
|
/** Scan one directory of pet folders; entries come back in name order. */
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pet remark (reaction copy) library — pure. Reaction bubbles the pet speaks
|
|
3
|
+
* on interaction events come from two layers:
|
|
4
|
+
* - the built-in default library below (a generous pool per event kind);
|
|
5
|
+
* - per-pet custom lines declared in a manifest's 'remarks' block, which
|
|
6
|
+
* community pet contributions (PRs) use to give their pet its own voice.
|
|
7
|
+
* A custom slot replaces the built-in pool for that slot only; the other
|
|
8
|
+
* slots keep the built-in lines. Picks cycle round-robin within a pool so
|
|
9
|
+
* repeated interactions stay varied while tests stay deterministic (no
|
|
10
|
+
* randomness, no clock).
|
|
11
|
+
* @module @linxin666/dsh-pet/remarks
|
|
12
|
+
*/
|
|
13
|
+
/** Interaction events a reaction line can accompany. */
|
|
14
|
+
export type RemarkKind = 'pet' | 'petCooldown' | 'feed' | 'feedCooldown' | 'noTreats';
|
|
15
|
+
/** Every remark slot, in a stable order. */
|
|
16
|
+
export declare const REMARK_KINDS: readonly RemarkKind[];
|
|
17
|
+
/** Per-pet remark overrides (normalized shape; each slot is a line pool). */
|
|
18
|
+
export type PetRemarks = Partial<Record<RemarkKind, string[]>>;
|
|
19
|
+
/** Raw manifest shape: each slot accepts one line or a pool of lines. */
|
|
20
|
+
export type PetRemarksManifest = Partial<Record<RemarkKind, string | string[]>>;
|
|
21
|
+
/** Longest accepted reaction line (characters, trimmed before slicing). */
|
|
22
|
+
export declare const REMARK_LINE_MAX = 120;
|
|
23
|
+
/** Longest accepted pool per slot. */
|
|
24
|
+
export declare const REMARK_LINES_MAX = 64;
|
|
25
|
+
/**
|
|
26
|
+
* Built-in default remark library. Every pool is plain zh copy in the
|
|
27
|
+
* whale-girl voice; the first line of each pool is the legacy reaction the
|
|
28
|
+
* plugin has always spoken, so existing installs and tests keep their
|
|
29
|
+
* wording while the pool adds variety. No emoji characters anywhere (the
|
|
30
|
+
* repo bans them); ~ is the whale-girl's signature.
|
|
31
|
+
*/
|
|
32
|
+
export declare const BUILTIN_REMARKS: Readonly<Record<RemarkKind, readonly string[]>>;
|
|
33
|
+
/** The legacy first line of one kind (direct callers' fallback copy). */
|
|
34
|
+
export declare function builtinRemark(kind: RemarkKind): string;
|
|
35
|
+
/**
|
|
36
|
+
* Normalize a manifest 'remarks' block into per-kind line pools. Unknown
|
|
37
|
+
* slots and non-string entries are skipped with a warning; empty pools are
|
|
38
|
+
* dropped so the built-in library takes the slot. Returns undefined when no
|
|
39
|
+
* usable slot remains.
|
|
40
|
+
*/
|
|
41
|
+
export declare function normalizePetRemarks(raw: unknown, onWarning?: (message: string) => void): PetRemarks | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Round-robin reaction picker over the effective pools (per-pet custom lines
|
|
44
|
+
* override the built-in pool per slot). Counters are per slot, so each slot
|
|
45
|
+
* cycles its own list independently and picks stay deterministic for tests.
|
|
46
|
+
*/
|
|
47
|
+
export declare class RemarkPicker {
|
|
48
|
+
private readonly counters;
|
|
49
|
+
private readonly pools;
|
|
50
|
+
constructor(overrides?: PetRemarks);
|
|
51
|
+
/** The effective pool for one slot (custom override or built-in). */
|
|
52
|
+
pool(kind: RemarkKind): readonly string[];
|
|
53
|
+
/** The next line for one slot (round-robin within its pool). */
|
|
54
|
+
pick(kind: RemarkKind): string;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=remarks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remarks.d.ts","sourceRoot":"","sources":["../../src/remarks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,aAAa,GAAG,MAAM,GAAG,cAAc,GAAG,UAAU,CAAA;AAErF,4CAA4C;AAC5C,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAM7C,CAAA;AAED,6EAA6E;AAC7E,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;AAE9D,yEAAyE;AACzE,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC,CAAA;AAE/E,2EAA2E;AAC3E,eAAO,MAAM,eAAe,MAAM,CAAA;AAClC,sCAAsC;AACtC,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAElC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC,CA6D3E,CAAA;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,OAAO,EACZ,SAAS,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAe,GAC9C,UAAU,GAAG,SAAS,CAyBxB;AAED;;;;GAIG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgC;IACzD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAE7D,YAAY,SAAS,CAAC,EAAE,UAAU,EAMjC;IAED,qEAAqE;IACrE,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,MAAM,EAAE,CAExC;IAED,gEAAgE;IAChE,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAK7B;CACF"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pet remark (reaction copy) library — pure. Reaction bubbles the pet speaks
|
|
3
|
+
* on interaction events come from two layers:
|
|
4
|
+
* - the built-in default library below (a generous pool per event kind);
|
|
5
|
+
* - per-pet custom lines declared in a manifest's 'remarks' block, which
|
|
6
|
+
* community pet contributions (PRs) use to give their pet its own voice.
|
|
7
|
+
* A custom slot replaces the built-in pool for that slot only; the other
|
|
8
|
+
* slots keep the built-in lines. Picks cycle round-robin within a pool so
|
|
9
|
+
* repeated interactions stay varied while tests stay deterministic (no
|
|
10
|
+
* randomness, no clock).
|
|
11
|
+
* @module @linxin666/dsh-pet/remarks
|
|
12
|
+
*/
|
|
13
|
+
/** Every remark slot, in a stable order. */
|
|
14
|
+
export const REMARK_KINDS = [
|
|
15
|
+
'pet',
|
|
16
|
+
'petCooldown',
|
|
17
|
+
'feed',
|
|
18
|
+
'feedCooldown',
|
|
19
|
+
'noTreats',
|
|
20
|
+
];
|
|
21
|
+
/** Longest accepted reaction line (characters, trimmed before slicing). */
|
|
22
|
+
export const REMARK_LINE_MAX = 120;
|
|
23
|
+
/** Longest accepted pool per slot. */
|
|
24
|
+
export const REMARK_LINES_MAX = 64;
|
|
25
|
+
/**
|
|
26
|
+
* Built-in default remark library. Every pool is plain zh copy in the
|
|
27
|
+
* whale-girl voice; the first line of each pool is the legacy reaction the
|
|
28
|
+
* plugin has always spoken, so existing installs and tests keep their
|
|
29
|
+
* wording while the pool adds variety. No emoji characters anywhere (the
|
|
30
|
+
* repo bans them); ~ is the whale-girl's signature.
|
|
31
|
+
*/
|
|
32
|
+
export const BUILTIN_REMARKS = {
|
|
33
|
+
pet: [
|
|
34
|
+
'咕噜咕噜~被摸摸好舒服!',
|
|
35
|
+
'再摸摸这里,痒痒的~',
|
|
36
|
+
'头顶温度刚刚好,安心~',
|
|
37
|
+
'被摸到耳朵啦,扑通扑通!',
|
|
38
|
+
'你的手掌好温暖,舍不得你走~',
|
|
39
|
+
'呼噜呼噜~就靠在这里不走了!',
|
|
40
|
+
'今天的摸头也收货成功!',
|
|
41
|
+
'蹭蹭你的手心,这是回礼~',
|
|
42
|
+
'多摸摸我,亲密度会涨哦!',
|
|
43
|
+
'闭眼享受中,请勿打扰~',
|
|
44
|
+
],
|
|
45
|
+
petCooldown: [
|
|
46
|
+
'摸过头啦,让鲸鱼娘歇口气~',
|
|
47
|
+
'羽毛都快被摸秃啦,缓一缓~',
|
|
48
|
+
'呼……先让我喘口气嘛!',
|
|
49
|
+
'再摸就要睡着了哦~',
|
|
50
|
+
'稍微休息一下,待会儿再摸~',
|
|
51
|
+
'头顶要冒烟啦,停一停!',
|
|
52
|
+
'我知道你喜欢我,但也要节制呀~',
|
|
53
|
+
'歇一歇,摸摸的手感会更好哦~',
|
|
54
|
+
'咕……等我回个蓝~',
|
|
55
|
+
'让我先消化一下刚才的爱!',
|
|
56
|
+
],
|
|
57
|
+
feed: [
|
|
58
|
+
'呜哇!小鱼干好好吃!',
|
|
59
|
+
'咔嚓咔嚓,美味到尾巴打结~',
|
|
60
|
+
'这条小鱼干是刚晒好的,好香!',
|
|
61
|
+
'谢谢你,胃里暖暖的~',
|
|
62
|
+
'囤粮 +1,今天也有好好被爱!',
|
|
63
|
+
'好吃到想转圈圈~',
|
|
64
|
+
'小鱼干最好吃了,再来亿条!',
|
|
65
|
+
'饱餐一顿,马上满血复活~',
|
|
66
|
+
'这个味道,是幸福的味道!',
|
|
67
|
+
'吃完了还不忘舔舔爪子~',
|
|
68
|
+
],
|
|
69
|
+
feedCooldown: [
|
|
70
|
+
'吃饱啦,晚点再喂~',
|
|
71
|
+
'肚子圆滚滚的,装不下啦~',
|
|
72
|
+
'再喂就要变成球啦!',
|
|
73
|
+
'让我慢慢消化这份心意~',
|
|
74
|
+
'小鱼干的香气还没散呢~',
|
|
75
|
+
'呼……满足得动不了了~',
|
|
76
|
+
'先散步一圈再吃下一顿!',
|
|
77
|
+
'肚皮已经鼓鼓的啦~',
|
|
78
|
+
'好吃是好吃,可也得节制呀~',
|
|
79
|
+
'等我饿了会告诉你哦~',
|
|
80
|
+
],
|
|
81
|
+
noTreats: [
|
|
82
|
+
'没有小鱼干了,多陪我工作一会儿吧~',
|
|
83
|
+
'粮仓空空,陪我完成几轮任务就会有小鱼干啦~',
|
|
84
|
+
'小鱼干在路上啦,先一起加油工作!',
|
|
85
|
+
'嘴巴寂寞了……快去完成一轮任务!',
|
|
86
|
+
'陪我多工作一会儿,鱼干自动到账~',
|
|
87
|
+
'现在喂我也只会饿着肚子说谢谢哦~',
|
|
88
|
+
'粮仓见底啦,用几轮任务换一条鱼干吧~',
|
|
89
|
+
'饿着肚子等你完成下一轮任务~',
|
|
90
|
+
'小鱼干藏在你的工作里,去找找看!',
|
|
91
|
+
'先工作后干饭,我们的约定哦~',
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
/** The legacy first line of one kind (direct callers' fallback copy). */
|
|
95
|
+
export function builtinRemark(kind) {
|
|
96
|
+
return BUILTIN_REMARKS[kind][0];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Normalize a manifest 'remarks' block into per-kind line pools. Unknown
|
|
100
|
+
* slots and non-string entries are skipped with a warning; empty pools are
|
|
101
|
+
* dropped so the built-in library takes the slot. Returns undefined when no
|
|
102
|
+
* usable slot remains.
|
|
103
|
+
*/
|
|
104
|
+
export function normalizePetRemarks(raw, onWarning = () => { }) {
|
|
105
|
+
if (raw === undefined)
|
|
106
|
+
return undefined;
|
|
107
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
108
|
+
onWarning('remarks must be an object with pet/petCooldown/feed/feedCooldown/noTreats slots');
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const remarks = {};
|
|
112
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
113
|
+
if (!REMARK_KINDS.includes(key)) {
|
|
114
|
+
onWarning('unknown remarks slot ' + JSON.stringify(key));
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const lines = (Array.isArray(value) ? value : [value])
|
|
118
|
+
.filter((line) => typeof line === 'string')
|
|
119
|
+
.map(line => line.trim())
|
|
120
|
+
.filter(line => line !== '')
|
|
121
|
+
.slice(0, REMARK_LINES_MAX)
|
|
122
|
+
.map(line => line.slice(0, REMARK_LINE_MAX));
|
|
123
|
+
if (lines.length === 0) {
|
|
124
|
+
onWarning('remarks slot ' + key + ' carries no usable lines');
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
remarks[key] = lines;
|
|
128
|
+
}
|
|
129
|
+
return Object.keys(remarks).length === 0 ? undefined : remarks;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Round-robin reaction picker over the effective pools (per-pet custom lines
|
|
133
|
+
* override the built-in pool per slot). Counters are per slot, so each slot
|
|
134
|
+
* cycles its own list independently and picks stay deterministic for tests.
|
|
135
|
+
*/
|
|
136
|
+
export class RemarkPicker {
|
|
137
|
+
counters = new Map();
|
|
138
|
+
pools;
|
|
139
|
+
constructor(overrides) {
|
|
140
|
+
this.pools = {};
|
|
141
|
+
for (const kind of REMARK_KINDS) {
|
|
142
|
+
const custom = overrides?.[kind];
|
|
143
|
+
this.pools[kind] = custom !== undefined && custom.length > 0 ? custom : BUILTIN_REMARKS[kind];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** The effective pool for one slot (custom override or built-in). */
|
|
147
|
+
pool(kind) {
|
|
148
|
+
return this.pools[kind];
|
|
149
|
+
}
|
|
150
|
+
/** The next line for one slot (round-robin within its pool). */
|
|
151
|
+
pick(kind) {
|
|
152
|
+
const pool = this.pools[kind];
|
|
153
|
+
const index = (this.counters.get(kind) ?? 0) % pool.length;
|
|
154
|
+
this.counters.set(kind, index + 1);
|
|
155
|
+
return pool[index];
|
|
156
|
+
}
|
|
157
|
+
}
|
package/lib/types/routes.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { existsSync } from 'node:fs';
|
|
12
12
|
import { readFile } from 'node:fs/promises';
|
|
13
13
|
import { join } from 'node:path';
|
|
14
|
-
import { petEntryView } from
|
|
14
|
+
import { petEntryView } from './registry.js';
|
|
15
15
|
/** Browser-facing base path of the pet API. */
|
|
16
16
|
export const PET_API_PREFIX = '/api/pet';
|
|
17
17
|
/** Browser-facing base path of the pet asset routes ('/pet/<id>/...'). */
|
|
@@ -273,4 +273,4 @@ export function makePetRoutes(deps) {
|
|
|
273
273
|
return [...apiRoutes, assetRoute];
|
|
274
274
|
}
|
|
275
275
|
// Re-exported for the package surface (the registry owns the definition now).
|
|
276
|
-
export { petPackageRoot } from
|
|
276
|
+
export { petPackageRoot } from './registry.js';
|