@linxin666/dsh-pet 0.1.3 → 0.1.7
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/lib/client.js +63 -53
- package/lib/client.js.map +1 -1
- package/lib/types/client/PetDockEntry.d.ts +5 -6
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +4 -5
- package/lib/types/client/WhalePet.d.ts.map +1 -1
- package/lib/types/client/WhalePet.js +15 -3
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +7 -6
- package/lib/types/index.d.ts +14 -13
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/PetDockEntry.tsx +5 -6
- package/src/client/WhalePet.tsx +16 -3
- package/src/client/index.ts +7 -6
- package/src/client/pet.module.css +1 -1
- package/lib/state-7ChK99mH.js +0 -217
- package/lib/state-CKh9f_dF.js +0 -196
- package/lib/state-DMgOYVFT.js +0 -198
- package/lib/types/client/DockPetDockEntry.d.ts +0 -23
- package/lib/types/client/DockPetDockEntry.d.ts.map +0 -1
- package/lib/types/client/DockPetDockEntry.js +0 -10
- package/lib/types/client/slots-augment.d.ts +0 -19
- package/lib/types/client/slots-augment.d.ts.map +0 -1
- package/lib/types/client/slots-augment.js +0 -1
- package/src/client/slots-augment.ts +0 -27
package/lib/types/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* @module @linxin666/dsh-pet
|
|
8
8
|
*/
|
|
9
9
|
import { Context } from '@deepseek-ai/cordis';
|
|
10
|
+
import z from 'schemastery';
|
|
10
11
|
import { type PetConfig } from './service.ts';
|
|
11
12
|
export { PetService } from './service.ts';
|
|
12
13
|
export type { PetConfig, PetInteractResult, PetStateView, } from './service.ts';
|
|
@@ -24,20 +25,20 @@ export declare const name = "pet";
|
|
|
24
25
|
/** Services required before the pet can mount its surfaces. */
|
|
25
26
|
export declare const inject: string[];
|
|
26
27
|
/** Settings section schema: the display fields and name the web settings surface edits. */
|
|
27
|
-
export declare const PET_SETTINGS_SCHEMA:
|
|
28
|
-
visible:
|
|
29
|
-
size:
|
|
30
|
-
right:
|
|
31
|
-
bottom:
|
|
32
|
-
name:
|
|
33
|
-
enabled:
|
|
28
|
+
export declare const PET_SETTINGS_SCHEMA: z<Schemastery.ObjectS<{
|
|
29
|
+
visible: z<boolean, boolean>;
|
|
30
|
+
size: z<number, number>;
|
|
31
|
+
right: z<number, number>;
|
|
32
|
+
bottom: z<number, number>;
|
|
33
|
+
name: z<string, string>;
|
|
34
|
+
enabled: z<boolean, boolean>;
|
|
34
35
|
}>, Schemastery.ObjectT<{
|
|
35
|
-
visible:
|
|
36
|
-
size:
|
|
37
|
-
right:
|
|
38
|
-
bottom:
|
|
39
|
-
name:
|
|
40
|
-
enabled:
|
|
36
|
+
visible: z<boolean, boolean>;
|
|
37
|
+
size: z<number, number>;
|
|
38
|
+
right: z<number, number>;
|
|
39
|
+
bottom: z<number, number>;
|
|
40
|
+
name: z<string, string>;
|
|
41
|
+
enabled: z<boolean, boolean>;
|
|
41
42
|
}>>;
|
|
42
43
|
/** Register the pet service and its API + asset routes on the context. */
|
|
43
44
|
export declare function apply(ctx: Context, config?: PetConfig): void;
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAG7C,OAAO,CAAC,MAAM,aAAa,CAAA;AAC3B,OAAO,EAAsC,KAAK,SAAS,EAA2B,MAAM,cAAc,CAAA;AAU1G,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,YAAY,GACb,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,MAAM,GACP,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,cAAc,GACf,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,KAAK,GACN,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,aAAa,EACb,YAAY,EACZ,cAAc,EACd,aAAa,EACb,gBAAgB,GACjB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,aAAa,CAAA;AACpB,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC5E,OAAO,EACL,oBAAoB,EACpB,YAAY,EACZ,cAAc,EACd,UAAU,EACV,cAAc,GACf,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEhE,OAAO,EACL,aAAa,EACb,cAAc,EACd,cAAc,EACd,gBAAgB,GACjB,MAAM,aAAa,CAAA;AAEpB,sEAAsE;AACtE,eAAO,MAAM,IAAI,QAAQ,CAAA;AAEzB,+DAA+D;AAC/D,eAAO,MAAM,MAAM,UAAgB,CAAA;AAEnC,2FAA2F;AAC3F,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;GAO9B,CAAA;AAEF,0EAA0E;AAC1E,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,SAAc,GAAG,IAAI,CAoDhE"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-pet",
|
|
3
3
|
"description": "鲸鱼娘宠物插件 for the dsh web GUI: a soft healing whale-girl companion that reacts to model activity (idle/waiting/thinking/tool/done), with petting/feeding interactions and an affinity score",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.7",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Dock anchor inside `conversation.input.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* floating WhalePet (portal); while hidden it renders the summon button.
|
|
2
|
+
* Dock anchor inside the official SDK's `conversation.input.dock` seat
|
|
3
|
+
* (the ambient readout band above the composer card, listed so several
|
|
4
|
+
* additions can stack). While visible it mounts the floating WhalePet
|
|
5
|
+
* (portal); while hidden it renders the summon button.
|
|
7
6
|
* @module @linxin666/dsh-pet/client/PetDockEntry
|
|
8
7
|
*/
|
|
9
8
|
|
|
@@ -40,7 +39,7 @@ export interface PetInjected {
|
|
|
40
39
|
|
|
41
40
|
/** Composed props of the dock entry (runtime + locale + injected). */
|
|
42
41
|
export type PetDockEntryProps =
|
|
43
|
-
PropsRuntime<'conversation.input.
|
|
42
|
+
PropsRuntime<'conversation.input.dock'>
|
|
44
43
|
& PetInjected
|
|
45
44
|
& PropsLocale<typeof NS>
|
|
46
45
|
|
package/src/client/WhalePet.tsx
CHANGED
|
@@ -71,6 +71,7 @@ export function WhalePet(props: WhalePetProps): ReactPortal {
|
|
|
71
71
|
const [nameDraft, setNameDraft] = useState('')
|
|
72
72
|
const [dragPos, setDragPos] = useState<{ right: number; bottom: number } | null>(null)
|
|
73
73
|
const dragRef = useRef<{ startX: number; startY: number; right: number; bottom: number } | null>(null)
|
|
74
|
+
const hideTimerRef = useRef<number | null>(null)
|
|
74
75
|
const frameRef = useRef<{ track: PetAnimation | null; index: number; elapsed: number }>({
|
|
75
76
|
track: null,
|
|
76
77
|
index: 0,
|
|
@@ -193,6 +194,13 @@ export function WhalePet(props: WhalePetProps): ReactPortal {
|
|
|
193
194
|
// `draggedRef` records whether the pointer actually moved, so the browser's
|
|
194
195
|
// trailing click (fired after pointerup) does not pet the whale.
|
|
195
196
|
const draggedRef = useRef(false)
|
|
197
|
+
const clearHideTimer = (): void => {
|
|
198
|
+
if (hideTimerRef.current !== null) {
|
|
199
|
+
window.clearTimeout(hideTimerRef.current)
|
|
200
|
+
hideTimerRef.current = null
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
196
204
|
const onPointerDown = (e: ReactPointerEvent<HTMLDivElement>): void => {
|
|
197
205
|
e.preventDefault()
|
|
198
206
|
;(e.target as HTMLElement).setPointerCapture?.(e.pointerId)
|
|
@@ -226,15 +234,20 @@ export function WhalePet(props: WhalePetProps): ReactPortal {
|
|
|
226
234
|
ref={floatRef}
|
|
227
235
|
className={styles.float}
|
|
228
236
|
style={{ right: pos.right, bottom: pos.bottom, zIndex: 2147483000 }}
|
|
229
|
-
onPointerEnter={() =>
|
|
237
|
+
onPointerEnter={() => {
|
|
238
|
+
clearHideTimer()
|
|
239
|
+
setHovered(true)
|
|
240
|
+
}}
|
|
230
241
|
onPointerLeave={(e) => {
|
|
231
242
|
// The panel and bubble render OUTSIDE the container's box (absolute,
|
|
232
243
|
// above the sprite), so moving onto them fires pointerleave on the
|
|
233
244
|
// container. Treat a target still inside the container's DOM (the
|
|
234
|
-
// overflowed panel) as "still hovering"
|
|
245
|
+
// overflowed panel) as "still hovering"; otherwise give the pointer a
|
|
246
|
+
// short grace period to reach the panel across the gap above it.
|
|
235
247
|
const next = e.relatedTarget
|
|
236
248
|
if (next instanceof Node && floatRef.current?.contains(next)) return
|
|
237
|
-
|
|
249
|
+
clearHideTimer()
|
|
250
|
+
hideTimerRef.current = window.setTimeout(() => setHovered(false), 200)
|
|
238
251
|
}}
|
|
239
252
|
>
|
|
240
253
|
<div
|
package/src/client/index.ts
CHANGED
|
@@ -233,13 +233,14 @@ export function apply(ctx: ClientContext): void {
|
|
|
233
233
|
},
|
|
234
234
|
})
|
|
235
235
|
|
|
236
|
-
// The
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
|
|
236
|
+
// The legacy selector-context hole (conversation.input.selector.context)
|
|
237
|
+
// is no longer declared by newer shells (rc.6 dropped it), so the pet
|
|
238
|
+
// mounts on the composer dock band instead. The dock is session-scoped,
|
|
239
|
+
// so the pet appears once a session is active (accepted downgrade, see
|
|
240
|
+
// PR #17).
|
|
241
|
+
const disposeDock = ctx.slots.inject('conversation.input.dock', () =>
|
|
241
242
|
ctx.slots.register({
|
|
242
|
-
name: 'conversation.input.
|
|
243
|
+
name: 'conversation.input.dock',
|
|
243
244
|
id: 'pet',
|
|
244
245
|
order: 110,
|
|
245
246
|
inject: injected,
|
package/lib/state-7ChK99mH.js
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
//#region lib/types/affinity.js
|
|
2
|
-
/**
|
|
3
|
-
* Affinity score — pure, clock-injected. The pet grows closer the more you
|
|
4
|
-
* work together and care for it: every completed turn earns a small reward,
|
|
5
|
-
* petting earns a tiny one (cooldown-gated), feeding earns the most.
|
|
6
|
-
* Persistence lives in the service; this module only computes transitions.
|
|
7
|
-
* @module @deepseek-ai/dsh-pet/affinity
|
|
8
|
-
*/
|
|
9
|
-
const AFFINITY_MAX = 100;
|
|
10
|
-
/** Affinity ranks by points; the pet visibly grows with its rank. */
|
|
11
|
-
const AFFINITY_RANKS = [
|
|
12
|
-
{
|
|
13
|
-
min: 0,
|
|
14
|
-
name: "幼鲸",
|
|
15
|
-
emoji: "🐣"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
min: 25,
|
|
19
|
-
name: "伙伴",
|
|
20
|
-
emoji: "🐬"
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
min: 50,
|
|
24
|
-
name: "挚友",
|
|
25
|
-
emoji: "🐳"
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
min: 80,
|
|
29
|
-
name: "深海羁绊",
|
|
30
|
-
emoji: "💙"
|
|
31
|
-
}
|
|
32
|
-
];
|
|
33
|
-
const defaultAffinityConfig = {
|
|
34
|
-
turnReward: 1,
|
|
35
|
-
petReward: 1,
|
|
36
|
-
petCooldownMs: 1e4,
|
|
37
|
-
feedReward: 5,
|
|
38
|
-
feedCooldownMs: 3e4
|
|
39
|
-
};
|
|
40
|
-
function emptyAffinity() {
|
|
41
|
-
return {
|
|
42
|
-
points: 0,
|
|
43
|
-
lastPetAt: 0,
|
|
44
|
-
lastFeedAt: 0,
|
|
45
|
-
pets: 0,
|
|
46
|
-
feeds: 0,
|
|
47
|
-
turns: 0
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
/** Rank for a point total. */
|
|
51
|
-
function rankOf(points) {
|
|
52
|
-
let rank = AFFINITY_RANKS[0];
|
|
53
|
-
for (const candidate of AFFINITY_RANKS) if (points >= candidate.min) rank = candidate;
|
|
54
|
-
return rank;
|
|
55
|
-
}
|
|
56
|
-
function clamp(points) {
|
|
57
|
-
return Math.min(100, Math.max(0, points));
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Apply one interaction to a copy of the state (immutable style: returns a
|
|
61
|
-
* new object; the caller replaces the persisted state). Cooldowns only
|
|
62
|
-
* apply once the pet has been interacted with at least once (last*At === 0
|
|
63
|
-
* means "never", so the first pet/feed always lands).
|
|
64
|
-
*/
|
|
65
|
-
function applyInteraction(state, kind, nowMs, config = defaultAffinityConfig) {
|
|
66
|
-
const next = { ...state };
|
|
67
|
-
if (kind === "pet") {
|
|
68
|
-
if (state.lastPetAt !== 0 && nowMs - state.lastPetAt < config.petCooldownMs) return {
|
|
69
|
-
affinity: state,
|
|
70
|
-
delta: 0,
|
|
71
|
-
reaction: "摸过头啦,让鲸鱼娘歇口气~",
|
|
72
|
-
accepted: false
|
|
73
|
-
};
|
|
74
|
-
next.lastPetAt = nowMs;
|
|
75
|
-
next.pets += 1;
|
|
76
|
-
next.points = clamp(state.points + config.petReward);
|
|
77
|
-
return {
|
|
78
|
-
affinity: next,
|
|
79
|
-
delta: config.petReward,
|
|
80
|
-
reaction: "咕噜咕噜~被摸摸好舒服!",
|
|
81
|
-
accepted: true
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
if (kind === "feed") {
|
|
85
|
-
if (state.lastFeedAt !== 0 && nowMs - state.lastFeedAt < config.feedCooldownMs) return {
|
|
86
|
-
affinity: state,
|
|
87
|
-
delta: 0,
|
|
88
|
-
reaction: "吃饱啦,晚点再喂~",
|
|
89
|
-
accepted: false
|
|
90
|
-
};
|
|
91
|
-
next.lastFeedAt = nowMs;
|
|
92
|
-
next.feeds += 1;
|
|
93
|
-
next.points = clamp(state.points + config.feedReward);
|
|
94
|
-
return {
|
|
95
|
-
affinity: next,
|
|
96
|
-
delta: config.feedReward,
|
|
97
|
-
reaction: "呜哇!小鱼干好好吃!",
|
|
98
|
-
accepted: true
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
affinity: state,
|
|
103
|
-
delta: 0,
|
|
104
|
-
reaction: "",
|
|
105
|
-
accepted: false
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
/** Reward one completed turn (called by the host on `done`). */
|
|
109
|
-
function applyTurnReward(state, config = defaultAffinityConfig) {
|
|
110
|
-
const next = { ...state };
|
|
111
|
-
next.turns += 1;
|
|
112
|
-
next.points = clamp(state.points + config.turnReward);
|
|
113
|
-
return next;
|
|
114
|
-
}
|
|
115
|
-
//#endregion
|
|
116
|
-
//#region lib/types/state.js
|
|
117
|
-
/**
|
|
118
|
-
* Pet state machine — pure, clock-injected. Maps the DSH `activity/status`
|
|
119
|
-
* phase vocabulary (session events) onto the 9-state Codex pet
|
|
120
|
-
* animation contract, plus the session lifecycle transitions the web UI
|
|
121
|
-
* exposes (turn end celebration, no-session idle).
|
|
122
|
-
*
|
|
123
|
-
* The machine is deliberately dumb: it holds the last input phase, the
|
|
124
|
-
* animation decision, and a one-shot "celebration" window after `done` so the
|
|
125
|
-
* pet visibly jumps before settling back to idle. Everything here is a pure
|
|
126
|
-
* function of (input, nowMs); persistence and RPC live in the service.
|
|
127
|
-
* @module @deepseek-ai/dsh-pet/state
|
|
128
|
-
*/
|
|
129
|
-
const defaultPetStateConfig = { celebrateMs: 2400 };
|
|
130
|
-
/**
|
|
131
|
-
* Map one activity phase onto the animation contract.
|
|
132
|
-
* - thinking / tool → `running` (focused work), with `running-right` as the
|
|
133
|
-
* side-alternating variant the client may use for tool activity.
|
|
134
|
-
* - waiting → `waiting` (expectant pose, needs user input).
|
|
135
|
-
* - done → `jumping` (celebration), then back to `idle` after the window.
|
|
136
|
-
* - idle → `idle` (calm breathing loop).
|
|
137
|
-
* `failed` has no DSH phase source yet; the machine keeps the mapping table
|
|
138
|
-
* so a future error event can light it up.
|
|
139
|
-
*/
|
|
140
|
-
function animationForPhase(phase) {
|
|
141
|
-
switch (phase) {
|
|
142
|
-
case "thinking": return "running";
|
|
143
|
-
case "tool": return "running-right";
|
|
144
|
-
case "waiting": return "waiting";
|
|
145
|
-
case "done": return "jumping";
|
|
146
|
-
case "idle": return "idle";
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
/** The spritesheet row index for one animation track. */
|
|
150
|
-
function rowOf(animation) {
|
|
151
|
-
return {
|
|
152
|
-
"idle": 0,
|
|
153
|
-
"running-right": 1,
|
|
154
|
-
"running-left": 2,
|
|
155
|
-
"waving": 3,
|
|
156
|
-
"jumping": 4,
|
|
157
|
-
"failed": 5,
|
|
158
|
-
"waiting": 6,
|
|
159
|
-
"running": 7,
|
|
160
|
-
"review": 8
|
|
161
|
-
}[animation];
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* PetStateMachine — one instance per host process. Holds only the latest
|
|
165
|
-
* input snapshot and the celebration timing; no storage, no side effects.
|
|
166
|
-
*/
|
|
167
|
-
var PetStateMachine = class {
|
|
168
|
-
config;
|
|
169
|
-
now;
|
|
170
|
-
phase = "idle";
|
|
171
|
-
line;
|
|
172
|
-
phrase;
|
|
173
|
-
sessionActive = false;
|
|
174
|
-
doneAt;
|
|
175
|
-
constructor(config = defaultPetStateConfig, now = Date.now) {
|
|
176
|
-
this.config = config;
|
|
177
|
-
this.now = now;
|
|
178
|
-
}
|
|
179
|
-
/** Consume one `activity/status` session event. */
|
|
180
|
-
onActivityStatus(input) {
|
|
181
|
-
this.phase = input.phase;
|
|
182
|
-
this.line = input.line;
|
|
183
|
-
this.phrase = input.phrase;
|
|
184
|
-
if (input.phase === "done") this.doneAt = this.now();
|
|
185
|
-
}
|
|
186
|
-
/** A session became the active one (or a fresh session started). */
|
|
187
|
-
onSessionActive() {
|
|
188
|
-
this.sessionActive = true;
|
|
189
|
-
}
|
|
190
|
-
/** The active session was disposed (or none left). */
|
|
191
|
-
onSessionDisposed() {
|
|
192
|
-
this.sessionActive = false;
|
|
193
|
-
this.phase = "idle";
|
|
194
|
-
this.line = void 0;
|
|
195
|
-
this.phrase = void 0;
|
|
196
|
-
this.doneAt = void 0;
|
|
197
|
-
}
|
|
198
|
-
/** Render the current animation decision. */
|
|
199
|
-
render() {
|
|
200
|
-
const nowMs = this.now();
|
|
201
|
-
let animation = animationForPhase(this.phase);
|
|
202
|
-
if (this.phase === "done" && this.doneAt !== void 0) {
|
|
203
|
-
if (nowMs - this.doneAt < this.config.celebrateMs) animation = "jumping";
|
|
204
|
-
else animation = "idle";
|
|
205
|
-
}
|
|
206
|
-
const bubble = this.phrase ?? this.line;
|
|
207
|
-
return {
|
|
208
|
-
animation,
|
|
209
|
-
...bubble === void 0 ? {} : { bubble },
|
|
210
|
-
animationStartedAt: nowMs,
|
|
211
|
-
phase: this.phase,
|
|
212
|
-
sessionActive: this.sessionActive
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
//#endregion
|
|
217
|
-
export { AFFINITY_MAX as a, applyTurnReward as c, rankOf as d, rowOf as i, defaultAffinityConfig as l, animationForPhase as n, AFFINITY_RANKS as o, defaultPetStateConfig as r, applyInteraction as s, PetStateMachine as t, emptyAffinity as u };
|
package/lib/state-CKh9f_dF.js
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
//#region src/affinity.ts
|
|
2
|
-
const AFFINITY_MAX = 100;
|
|
3
|
-
/** Affinity ranks by points; the pet visibly grows with its rank. */
|
|
4
|
-
const AFFINITY_RANKS = [
|
|
5
|
-
{
|
|
6
|
-
min: 0,
|
|
7
|
-
name: "幼鲸",
|
|
8
|
-
emoji: "🐣"
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
min: 25,
|
|
12
|
-
name: "伙伴",
|
|
13
|
-
emoji: "🐬"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
min: 50,
|
|
17
|
-
name: "挚友",
|
|
18
|
-
emoji: "🐳"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
min: 80,
|
|
22
|
-
name: "深海羁绊",
|
|
23
|
-
emoji: "💙"
|
|
24
|
-
}
|
|
25
|
-
];
|
|
26
|
-
const defaultAffinityConfig = {
|
|
27
|
-
turnReward: 1,
|
|
28
|
-
petReward: 1,
|
|
29
|
-
petCooldownMs: 1e4,
|
|
30
|
-
feedReward: 5,
|
|
31
|
-
feedCooldownMs: 3e4
|
|
32
|
-
};
|
|
33
|
-
function emptyAffinity() {
|
|
34
|
-
return {
|
|
35
|
-
points: 0,
|
|
36
|
-
lastPetAt: 0,
|
|
37
|
-
lastFeedAt: 0,
|
|
38
|
-
pets: 0,
|
|
39
|
-
feeds: 0,
|
|
40
|
-
turns: 0
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
/** Rank for a point total. */
|
|
44
|
-
function rankOf(points) {
|
|
45
|
-
let rank = AFFINITY_RANKS[0];
|
|
46
|
-
for (const candidate of AFFINITY_RANKS) if (points >= candidate.min) rank = candidate;
|
|
47
|
-
return rank;
|
|
48
|
-
}
|
|
49
|
-
function clamp(points) {
|
|
50
|
-
return Math.min(100, Math.max(0, points));
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Apply one interaction to a copy of the state (immutable style: returns a
|
|
54
|
-
* new object; the caller replaces the persisted state). Cooldowns only
|
|
55
|
-
* apply once the pet has been interacted with at least once (last*At === 0
|
|
56
|
-
* means "never", so the first pet/feed always lands).
|
|
57
|
-
*/
|
|
58
|
-
function applyInteraction(state, kind, nowMs, config = defaultAffinityConfig) {
|
|
59
|
-
const next = { ...state };
|
|
60
|
-
if (kind === "pet") {
|
|
61
|
-
if (state.lastPetAt !== 0 && nowMs - state.lastPetAt < config.petCooldownMs) return {
|
|
62
|
-
affinity: state,
|
|
63
|
-
delta: 0,
|
|
64
|
-
reaction: "摸过头啦,让鲸鱼娘歇口气~",
|
|
65
|
-
accepted: false
|
|
66
|
-
};
|
|
67
|
-
next.lastPetAt = nowMs;
|
|
68
|
-
next.pets += 1;
|
|
69
|
-
next.points = clamp(state.points + config.petReward);
|
|
70
|
-
return {
|
|
71
|
-
affinity: next,
|
|
72
|
-
delta: config.petReward,
|
|
73
|
-
reaction: "咕噜咕噜~被摸摸好舒服!",
|
|
74
|
-
accepted: true
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
if (kind === "feed") {
|
|
78
|
-
if (state.lastFeedAt !== 0 && nowMs - state.lastFeedAt < config.feedCooldownMs) return {
|
|
79
|
-
affinity: state,
|
|
80
|
-
delta: 0,
|
|
81
|
-
reaction: "吃饱啦,晚点再喂~",
|
|
82
|
-
accepted: false
|
|
83
|
-
};
|
|
84
|
-
next.lastFeedAt = nowMs;
|
|
85
|
-
next.feeds += 1;
|
|
86
|
-
next.points = clamp(state.points + config.feedReward);
|
|
87
|
-
return {
|
|
88
|
-
affinity: next,
|
|
89
|
-
delta: config.feedReward,
|
|
90
|
-
reaction: "呜哇!小鱼干好好吃!",
|
|
91
|
-
accepted: true
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
return {
|
|
95
|
-
affinity: state,
|
|
96
|
-
delta: 0,
|
|
97
|
-
reaction: "",
|
|
98
|
-
accepted: false
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
/** Reward one completed turn (called by the host on `done`). */
|
|
102
|
-
function applyTurnReward(state, config = defaultAffinityConfig) {
|
|
103
|
-
const next = { ...state };
|
|
104
|
-
next.turns += 1;
|
|
105
|
-
next.points = clamp(state.points + config.turnReward);
|
|
106
|
-
return next;
|
|
107
|
-
}
|
|
108
|
-
//#endregion
|
|
109
|
-
//#region src/state.ts
|
|
110
|
-
const defaultPetStateConfig = { celebrateMs: 2400 };
|
|
111
|
-
/**
|
|
112
|
-
* Map one activity phase onto the animation contract.
|
|
113
|
-
* - thinking / tool → `running` (focused work), with `running-right` as the
|
|
114
|
-
* side-alternating variant the client may use for tool activity.
|
|
115
|
-
* - waiting → `waiting` (expectant pose, needs user input).
|
|
116
|
-
* - done → `jumping` (celebration), then back to `idle` after the window.
|
|
117
|
-
* - idle → `idle` (calm breathing loop).
|
|
118
|
-
* `failed` has no DSH phase source yet; the machine keeps the mapping table
|
|
119
|
-
* so a future error event can light it up.
|
|
120
|
-
*/
|
|
121
|
-
function animationForPhase(phase) {
|
|
122
|
-
switch (phase) {
|
|
123
|
-
case "thinking": return "running";
|
|
124
|
-
case "tool": return "running-right";
|
|
125
|
-
case "waiting": return "waiting";
|
|
126
|
-
case "done": return "jumping";
|
|
127
|
-
case "idle": return "idle";
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/** The spritesheet row index for one animation track. */
|
|
131
|
-
function rowOf(animation) {
|
|
132
|
-
return {
|
|
133
|
-
"idle": 0,
|
|
134
|
-
"running-right": 1,
|
|
135
|
-
"running-left": 2,
|
|
136
|
-
"waving": 3,
|
|
137
|
-
"jumping": 4,
|
|
138
|
-
"failed": 5,
|
|
139
|
-
"waiting": 6,
|
|
140
|
-
"running": 7,
|
|
141
|
-
"review": 8
|
|
142
|
-
}[animation];
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* PetStateMachine — one instance per host process. Holds only the latest
|
|
146
|
-
* input snapshot and the celebration timing; no storage, no side effects.
|
|
147
|
-
*/
|
|
148
|
-
var PetStateMachine = class {
|
|
149
|
-
config;
|
|
150
|
-
now;
|
|
151
|
-
phase = "idle";
|
|
152
|
-
line;
|
|
153
|
-
phrase;
|
|
154
|
-
sessionActive = false;
|
|
155
|
-
doneAt;
|
|
156
|
-
constructor(config = defaultPetStateConfig, now = Date.now) {
|
|
157
|
-
this.config = config;
|
|
158
|
-
this.now = now;
|
|
159
|
-
}
|
|
160
|
-
/** Consume one `activity/status` session event. */
|
|
161
|
-
onActivityStatus(input) {
|
|
162
|
-
this.phase = input.phase;
|
|
163
|
-
this.line = input.line;
|
|
164
|
-
this.phrase = input.phrase;
|
|
165
|
-
if (input.phase === "done") this.doneAt = this.now();
|
|
166
|
-
}
|
|
167
|
-
/** A session became the active one (or a fresh session started). */
|
|
168
|
-
onSessionActive() {
|
|
169
|
-
this.sessionActive = true;
|
|
170
|
-
}
|
|
171
|
-
/** The active session was disposed (or none left). */
|
|
172
|
-
onSessionDisposed() {
|
|
173
|
-
this.sessionActive = false;
|
|
174
|
-
this.phase = "idle";
|
|
175
|
-
this.line = void 0;
|
|
176
|
-
this.phrase = void 0;
|
|
177
|
-
this.doneAt = void 0;
|
|
178
|
-
}
|
|
179
|
-
/** Render the current animation decision. */
|
|
180
|
-
render() {
|
|
181
|
-
const nowMs = this.now();
|
|
182
|
-
let animation = animationForPhase(this.phase);
|
|
183
|
-
if (this.phase === "done" && this.doneAt !== void 0) if (nowMs - this.doneAt < this.config.celebrateMs) animation = "jumping";
|
|
184
|
-
else animation = "idle";
|
|
185
|
-
const bubble = this.phrase ?? this.line;
|
|
186
|
-
return {
|
|
187
|
-
animation,
|
|
188
|
-
...bubble === void 0 ? {} : { bubble },
|
|
189
|
-
animationStartedAt: nowMs,
|
|
190
|
-
phase: this.phase,
|
|
191
|
-
sessionActive: this.sessionActive
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
//#endregion
|
|
196
|
-
export { AFFINITY_MAX as a, applyTurnReward as c, rankOf as d, rowOf as i, defaultAffinityConfig as l, animationForPhase as n, AFFINITY_RANKS as o, defaultPetStateConfig as r, applyInteraction as s, PetStateMachine as t, emptyAffinity as u };
|