@linxin666/dsh-pet 0.1.15 → 0.1.16
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 +73 -38
- package/README.zh.md +91 -55
- package/assets/whale/pet.json +12 -1
- package/cordis.patch.yml +4 -4
- package/lib/client.js +270 -334
- package/lib/client.js.map +1 -1
- package/lib/index.js +641 -87
- package/lib/types/client/PetDockEntry.d.ts +14 -12
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +13 -8
- package/lib/types/client/PetSettingsCard.d.ts +22 -9
- package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PetSettingsCard.js +42 -7
- package/lib/types/client/{WhalePet.d.ts → PetSprite.d.ts} +21 -20
- package/lib/types/client/PetSprite.d.ts.map +1 -0
- package/lib/types/client/{WhalePet.js → PetSprite.js} +44 -67
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +3 -1
- package/lib/types/client/index.d.ts +11 -8
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +30 -12
- package/lib/types/client/locales.d.ts +10 -10
- package/lib/types/client/locales.js +10 -10
- package/lib/types/client/pet-store.d.ts +5 -0
- package/lib/types/client/pet-store.d.ts.map +1 -1
- package/lib/types/client/pet-store.js +4 -0
- package/lib/types/client/settings-form.d.ts +62 -0
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +61 -12
- package/lib/types/client/spritesheet.d.ts +14 -48
- package/lib/types/client/spritesheet.d.ts.map +1 -1
- package/lib/types/client/spritesheet.js +13 -81
- package/lib/types/index.d.ts +24 -11
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +53 -33
- package/lib/types/ledger.d.ts +4 -2
- package/lib/types/ledger.d.ts.map +1 -1
- package/lib/types/ledger.js +11 -4
- package/lib/types/persist.d.ts +10 -3
- package/lib/types/persist.d.ts.map +1 -1
- package/lib/types/persist.js +32 -5
- package/lib/types/registry.d.ts +158 -0
- package/lib/types/registry.d.ts.map +1 -0
- package/lib/types/registry.js +276 -0
- package/lib/types/routes.d.ts +10 -10
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +169 -51
- package/lib/types/service.d.ts +46 -13
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +73 -16
- package/package.json +2 -2
- package/src/client/PetDockEntry.tsx +34 -26
- package/src/client/PetSettingsCard.tsx +67 -22
- package/src/client/{WhalePet.test.tsx → PetSprite.test.tsx} +56 -8
- package/src/client/{WhalePet.tsx → PetSprite.tsx} +55 -73
- package/src/client/PluginSettingsCard.tsx +7 -1
- package/src/client/index.ts +36 -14
- package/src/client/locales.ts +10 -10
- package/src/client/pet-store.ts +9 -0
- package/src/client/settings-card.module.css +3 -0
- package/src/client/settings-form.ts +114 -12
- package/src/client/spritesheet.ts +16 -92
- package/src/index.test.ts +23 -0
- package/src/index.ts +89 -45
- package/src/ledger.test.ts +5 -3
- package/src/ledger.ts +11 -4
- package/src/persist.test.ts +52 -3
- package/src/persist.ts +42 -8
- package/src/registry.test.ts +132 -0
- package/src/registry.ts +400 -0
- package/src/routes.ts +166 -53
- package/src/service.ts +102 -20
- package/lib/types/client/WhalePet.d.ts.map +0 -1
|
@@ -1,42 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 5 failed, 6 waiting, 7 running, 8 review
|
|
8
|
-
*
|
|
9
|
-
* Frame counts and per-frame durations are per-track definitions below; the
|
|
10
|
-
* whale-girl atlas is produced by the hatch-pet pipeline, so calibrate
|
|
11
|
-
* `TRACKS` against the actual run (`pet_request.json` frame counts) when the
|
|
12
|
-
* asset lands. Tracks that do not loop hand off to `fallback`.
|
|
2
|
+
* Spritesheet geometry helpers — parameterized by the pet definition the
|
|
3
|
+
* host serves over '/api/pet/pets', so the browser half renders any registry
|
|
4
|
+
* entry without per-pet code. The per-track tables (frames, durations, loop,
|
|
5
|
+
* fallback) also come from the registry; these helpers only place frames,
|
|
6
|
+
* guard track lengths, and map the fixed 9-row animation contract.
|
|
13
7
|
* @module @linxin666/dsh-pet/client/spritesheet
|
|
14
8
|
*/
|
|
15
9
|
import type { PetAnimation } from '../state.ts';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export
|
|
19
|
-
/**
|
|
20
|
-
export declare const FRAME_COLUMNS = 8;
|
|
21
|
-
/** One animation track: frame indices into the row + per-frame durations. */
|
|
22
|
-
export interface TrackDef {
|
|
23
|
-
/** Frame indices (columns) played in order; must be < FRAME_COLUMNS. */
|
|
24
|
-
frames: readonly number[];
|
|
25
|
-
/** Per-frame duration in ms; same length as frames. */
|
|
26
|
-
durations: readonly number[];
|
|
27
|
-
/** Whether the track loops; a non-looping track hands off to fallback. */
|
|
28
|
-
loop: boolean;
|
|
29
|
-
/** Track to play after a non-looping track finishes. */
|
|
30
|
-
fallback?: PetAnimation;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Track definitions for the whale-girl. Durations are tuned for a soft,
|
|
34
|
-
* slow-healing feel (roughly 2.5× the earlier fast draft — the pet should
|
|
35
|
-
* breathe, not race); calibrate frame counts against the hatch-pet run when
|
|
36
|
-
* the asset lands (rows may carry 4–8 frames).
|
|
37
|
-
*/
|
|
38
|
-
export declare const TRACKS: Record<PetAnimation, TrackDef>;
|
|
39
|
-
/** Row index of one animation track (mirrors state.ts rowOf). */
|
|
10
|
+
import type { PetCell, PetTrackDef } from '../registry.ts';
|
|
11
|
+
/** Animation track shape the frame loop consumes. */
|
|
12
|
+
export type TrackDef = PetTrackDef;
|
|
13
|
+
/** Row index of one animation track (the fixed 9-row contract). */
|
|
40
14
|
export declare function rowOfTrack(animation: PetAnimation): number;
|
|
41
15
|
/**
|
|
42
16
|
* Background-position (px) of one frame cell within the scaled atlas.
|
|
@@ -45,25 +19,17 @@ export declare function rowOfTrack(animation: PetAnimation): number;
|
|
|
45
19
|
* atlas coordinates here would drift each frame by the scale factor and
|
|
46
20
|
* render torn/overlapping frames.
|
|
47
21
|
*/
|
|
48
|
-
export declare function framePosition(row: number, col: number, scale?: number): {
|
|
22
|
+
export declare function framePosition(cell: PetCell, columns: number, row: number, col: number, scale?: number): {
|
|
49
23
|
x: number;
|
|
50
24
|
y: number;
|
|
51
25
|
};
|
|
52
26
|
/** Total duration of one track, ms. */
|
|
53
27
|
export declare function trackDuration(track: TrackDef): number;
|
|
54
28
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* @param image - the fully decoded spritesheet (natural size 1536×1872).
|
|
60
|
-
* @returns per-row frame counts, length 9.
|
|
61
|
-
*/
|
|
62
|
-
export declare function detectFrameCounts(image: HTMLImageElement): number[];
|
|
63
|
-
/**
|
|
64
|
-
* Trim a track to the actual frame count of its row. A row with 0 detected
|
|
65
|
-
* frames degrades to the first frame (the atlas is still loading or corrupt)
|
|
66
|
-
* so the pet never renders blank.
|
|
29
|
+
* Trim a track to the actual frame count of its row (the manifest's per-row
|
|
30
|
+
* counts are authoritative; this is a last-line guard against a definition
|
|
31
|
+
* whose row count disagrees with its track table). A row with 0 detected
|
|
32
|
+
* frames degrades to the first frame so the pet never renders blank.
|
|
67
33
|
*/
|
|
68
34
|
export declare function trimTrack(track: TrackDef, frameCount: number): TrackDef;
|
|
69
35
|
//# sourceMappingURL=spritesheet.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spritesheet.d.ts","sourceRoot":"","sources":["../../../src/client/spritesheet.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"spritesheet.d.ts","sourceRoot":"","sources":["../../../src/client/spritesheet.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAE1D,qDAAqD;AACrD,MAAM,MAAM,QAAQ,GAAG,WAAW,CAAA;AAElC,mEAAmE;AACnE,wBAAgB,UAAU,CAAC,SAAS,EAAE,YAAY,GAAG,MAAM,CAa1D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAE3H;AAED,uCAAuC;AACvC,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAErD;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,QAAQ,CAQvE"}
|
|
@@ -1,40 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 5 failed, 6 waiting, 7 running, 8 review
|
|
8
|
-
*
|
|
9
|
-
* Frame counts and per-frame durations are per-track definitions below; the
|
|
10
|
-
* whale-girl atlas is produced by the hatch-pet pipeline, so calibrate
|
|
11
|
-
* `TRACKS` against the actual run (`pet_request.json` frame counts) when the
|
|
12
|
-
* asset lands. Tracks that do not loop hand off to `fallback`.
|
|
2
|
+
* Spritesheet geometry helpers — parameterized by the pet definition the
|
|
3
|
+
* host serves over '/api/pet/pets', so the browser half renders any registry
|
|
4
|
+
* entry without per-pet code. The per-track tables (frames, durations, loop,
|
|
5
|
+
* fallback) also come from the registry; these helpers only place frames,
|
|
6
|
+
* guard track lengths, and map the fixed 9-row animation contract.
|
|
13
7
|
* @module @linxin666/dsh-pet/client/spritesheet
|
|
14
8
|
*/
|
|
15
|
-
/**
|
|
16
|
-
export const FRAME_WIDTH = 192;
|
|
17
|
-
export const FRAME_HEIGHT = 208;
|
|
18
|
-
/** Columns per row (max frames per track). */
|
|
19
|
-
export const FRAME_COLUMNS = 8;
|
|
20
|
-
/**
|
|
21
|
-
* Track definitions for the whale-girl. Durations are tuned for a soft,
|
|
22
|
-
* slow-healing feel (roughly 2.5× the earlier fast draft — the pet should
|
|
23
|
-
* breathe, not race); calibrate frame counts against the hatch-pet run when
|
|
24
|
-
* the asset lands (rows may carry 4–8 frames).
|
|
25
|
-
*/
|
|
26
|
-
export const TRACKS = {
|
|
27
|
-
idle: { frames: [0, 1, 2, 3, 4, 5], durations: [400, 400, 500, 400, 400, 500], loop: true },
|
|
28
|
-
'running-right': { frames: [0, 1, 2, 3, 4, 5, 6, 7], durations: [225, 225, 225, 225, 225, 225, 225, 225], loop: true },
|
|
29
|
-
'running-left': { frames: [0, 1, 2, 3, 4, 5, 6, 7], durations: [225, 225, 225, 225, 225, 225, 225, 225], loop: true },
|
|
30
|
-
waving: { frames: [0, 1, 2, 3], durations: [350, 350, 350, 350], loop: true },
|
|
31
|
-
jumping: { frames: [0, 1, 2, 3, 4], durations: [300, 300, 300, 350, 350], loop: false, fallback: 'idle' },
|
|
32
|
-
failed: { frames: [0, 1, 2, 3, 4, 5, 6, 7], durations: [450, 450, 450, 500, 550, 600, 450, 450], loop: false, fallback: 'idle' },
|
|
33
|
-
waiting: { frames: [0, 1, 2, 3, 4, 5], durations: [450, 450, 500, 450, 450, 500], loop: true },
|
|
34
|
-
running: { frames: [0, 1, 2, 3, 4, 5], durations: [250, 250, 250, 250, 250, 250], loop: true },
|
|
35
|
-
review: { frames: [0, 1, 2, 3, 4, 5], durations: [550, 550, 550, 550, 550, 550], loop: true },
|
|
36
|
-
};
|
|
37
|
-
/** Row index of one animation track (mirrors state.ts rowOf). */
|
|
9
|
+
/** Row index of one animation track (the fixed 9-row contract). */
|
|
38
10
|
export function rowOfTrack(animation) {
|
|
39
11
|
const rows = {
|
|
40
12
|
idle: 0,
|
|
@@ -56,61 +28,21 @@ export function rowOfTrack(animation) {
|
|
|
56
28
|
* atlas coordinates here would drift each frame by the scale factor and
|
|
57
29
|
* render torn/overlapping frames.
|
|
58
30
|
*/
|
|
59
|
-
export function framePosition(row, col, scale = 1) {
|
|
60
|
-
return { x: -col *
|
|
31
|
+
export function framePosition(cell, columns, row, col, scale = 1) {
|
|
32
|
+
return { x: -col * cell.width * scale, y: -row * cell.height * scale };
|
|
61
33
|
}
|
|
62
34
|
/** Total duration of one track, ms. */
|
|
63
35
|
export function trackDuration(track) {
|
|
64
36
|
return track.durations.reduce((sum, d) => sum + d, 0);
|
|
65
37
|
}
|
|
66
38
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* @param image - the fully decoded spritesheet (natural size 1536×1872).
|
|
72
|
-
* @returns per-row frame counts, length 9.
|
|
73
|
-
*/
|
|
74
|
-
export function detectFrameCounts(image) {
|
|
75
|
-
const canvas = document.createElement('canvas');
|
|
76
|
-
canvas.width = image.naturalWidth;
|
|
77
|
-
canvas.height = image.naturalHeight;
|
|
78
|
-
const ctx = canvas.getContext('2d');
|
|
79
|
-
if (ctx === null)
|
|
80
|
-
return Array.from({ length: 9 }, () => FRAME_COLUMNS);
|
|
81
|
-
ctx.drawImage(image, 0, 0);
|
|
82
|
-
const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
|
83
|
-
const counts = [];
|
|
84
|
-
const stride = FRAME_COLUMNS * FRAME_WIDTH;
|
|
85
|
-
const probeStep = 8;
|
|
86
|
-
const margin = 12;
|
|
87
|
-
for (let row = 0; row < 9; row++) {
|
|
88
|
-
let count = 0;
|
|
89
|
-
for (let col = 0; col < FRAME_COLUMNS; col++) {
|
|
90
|
-
let hasContent = false;
|
|
91
|
-
const x0 = col * FRAME_WIDTH;
|
|
92
|
-
const y0 = row * FRAME_HEIGHT;
|
|
93
|
-
for (let y = y0 + margin; y < y0 + FRAME_HEIGHT - margin && !hasContent; y += probeStep) {
|
|
94
|
-
for (let x = x0 + margin; x < x0 + FRAME_WIDTH - margin && !hasContent; x += probeStep) {
|
|
95
|
-
const idx = (y * stride + x) * 4;
|
|
96
|
-
if ((data[idx + 3] ?? 0) > 8)
|
|
97
|
-
hasContent = true;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
if (hasContent)
|
|
101
|
-
count += 1;
|
|
102
|
-
}
|
|
103
|
-
counts.push(count);
|
|
104
|
-
}
|
|
105
|
-
return counts;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Trim a track to the actual frame count of its row. A row with 0 detected
|
|
109
|
-
* frames degrades to the first frame (the atlas is still loading or corrupt)
|
|
110
|
-
* so the pet never renders blank.
|
|
39
|
+
* Trim a track to the actual frame count of its row (the manifest's per-row
|
|
40
|
+
* counts are authoritative; this is a last-line guard against a definition
|
|
41
|
+
* whose row count disagrees with its track table). A row with 0 detected
|
|
42
|
+
* frames degrades to the first frame so the pet never renders blank.
|
|
111
43
|
*/
|
|
112
44
|
export function trimTrack(track, frameCount) {
|
|
113
|
-
const n = Math.max(1, Math.min(frameCount, track.frames.length));
|
|
45
|
+
const n = Math.max(1, Math.min(frameCount, track.frames.length, track.durations.length));
|
|
114
46
|
return {
|
|
115
47
|
frames: track.frames.slice(0, n),
|
|
116
48
|
durations: track.durations.slice(0, n),
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,43 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-pet host half — mounts the pet service and its HTTP routes. The
|
|
3
|
-
* browser half (the
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* browser half (the './client' entry) renders the selected pet and drives it
|
|
4
|
+
* through the same-origin '/api/pet/*' JSON endpoints plus the '/pet/<id>/*'
|
|
5
|
+
* media route. The host builds the multi-pet registry once at startup from
|
|
6
|
+
* the package assets, the hatch-pet custom pets directory, and composed
|
|
7
|
+
* config entries; adding a pet means dropping a manifest + atlas into one of
|
|
8
|
+
* those sources, never touching host or client code. Install via
|
|
9
|
+
* 'dsh plugin --profile web add link:<dsh-web-ui>/packages/dsh-pet'; the
|
|
10
|
+
* cordis.patch.yml inserts this plugin row.
|
|
7
11
|
* @module @linxin666/dsh-pet
|
|
8
12
|
*/
|
|
9
13
|
import { Context } from '@deepseek-ai/cordis';
|
|
10
14
|
import z from 'schemastery';
|
|
11
15
|
import { type PetConfig } from './service.ts';
|
|
12
16
|
export { PetService } from './service.ts';
|
|
13
|
-
export type { PetConfig, PetInteractResult, PetStateView, } from './service.ts';
|
|
17
|
+
export type { PetConfig, PetInteractResult, PetSettingsSection, PetStateView, } from './service.ts';
|
|
14
18
|
export { AFFINITY_MAX, AFFINITY_RANKS, applyInteraction, applyTurnReward, emptyAffinity, rankOf, } from './affinity.ts';
|
|
15
19
|
export type { AffinityConfig, AffinityState, InteractionOutcome, PetInteraction, } from './affinity.ts';
|
|
16
20
|
export { animationForPhase, PetStateMachine, rowOf, } from './state.ts';
|
|
17
21
|
export type { ActivityPhase, PetAnimation, PetStateConfig, PetStateInput, PetStateSnapshot, } from './state.ts';
|
|
18
22
|
export { consumeTreat, defaultTreatConfig, emptyTreatLedger, settleTreatGrants, } from './treats.ts';
|
|
19
23
|
export type { TreatConfig, TreatLedger, TreatSettlement } from './treats.ts';
|
|
20
|
-
export { defaultDisplayConfig, emptyPersist, loadPetPersist, petHomeDir, savePetPersist, } from './persist.ts';
|
|
24
|
+
export { DEFAULT_PET_ID, DEFAULT_PET_NAME, PET_NAME_MAX_LENGTH, defaultDisplayConfig, emptyPersist, loadPetPersist, petHomeDir, savePetPersist, } from './persist.ts';
|
|
21
25
|
export type { PetDisplayConfig, PetPersist } from './persist.ts';
|
|
22
|
-
export {
|
|
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.ts';
|
|
27
|
+
export type { PetDefinition, PetEntry, PetManifest, PetRegistry, PetRegistryOptions, PetTrackDef, PetTrackOverride, } from './registry.ts';
|
|
28
|
+
export { makePetRoutes, PET_API_PREFIX, PET_ASSET_PREFIX, } from './routes.ts';
|
|
23
29
|
/** Stable cordis plugin name (matches cordis.patch.yml insert id). */
|
|
24
30
|
export declare const name = "pet";
|
|
25
31
|
/** Services required before the pet can mount its surfaces. */
|
|
26
32
|
export declare const inject: string[];
|
|
27
|
-
/**
|
|
28
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Settings section schema: pet selection and display fields the web settings
|
|
35
|
+
* surface edits. petId is a plain string on purpose: the service clamps the
|
|
36
|
+
* resolved value against the registry, so a stored selection that points at
|
|
37
|
+
* a removed pet cannot invalidate the section (a strict union would refuse
|
|
38
|
+
* the whole registration). The settings card renders the actual registry
|
|
39
|
+
* choices itself from '/api/pet/pets'.
|
|
40
|
+
*/
|
|
41
|
+
export declare function makePetSettingsSchema(fallbackPetId: string): z<Schemastery.ObjectS<{
|
|
29
42
|
visible: z<boolean, boolean>;
|
|
30
43
|
size: z<number, number>;
|
|
31
44
|
right: z<number, number>;
|
|
32
45
|
bottom: z<number, number>;
|
|
33
|
-
|
|
46
|
+
petId: z<string, string>;
|
|
34
47
|
enabled: z<boolean, boolean>;
|
|
35
48
|
}>, Schemastery.ObjectT<{
|
|
36
49
|
visible: z<boolean, boolean>;
|
|
37
50
|
size: z<number, number>;
|
|
38
51
|
right: z<number, number>;
|
|
39
52
|
bottom: z<number, number>;
|
|
40
|
-
|
|
53
|
+
petId: z<string, string>;
|
|
41
54
|
enabled: z<boolean, boolean>;
|
|
42
55
|
}>>;
|
|
43
56
|
/** Register the pet service and its API + asset routes on the context. */
|
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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAG7C,OAAO,CAAC,MAAM,aAAa,CAAA;AAC3B,OAAO,EAAsC,KAAK,SAAS,EAA2B,MAAM,cAAc,CAAA;AAK1G,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,kBAAkB,EAClB,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,cAAc,EACd,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,cAAc,EACd,UAAU,EACV,cAAc,GACf,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAChE,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,cAAc,EACd,kBAAkB,GACnB,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,aAAa,EACb,QAAQ,EACR,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,WAAW,EACX,gBAAgB,GACjB,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,aAAa,EACb,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;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM;;;;;;;;;;;;;;IAS1D;AAED,0EAA0E;AAC1E,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,SAAc,GAAG,IAAI,CA+DhE"}
|
package/lib/types/index.js
CHANGED
|
@@ -1,61 +1,81 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-pet host half — mounts the pet service and its HTTP routes. The
|
|
3
|
-
* browser half (the
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* browser half (the './client' entry) renders the selected pet and drives it
|
|
4
|
+
* through the same-origin '/api/pet/*' JSON endpoints plus the '/pet/<id>/*'
|
|
5
|
+
* media route. The host builds the multi-pet registry once at startup from
|
|
6
|
+
* the package assets, the hatch-pet custom pets directory, and composed
|
|
7
|
+
* config entries; adding a pet means dropping a manifest + atlas into one of
|
|
8
|
+
* those sources, never touching host or client code. Install via
|
|
9
|
+
* 'dsh plugin --profile web add link:<dsh-web-ui>/packages/dsh-pet'; the
|
|
10
|
+
* cordis.patch.yml inserts this plugin row.
|
|
7
11
|
* @module @linxin666/dsh-pet
|
|
8
12
|
*/
|
|
9
13
|
import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
10
14
|
import z from 'schemastery';
|
|
11
15
|
import { PetService, PET_SETTINGS_NAMESPACE } from "./service.js";
|
|
12
|
-
import { makePetRoutes
|
|
13
|
-
import {
|
|
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";
|
|
14
19
|
export { PetService } from "./service.js";
|
|
15
20
|
export { AFFINITY_MAX, AFFINITY_RANKS, applyInteraction, applyTurnReward, emptyAffinity, rankOf, } from "./affinity.js";
|
|
16
21
|
export { animationForPhase, PetStateMachine, rowOf, } from "./state.js";
|
|
17
22
|
export { consumeTreat, defaultTreatConfig, emptyTreatLedger, settleTreatGrants, } from "./treats.js";
|
|
18
|
-
export { defaultDisplayConfig, emptyPersist, loadPetPersist, petHomeDir, savePetPersist, } from "./persist.js";
|
|
19
|
-
export {
|
|
23
|
+
export { DEFAULT_PET_ID, DEFAULT_PET_NAME, PET_NAME_MAX_LENGTH, defaultDisplayConfig, emptyPersist, loadPetPersist, petHomeDir, savePetPersist, } from "./persist.js";
|
|
24
|
+
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";
|
|
25
|
+
export { makePetRoutes, PET_API_PREFIX, PET_ASSET_PREFIX, } from "./routes.js";
|
|
20
26
|
/** Stable cordis plugin name (matches cordis.patch.yml insert id). */
|
|
21
27
|
export const name = 'pet';
|
|
22
28
|
/** Services required before the pet can mount its surfaces. */
|
|
23
29
|
export const inject = ['webServer'];
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Settings section schema: pet selection and display fields the web settings
|
|
32
|
+
* surface edits. petId is a plain string on purpose: the service clamps the
|
|
33
|
+
* resolved value against the registry, so a stored selection that points at
|
|
34
|
+
* a removed pet cannot invalidate the section (a strict union would refuse
|
|
35
|
+
* the whole registration). The settings card renders the actual registry
|
|
36
|
+
* choices itself from '/api/pet/pets'.
|
|
37
|
+
*/
|
|
38
|
+
export function makePetSettingsSchema(fallbackPetId) {
|
|
39
|
+
return z.object({
|
|
40
|
+
visible: z.boolean().default(true),
|
|
41
|
+
size: z.number().step(1).min(DISPLAY_SIZE_MIN).max(DISPLAY_SIZE_MAX).default(160),
|
|
42
|
+
right: z.number().step(1).min(0).max(DISPLAY_INSET_MAX).default(24),
|
|
43
|
+
bottom: z.number().step(1).min(0).max(DISPLAY_INSET_MAX).default(20),
|
|
44
|
+
petId: z.string().default(fallbackPetId),
|
|
45
|
+
enabled: z.boolean().default(true),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
33
48
|
/** Register the pet service and its API + asset routes on the context. */
|
|
34
49
|
export function apply(ctx, config = {}) {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
//
|
|
50
|
+
const registry = config.registry
|
|
51
|
+
?? loadPetRegistry({
|
|
52
|
+
packageRoot: petPackageRoot(import.meta.url),
|
|
53
|
+
...(config.pets === undefined ? {} : { extra: config.pets }),
|
|
54
|
+
});
|
|
55
|
+
const service = new PetService(ctx, { ...config, registry });
|
|
56
|
+
// The settings surface edits the pet selection + display config through
|
|
57
|
+
// the 'pet' namespace. The composition 'base' starts as the persisted
|
|
58
|
+
// pet.json values (clamped to schema bounds), so an empty user layer
|
|
59
|
+
// resolves to exactly what the pet already shows — a fresh deployment
|
|
60
|
+
// never overwrites a customized layout, and reset re-inherits it. Runtime
|
|
61
|
+
// drag interactions mirror back into the settings document through the
|
|
62
|
+
// service (see syncSettingsFromPet), keeping both views consistent.
|
|
43
63
|
let current = () => base;
|
|
44
64
|
const base = {
|
|
45
65
|
visible: service.display().visible,
|
|
46
66
|
size: service.display().size,
|
|
47
67
|
right: service.display().right,
|
|
48
68
|
bottom: service.display().bottom,
|
|
49
|
-
|
|
69
|
+
petId: service.selectedPetId(),
|
|
50
70
|
enabled: config.enabled ?? true,
|
|
51
71
|
};
|
|
52
72
|
// The browser half talks to the pet through same-origin JSON endpoints and
|
|
53
|
-
// loads
|
|
54
|
-
// platform-registered, so the pet serves its own API — the same
|
|
55
|
-
// dsh-remote-web-ui's /api/pair family). The routes are
|
|
56
|
-
// the plugin is enabled; toggling the setting off makes
|
|
57
|
-
// disappear until it is re-enabled.
|
|
58
|
-
const routes = makePetRoutes({ service
|
|
73
|
+
// loads each pet's atlas from the registry's own media route (RPC domains
|
|
74
|
+
// are platform-registered, so the pet serves its own API — the same
|
|
75
|
+
// pattern as dsh-remote-web-ui's /api/pair family). The routes are
|
|
76
|
+
// registered while the plugin is enabled; toggling the setting off makes
|
|
77
|
+
// the pet API disappear until it is re-enabled.
|
|
78
|
+
const routes = makePetRoutes({ service });
|
|
59
79
|
let disposeRoutes;
|
|
60
80
|
const syncRoutes = () => {
|
|
61
81
|
const enabled = current().enabled ?? true;
|
|
@@ -71,7 +91,7 @@ export function apply(ctx, config = {}) {
|
|
|
71
91
|
disposeRoutes = undefined;
|
|
72
92
|
}
|
|
73
93
|
};
|
|
74
|
-
installSettingsSection(ctx, settingsNamespace(PET_SETTINGS_NAMESPACE),
|
|
94
|
+
installSettingsSection(ctx, settingsNamespace(PET_SETTINGS_NAMESPACE), makePetSettingsSchema(service.selectedPetId()), base, {
|
|
75
95
|
setSource: (source) => { current = source; },
|
|
76
96
|
onChange: () => {
|
|
77
97
|
const section = current();
|
package/lib/types/ledger.d.ts
CHANGED
|
@@ -47,8 +47,10 @@ export declare class PetLedger {
|
|
|
47
47
|
takeDirty(): boolean;
|
|
48
48
|
/** Replace the display block (clamping stays a caller concern). */
|
|
49
49
|
setDisplay(display: PetDisplayConfig): void;
|
|
50
|
-
/** Replace the pet
|
|
51
|
-
|
|
50
|
+
/** Replace the selected pet id (validation stays a caller concern). */
|
|
51
|
+
setPetId(petId: string): void;
|
|
52
|
+
/** Replace one pet's display name (validation stays a caller concern). */
|
|
53
|
+
setPetName(petId: string, name: string): void;
|
|
52
54
|
/**
|
|
53
55
|
* Settle the treat economy (work + time output since the last settlement).
|
|
54
56
|
* A zero-gain first settlement still starts the time clock (anchor write),
|
|
@@ -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;CAC9B;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,OAAO,CAAC,OAAO,CAAY;IAC3B,oFAAoF;IACpF,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,sBAAsB,CAAI;IAClC,OAAO,CAAC,KAAK,CAAQ;gBAET,OAAO,EAAE,UAAU,EAAE,MAAM,GAAE,YAAiB;IAM1D,iDAAiD;IACjD,IAAI,QAAQ,IAAI,cAAc,CAE7B;IAED,qEAAqE;IACrE,IAAI,QAAQ,IAAI,UAAU,CAEzB;IAED,qCAAqC;IACrC,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,+DAA+D;IAC/D,SAAS,IAAI,OAAO;IAMpB,mEAAmE;IACnE,UAAU,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAK3C,
|
|
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;CAC9B;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,OAAO,CAAC,OAAO,CAAY;IAC3B,oFAAoF;IACpF,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,sBAAsB,CAAI;IAClC,OAAO,CAAC,KAAK,CAAQ;gBAET,OAAO,EAAE,UAAU,EAAE,MAAM,GAAE,YAAiB;IAM1D,iDAAiD;IACjD,IAAI,QAAQ,IAAI,cAAc,CAE7B;IAED,qEAAqE;IACrE,IAAI,QAAQ,IAAI,UAAU,CAEzB;IAED,qCAAqC;IACrC,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,+DAA+D;IAC/D,SAAS,IAAI,OAAO;IAMpB,mEAAmE;IACnE,UAAU,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAK3C,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAM7B,0EAA0E;IAC1E,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAK7C;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAapC;;;;OAIG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IASnE,8EAA8E;IAC9E,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IASxC,OAAO,CAAC,eAAe;IASvB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,uBAAuB;IAyBtE,kDAAkD;IAClD,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe;CAG7C"}
|
package/lib/types/ledger.js
CHANGED
|
@@ -49,9 +49,16 @@ export class PetLedger {
|
|
|
49
49
|
this.current = { ...this.current, display };
|
|
50
50
|
this.dirty = true;
|
|
51
51
|
}
|
|
52
|
-
/** Replace the pet
|
|
53
|
-
|
|
54
|
-
this.current
|
|
52
|
+
/** Replace the selected pet id (validation stays a caller concern). */
|
|
53
|
+
setPetId(petId) {
|
|
54
|
+
if (this.current.petId === petId)
|
|
55
|
+
return;
|
|
56
|
+
this.current = { ...this.current, petId };
|
|
57
|
+
this.dirty = true;
|
|
58
|
+
}
|
|
59
|
+
/** Replace one pet's display name (validation stays a caller concern). */
|
|
60
|
+
setPetName(petId, name) {
|
|
61
|
+
this.current = { ...this.current, names: { ...this.current.names, [petId]: name } };
|
|
55
62
|
this.dirty = true;
|
|
56
63
|
}
|
|
57
64
|
/**
|
|
@@ -118,7 +125,7 @@ export class PetLedger {
|
|
|
118
125
|
const consume = consumeTreat(this.current.treats);
|
|
119
126
|
if (!consume.ok) {
|
|
120
127
|
return {
|
|
121
|
-
reaction: '
|
|
128
|
+
reaction: '没有小鱼干了,多陪我工作一会儿吧~',
|
|
122
129
|
delta: 0,
|
|
123
130
|
affinity: this.affinityView(nowMs),
|
|
124
131
|
};
|
package/lib/types/persist.d.ts
CHANGED
|
@@ -24,14 +24,21 @@ export declare const DISPLAY_SIZE_MAX = 512;
|
|
|
24
24
|
export declare const DISPLAY_INSET_MAX = 10000;
|
|
25
25
|
/** Everything persisted for the pet. */
|
|
26
26
|
export interface PetPersist {
|
|
27
|
-
/**
|
|
28
|
-
|
|
27
|
+
/** Selected pet id (a registry entry; clamped at service startup). */
|
|
28
|
+
petId: string;
|
|
29
|
+
/**
|
|
30
|
+
* Per-pet display names keyed by pet id. A pet without an entry falls back
|
|
31
|
+
* to its manifest displayName, so only user renames are stored here.
|
|
32
|
+
*/
|
|
33
|
+
names: Record<string, string>;
|
|
29
34
|
affinity: AffinityState;
|
|
30
35
|
/** Treat (小鱼干) stock ledger. */
|
|
31
36
|
treats: TreatLedger;
|
|
32
37
|
display: PetDisplayConfig;
|
|
33
38
|
}
|
|
34
|
-
/**
|
|
39
|
+
/** Pet id the legacy single-pet installs resolve to on migration. */
|
|
40
|
+
export declare const DEFAULT_PET_ID = "whale-girl";
|
|
41
|
+
/** Default pet name (used only when a manifest carries no displayName). */
|
|
35
42
|
export declare const DEFAULT_PET_NAME = "\u9CB8\u9C7C\u5A18";
|
|
36
43
|
/** Name constraints. */
|
|
37
44
|
export declare const PET_NAME_MAX_LENGTH = 20;
|
|
@@ -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,
|
|
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,CAiDrE;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,GAAE,MAAqB,GAAG,IAAI,CAMjF"}
|
package/lib/types/persist.js
CHANGED
|
@@ -19,13 +19,16 @@ export const defaultDisplayConfig = {
|
|
|
19
19
|
export const DISPLAY_SIZE_MIN = 32;
|
|
20
20
|
export const DISPLAY_SIZE_MAX = 512;
|
|
21
21
|
export const DISPLAY_INSET_MAX = 10_000;
|
|
22
|
-
/**
|
|
22
|
+
/** Pet id the legacy single-pet installs resolve to on migration. */
|
|
23
|
+
export const DEFAULT_PET_ID = 'whale-girl';
|
|
24
|
+
/** Default pet name (used only when a manifest carries no displayName). */
|
|
23
25
|
export const DEFAULT_PET_NAME = '鲸鱼娘';
|
|
24
26
|
/** Name constraints. */
|
|
25
27
|
export const PET_NAME_MAX_LENGTH = 20;
|
|
26
28
|
export function emptyPersist() {
|
|
27
29
|
return {
|
|
28
|
-
|
|
30
|
+
petId: DEFAULT_PET_ID,
|
|
31
|
+
names: {},
|
|
29
32
|
affinity: emptyAffinity(),
|
|
30
33
|
treats: emptyTreatLedger(),
|
|
31
34
|
display: { ...defaultDisplayConfig },
|
|
@@ -43,6 +46,21 @@ export function petHomeDir() {
|
|
|
43
46
|
function finiteNum(value, fallback) {
|
|
44
47
|
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
45
48
|
}
|
|
49
|
+
/** Sanitize the per-pet names map (string keys, non-empty trimmed values). */
|
|
50
|
+
function loadPetNames(parsed) {
|
|
51
|
+
const names = {};
|
|
52
|
+
if (typeof parsed.names !== 'object' || parsed.names === null)
|
|
53
|
+
return names;
|
|
54
|
+
for (const [id, value] of Object.entries(parsed.names)) {
|
|
55
|
+
if (id === '' || typeof value !== 'string')
|
|
56
|
+
continue;
|
|
57
|
+
const name = value.trim();
|
|
58
|
+
if (name === '')
|
|
59
|
+
continue;
|
|
60
|
+
names[id] = name.slice(0, PET_NAME_MAX_LENGTH);
|
|
61
|
+
}
|
|
62
|
+
return names;
|
|
63
|
+
}
|
|
46
64
|
/** Clamp one count/score into [0, max]. */
|
|
47
65
|
function clamp(value, max) {
|
|
48
66
|
return Math.min(max, Math.max(0, value));
|
|
@@ -77,10 +95,19 @@ export function loadPetPersist(dir = petHomeDir()) {
|
|
|
77
95
|
right: Math.round(clamp(finiteNum(rawDisplay.right, base.display.right), DISPLAY_INSET_MAX)),
|
|
78
96
|
bottom: Math.round(clamp(finiteNum(rawDisplay.bottom, base.display.bottom), DISPLAY_INSET_MAX)),
|
|
79
97
|
};
|
|
98
|
+
const petId = typeof parsed.petId === 'string' && parsed.petId.trim() !== ''
|
|
99
|
+
? parsed.petId.trim()
|
|
100
|
+
: base.petId;
|
|
101
|
+
const names = loadPetNames(parsed);
|
|
102
|
+
// Legacy migration: pre-registry installs persisted one flat `name`
|
|
103
|
+
// field. Move it onto the selected pet (the legacy whale-girl unless the
|
|
104
|
+
// file already names another pet) so renames survive the upgrade.
|
|
105
|
+
if (typeof parsed.name === 'string' && parsed.name.trim() !== '' && names[petId] === undefined) {
|
|
106
|
+
names[petId] = parsed.name.trim().slice(0, PET_NAME_MAX_LENGTH);
|
|
107
|
+
}
|
|
80
108
|
return {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
: base.name,
|
|
109
|
+
petId,
|
|
110
|
+
names,
|
|
84
111
|
affinity,
|
|
85
112
|
treats,
|
|
86
113
|
display,
|