@linxin666/dsh-pet 0.2.2 → 0.2.4

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.
Files changed (125) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +129 -3
  3. package/README.zh.md +129 -3
  4. package/assets/decorations/whale/decoration.json +20 -0
  5. package/assets/decorations/whale/whale-frames.png +0 -0
  6. package/assets/whale/pet.json +169 -20
  7. package/assets/whale-refined/pet.json +17 -2
  8. package/contracts/pet-manifest-v2.schema.json +281 -0
  9. package/contracts/status-decoration-v1.schema.json +144 -0
  10. package/contracts/voice-pack-v1.schema.json +234 -0
  11. package/lib/client.js +581 -38
  12. package/lib/client.js.map +1 -1
  13. package/lib/index.js +1684 -59
  14. package/lib/live2d-vendor.js +995 -0
  15. package/lib/live2d-vendor.js.map +1 -0
  16. package/lib/types/chatter.d.ts +61 -3
  17. package/lib/types/chatter.d.ts.map +1 -1
  18. package/lib/types/chatter.js +71 -12
  19. package/lib/types/client/PetDockEntry.d.ts.map +1 -1
  20. package/lib/types/client/PetDockEntry.js +2 -1
  21. package/lib/types/client/PetSettingsCard.d.ts +14 -0
  22. package/lib/types/client/PetSettingsCard.d.ts.map +1 -1
  23. package/lib/types/client/PetSettingsCard.js +24 -1
  24. package/lib/types/client/PetSprite.d.ts +7 -1
  25. package/lib/types/client/PetSprite.d.ts.map +1 -1
  26. package/lib/types/client/PetSprite.js +120 -12
  27. package/lib/types/client/index.d.ts.map +1 -1
  28. package/lib/types/client/index.js +5 -0
  29. package/lib/types/client/locales.d.ts +14 -0
  30. package/lib/types/client/locales.d.ts.map +1 -1
  31. package/lib/types/client/locales.js +14 -0
  32. package/lib/types/client/phase-stream.d.ts +22 -0
  33. package/lib/types/client/phase-stream.d.ts.map +1 -0
  34. package/lib/types/client/phase-stream.js +28 -0
  35. package/lib/types/client/renderers/PetRendererSwitch.d.ts +25 -0
  36. package/lib/types/client/renderers/PetRendererSwitch.d.ts.map +1 -0
  37. package/lib/types/client/renderers/PetRendererSwitch.js +24 -0
  38. package/lib/types/client/renderers/live2d/Live2dVisualMount.d.ts +22 -0
  39. package/lib/types/client/renderers/live2d/Live2dVisualMount.d.ts.map +1 -0
  40. package/lib/types/client/renderers/live2d/Live2dVisualMount.js +75 -0
  41. package/lib/types/client/renderers/live2d/runtime.d.ts +89 -0
  42. package/lib/types/client/renderers/live2d/runtime.d.ts.map +1 -0
  43. package/lib/types/client/renderers/live2d/runtime.js +63 -0
  44. package/lib/types/client/renderers/live2d/vendor-entry.d.ts +15 -0
  45. package/lib/types/client/renderers/live2d/vendor-entry.d.ts.map +1 -0
  46. package/lib/types/client/renderers/live2d/vendor-entry.js +14 -0
  47. package/lib/types/client/renderers/live2d.d.ts +52 -0
  48. package/lib/types/client/renderers/live2d.d.ts.map +1 -0
  49. package/lib/types/client/renderers/live2d.js +192 -0
  50. package/lib/types/client/renderers/registry.d.ts +32 -0
  51. package/lib/types/client/renderers/registry.d.ts.map +1 -0
  52. package/lib/types/client/renderers/registry.js +49 -0
  53. package/lib/types/client/settings-form.d.ts.map +1 -1
  54. package/lib/types/client/settings-form.js +9 -6
  55. package/lib/types/contracts/renderer.d.ts +50 -0
  56. package/lib/types/contracts/renderer.d.ts.map +1 -0
  57. package/lib/types/contracts/renderer.js +15 -0
  58. package/lib/types/contracts/status-decoration.d.ts +85 -0
  59. package/lib/types/contracts/status-decoration.d.ts.map +1 -0
  60. package/lib/types/contracts/status-decoration.js +21 -0
  61. package/lib/types/decoration.d.ts +39 -0
  62. package/lib/types/decoration.d.ts.map +1 -0
  63. package/lib/types/decoration.js +210 -0
  64. package/lib/types/event-projection.d.ts +8 -3
  65. package/lib/types/event-projection.d.ts.map +1 -1
  66. package/lib/types/event-projection.js +9 -4
  67. package/lib/types/index.d.ts +2 -0
  68. package/lib/types/index.d.ts.map +1 -1
  69. package/lib/types/index.js +2 -0
  70. package/lib/types/manifest-v2.d.ts +129 -0
  71. package/lib/types/manifest-v2.d.ts.map +1 -0
  72. package/lib/types/manifest-v2.js +387 -0
  73. package/lib/types/model3.d.ts +26 -0
  74. package/lib/types/model3.d.ts.map +1 -0
  75. package/lib/types/model3.js +96 -0
  76. package/lib/types/registry.d.ts +112 -5
  77. package/lib/types/registry.d.ts.map +1 -1
  78. package/lib/types/registry.js +395 -38
  79. package/lib/types/routes.d.ts +39 -2
  80. package/lib/types/routes.d.ts.map +1 -1
  81. package/lib/types/routes.js +312 -13
  82. package/lib/types/service.d.ts +38 -1
  83. package/lib/types/service.d.ts.map +1 -1
  84. package/lib/types/service.js +46 -3
  85. package/lib/types/voice-pack.d.ts +98 -0
  86. package/lib/types/voice-pack.d.ts.map +1 -0
  87. package/lib/types/voice-pack.js +384 -0
  88. package/package.json +13 -10
  89. package/src/chatter.test.ts +89 -2
  90. package/src/chatter.ts +120 -14
  91. package/src/client/PetDockEntry.tsx +19 -11
  92. package/src/client/PetSettingsCard.tsx +57 -0
  93. package/src/client/PetSprite.test.tsx +265 -12
  94. package/src/client/PetSprite.tsx +165 -33
  95. package/src/client/index.ts +6 -0
  96. package/src/client/locales.ts +14 -0
  97. package/src/client/phase-stream.test.ts +76 -0
  98. package/src/client/phase-stream.ts +39 -0
  99. package/src/client/renderers/PetRendererSwitch.test.tsx +66 -0
  100. package/src/client/renderers/PetRendererSwitch.tsx +48 -0
  101. package/src/client/renderers/live2d/Live2dVisualMount.tsx +98 -0
  102. package/src/client/renderers/live2d/runtime.test.ts +61 -0
  103. package/src/client/renderers/live2d/runtime.ts +119 -0
  104. package/src/client/renderers/live2d/vendor-entry.ts +14 -0
  105. package/src/client/renderers/live2d.test.ts +263 -0
  106. package/src/client/renderers/live2d.ts +231 -0
  107. package/src/client/renderers/registry.ts +58 -0
  108. package/src/client/settings-form.ts +8 -6
  109. package/src/client/settings-section.module.css +23 -0
  110. package/src/contracts/renderer.ts +54 -0
  111. package/src/contracts/status-decoration.ts +78 -0
  112. package/src/decoration.test.ts +178 -0
  113. package/src/decoration.ts +220 -0
  114. package/src/event-projection.ts +10 -5
  115. package/src/index.ts +2 -0
  116. package/src/manifest-v2.test.ts +251 -0
  117. package/src/manifest-v2.ts +414 -0
  118. package/src/model3.test.ts +79 -0
  119. package/src/model3.ts +91 -0
  120. package/src/registry.test.ts +438 -2
  121. package/src/registry.ts +470 -38
  122. package/src/routes.ts +328 -14
  123. package/src/service.ts +65 -3
  124. package/src/voice-pack.test.ts +216 -0
  125. package/src/voice-pack.ts +413 -0
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Live2D runtime loading (pet-center M3) — the two scripts a live2d mount
3
+ * needs, fetched lazily through the plugin's own runtime route: the
4
+ * user-supplied Cubism Core (proprietary; the plugin never bundles or
5
+ * downloads it — issue #623 M1 §0) and the plugin-shipped MIT vendor bundle
6
+ * (pixi.js + untitled-pixi-live2d-engine). Each loads at most once per page;
7
+ * concurrent mounts share the in-flight promise, and a failure is cached as
8
+ * 'absent' so a broken install stops retrying the network every mount.
9
+ *
10
+ * The vendor surface below is the structural slice the renderer consumes;
11
+ * the real objects come from 'window.__dshPetLive2d' (lib/live2d-vendor.js),
12
+ * so this module never imports pixi — the client bundle stays lean.
13
+ * @module @linxin666/dsh-pet/client/renderers/live2d/runtime
14
+ */
15
+ /** The pixi Application slice the renderer uses. */
16
+ export interface Live2dVendorApp {
17
+ canvas: HTMLCanvasElement;
18
+ stage: {
19
+ addChild(child: unknown): unknown;
20
+ };
21
+ renderer: {
22
+ readonly width: number;
23
+ readonly height: number;
24
+ };
25
+ init(options: Record<string, unknown>): Promise<void>;
26
+ destroy(removeView?: boolean, options?: Record<string, unknown>): void;
27
+ }
28
+ /** The Live2DModel slice the renderer uses. */
29
+ export interface Live2dVendorModel {
30
+ anchor: {
31
+ set(x: number, y?: number): void;
32
+ };
33
+ position: {
34
+ set(x: number, y: number): void;
35
+ };
36
+ scale: {
37
+ set(x: number, y?: number): void;
38
+ };
39
+ readonly width: number;
40
+ readonly height: number;
41
+ internalModel: {
42
+ settings: {
43
+ motions?: Record<string, unknown[]>;
44
+ hitAreas?: readonly {
45
+ Name?: string;
46
+ }[];
47
+ };
48
+ };
49
+ motion(group: string, index?: number): Promise<unknown>;
50
+ expression(name?: string): unknown;
51
+ hitTest(x: number, y: number): string[];
52
+ on(event: string, fn: () => void): unknown;
53
+ }
54
+ /** The vendor bundle global (window.__dshPetLive2d). */
55
+ export interface Live2dVendor {
56
+ Application: new () => Live2dVendorApp;
57
+ extensions: {
58
+ add(...items: unknown[]): void;
59
+ };
60
+ Live2DPlugin: unknown;
61
+ configureCubismSDK(options: Record<string, unknown>): void;
62
+ Live2DModel: {
63
+ from(source: string, options?: Record<string, unknown>): Promise<Live2dVendorModel>;
64
+ };
65
+ }
66
+ declare global {
67
+ interface Window {
68
+ Live2DCubismCore?: unknown;
69
+ __dshPetLive2d?: Live2dVendor;
70
+ }
71
+ }
72
+ /** Injects one classic script tag; resolves on load, rejects on error. */
73
+ type ScriptInjector = (src: string) => Promise<void>;
74
+ /** Test seam: swap the network for a stub injector. */
75
+ export interface Live2dRuntimeProbe {
76
+ inject?: ScriptInjector;
77
+ }
78
+ /**
79
+ * Ensure the Cubism Core global exists, injecting the runtime-route script
80
+ * once when absent. Resolves false when the user has not installed the core
81
+ * (a normal state — the renderer turns it into install guidance).
82
+ */
83
+ export declare function ensureCubismCore(probe?: Live2dRuntimeProbe): Promise<boolean>;
84
+ /** Ensure the plugin vendor bundle global exists (same caching discipline). */
85
+ export declare function ensureLive2dVendor(probe?: Live2dRuntimeProbe): Promise<Live2dVendor | undefined>;
86
+ /** Reset the cached script promises (tests). */
87
+ export declare function resetLive2dRuntime(): void;
88
+ export {};
89
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../../src/client/renderers/live2d/runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Live2D runtime loading (pet-center M3) — the two scripts a live2d mount
3
+ * needs, fetched lazily through the plugin's own runtime route: the
4
+ * user-supplied Cubism Core (proprietary; the plugin never bundles or
5
+ * downloads it — issue #623 M1 §0) and the plugin-shipped MIT vendor bundle
6
+ * (pixi.js + untitled-pixi-live2d-engine). Each loads at most once per page;
7
+ * concurrent mounts share the in-flight promise, and a failure is cached as
8
+ * 'absent' so a broken install stops retrying the network every mount.
9
+ *
10
+ * The vendor surface below is the structural slice the renderer consumes;
11
+ * the real objects come from 'window.__dshPetLive2d' (lib/live2d-vendor.js),
12
+ * so this module never imports pixi — the client bundle stays lean.
13
+ * @module @linxin666/dsh-pet/client/renderers/live2d/runtime
14
+ */
15
+ /** Runtime file URLs the host serves ('/api/pet/runtime/<name>', M3-2). */
16
+ const CORE_URL = '/api/pet/runtime/live2dcubismcore.min.js';
17
+ const VENDOR_URL = '/api/pet/runtime/live2d-vendor.js';
18
+ const defaultInjector = (src) => new Promise((resolve, reject) => {
19
+ const tag = document.createElement('script');
20
+ tag.src = src;
21
+ tag.onload = () => resolve();
22
+ tag.onerror = () => reject(new Error('script failed to load: ' + src));
23
+ document.head.appendChild(tag);
24
+ });
25
+ let corePromise;
26
+ let vendorPromise;
27
+ /**
28
+ * Ensure the Cubism Core global exists, injecting the runtime-route script
29
+ * once when absent. Resolves false when the user has not installed the core
30
+ * (a normal state — the renderer turns it into install guidance).
31
+ */
32
+ export function ensureCubismCore(probe = {}) {
33
+ if (typeof window !== 'undefined' && window.Live2DCubismCore !== undefined)
34
+ return Promise.resolve(true);
35
+ if (probe.inject !== undefined) {
36
+ return probe.inject(CORE_URL)
37
+ .then(() => typeof window !== 'undefined' && window.Live2DCubismCore !== undefined)
38
+ .catch(() => false);
39
+ }
40
+ corePromise ??= defaultInjector(CORE_URL)
41
+ .then(() => typeof window !== 'undefined' && window.Live2DCubismCore !== undefined)
42
+ .catch(() => false);
43
+ return corePromise;
44
+ }
45
+ /** Ensure the plugin vendor bundle global exists (same caching discipline). */
46
+ export function ensureLive2dVendor(probe = {}) {
47
+ if (typeof window !== 'undefined' && window.__dshPetLive2d !== undefined)
48
+ return Promise.resolve(window.__dshPetLive2d);
49
+ if (probe.inject !== undefined) {
50
+ return probe.inject(VENDOR_URL)
51
+ .then(() => typeof window !== 'undefined' ? window.__dshPetLive2d : undefined)
52
+ .catch(() => undefined);
53
+ }
54
+ vendorPromise ??= defaultInjector(VENDOR_URL)
55
+ .then(() => typeof window !== 'undefined' ? window.__dshPetLive2d : undefined)
56
+ .catch(() => undefined);
57
+ return vendorPromise;
58
+ }
59
+ /** Reset the cached script promises (tests). */
60
+ export function resetLive2dRuntime() {
61
+ corePromise = undefined;
62
+ vendorPromise = undefined;
63
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Live2D vendor bundle entry (pet-center M3) — builds pixi.js plus the
3
+ * untitled-pixi-live2d-engine Cubism stack into lib/live2d-vendor.js, an IIFE
4
+ * exposing 'window.__dshPetLive2d'. The sprite-only client bundle never pays
5
+ * for this stack: the live2d renderer lazy-loads the vendor file through the
6
+ * plugin's own runtime route, after the user-supplied Cubism Core script
7
+ * ('window.Live2DCubismCore') is already present.
8
+ *
9
+ * Redistribution: pixi.js and untitled-pixi-live2d-engine are MIT-licensed
10
+ * and may ship inside this plugin; the proprietary Cubism Core is NEVER
11
+ * bundled (issue #623, milestone M1 §0).
12
+ */
13
+ export { Application, Point, extensions } from 'pixi.js';
14
+ export { configureCubismSDK, Live2DModel, Live2DPlugin } from 'untitled-pixi-live2d-engine/cubism';
15
+ //# sourceMappingURL=vendor-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vendor-entry.d.ts","sourceRoot":"","sources":["../../../../../src/client/renderers/live2d/vendor-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Live2D vendor bundle entry (pet-center M3) — builds pixi.js plus the
3
+ * untitled-pixi-live2d-engine Cubism stack into lib/live2d-vendor.js, an IIFE
4
+ * exposing 'window.__dshPetLive2d'. The sprite-only client bundle never pays
5
+ * for this stack: the live2d renderer lazy-loads the vendor file through the
6
+ * plugin's own runtime route, after the user-supplied Cubism Core script
7
+ * ('window.Live2DCubismCore') is already present.
8
+ *
9
+ * Redistribution: pixi.js and untitled-pixi-live2d-engine are MIT-licensed
10
+ * and may ship inside this plugin; the proprietary Cubism Core is NEVER
11
+ * bundled (issue #623, milestone M1 §0).
12
+ */
13
+ export { Application, Point, extensions } from 'pixi.js';
14
+ export { configureCubismSDK, Live2DModel, Live2DPlugin } from 'untitled-pixi-live2d-engine/cubism';
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Live2D renderer (pet-center M3, issue #623) — mounts a Cubism model into
3
+ * the center-owned container through the lazy vendor stack. The mount call
4
+ * itself is synchronous per the renderer contract: the boot (core script →
5
+ * vendor script → pixi → model) continues asynchronously and reports fatal
6
+ * failures through the handle's error sink, so the React bridge can render
7
+ * localized guidance. Disposing mid-boot is race-safe.
8
+ *
9
+ * Interaction model: the center chrome owns the affinity economy (its click
10
+ * handler fires the pet interaction exactly like sprite2d); this renderer's
11
+ * 'tap' affordance only drives the model's hit-area motion feedback. The
12
+ * contract's interact write-back stays available for future standalone
13
+ * mounts and is intentionally not invoked here.
14
+ *
15
+ * Motion mapping: manifests map ActivityPhases to motion GROUP names; every
16
+ * unmapped phase (and any mapped-but-absent group) falls back to the idle
17
+ * group — official sample models only ship Idle/TapBody, so the fallback is
18
+ * mandatory. Groups with multiple motions pick a random entry, and a tap
19
+ * that hits a declared hit area plays the conventional 'TapBody' group,
20
+ * returning to the phase's group when the tap motion finishes.
21
+ * @module @linxin666/dsh-pet/client/renderers/live2d
22
+ */
23
+ import type { ActivityPhase } from '../../state.ts';
24
+ import { type PetRenderer, type PetRendererHandle } from '../../contracts/renderer.ts';
25
+ /** Renderer config: the client-visible live2d block (fail-closed validated). */
26
+ export interface PetLive2dConfig {
27
+ modelUrl: string;
28
+ scale?: number;
29
+ translate?: {
30
+ x?: number;
31
+ y?: number;
32
+ };
33
+ motions: Partial<Record<ActivityPhase, string>> & {
34
+ idle: string;
35
+ };
36
+ expressions?: Partial<Record<ActivityPhase, string>>;
37
+ hitAreas?: string[];
38
+ }
39
+ /** Fatal mount failure codes the bridge localizes. */
40
+ export type Live2dErrorCode = 'core-missing' | 'vendor-missing' | 'load-failed';
41
+ /** The live2d activation handle: contract dispose plus tap + error sink. */
42
+ export interface Live2dRendererHandle extends PetRendererHandle {
43
+ /** Forward a chrome tap in container coordinates; plays the hit motion. */
44
+ tap(x: number, y: number): void;
45
+ /** Subscribe to fatal mount errors (at most one fires per activation). */
46
+ onError(listener: (code: Live2dErrorCode) => void): void;
47
+ }
48
+ /** Reset module state (tests). */
49
+ export declare function resetLive2dRenderer(): void;
50
+ /** The live2d renderer implementation. */
51
+ export declare const live2dRenderer: PetRenderer<PetLive2dConfig>;
52
+ //# sourceMappingURL=live2d.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live2d.d.ts","sourceRoot":"","sources":["../../../../src/client/renderers/live2d.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAEL,KAAK,WAAW,EAEhB,KAAK,iBAAiB,EACvB,MAAM,6BAA6B,CAAA;AASpC,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACtC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAA;IACpD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,sDAAsD;AACtD,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,gBAAgB,GAAG,aAAa,CAAA;AAE/E,4EAA4E;AAC5E,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,2EAA2E;IAC3E,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,0EAA0E;IAC1E,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI,CAAA;CACzD;AAwBD,kCAAkC;AAClC,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAcD,0CAA0C;AAC1C,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,eAAe,CAmIvD,CAAA"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Live2D renderer (pet-center M3, issue #623) — mounts a Cubism model into
3
+ * the center-owned container through the lazy vendor stack. The mount call
4
+ * itself is synchronous per the renderer contract: the boot (core script →
5
+ * vendor script → pixi → model) continues asynchronously and reports fatal
6
+ * failures through the handle's error sink, so the React bridge can render
7
+ * localized guidance. Disposing mid-boot is race-safe.
8
+ *
9
+ * Interaction model: the center chrome owns the affinity economy (its click
10
+ * handler fires the pet interaction exactly like sprite2d); this renderer's
11
+ * 'tap' affordance only drives the model's hit-area motion feedback. The
12
+ * contract's interact write-back stays available for future standalone
13
+ * mounts and is intentionally not invoked here.
14
+ *
15
+ * Motion mapping: manifests map ActivityPhases to motion GROUP names; every
16
+ * unmapped phase (and any mapped-but-absent group) falls back to the idle
17
+ * group — official sample models only ship Idle/TapBody, so the fallback is
18
+ * mandatory. Groups with multiple motions pick a random entry, and a tap
19
+ * that hits a declared hit area plays the conventional 'TapBody' group,
20
+ * returning to the phase's group when the tap motion finishes.
21
+ * @module @linxin666/dsh-pet/client/renderers/live2d
22
+ */
23
+ import { PET_RENDERER_API_VERSION, } from '../../contracts/renderer.js';
24
+ import { ensureCubismCore, ensureLive2dVendor, } from './live2d/runtime.js';
25
+ /** The de-facto tap-motion group of Cubism sample models. */
26
+ const TAP_GROUP = 'TapBody';
27
+ /**
28
+ * Keep one screen-appropriate atlas LOD instead of asking Pixi for the
29
+ * engine's default full mip chain. A user model can legitimately carry an
30
+ * 8192px texture while the pet itself is only a few hundred pixels tall;
31
+ * `single-auto` preserves the source for larger renders and generates one
32
+ * downsampled atlas only when the effective on-screen scale warrants it.
33
+ */
34
+ const TEXTURE_OPTIONS = { lod: 'single-auto' };
35
+ let vendorConfigured = false;
36
+ /** Configure pixi extensions + the Cubism SDK once per page. */
37
+ function configureOnce(vendor) {
38
+ if (vendorConfigured)
39
+ return;
40
+ vendorConfigured = true;
41
+ vendor.extensions.add(vendor.Live2DPlugin);
42
+ vendor.configureCubismSDK({ memorySizeMB: 32 });
43
+ }
44
+ /** Reset module state (tests). */
45
+ export function resetLive2dRenderer() {
46
+ vendorConfigured = false;
47
+ }
48
+ /** Fail-closed config validation (contract: unknown manifest block in). */
49
+ function validateLive2dConfig(config) {
50
+ if (typeof config !== 'object' || config === null)
51
+ throw new Error('live2d config is not an object');
52
+ const source = config;
53
+ if (typeof source.modelUrl !== 'string' || source.modelUrl === '')
54
+ throw new Error('live2d config modelUrl is required');
55
+ const motions = source.motions;
56
+ if (typeof motions !== 'object' || motions === null || typeof motions.idle !== 'string') {
57
+ throw new Error('live2d config motions.idle is required');
58
+ }
59
+ return config;
60
+ }
61
+ /** The live2d renderer implementation. */
62
+ export const live2dRenderer = {
63
+ id: 'live2d',
64
+ apiVersion: PET_RENDERER_API_VERSION,
65
+ validateConfig: validateLive2dConfig,
66
+ mount(ctx, config) {
67
+ let disposed = false;
68
+ let app;
69
+ let model;
70
+ let errorListener;
71
+ let unsubscribe;
72
+ /** The motion group the current phase maps to (resume target after taps). */
73
+ let phaseGroup = config.motions.idle;
74
+ let tapPlaying = false;
75
+ const playGroup = (group) => {
76
+ if (model === undefined)
77
+ return;
78
+ const groups = model.internalModel.settings.motions ?? {};
79
+ const count = Array.isArray(groups[group]) ? groups[group].length : 0;
80
+ if (count === 0) {
81
+ // A mapped-but-absent group falls back to idle (never blank motion).
82
+ if (group !== config.motions.idle)
83
+ playGroup(config.motions.idle);
84
+ return;
85
+ }
86
+ const index = count > 1 ? Math.floor(Math.random() * count) : 0;
87
+ void model.motion(group, index);
88
+ };
89
+ const applyPhase = (phase) => {
90
+ phaseGroup = config.motions[phase] ?? config.motions.idle;
91
+ playGroup(phaseGroup);
92
+ const expression = config.expressions?.[phase];
93
+ if (expression !== undefined && model !== undefined)
94
+ void model.expression(expression);
95
+ };
96
+ const boot = async () => {
97
+ if (!await ensureCubismCore()) {
98
+ if (!disposed)
99
+ errorListener?.('core-missing');
100
+ return;
101
+ }
102
+ const vendor = await ensureLive2dVendor();
103
+ if (vendor === undefined) {
104
+ if (!disposed)
105
+ errorListener?.('vendor-missing');
106
+ return;
107
+ }
108
+ configureOnce(vendor);
109
+ const pixiApp = new vendor.Application();
110
+ await pixiApp.init({
111
+ width: Math.max(1, ctx.container.clientWidth || 160),
112
+ height: Math.max(1, ctx.container.clientHeight || 174),
113
+ backgroundAlpha: 0,
114
+ antialias: true,
115
+ autoDensity: true,
116
+ preference: 'webgl',
117
+ });
118
+ if (disposed) {
119
+ pixiApp.destroy(true, { children: true });
120
+ return;
121
+ }
122
+ pixiApp.canvas.style.display = 'block';
123
+ pixiApp.canvas.style.width = '100%';
124
+ pixiApp.canvas.style.height = '100%';
125
+ ctx.container.appendChild(pixiApp.canvas);
126
+ const loaded = await vendor.Live2DModel.from(config.modelUrl, {
127
+ autoHitTest: false,
128
+ autoFocus: false,
129
+ textureOptions: TEXTURE_OPTIONS,
130
+ });
131
+ if (disposed) {
132
+ pixiApp.destroy(true, { children: true });
133
+ return;
134
+ }
135
+ app = pixiApp;
136
+ model = loaded;
137
+ // Auto-fit the model into the container; the manifest scale multiplies
138
+ // the fit and translate offsets from the center anchor.
139
+ const fit = Math.min(pixiApp.renderer.width / Math.max(1, loaded.width), pixiApp.renderer.height / Math.max(1, loaded.height)) * 0.92;
140
+ loaded.scale.set(fit * (config.scale ?? 1));
141
+ loaded.anchor.set(0.5);
142
+ loaded.position.set(pixiApp.renderer.width / 2 + (config.translate?.x ?? 0), pixiApp.renderer.height / 2 + (config.translate?.y ?? 0));
143
+ pixiApp.stage.addChild(loaded);
144
+ // Resume the phase group once a tap motion finishes playing.
145
+ loaded.on('motionFinish', () => {
146
+ if (tapPlaying) {
147
+ tapPlaying = false;
148
+ playGroup(phaseGroup);
149
+ }
150
+ });
151
+ applyPhase(ctx.phase.get());
152
+ unsubscribe = ctx.phase.subscribe(applyPhase);
153
+ };
154
+ void boot().catch(() => {
155
+ if (!disposed)
156
+ errorListener?.('load-failed');
157
+ });
158
+ return {
159
+ dispose() {
160
+ if (disposed)
161
+ return;
162
+ disposed = true;
163
+ unsubscribe?.();
164
+ model = undefined;
165
+ if (app !== undefined) {
166
+ app.destroy(true, { children: true });
167
+ app = undefined;
168
+ }
169
+ },
170
+ tap(x, y) {
171
+ const current = model;
172
+ if (disposed || current === undefined)
173
+ return;
174
+ const hits = current.hitTest(x, y);
175
+ const allowed = config.hitAreas;
176
+ const hit = allowed === undefined ? hits.length > 0 : hits.some(name => allowed.includes(name));
177
+ if (!hit)
178
+ return;
179
+ const groups = current.internalModel.settings.motions ?? {};
180
+ const group = groups[TAP_GROUP];
181
+ if (!Array.isArray(group) || group.length === 0)
182
+ return;
183
+ tapPlaying = true;
184
+ const index = group.length > 1 ? Math.floor(Math.random() * group.length) : 0;
185
+ void current.motion(TAP_GROUP, index);
186
+ },
187
+ onError(listener) {
188
+ errorListener = listener;
189
+ },
190
+ };
191
+ },
192
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Renderer registry — dispatches a manifest's renderer kind to its
3
+ * implementation (pet-center M2 P4, issue #623). Unknown kinds never blank
4
+ * the pet: a fallback card names the problem and reports the kinds this
5
+ * build actually supports.
6
+ * @module @linxin666/dsh-pet/client/renderers/registry
7
+ */
8
+ import type { PetRenderer, PetRendererContext, PetRendererHandle } from '../../contracts/renderer.ts';
9
+ /** Renderer dispatch table. */
10
+ export declare class RendererRegistry {
11
+ private readonly renderers;
12
+ /** Register one renderer implementation (id wins on re-register). */
13
+ register(renderer: PetRenderer): void;
14
+ /** Whether a renderer kind is available in this build. */
15
+ has(id: string): boolean;
16
+ /** The registered renderer kinds (for diagnostics). */
17
+ kinds(): string[];
18
+ /** Remove every registration (tests; the client index registers once). */
19
+ clear(): void;
20
+ /**
21
+ * Mount a renderer for one activation. An unknown kind renders a clear
22
+ * diagnostic card into the container instead of failing silently.
23
+ */
24
+ mount(kind: string, ctx: PetRendererContext, config: unknown): PetRendererHandle;
25
+ }
26
+ /**
27
+ * The plugin-wide renderer registry. The client entry registers the
28
+ * built-in renderers at apply time; the renderer switch and the live2d
29
+ * bridge dispatch through this instance.
30
+ */
31
+ export declare const defaultPetRendererRegistry: RendererRegistry;
32
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../../src/client/renderers/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAErG,+BAA+B;AAC/B,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiC;IAE3D,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAEpC;IAED,0DAA0D;IAC1D,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAEvB;IAED,uDAAuD;IACvD,KAAK,IAAI,MAAM,EAAE,CAEhB;IAED,0EAA0E;IAC1E,KAAK,IAAI,IAAI,CAEZ;IAED;;;OAGG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,EAAE,MAAM,EAAE,OAAO,GAAG,iBAAiB,CAW/E;CACF;AAED;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,kBAAyB,CAAA"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Renderer registry — dispatches a manifest's renderer kind to its
3
+ * implementation (pet-center M2 P4, issue #623). Unknown kinds never blank
4
+ * the pet: a fallback card names the problem and reports the kinds this
5
+ * build actually supports.
6
+ * @module @linxin666/dsh-pet/client/renderers/registry
7
+ */
8
+ /** Renderer dispatch table. */
9
+ export class RendererRegistry {
10
+ renderers = new Map();
11
+ /** Register one renderer implementation (id wins on re-register). */
12
+ register(renderer) {
13
+ this.renderers.set(renderer.id, renderer);
14
+ }
15
+ /** Whether a renderer kind is available in this build. */
16
+ has(id) {
17
+ return this.renderers.has(id);
18
+ }
19
+ /** The registered renderer kinds (for diagnostics). */
20
+ kinds() {
21
+ return [...this.renderers.keys()].sort();
22
+ }
23
+ /** Remove every registration (tests; the client index registers once). */
24
+ clear() {
25
+ this.renderers.clear();
26
+ }
27
+ /**
28
+ * Mount a renderer for one activation. An unknown kind renders a clear
29
+ * diagnostic card into the container instead of failing silently.
30
+ */
31
+ mount(kind, ctx, config) {
32
+ const renderer = this.renderers.get(kind);
33
+ if (renderer === undefined) {
34
+ const note = document.createElement('div');
35
+ note.dataset.dshPetRendererFallback = kind;
36
+ note.textContent = 'Pet renderer "' + kind + '" is not available in this build (supported: ' + this.kinds().join(', ') + ').';
37
+ ctx.container.appendChild(note);
38
+ ctx.onCleanup(() => note.remove());
39
+ return { dispose: () => note.remove() };
40
+ }
41
+ return renderer.mount(ctx, renderer.validateConfig(config));
42
+ }
43
+ }
44
+ /**
45
+ * The plugin-wide renderer registry. The client entry registers the
46
+ * built-in renderers at apply time; the renderer switch and the live2d
47
+ * bridge dispatch through this instance.
48
+ */
49
+ export const defaultPetRendererRegistry = new RendererRegistry();
@@ -1 +1 @@
1
- {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}
@@ -184,9 +184,12 @@ export class CardForm {
184
184
  if (plan.length === 0 || this.saving || valid.length !== plan.length)
185
185
  return;
186
186
  const plannedWrites = valid.map(item => item.op);
187
- // Snapshot the fields this save writes, so edits staged while it is in
188
- // flight survive: only the staged keys this save actually wrote are cleared.
189
- const fields = new Set(plan.map(item => item.field));
187
+ // Snapshot the staged entries this save writes, so an edit staged while it
188
+ // is in flight (which replaces the same key) survives: only delete the key
189
+ // when the entry is still the one this save started from.
190
+ const pending = new Map();
191
+ for (const item of plan)
192
+ pending.set(item.field, this.staged.get(item.field));
190
193
  this.saving = true;
191
194
  this.failed = false;
192
195
  this.failedReason = undefined;
@@ -211,12 +214,12 @@ export class CardForm {
211
214
  landed.add(item.field);
212
215
  }
213
216
  }
214
- for (const field of fields) {
215
- if (landed.has(field))
217
+ for (const [field, before] of pending) {
218
+ if (landed.has(field) && this.staged.get(field) === before)
216
219
  this.staged.delete(field);
217
220
  }
218
221
  this.saving = false;
219
- this.failed = landed.size !== fields.size;
222
+ this.failed = landed.size !== pending.size;
220
223
  this.publish();
221
224
  }
222
225
  /** The scope's batch surface when it supports one; undefined conservatively otherwise. */
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Renderer contract — the seam between the pet center and its renderers
3
+ * (issue #623, milestone M2 P4). Renderers never see the DSH session, the
4
+ * registry, or the network: they receive exactly three capabilities — an
5
+ * asset base URL, the ActivityPhase stream, and the interaction write-back —
6
+ * inside a center-owned container. Every mount is a fresh activation whose
7
+ * cleanups must be idempotent.
8
+ *
9
+ * This contract only serves real consumers: sprite2d (existing) and live2d
10
+ * (M3). Speculative capabilities join only when a renderer actually needs
11
+ * them.
12
+ * @module @linxin666/dsh-pet/contracts/renderer
13
+ */
14
+ import type { ActivityPhase } from '../state.ts';
15
+ /** Contract version renderers declare against (independent of the manifest). */
16
+ export declare const PET_RENDERER_API_VERSION = "x-org.linxin666.pet-center/v1alpha1";
17
+ /** What the pet center hands a renderer on mount. */
18
+ export interface PetRendererContext {
19
+ /** The selected pet's id. */
20
+ readonly petId: string;
21
+ /** Same-origin URL prefix of this pet's assets ('/pet/<id>'). */
22
+ readonly assetBase: string;
23
+ /** Center-owned mount root; renderers must not attach to document.body. */
24
+ readonly container: HTMLElement;
25
+ /** The ActivityPhase stream (pet-center owned; renderers subscribe). */
26
+ readonly phase: {
27
+ get(): ActivityPhase;
28
+ subscribe(listener: (phase: ActivityPhase) => void): () => void;
29
+ };
30
+ /** The single interaction write-back into the affinity economy. */
31
+ readonly interact: (kind: 'tap') => void;
32
+ /** Register an activation-scoped cleanup; run on dispose, idempotently. */
33
+ onCleanup(fn: () => void): void;
34
+ }
35
+ /** A mounted renderer activation. */
36
+ export interface PetRendererHandle {
37
+ /** Tear the activation down; may be called 0/1/N times. */
38
+ dispose(): void;
39
+ }
40
+ /**
41
+ * One renderer implementation. validateConfig is fail-closed over the
42
+ * renderer-specific manifest block (schema v2 'sprite2d'/'live2d' blocks).
43
+ */
44
+ export interface PetRenderer<Config = unknown> {
45
+ readonly id: string;
46
+ readonly apiVersion: string;
47
+ validateConfig(config: unknown): Config;
48
+ mount(ctx: PetRendererContext, config: Config): PetRendererHandle;
49
+ }
50
+ //# sourceMappingURL=renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/contracts/renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD,gFAAgF;AAChF,eAAO,MAAM,wBAAwB,wCAAwC,CAAA;AAE7E,qDAAqD;AACrD,MAAM,WAAW,kBAAkB;IACjC,6BAA6B;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,iEAAiE;IACjE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAA;IAC/B,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE;QACd,GAAG,IAAI,aAAa,CAAA;QACpB,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI,CAAA;KAChE,CAAA;IACD,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAA;IACxC,2EAA2E;IAC3E,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;CAChC;AAED,qCAAqC;AACrC,MAAM,WAAW,iBAAiB;IAChC,2DAA2D;IAC3D,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW,CAAC,MAAM,GAAG,OAAO;IAC3C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAA;IACvC,KAAK,CAAC,GAAG,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAAA;CAClE"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Renderer contract — the seam between the pet center and its renderers
3
+ * (issue #623, milestone M2 P4). Renderers never see the DSH session, the
4
+ * registry, or the network: they receive exactly three capabilities — an
5
+ * asset base URL, the ActivityPhase stream, and the interaction write-back —
6
+ * inside a center-owned container. Every mount is a fresh activation whose
7
+ * cleanups must be idempotent.
8
+ *
9
+ * This contract only serves real consumers: sprite2d (existing) and live2d
10
+ * (M3). Speculative capabilities join only when a renderer actually needs
11
+ * them.
12
+ * @module @linxin666/dsh-pet/contracts/renderer
13
+ */
14
+ /** Contract version renderers declare against (independent of the manifest). */
15
+ export const PET_RENDERER_API_VERSION = 'x-org.linxin666.pet-center/v1alpha1';