@linxin666/dsh-pet 0.3.21 → 0.3.22
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PetSprite.d.ts","sourceRoot":"","sources":["../../../src/client/PetSprite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAkE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAEjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAIjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,UAAU,EAAE,aAAa,CAAA;IACzB,qDAAqD;IACrD,OAAO,EAAE,gBAAgB,CAAA;IACzB,sCAAsC;IACtC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAClD,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9C,uDAAuD;IACvD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,4DAA4D;IAC5D,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB;;;;OAIG;IACH,GAAG,CAAC,EAAE,SAAS,CAAA;IACf;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9D;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,qEAAqE;IACrE,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;
|
|
1
|
+
{"version":3,"file":"PetSprite.d.ts","sourceRoot":"","sources":["../../../src/client/PetSprite.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAkE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAEjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAIjD,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAA;AAGjC,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,UAAU,EAAE,aAAa,CAAA;IACzB,qDAAqD;IACrD,OAAO,EAAE,gBAAgB,CAAA;IACzB,sCAAsC;IACtC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;IAC5B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,sCAAsC;IACtC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,+BAA+B;IAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAClD,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9C,uDAAuD;IACvD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,4DAA4D;IAC5D,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,2DAA2D;IAC3D,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB;;;;OAIG;IACH,GAAG,CAAC,EAAE,SAAS,CAAA;IACf;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9D;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,qEAAqE;IACrE,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,+CAA+C;IAC/C,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1B;AAkJD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,WAAW,CA+jB5D"}
|
|
@@ -67,7 +67,7 @@ function StatusOrnament(props) {
|
|
|
67
67
|
const delta = now - last;
|
|
68
68
|
last = now;
|
|
69
69
|
elapsed += delta;
|
|
70
|
-
|
|
70
|
+
let duration = decoration.durations[index] ?? 120;
|
|
71
71
|
// The segment's frame rate (duration ms, typically 90-160) is far
|
|
72
72
|
// below the rAF cadence, so a 60fps loop would spend ~90% of its
|
|
73
73
|
// ticks doing nothing. Schedule by the remaining time to the next
|
|
@@ -81,6 +81,10 @@ function StatusOrnament(props) {
|
|
|
81
81
|
index += 1;
|
|
82
82
|
else if (decoration.loop)
|
|
83
83
|
index = segment.from;
|
|
84
|
+
// Durations are per frame: a catch-up that crosses frames must
|
|
85
|
+
// subtract and schedule with the frame it lands on, not the one
|
|
86
|
+
// the tick started from.
|
|
87
|
+
duration = decoration.durations[index] ?? 120;
|
|
84
88
|
} while (elapsed >= duration);
|
|
85
89
|
// Only advance the background when the frame actually changes.
|
|
86
90
|
el.style.backgroundPosition = position(index);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-pet",
|
|
3
3
|
"description": "Multi-pet companion plugin for the dsh web GUI: a registry-driven floating pet that reacts to model activity, with per-pet naming, petting/feeding interactions and an affinity score",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.22",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -858,6 +858,68 @@ describe('PetSprite status decoration (pet-center M5, #567)', () => {
|
|
|
858
858
|
expect(el.style.backgroundPosition).toBe('0px 0px')
|
|
859
859
|
})
|
|
860
860
|
|
|
861
|
+
it('advances uneven per-frame durations on each frame own clock', () => {
|
|
862
|
+
vi.spyOn(window, 'matchMedia').mockReturnValue({
|
|
863
|
+
matches: false,
|
|
864
|
+
media: '(prefers-reduced-motion: reduce)',
|
|
865
|
+
onchange: null,
|
|
866
|
+
addEventListener: () => {},
|
|
867
|
+
removeEventListener: () => {},
|
|
868
|
+
addListener: () => {},
|
|
869
|
+
removeListener: () => {},
|
|
870
|
+
dispatchEvent: () => false,
|
|
871
|
+
})
|
|
872
|
+
let now = 0
|
|
873
|
+
vi.spyOn(performance, 'now').mockImplementation(() => now)
|
|
874
|
+
const timers: { at: number; callback: () => void }[] = []
|
|
875
|
+
let timerId = 0
|
|
876
|
+
vi.spyOn(window, 'setTimeout').mockImplementation(((callback: () => void, delay = 0) => {
|
|
877
|
+
timers.push({ at: now + delay, callback })
|
|
878
|
+
return ++timerId
|
|
879
|
+
}) as typeof window.setTimeout)
|
|
880
|
+
vi.spyOn(window, 'clearTimeout').mockImplementation(() => {})
|
|
881
|
+
const frames: FrameRequestCallback[] = []
|
|
882
|
+
vi.spyOn(window, 'requestAnimationFrame').mockImplementation(callback => {
|
|
883
|
+
frames.push(callback)
|
|
884
|
+
return frames.length
|
|
885
|
+
})
|
|
886
|
+
vi.spyOn(window, 'cancelAnimationFrame').mockImplementation(() => {})
|
|
887
|
+
const uneven: DecorationView = { ...decoration, durations: [100, 300, 50, 50] }
|
|
888
|
+
renderPet({ snapshot: { ...snapshot, bubble: '正在思考', phase: 'thinking', decoration: uneven } })
|
|
889
|
+
const el = ornament()!
|
|
890
|
+
const step = (ms: number): void => {
|
|
891
|
+
for (const callback of frames.splice(0)) callback(now)
|
|
892
|
+
now += ms
|
|
893
|
+
for (;;) {
|
|
894
|
+
const due = timers.filter(t => t.at <= now)
|
|
895
|
+
if (due.length === 0) break
|
|
896
|
+
for (const t of due) {
|
|
897
|
+
const idx = timers.indexOf(t)
|
|
898
|
+
if (idx >= 0) timers.splice(idx, 1)
|
|
899
|
+
t.callback()
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
expect(el.style.backgroundPosition).toBe('0px 0px')
|
|
904
|
+
// One 500 ms segment (100 + 300 + 50 + 50) plus 1 ms: the catch-up loop
|
|
905
|
+
// must subtract each frame's own duration, so it lands one full cycle
|
|
906
|
+
// later on frame 0 with 99 ms of its 100 ms left — not on frame 1.
|
|
907
|
+
act(() => { step(501) })
|
|
908
|
+
expect(el.style.backgroundPosition).toBe('0px 0px')
|
|
909
|
+
act(() => { step(99) })
|
|
910
|
+
expect(el.style.backgroundPosition).toBe('-24px 0px')
|
|
911
|
+
// Frame 1 lasts 300 ms and the next wake uses that duration.
|
|
912
|
+
act(() => { step(299) })
|
|
913
|
+
expect(el.style.backgroundPosition).toBe('-24px 0px')
|
|
914
|
+
act(() => { step(1) })
|
|
915
|
+
expect(el.style.backgroundPosition).toBe('-48px 0px')
|
|
916
|
+
// Frames 2 and 3 are 50 ms each.
|
|
917
|
+
act(() => { step(50) })
|
|
918
|
+
expect(el.style.backgroundPosition).toBe('-72px 0px')
|
|
919
|
+
act(() => { step(50) })
|
|
920
|
+
expect(el.style.backgroundPosition).toBe('0px 0px')
|
|
921
|
+
})
|
|
922
|
+
|
|
861
923
|
it('holds the segment last frame when the loop is off and stops scheduling', () => {
|
|
862
924
|
vi.spyOn(window, 'matchMedia').mockReturnValue({
|
|
863
925
|
matches: false,
|
package/src/client/PetSprite.tsx
CHANGED
|
@@ -139,7 +139,7 @@ function StatusOrnament(props: { decoration: DecorationView; phase: ActivityPhas
|
|
|
139
139
|
const delta = now - last
|
|
140
140
|
last = now
|
|
141
141
|
elapsed += delta
|
|
142
|
-
|
|
142
|
+
let duration = decoration.durations[index] ?? 120
|
|
143
143
|
// The segment's frame rate (duration ms, typically 90-160) is far
|
|
144
144
|
// below the rAF cadence, so a 60fps loop would spend ~90% of its
|
|
145
145
|
// ticks doing nothing. Schedule by the remaining time to the next
|
|
@@ -151,6 +151,10 @@ function StatusOrnament(props: { decoration: DecorationView; phase: ActivityPhas
|
|
|
151
151
|
elapsed -= duration
|
|
152
152
|
if (index < segment.to) index += 1
|
|
153
153
|
else if (decoration.loop) index = segment.from
|
|
154
|
+
// Durations are per frame: a catch-up that crosses frames must
|
|
155
|
+
// subtract and schedule with the frame it lands on, not the one
|
|
156
|
+
// the tick started from.
|
|
157
|
+
duration = decoration.durations[index] ?? 120
|
|
154
158
|
} while (elapsed >= duration)
|
|
155
159
|
// Only advance the background when the frame actually changes.
|
|
156
160
|
el.style.backgroundPosition = position(index)
|