@linxin666/dsh-pet 0.1.20 → 0.2.1
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 +18 -11
- package/README.zh.md +18 -11
- package/assets/whale/pet.json +9 -0
- package/lib/client.js +394 -58
- package/lib/client.js.map +1 -1
- package/lib/index.js +1007 -43
- package/lib/invariant.js +1 -1
- package/lib/{state-Diiq8vjk.js → state-DrMX22GL.js} +86 -28
- package/lib/types/affinity.d.ts +4 -0
- package/lib/types/affinity.d.ts.map +1 -1
- package/lib/types/affinity.js +32 -6
- package/lib/types/chatter.d.ts +111 -0
- package/lib/types/chatter.d.ts.map +1 -0
- package/lib/types/chatter.js +756 -0
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +1 -1
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +133 -16
- package/lib/types/client/PluginSettingsCard.d.ts +28 -0
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +147 -6
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +1 -0
- package/lib/types/client/locales.d.ts +4 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +4 -0
- package/lib/types/client/sequences.d.ts +10 -0
- package/lib/types/client/sequences.d.ts.map +1 -0
- package/lib/types/client/sequences.js +20 -0
- package/lib/types/event-projection.d.ts +14 -1
- package/lib/types/event-projection.d.ts.map +1 -1
- package/lib/types/event-projection.js +38 -17
- package/lib/types/ledger.d.ts.map +1 -1
- package/lib/types/ledger.js +15 -7
- package/lib/types/loopback.d.ts +25 -0
- package/lib/types/loopback.d.ts.map +1 -0
- package/lib/types/loopback.js +68 -0
- package/lib/types/persist.d.ts.map +1 -1
- package/lib/types/persist.js +3 -1
- package/lib/types/registry.d.ts +7 -1
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +47 -3
- package/lib/types/remarks.d.ts +2 -0
- package/lib/types/remarks.d.ts.map +1 -1
- package/lib/types/remarks.js +35 -0
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +14 -0
- package/lib/types/service.d.ts +6 -0
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +15 -2
- package/lib/types/state.d.ts +7 -4
- package/lib/types/state.d.ts.map +1 -1
- package/lib/types/state.js +20 -20
- package/package.json +9 -9
- package/src/affinity.test.ts +4 -2
- package/src/affinity.ts +37 -6
- package/src/chatter.test.ts +154 -0
- package/src/chatter.ts +787 -0
- package/src/client/PetDockEntry.test.tsx +93 -0
- package/src/client/PetDockEntry.tsx +1 -0
- package/src/client/PetSprite.test.tsx +253 -3
- package/src/client/PetSprite.tsx +192 -37
- package/src/client/PluginSettingsCard.tsx +229 -18
- package/src/client/index.test.tsx +95 -0
- package/src/client/index.ts +1 -0
- package/src/client/locales.ts +4 -0
- package/src/client/pet-css.test.ts +54 -0
- package/src/client/pet.module.css +215 -57
- package/src/client/sequences.test.ts +33 -0
- package/src/client/sequences.ts +33 -0
- package/src/client/settings-card.module.css +90 -1
- package/src/event-projection.ts +49 -16
- package/src/ledger.test.ts +42 -1
- package/src/ledger.ts +15 -7
- package/src/loopback.ts +63 -0
- package/src/persist.test.ts +18 -1
- package/src/persist.ts +3 -1
- package/src/registry.test.ts +79 -4
- package/src/registry.ts +58 -6
- package/src/remarks.ts +36 -0
- package/src/routes.ts +11 -0
- package/src/service.ts +27 -2
- package/src/state.test.ts +13 -0
- package/src/state.ts +24 -18
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Pure timing helpers for manifest-defined scene animation sequences. */
|
|
2
|
+
import type { PetTrackDef } from '../registry.ts';
|
|
3
|
+
import type { PetAnimation } from '../state.ts';
|
|
4
|
+
export interface SequenceFrame {
|
|
5
|
+
animation: PetAnimation;
|
|
6
|
+
frameIndex: number;
|
|
7
|
+
}
|
|
8
|
+
/** Resolve the active track and frame after elapsed milliseconds of a looping sequence. */
|
|
9
|
+
export declare function sequenceFrameAt(sequence: readonly PetAnimation[], tracks: Record<PetAnimation, PetTrackDef>, elapsedMs: number): SequenceFrame;
|
|
10
|
+
//# sourceMappingURL=sequences.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sequences.d.ts","sourceRoot":"","sources":["../../../src/client/sequences.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAE1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,YAAY,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,2FAA2F;AAC3F,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,SAAS,YAAY,EAAE,EACjC,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,WAAW,CAAC,EACzC,SAAS,EAAE,MAAM,GAChB,aAAa,CAiBf"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Pure timing helpers for manifest-defined scene animation sequences. */
|
|
2
|
+
/** Resolve the active track and frame after elapsed milliseconds of a looping sequence. */
|
|
3
|
+
export function sequenceFrameAt(sequence, tracks, elapsedMs) {
|
|
4
|
+
const itemDurations = sequence.map(animation => tracks[animation].durations.reduce((sum, value) => sum + value, 0));
|
|
5
|
+
const sequenceDuration = itemDurations.reduce((sum, value) => sum + value, 0);
|
|
6
|
+
let offset = Math.max(0, elapsedMs) % sequenceDuration;
|
|
7
|
+
let itemIndex = 0;
|
|
8
|
+
while (itemIndex < sequence.length - 1 && offset >= itemDurations[itemIndex]) {
|
|
9
|
+
offset -= itemDurations[itemIndex];
|
|
10
|
+
itemIndex += 1;
|
|
11
|
+
}
|
|
12
|
+
const animation = sequence[itemIndex];
|
|
13
|
+
const track = tracks[animation];
|
|
14
|
+
let frameIndex = 0;
|
|
15
|
+
while (frameIndex < track.frames.length - 1 && offset >= track.durations[frameIndex]) {
|
|
16
|
+
offset -= track.durations[frameIndex];
|
|
17
|
+
frameIndex += 1;
|
|
18
|
+
}
|
|
19
|
+
return { animation, frameIndex };
|
|
20
|
+
}
|
|
@@ -3,10 +3,16 @@
|
|
|
3
3
|
* vocabulary onto the pet's visual phases and carries an optional completed-
|
|
4
4
|
* turn reward for the ledger. Holds no state of its own; callers keep a
|
|
5
5
|
* {@link ProjectionRuntime} per session and feed events in arrival order.
|
|
6
|
+
*
|
|
7
|
+
* Status copy comes from the chatter voice (big rotating pools, per-tool
|
|
8
|
+
* families, real-argument hints), and streamed model output feeds the murmur
|
|
9
|
+
* engine so the pet can whisper its inner voice (碎碎念). The wall clock is
|
|
10
|
+
* injected by the caller, keeping every projection reproducible.
|
|
6
11
|
* @module @linxin666/dsh-pet/event-projection
|
|
7
12
|
*/
|
|
8
13
|
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
9
14
|
import type { PetStateInput } from './state.ts';
|
|
15
|
+
import { StatusVoice, WhisperEngine } from './chatter.ts';
|
|
10
16
|
/** Runtime shape of the optional legacy activity event. */
|
|
11
17
|
export interface ActivityStatusEventLike {
|
|
12
18
|
phase?: string;
|
|
@@ -18,11 +24,17 @@ export interface ProjectionRuntime {
|
|
|
18
24
|
activeTools: Set<string>;
|
|
19
25
|
officialEventsSeen: boolean;
|
|
20
26
|
stepHadFailure: boolean;
|
|
27
|
+
/** Round-robin status copy voice (scene-stable, cadence-rotated). */
|
|
28
|
+
voice: StatusVoice;
|
|
29
|
+
/** Inner-whisper engine fed by the model's own streamed output. */
|
|
30
|
+
whispers: WhisperEngine;
|
|
21
31
|
}
|
|
22
32
|
/** One official event projection, optionally carrying a completed turn reward. */
|
|
23
33
|
export interface PetActivityTransition {
|
|
24
34
|
input: PetStateInput;
|
|
25
35
|
completedTurn?: number;
|
|
36
|
+
/** A fresh inner whisper woken by this event's model output, when any. */
|
|
37
|
+
whisper?: string;
|
|
26
38
|
}
|
|
27
39
|
/** Fresh projection runtime for a newly seen session. */
|
|
28
40
|
export declare function emptyProjectionRuntime(): ProjectionRuntime;
|
|
@@ -31,6 +43,7 @@ export declare function isActivityPhase(phase: string): phase is PetStateInput['
|
|
|
31
43
|
/**
|
|
32
44
|
* Project the durable DSH session vocabulary into the pet's visual phases.
|
|
33
45
|
* Unknown and log-only events do not disturb the last meaningful activity.
|
|
46
|
+
* @param nowMs - injected wall clock for copy rotation and whisper pacing.
|
|
34
47
|
*/
|
|
35
|
-
export declare function projectOfficialEvent(event: SessionEvent, runtime: ProjectionRuntime): PetActivityTransition | undefined;
|
|
48
|
+
export declare function projectOfficialEvent(event: SessionEvent, runtime: ProjectionRuntime, nowMs?: number): PetActivityTransition | undefined;
|
|
36
49
|
//# sourceMappingURL=event-projection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-projection.d.ts","sourceRoot":"","sources":["../../src/event-projection.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"event-projection.d.ts","sourceRoot":"","sources":["../../src/event-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAe,aAAa,EAAE,MAAM,cAAc,CAAA;AAEtE,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,cAAc,EAAE,OAAO,CAAA;IACvB,qEAAqE;IACrE,KAAK,EAAE,WAAW,CAAA;IAClB,mEAAmE;IACnE,QAAQ,EAAE,aAAa,CAAA;CACxB;AAED,kFAAkF;AAClF,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,aAAa,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,yDAAyD;AACzD,wBAAgB,sBAAsB,IAAI,iBAAiB,CAQ1D;AAQD,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAAC,OAAO,CAAC,CAE9E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,iBAAiB,EAC1B,KAAK,GAAE,MAAmB,GACzB,qBAAqB,GAAG,SAAS,CAwFnC"}
|
|
@@ -3,16 +3,28 @@
|
|
|
3
3
|
* vocabulary onto the pet's visual phases and carries an optional completed-
|
|
4
4
|
* turn reward for the ledger. Holds no state of its own; callers keep a
|
|
5
5
|
* {@link ProjectionRuntime} per session and feed events in arrival order.
|
|
6
|
+
*
|
|
7
|
+
* Status copy comes from the chatter voice (big rotating pools, per-tool
|
|
8
|
+
* families, real-argument hints), and streamed model output feeds the murmur
|
|
9
|
+
* engine so the pet can whisper its inner voice (碎碎念). The wall clock is
|
|
10
|
+
* injected by the caller, keeping every projection reproducible.
|
|
6
11
|
* @module @linxin666/dsh-pet/event-projection
|
|
7
12
|
*/
|
|
13
|
+
import { StatusVoice, toolArgHint, WhisperEngine } from './chatter.js';
|
|
8
14
|
/** Fresh projection runtime for a newly seen session. */
|
|
9
15
|
export function emptyProjectionRuntime() {
|
|
10
|
-
return {
|
|
16
|
+
return {
|
|
17
|
+
activeTools: new Set(),
|
|
18
|
+
officialEventsSeen: false,
|
|
19
|
+
stepHadFailure: false,
|
|
20
|
+
voice: new StatusVoice(),
|
|
21
|
+
whispers: new WhisperEngine(),
|
|
22
|
+
};
|
|
11
23
|
}
|
|
12
24
|
/** Keep tool names readable inside the compact status bubble. */
|
|
13
25
|
function displayToolName(name) {
|
|
14
26
|
const compact = name.replace(/\s+/g, ' ').trim() || '工具';
|
|
15
|
-
return compact.length <= 24 ? compact :
|
|
27
|
+
return compact.length <= 24 ? compact : compact.slice(0, 21) + '...';
|
|
16
28
|
}
|
|
17
29
|
/** Whether a legacy phase is part of the pet's supported vocabulary. */
|
|
18
30
|
export function isActivityPhase(phase) {
|
|
@@ -21,35 +33,44 @@ export function isActivityPhase(phase) {
|
|
|
21
33
|
/**
|
|
22
34
|
* Project the durable DSH session vocabulary into the pet's visual phases.
|
|
23
35
|
* Unknown and log-only events do not disturb the last meaningful activity.
|
|
36
|
+
* @param nowMs - injected wall clock for copy rotation and whisper pacing.
|
|
24
37
|
*/
|
|
25
|
-
export function projectOfficialEvent(event, runtime) {
|
|
38
|
+
export function projectOfficialEvent(event, runtime, nowMs = Date.now()) {
|
|
26
39
|
switch (event.type) {
|
|
27
40
|
case 'turn/start':
|
|
28
41
|
runtime.activeTools.clear();
|
|
29
42
|
runtime.stepHadFailure = false;
|
|
30
|
-
return { input: { phase: 'waiting', line: '
|
|
43
|
+
return { input: { phase: 'waiting', line: runtime.voice.scene('prepare', nowMs) } };
|
|
31
44
|
case 'step/start':
|
|
32
45
|
runtime.activeTools.clear();
|
|
33
46
|
runtime.stepHadFailure = false;
|
|
34
|
-
return { input: { phase: 'waiting', line: '
|
|
47
|
+
return { input: { phase: 'waiting', line: runtime.voice.scene('waiting', nowMs) } };
|
|
35
48
|
case 'assistant/chunk': {
|
|
36
49
|
const { chunk } = event.data;
|
|
37
50
|
if (chunk.type === 'reasoning-delta' && chunk.text.length > 0) {
|
|
38
|
-
|
|
51
|
+
const whisper = runtime.whispers.feed(chunk.text, nowMs);
|
|
52
|
+
return {
|
|
53
|
+
input: { phase: 'thinking', line: runtime.voice.scene('thinking', nowMs) },
|
|
54
|
+
...(whisper === undefined ? {} : { whisper }),
|
|
55
|
+
};
|
|
39
56
|
}
|
|
40
57
|
if (chunk.type === 'text-delta' && chunk.text.length > 0) {
|
|
41
|
-
|
|
58
|
+
const whisper = runtime.whispers.feed(chunk.text, nowMs);
|
|
59
|
+
return {
|
|
60
|
+
input: { phase: 'review', line: runtime.voice.scene('review', nowMs) },
|
|
61
|
+
...(whisper === undefined ? {} : { whisper }),
|
|
62
|
+
};
|
|
42
63
|
}
|
|
43
64
|
return undefined;
|
|
44
65
|
}
|
|
45
66
|
case 'assistant/message':
|
|
46
|
-
return { input: { phase: 'review', line: '
|
|
67
|
+
return { input: { phase: 'review', line: runtime.voice.scene('review', nowMs) } };
|
|
47
68
|
case 'tool/call':
|
|
48
69
|
runtime.activeTools.add(String(event.data.callId));
|
|
49
70
|
return {
|
|
50
71
|
input: {
|
|
51
72
|
phase: 'tool',
|
|
52
|
-
line:
|
|
73
|
+
line: runtime.voice.tool(event.data.name, displayToolName(event.data.name), toolArgHint(event.data.name, event.data.arguments), nowMs),
|
|
53
74
|
},
|
|
54
75
|
};
|
|
55
76
|
case 'tool/result': {
|
|
@@ -60,30 +81,30 @@ export function projectOfficialEvent(event, runtime) {
|
|
|
60
81
|
return {
|
|
61
82
|
input: {
|
|
62
83
|
phase: 'tool',
|
|
63
|
-
line:
|
|
84
|
+
line: runtime.voice.toolRemaining(runtime.activeTools.size, nowMs),
|
|
64
85
|
},
|
|
65
86
|
};
|
|
66
87
|
}
|
|
67
88
|
return runtime.stepHadFailure
|
|
68
|
-
? { input: { phase: 'failed', line: '
|
|
69
|
-
: { input: { phase: 'thinking', line: '
|
|
89
|
+
? { input: { phase: 'failed', line: runtime.voice.scene('toolFailed', nowMs) } }
|
|
90
|
+
: { input: { phase: 'thinking', line: runtime.voice.scene('toolResult', nowMs) } };
|
|
70
91
|
}
|
|
71
92
|
case 'turn/end': {
|
|
72
93
|
runtime.activeTools.clear();
|
|
73
94
|
switch (event.data.reason.kind) {
|
|
74
95
|
case 'completed':
|
|
75
96
|
return {
|
|
76
|
-
input: { phase: 'done', line: '
|
|
97
|
+
input: { phase: 'done', line: runtime.voice.scene('done', nowMs) },
|
|
77
98
|
completedTurn: event.data.turn,
|
|
78
99
|
};
|
|
79
100
|
case 'error':
|
|
80
|
-
return { input: { phase: 'failed', line: '
|
|
101
|
+
return { input: { phase: 'failed', line: runtime.voice.scene('failed', nowMs) } };
|
|
81
102
|
case 'max-tokens':
|
|
82
|
-
return { input: { phase: 'failed', line: '
|
|
103
|
+
return { input: { phase: 'failed', line: runtime.voice.scene('maxTokens', nowMs) } };
|
|
83
104
|
case 'interrupted':
|
|
84
|
-
return { input: { phase: 'failed', line: '
|
|
105
|
+
return { input: { phase: 'failed', line: runtime.voice.scene('interrupted', nowMs) } };
|
|
85
106
|
case 'blocked':
|
|
86
|
-
return { input: { phase: 'waiting', line: '
|
|
107
|
+
return { input: { phase: 'waiting', line: runtime.voice.scene('blocked', nowMs) } };
|
|
87
108
|
case 'aborted':
|
|
88
109
|
// A stopped session settles to idle without a bubble: the pet
|
|
89
110
|
// visibly calms down and the session drops out of the bubble stack.
|
|
@@ -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,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,
|
|
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,CAsCrE;IAED,kDAAkD;IAClD,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,CAE3C;CACF"}
|
package/lib/types/ledger.js
CHANGED
|
@@ -135,12 +135,19 @@ export class PetLedger {
|
|
|
135
135
|
interact(kind, nowMs) {
|
|
136
136
|
if (kind === 'feed')
|
|
137
137
|
this.settleTreats(nowMs);
|
|
138
|
-
const
|
|
138
|
+
const before = this.current.affinity;
|
|
139
|
+
const outcome = applyInteraction(before, kind, nowMs, this.affinityConfig);
|
|
139
140
|
// Reactions come from the picker pools (custom per-pet slots override
|
|
140
141
|
// the built-in library per slot); outcome.reaction stays the fallback
|
|
141
142
|
// copy for direct applyInteraction callers.
|
|
142
143
|
if (kind === 'feed' && !outcome.accepted) {
|
|
143
|
-
|
|
144
|
+
this.current = { ...this.current, affinity: outcome.affinity };
|
|
145
|
+
this.dirty = true;
|
|
146
|
+
return {
|
|
147
|
+
reaction: this.picker.pickAt('feedCooldown', before.feedRejects),
|
|
148
|
+
delta: 0,
|
|
149
|
+
affinity: this.affinityView(nowMs),
|
|
150
|
+
};
|
|
144
151
|
}
|
|
145
152
|
if (kind === 'feed') {
|
|
146
153
|
const consume = consumeTreat(this.current.treats);
|
|
@@ -154,12 +161,13 @@ export class PetLedger {
|
|
|
154
161
|
this.current = { ...this.current, treats: consume.ledger };
|
|
155
162
|
this.dirty = true;
|
|
156
163
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
164
|
+
this.current = { ...this.current, affinity: outcome.affinity };
|
|
165
|
+
this.dirty = true;
|
|
166
|
+
const count = kind === 'pet'
|
|
167
|
+
? outcome.accepted ? before.pets : before.petRejects
|
|
168
|
+
: before.feeds;
|
|
161
169
|
return {
|
|
162
|
-
reaction: this.picker.
|
|
170
|
+
reaction: this.picker.pickAt(outcome.accepted ? kind : 'petCooldown', count),
|
|
163
171
|
delta: outcome.delta,
|
|
164
172
|
affinity: this.affinityView(nowMs),
|
|
165
173
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loopback trust fence shared by the host route families: socket address,
|
|
3
|
+
* Host header, and browser same-origin markers. Packages receive this file as
|
|
4
|
+
* a generated copy via scripts/sync-shared.mjs; edit the shared source and
|
|
5
|
+
* re-run the sync instead of editing a copy.
|
|
6
|
+
*
|
|
7
|
+
* Semantics: RFC 5735 IPv4 127/8, ::1, IPv4-mapped ::ffff:127/8 (matching the
|
|
8
|
+
* remote-web-ui gate), localhost hostnames, plus the browser same-origin
|
|
9
|
+
* markers (sec-fetch-site and Origin) for the request-level fence.
|
|
10
|
+
* @module dsh-web-ui-shared/host/loopback
|
|
11
|
+
*/
|
|
12
|
+
import type { IncomingMessage } from 'node:http';
|
|
13
|
+
/** IPv4 127/8 predicate (four decimal octets, first == 127). */
|
|
14
|
+
export declare function isIPv4Loopback(v4: string): boolean;
|
|
15
|
+
/** Whether a socket remote address names the loopback range (127/8, ::1, IPv4-mapped). */
|
|
16
|
+
export declare function isLoopbackAddress(address: string | undefined): boolean;
|
|
17
|
+
/** Whether a normalized URL hostname names the loopback authority (localhost, [::1], 127/8). */
|
|
18
|
+
export declare function isLoopbackHostname(hostname: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Request-level trust fence: a loopback socket address AND a loopback Host
|
|
21
|
+
* header, plus browser same-origin markers. The socket address is
|
|
22
|
+
* authoritative; X-Forwarded-For is never trusted.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isLoopbackRequest(request: IncomingMessage): boolean;
|
|
25
|
+
//# sourceMappingURL=loopback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopback.d.ts","sourceRoot":"","sources":["../../src/loopback.ts"],"names":[],"mappings":"AACA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAEhD,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAKlD;AAED,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAMtE;AAED,gGAAgG;AAChG,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG5D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAmBnE"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Generated by scripts/sync-shared.mjs from shared/host/loopback.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
|
|
2
|
+
/**
|
|
3
|
+
* Loopback trust fence shared by the host route families: socket address,
|
|
4
|
+
* Host header, and browser same-origin markers. Packages receive this file as
|
|
5
|
+
* a generated copy via scripts/sync-shared.mjs; edit the shared source and
|
|
6
|
+
* re-run the sync instead of editing a copy.
|
|
7
|
+
*
|
|
8
|
+
* Semantics: RFC 5735 IPv4 127/8, ::1, IPv4-mapped ::ffff:127/8 (matching the
|
|
9
|
+
* remote-web-ui gate), localhost hostnames, plus the browser same-origin
|
|
10
|
+
* markers (sec-fetch-site and Origin) for the request-level fence.
|
|
11
|
+
* @module dsh-web-ui-shared/host/loopback
|
|
12
|
+
*/
|
|
13
|
+
/** IPv4 127/8 predicate (four decimal octets, first == 127). */
|
|
14
|
+
export function isIPv4Loopback(v4) {
|
|
15
|
+
const parts = v4.split('.');
|
|
16
|
+
return parts.length === 4
|
|
17
|
+
&& parts[0] === '127'
|
|
18
|
+
&& parts.every(part => /^\d{1,3}$/.test(part) && Number(part) <= 255);
|
|
19
|
+
}
|
|
20
|
+
/** Whether a socket remote address names the loopback range (127/8, ::1, IPv4-mapped). */
|
|
21
|
+
export function isLoopbackAddress(address) {
|
|
22
|
+
if (address === undefined)
|
|
23
|
+
return false;
|
|
24
|
+
const normalized = address.toLowerCase();
|
|
25
|
+
if (normalized === '::1')
|
|
26
|
+
return true;
|
|
27
|
+
if (normalized.startsWith('::ffff:'))
|
|
28
|
+
return isIPv4Loopback(normalized.slice('::ffff:'.length));
|
|
29
|
+
return isIPv4Loopback(normalized);
|
|
30
|
+
}
|
|
31
|
+
/** Whether a normalized URL hostname names the loopback authority (localhost, [::1], 127/8). */
|
|
32
|
+
export function isLoopbackHostname(hostname) {
|
|
33
|
+
if (hostname === 'localhost' || hostname === '[::1]')
|
|
34
|
+
return true;
|
|
35
|
+
return isIPv4Loopback(hostname);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Request-level trust fence: a loopback socket address AND a loopback Host
|
|
39
|
+
* header, plus browser same-origin markers. The socket address is
|
|
40
|
+
* authoritative; X-Forwarded-For is never trusted.
|
|
41
|
+
*/
|
|
42
|
+
export function isLoopbackRequest(request) {
|
|
43
|
+
if (!isLoopbackAddress(request.socket.remoteAddress))
|
|
44
|
+
return false;
|
|
45
|
+
const host = request.headers.host;
|
|
46
|
+
if (typeof host !== 'string')
|
|
47
|
+
return false;
|
|
48
|
+
let hostUrl;
|
|
49
|
+
try {
|
|
50
|
+
hostUrl = new URL('http://' + host);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (!isLoopbackHostname(hostUrl.hostname))
|
|
56
|
+
return false;
|
|
57
|
+
if (request.headers['sec-fetch-site'] === 'cross-site')
|
|
58
|
+
return false;
|
|
59
|
+
const origin = request.headers.origin;
|
|
60
|
+
if (origin === undefined)
|
|
61
|
+
return true;
|
|
62
|
+
try {
|
|
63
|
+
return new URL(origin).host === hostUrl.host;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -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,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,MAAM,CAAA;AACnC,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;CAC1B;AAED,qEAAqE;AACrE,eAAO,MAAM,cAAc,eAAe,CAAA;AAE1C,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,uBAAQ,CAAA;AAErC,wBAAwB;AACxB,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,wBAAgB,YAAY,IAAI,UAAU,CAQzC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AA4BD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAqB,GAAG,UAAU,
|
|
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,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,MAAM,CAAA;AACnC,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;CAC1B;AAED,qEAAqE;AACrE,eAAO,MAAM,cAAc,eAAe,CAAA;AAE1C,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,uBAAQ,CAAA;AAErC,wBAAwB;AACxB,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,wBAAgB,YAAY,IAAI,UAAU,CAQzC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AA4BD,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAqB,GAAG,UAAU,CAmDrE;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,GAAE,MAAqB,GAAG,IAAI,CAMjF"}
|
package/lib/types/persist.js
CHANGED
|
@@ -13,7 +13,7 @@ export const defaultDisplayConfig = {
|
|
|
13
13
|
visible: true,
|
|
14
14
|
size: 160,
|
|
15
15
|
right: 24,
|
|
16
|
-
bottom:
|
|
16
|
+
bottom: 120,
|
|
17
17
|
};
|
|
18
18
|
/** Display value bounds (shared by load-time validation and setConfig). */
|
|
19
19
|
export const DISPLAY_SIZE_MIN = 32;
|
|
@@ -78,6 +78,8 @@ export function loadPetPersist(dir = petHomeDir()) {
|
|
|
78
78
|
lastFeedAt: clamp(finiteNum(rawAffinity.lastFeedAt, 0), Number.MAX_SAFE_INTEGER),
|
|
79
79
|
pets: clamp(finiteNum(rawAffinity.pets, 0), Number.MAX_SAFE_INTEGER),
|
|
80
80
|
feeds: clamp(finiteNum(rawAffinity.feeds, 0), Number.MAX_SAFE_INTEGER),
|
|
81
|
+
petRejects: clamp(finiteNum(rawAffinity.petRejects, 0), Number.MAX_SAFE_INTEGER),
|
|
82
|
+
feedRejects: clamp(finiteNum(rawAffinity.feedRejects, 0), Number.MAX_SAFE_INTEGER),
|
|
81
83
|
turns: clamp(finiteNum(rawAffinity.turns, 0), Number.MAX_SAFE_INTEGER),
|
|
82
84
|
};
|
|
83
85
|
const rawTreats = (parsed.treats ?? {});
|
package/lib/types/registry.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* whale-girl manifest overrides its own durations.
|
|
17
17
|
* @module @linxin666/dsh-pet/registry
|
|
18
18
|
*/
|
|
19
|
-
import type { PetAnimation } from './state.ts';
|
|
19
|
+
import type { ActivityPhase, PetAnimation } from './state.ts';
|
|
20
20
|
import { type PetRemarks, type PetRemarksManifest } from './remarks.ts';
|
|
21
21
|
/** Fixed row order of the 9-state animation contract. */
|
|
22
22
|
export declare const PET_ROW_ORDER: readonly PetAnimation[];
|
|
@@ -81,6 +81,8 @@ export interface PetManifest {
|
|
|
81
81
|
frames?: number[];
|
|
82
82
|
/** Optional per-track rhythm overrides; omitted tracks use the defaults. */
|
|
83
83
|
tracks?: Partial<Record<PetAnimation, PetTrackOverride>>;
|
|
84
|
+
/** Optional per-scene track sequences; every declared sequence has at least 5 items. */
|
|
85
|
+
sequences?: Partial<Record<ActivityPhase, PetAnimation[]>>;
|
|
84
86
|
/**
|
|
85
87
|
* Optional witty-remark overrides the pet speaks on interactions
|
|
86
88
|
* (community contributions use this to give their pet its own voice).
|
|
@@ -109,8 +111,12 @@ export interface PetDefinition {
|
|
|
109
111
|
columns: number;
|
|
110
112
|
/** Per-row frame counts (length 9, row order above). */
|
|
111
113
|
rows: number[];
|
|
114
|
+
/** Total atlas rows (9 for v1, 11 for v2 look-row atlases). */
|
|
115
|
+
atlasRows: number;
|
|
112
116
|
/** Fully resolved animation tracks (frames + durations + loop/fallback). */
|
|
113
117
|
tracks: Record<PetAnimation, PetTrackDef>;
|
|
118
|
+
/** Validated per-scene track sequences; omitted scenes keep single-track playback. */
|
|
119
|
+
sequences?: Partial<Record<ActivityPhase, PetAnimation[]>>;
|
|
114
120
|
/** Browser URL of the atlas (served by the host asset route). */
|
|
115
121
|
atlasUrl: string;
|
|
116
122
|
/** Browser URL of the manifest (served by the host asset route). */
|
|
@@ -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
|
@@ -86,6 +86,35 @@ const PET_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
|
86
86
|
/** Safe path-segment charset for atlas files. */
|
|
87
87
|
const PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
88
88
|
const PET_NAME_MAX_LENGTH = 80;
|
|
89
|
+
const PET_PHASES = ['idle', 'waiting', 'thinking', 'tool', 'review', 'done', 'failed'];
|
|
90
|
+
/** Validate optional scene sequences without rejecting an otherwise usable pet. */
|
|
91
|
+
function normalizeSequences(raw, id, warn) {
|
|
92
|
+
if (raw === undefined)
|
|
93
|
+
return undefined;
|
|
94
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
95
|
+
warn('manifest ' + id + ': sequences must be an object keyed by activity phase');
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
const sequences = {};
|
|
99
|
+
for (const [phase, value] of Object.entries(raw)) {
|
|
100
|
+
if (!PET_PHASES.includes(phase)) {
|
|
101
|
+
warn('manifest ' + id + ': unknown sequence phase ' + JSON.stringify(phase));
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (!Array.isArray(value) || value.length < 5) {
|
|
105
|
+
warn('manifest ' + id + ': sequence ' + phase + ' must contain at least 5 animations');
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const unknownIndex = value.findIndex(animation => typeof animation !== 'string' || !PET_ROW_ORDER.includes(animation));
|
|
109
|
+
if (unknownIndex !== -1) {
|
|
110
|
+
const unknown = value[unknownIndex];
|
|
111
|
+
warn('manifest ' + id + ': sequence ' + phase + ' contains unknown animation ' + JSON.stringify(unknown));
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
sequences[phase] = value;
|
|
115
|
+
}
|
|
116
|
+
return Object.keys(sequences).length === 0 ? undefined : sequences;
|
|
117
|
+
}
|
|
89
118
|
/**
|
|
90
119
|
* Normalize one parsed manifest into a renderable pet entry, or undefined
|
|
91
120
|
* (with a warning recorded) when the manifest violates the contract.
|
|
@@ -126,11 +155,14 @@ export function resolvePetManifest(raw, dir, options = {}) {
|
|
|
126
155
|
height: finiteInt(rawCell.height, DEFAULT_PET_CELL.height, 2048),
|
|
127
156
|
};
|
|
128
157
|
const columns = finiteInt(source.columns, DEFAULT_PET_COLUMNS, 32);
|
|
158
|
+
// v2 atlases (spriteVersionNumber 2) hold 11 rows: 9 animation rows + 2 look rows.
|
|
159
|
+
const atlasRowCount = source.spriteVersionNumber === 2 ? 11 : DEFAULT_PET_ROW_COUNT;
|
|
129
160
|
const rows = DEFAULT_FRAME_COUNTS.map((fallback, index) => {
|
|
130
161
|
const value = Array.isArray(source.frames) ? source.frames[index] : undefined;
|
|
131
162
|
return finiteInt(value, fallback, columns);
|
|
132
163
|
});
|
|
133
164
|
const remarks = normalizePetRemarks(source.remarks, message => warn('manifest ' + id + ': ' + message));
|
|
165
|
+
const sequences = normalizeSequences(source.sequences, id, warn);
|
|
134
166
|
const trackOverrides = (typeof source.tracks === 'object' && source.tracks !== null ? source.tracks : {});
|
|
135
167
|
const tracks = {};
|
|
136
168
|
for (const [row, animation] of PET_ROW_ORDER.entries()) {
|
|
@@ -165,7 +197,9 @@ export function resolvePetManifest(raw, dir, options = {}) {
|
|
|
165
197
|
cell,
|
|
166
198
|
columns,
|
|
167
199
|
rows,
|
|
200
|
+
atlasRows: atlasRowCount,
|
|
168
201
|
tracks,
|
|
202
|
+
...(sequences === undefined ? {} : { sequences }),
|
|
169
203
|
atlasUrl: assetUrl(assetPrefix, id, spritesheet),
|
|
170
204
|
manifestUrl: assetUrl(assetPrefix, id, 'pet.json'),
|
|
171
205
|
dir,
|
|
@@ -237,10 +271,18 @@ export function loadPetRegistry(options) {
|
|
|
237
271
|
}
|
|
238
272
|
}
|
|
239
273
|
for (const manifest of options.extra ?? []) {
|
|
240
|
-
const
|
|
274
|
+
const raw = manifest.spritesheetPath;
|
|
275
|
+
const dir = raw === undefined || isAbsolute(raw)
|
|
241
276
|
? join(packageRoot, 'assets', 'extra')
|
|
242
|
-
: dirname(resolve(packageRoot,
|
|
243
|
-
|
|
277
|
+
: dirname(resolve(packageRoot, raw));
|
|
278
|
+
// petAtlasFile joins entry.dir (already the spritesheet's parent when the
|
|
279
|
+
// path is package-relative) with entry.spritesheetPath, so the stored path
|
|
280
|
+
// must be the basename only — otherwise the directory segment is applied
|
|
281
|
+
// twice and the atlas 404s.
|
|
282
|
+
const source = raw === undefined || isAbsolute(raw)
|
|
283
|
+
? manifest
|
|
284
|
+
: { ...manifest, spritesheetPath: basename(raw) };
|
|
285
|
+
const entry = resolvePetManifest(source, dir, { assetPrefix, warnings });
|
|
244
286
|
if (entry === undefined)
|
|
245
287
|
continue;
|
|
246
288
|
if (byId.has(entry.id))
|
|
@@ -264,7 +306,9 @@ export function petEntryView(entry) {
|
|
|
264
306
|
cell: entry.cell,
|
|
265
307
|
columns: entry.columns,
|
|
266
308
|
rows: entry.rows,
|
|
309
|
+
atlasRows: entry.atlasRows,
|
|
267
310
|
tracks: entry.tracks,
|
|
311
|
+
...(entry.sequences === undefined ? {} : { sequences: entry.sequences }),
|
|
268
312
|
atlasUrl: entry.atlasUrl,
|
|
269
313
|
manifestUrl: entry.manifestUrl,
|
|
270
314
|
};
|
package/lib/types/remarks.d.ts
CHANGED
|
@@ -52,5 +52,7 @@ export declare class RemarkPicker {
|
|
|
52
52
|
pool(kind: RemarkKind): readonly string[];
|
|
53
53
|
/** The next line for one slot (round-robin within its pool). */
|
|
54
54
|
pick(kind: RemarkKind): string;
|
|
55
|
+
/** Select a line from a stable external counter without changing local picker state. */
|
|
56
|
+
pickAt(kind: RemarkKind, count: number): string;
|
|
55
57
|
}
|
|
56
58
|
//# sourceMappingURL=remarks.d.ts.map
|
|
@@ -1 +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,
|
|
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,CA2F3E,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;IAED,wFAAwF;IACxF,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG9C;CACF"}
|
package/lib/types/remarks.js
CHANGED
|
@@ -41,6 +41,12 @@ export const BUILTIN_REMARKS = {
|
|
|
41
41
|
'蹭蹭你的手心,这是回礼~',
|
|
42
42
|
'多摸摸我,亲密度会涨哦!',
|
|
43
43
|
'闭眼享受中,请勿打扰~',
|
|
44
|
+
'头再低一点,够不着了~',
|
|
45
|
+
'呼噜呼噜,声音都冒出来了',
|
|
46
|
+
'这手感,比小鱼干还上瘾',
|
|
47
|
+
'摸到第三下,满意',
|
|
48
|
+
'耳朵后面,别漏了……啊,舒服',
|
|
49
|
+
'被摸得尾巴都卷起来了',
|
|
44
50
|
],
|
|
45
51
|
petCooldown: [
|
|
46
52
|
'摸过头啦,让鲸鱼娘歇口气~',
|
|
@@ -53,6 +59,12 @@ export const BUILTIN_REMARKS = {
|
|
|
53
59
|
'歇一歇,摸摸的手感会更好哦~',
|
|
54
60
|
'咕……等我回个蓝~',
|
|
55
61
|
'让我先消化一下刚才的爱!',
|
|
62
|
+
'痒痒的,先让我缓一下……',
|
|
63
|
+
'再摸就掉线了,真的',
|
|
64
|
+
'库存的呼噜声用完了',
|
|
65
|
+
'手歇会儿,我也要补个蓝',
|
|
66
|
+
'舒服归舒服,得缓缓呀',
|
|
67
|
+
'再摸下去,我就要融化了',
|
|
56
68
|
],
|
|
57
69
|
feed: [
|
|
58
70
|
'呜哇!小鱼干好好吃!',
|
|
@@ -65,6 +77,12 @@ export const BUILTIN_REMARKS = {
|
|
|
65
77
|
'饱餐一顿,马上满血复活~',
|
|
66
78
|
'这个味道,是幸福的味道!',
|
|
67
79
|
'吃完了还不忘舔舔爪子~',
|
|
80
|
+
'这小鱼干,是今天的顶配',
|
|
81
|
+
'一口下去,精神头全回来了',
|
|
82
|
+
'脆!香!就是这个味儿',
|
|
83
|
+
'边吃边摇尾巴,形象不要了',
|
|
84
|
+
'好吃到眼睛都眯起来了',
|
|
85
|
+
'这块鱼干我记住了,懂我的',
|
|
68
86
|
],
|
|
69
87
|
feedCooldown: [
|
|
70
88
|
'吃饱啦,晚点再喂~',
|
|
@@ -77,6 +95,12 @@ export const BUILTIN_REMARKS = {
|
|
|
77
95
|
'肚皮已经鼓鼓的啦~',
|
|
78
96
|
'好吃是好吃,可也得节制呀~',
|
|
79
97
|
'等我饿了会告诉你哦~',
|
|
98
|
+
'胃说它满了,脑子说还能吃',
|
|
99
|
+
'这条得留着慢慢品',
|
|
100
|
+
'先消消食,待会儿再战',
|
|
101
|
+
'塞不下了,真塞不下了',
|
|
102
|
+
'闻着香,可惜没地方放了',
|
|
103
|
+
'嗝……这顿值了',
|
|
80
104
|
],
|
|
81
105
|
noTreats: [
|
|
82
106
|
'没有小鱼干了,多陪我工作一会儿吧~',
|
|
@@ -89,6 +113,12 @@ export const BUILTIN_REMARKS = {
|
|
|
89
113
|
'饿着肚子等你完成下一轮任务~',
|
|
90
114
|
'小鱼干藏在你的工作里,去找找看!',
|
|
91
115
|
'先工作后干饭,我们的约定哦~',
|
|
116
|
+
'粮仓见底,全靠感情撑着了',
|
|
117
|
+
'饿是真饿,活也是真得干',
|
|
118
|
+
'画饼充饥……不对,画鱼干充饥',
|
|
119
|
+
'没鱼干的日子,靠意志力过',
|
|
120
|
+
'下一轮任务,我闻到了鱼干味',
|
|
121
|
+
'先记账上,欠我两条,记住啦',
|
|
92
122
|
],
|
|
93
123
|
};
|
|
94
124
|
/** The legacy first line of one kind (direct callers' fallback copy). */
|
|
@@ -154,4 +184,9 @@ export class RemarkPicker {
|
|
|
154
184
|
this.counters.set(kind, index + 1);
|
|
155
185
|
return pool[index];
|
|
156
186
|
}
|
|
187
|
+
/** Select a line from a stable external counter without changing local picker state. */
|
|
188
|
+
pickAt(kind, count) {
|
|
189
|
+
const pool = this.pools[kind];
|
|
190
|
+
return pool[Math.max(0, Math.floor(count)) % pool.length];
|
|
191
|
+
}
|
|
157
192
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAK9C,+CAA+C;AAC/C,eAAO,MAAM,cAAc,aAAa,CAAA;AAExC,0EAA0E;AAC1E,eAAO,MAAM,gBAAgB,SAAS,CAAA;AAiOtC,kEAAkE;AAClE,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,UAAU,CAAA;CAAE,GAAG,QAAQ,EAAE,CAwCvE;AAGD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA"}
|