@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,85 @@
1
+ /**
2
+ * Status-decoration contract — the L3 extension slot for small status
3
+ * ornaments (issue #623 milestone M5, protocol #567, first reference
4
+ * implementation #463). A decoration is an INDEPENDENT content entry (own
5
+ * id, own directory, own descriptor) whose PNG/WebP sprite strip ornaments
6
+ * the pet center's status bubble chrome; it never touches the pet
7
+ * manifests and never changes the bubble's semantics.
8
+ *
9
+ * Adopted disciplines (#623): entry assets are PNG/WebP sprite strips only
10
+ * (no SVG, no CSS animation); the bubble's own role=status/aria-live (or
11
+ * session-bubble button semantics) always stays intact and the ornament is
12
+ * aria-hidden; load failure or prefers-reduced-motion degrades to no
13
+ * ornament or the static first frame.
14
+ *
15
+ * The ActivityPhase stream the pet center owns drives the ornament: each
16
+ * phase binds to a frame segment (inclusive from/to indices into the
17
+ * strip) or to 'hide' (no ornament for that phase; the default).
18
+ * @module @linxin666/dsh-pet/contracts/status-decoration
19
+ */
20
+ import type { ActivityPhase } from '../state.ts';
21
+ /** Contract version decorations declare against (independent of manifests). */
22
+ export declare const PET_DECORATION_API_VERSION: 'x-org.linxin666.pet-center/status-decoration-v1';
23
+ /** One phase binding: a frame segment, or hidden. */
24
+ export type PhaseSegment = {
25
+ from: number;
26
+ to: number;
27
+ } | 'hide';
28
+ /** The ActivityPhase -> frame-segment binding table (unmapped phases hide). */
29
+ export type PhaseBindings = Partial<Record<ActivityPhase, PhaseSegment>>;
30
+ /** Normalized decoration descriptor as the registry consumes it. */
31
+ export interface DecorationManifest {
32
+ decorationManifestVersion: 1;
33
+ id: string;
34
+ displayName: string;
35
+ license: string;
36
+ /** Strip path relative to the descriptor directory (PNG/WebP). */
37
+ entry: string;
38
+ cell: {
39
+ width: number;
40
+ height: number;
41
+ };
42
+ columns: number;
43
+ /** Per-frame duration ms (same length as columns, or a single constant). */
44
+ durations: number[];
45
+ loop: boolean;
46
+ phases: PhaseBindings;
47
+ }
48
+ /** One structured diagnostic emitted while parsing a descriptor. */
49
+ export interface DecorationDiagnostic {
50
+ level: 'error' | 'warning';
51
+ message: string;
52
+ }
53
+ /** Parse outcome: a usable descriptor plus diagnostics, or rejection. */
54
+ export type DecorationManifestParse = {
55
+ ok: true;
56
+ manifest: DecorationManifest;
57
+ diagnostics: DecorationDiagnostic[];
58
+ } | {
59
+ ok: false;
60
+ diagnostics: DecorationDiagnostic[];
61
+ };
62
+ /**
63
+ * The decoration block the host serves inside the pet state view — exactly
64
+ * what the browser half needs to render the ornament, nothing more. The
65
+ * apiVersion rides the wire so a future protocol revision can be detected
66
+ * and negotiated by clients (review-spd follow-up, pet-center M5).
67
+ */
68
+ export interface DecorationView {
69
+ /** The protocol version this view speaks (PET_DECORATION_API_VERSION). */
70
+ apiVersion: typeof PET_DECORATION_API_VERSION;
71
+ id: string;
72
+ /** Same-origin URL prefix of the decoration's assets. */
73
+ assetBase: string;
74
+ /** Browser URL of the strip. */
75
+ entryUrl: string;
76
+ cell: {
77
+ width: number;
78
+ height: number;
79
+ };
80
+ columns: number;
81
+ durations: number[];
82
+ loop: boolean;
83
+ phases: PhaseBindings;
84
+ }
85
+ //# sourceMappingURL=status-decoration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-decoration.d.ts","sourceRoot":"","sources":["../../../src/contracts/status-decoration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD,+EAA+E;AAC/E,eAAO,MAAM,0BAA0B,EAAG,iDAA0D,CAAA;AAEpG,qDAAqD;AACrD,MAAM,MAAM,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAA;AAEhE,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC,CAAA;AAExE,oEAAoE;AACpE,MAAM,WAAW,kBAAkB;IACjC,yBAAyB,EAAE,CAAC,CAAA;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACvC,OAAO,EAAE,MAAM,CAAA;IACf,4EAA4E;IAC5E,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,EAAE,aAAa,CAAA;CACtB;AAED,oEAAoE;AACpE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,OAAO,GAAG,SAAS,CAAA;IAC1B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,yEAAyE;AACzE,MAAM,MAAM,uBAAuB,GAC/B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,kBAAkB,CAAC;IAAC,WAAW,EAAE,oBAAoB,EAAE,CAAA;CAAE,GAC/E;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,WAAW,EAAE,oBAAoB,EAAE,CAAA;CAAE,CAAA;AAEtD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,UAAU,EAAE,OAAO,0BAA0B,CAAA;IAC7C,EAAE,EAAE,MAAM,CAAA;IACV,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAA;IACjB,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACvC,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,EAAE,aAAa,CAAA;CACtB"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Status-decoration contract — the L3 extension slot for small status
3
+ * ornaments (issue #623 milestone M5, protocol #567, first reference
4
+ * implementation #463). A decoration is an INDEPENDENT content entry (own
5
+ * id, own directory, own descriptor) whose PNG/WebP sprite strip ornaments
6
+ * the pet center's status bubble chrome; it never touches the pet
7
+ * manifests and never changes the bubble's semantics.
8
+ *
9
+ * Adopted disciplines (#623): entry assets are PNG/WebP sprite strips only
10
+ * (no SVG, no CSS animation); the bubble's own role=status/aria-live (or
11
+ * session-bubble button semantics) always stays intact and the ornament is
12
+ * aria-hidden; load failure or prefers-reduced-motion degrades to no
13
+ * ornament or the static first frame.
14
+ *
15
+ * The ActivityPhase stream the pet center owns drives the ornament: each
16
+ * phase binds to a frame segment (inclusive from/to indices into the
17
+ * strip) or to 'hide' (no ornament for that phase; the default).
18
+ * @module @linxin666/dsh-pet/contracts/status-decoration
19
+ */
20
+ /** Contract version decorations declare against (independent of manifests). */
21
+ export const PET_DECORATION_API_VERSION = 'x-org.linxin666.pet-center/status-decoration-v1';
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Status-decoration manifest v1 — fail-closed structure + warn-and-drop
3
+ * content, mirroring the pet manifest-v2 discipline (issue #623 M5,
4
+ * protocol #567). Unknown top-level fields, unsafe entry paths, out-of-
5
+ * range geometry and unknown renderer content reject the descriptor with
6
+ * human-readable diagnostics; per-phase binding issues drop that binding
7
+ * only. The JSON Schema twin lives at
8
+ * contracts/status-decoration-v1.schema.json; this hand-rolled parser is
9
+ * authoritative. Keep this file erasable-syntax-only (scripts/ import it
10
+ * under node's strip-only mode).
11
+ * @module @linxin666/dsh-pet/decoration
12
+ */
13
+ import type { DecorationManifestParse } from './contracts/status-decoration.ts';
14
+ /** Geometry and content caps (the adopted PNG/WebP sprite-strip bounds). */
15
+ export declare const DECORATION_CELL_MAX = 256;
16
+ export declare const DECORATION_COLUMNS_MAX = 16;
17
+ export declare const DECORATION_DURATION_MAX_MS = 2000;
18
+ export declare const DECORATION_ENTRY_EXTENSIONS: readonly ['.webp', '.png'];
19
+ export declare const DECORATION_DISPLAY_NAME_MAX = 64;
20
+ /** Field allow-list (drift-locked to the schema twin in tests). */
21
+ export declare const KNOWN_DECORATION_TOP_LEVEL: Set<string>;
22
+ /**
23
+ * Validate a descriptor-relative entry path: no absolute paths, no
24
+ * backslashes, no traversal, plain safe segments only, and an exact
25
+ * lowercase PNG/WebP extension (the adopted entry discipline — SVG/CSS are
26
+ * not accepted). The extension match is case-sensitive on purpose: the
27
+ * asset route serves the declared path verbatim, so a case-mismatched
28
+ * suffix (frames.PNG vs frames.png) would pass a lenient check but 403 on
29
+ * case-sensitive filesystems.
30
+ * Returns the normalized path or undefined.
31
+ */
32
+ export declare function safeDecorationEntry(raw: unknown): string | undefined;
33
+ /**
34
+ * Parse and validate one decoration.json document. Fail-closed over the
35
+ * structure (types, key sets, paths, ranges); phase-binding content issues
36
+ * drop that binding only (warn-and-drop, the registry never-throw rule).
37
+ */
38
+ export declare function parseDecorationManifest(raw: unknown, source?: string): DecorationManifestParse;
39
+ //# sourceMappingURL=decoration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decoration.d.ts","sourceRoot":"","sources":["../../src/decoration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EAGV,uBAAuB,EAGxB,MAAM,kCAAkC,CAAA;AAEzC,4EAA4E;AAC5E,eAAO,MAAM,mBAAmB,MAAM,CAAA;AACtC,eAAO,MAAM,sBAAsB,KAAK,CAAA;AACxC,eAAO,MAAM,0BAA0B,OAAO,CAAA;AAC9C,eAAO,MAAM,2BAA2B,YAAI,OAAO,EAAE,MAAM,CAAU,CAAA;AACrE,eAAO,MAAM,2BAA2B,KAAK,CAAA;AAE7C,mEAAmE;AACnE,eAAO,MAAM,0BAA0B,aAGrC,CAAA;AA6BF;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAYpE;AAsBD;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,OAAO,EACZ,MAAM,GAAE,MAA0B,GACjC,uBAAuB,CAuGzB"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Status-decoration manifest v1 — fail-closed structure + warn-and-drop
3
+ * content, mirroring the pet manifest-v2 discipline (issue #623 M5,
4
+ * protocol #567). Unknown top-level fields, unsafe entry paths, out-of-
5
+ * range geometry and unknown renderer content reject the descriptor with
6
+ * human-readable diagnostics; per-phase binding issues drop that binding
7
+ * only. The JSON Schema twin lives at
8
+ * contracts/status-decoration-v1.schema.json; this hand-rolled parser is
9
+ * authoritative. Keep this file erasable-syntax-only (scripts/ import it
10
+ * under node's strip-only mode).
11
+ * @module @linxin666/dsh-pet/decoration
12
+ */
13
+ import { isAbsolute } from 'node:path';
14
+ import { PET_ACTIVITY_PHASES } from './manifest-v2.js';
15
+ /** Geometry and content caps (the adopted PNG/WebP sprite-strip bounds). */
16
+ export const DECORATION_CELL_MAX = 256;
17
+ export const DECORATION_COLUMNS_MAX = 16;
18
+ export const DECORATION_DURATION_MAX_MS = 2000;
19
+ export const DECORATION_ENTRY_EXTENSIONS = ['.webp', '.png'];
20
+ export const DECORATION_DISPLAY_NAME_MAX = 64;
21
+ /** Field allow-list (drift-locked to the schema twin in tests). */
22
+ export const KNOWN_DECORATION_TOP_LEVEL = new Set([
23
+ '$schema', 'decorationManifestVersion', 'id', 'displayName', 'license',
24
+ 'entry', 'cell', 'columns', 'frameMs', 'durations', 'loop', 'phases',
25
+ ]);
26
+ const PET_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
27
+ const PATH_SEGMENT_PATTERN = /^[A-Za-z0-9._-]+$/;
28
+ class Diagnostics {
29
+ list = [];
30
+ source;
31
+ constructor(source) { this.source = source; }
32
+ error(message) { this.list.push({ level: 'error', message: this.source + ': ' + message }); }
33
+ warn(message) { this.list.push({ level: 'warning', message: this.source + ': ' + message }); }
34
+ get hasErrors() { return this.list.some(d => d.level === 'error'); }
35
+ }
36
+ function isRecord(value) {
37
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
38
+ }
39
+ function unknownKeys(source, known) {
40
+ return Object.keys(source).filter(key => !known.has(key));
41
+ }
42
+ /** Positive integer in [min, max], else undefined. */
43
+ function finiteInt(value, min, max) {
44
+ return typeof value === 'number' && Number.isInteger(value) && value >= min && value <= max
45
+ ? value
46
+ : undefined;
47
+ }
48
+ /**
49
+ * Validate a descriptor-relative entry path: no absolute paths, no
50
+ * backslashes, no traversal, plain safe segments only, and an exact
51
+ * lowercase PNG/WebP extension (the adopted entry discipline — SVG/CSS are
52
+ * not accepted). The extension match is case-sensitive on purpose: the
53
+ * asset route serves the declared path verbatim, so a case-mismatched
54
+ * suffix (frames.PNG vs frames.png) would pass a lenient check but 403 on
55
+ * case-sensitive filesystems.
56
+ * Returns the normalized path or undefined.
57
+ */
58
+ export function safeDecorationEntry(raw) {
59
+ if (typeof raw !== 'string' || raw.trim() === '')
60
+ return undefined;
61
+ const value = raw.trim();
62
+ if (value.length > 256)
63
+ return undefined;
64
+ if (isAbsolute(value) || value.includes('\\') || /^[a-z][a-z0-9+.-]*:/i.test(value))
65
+ return undefined;
66
+ const segments = value.split('/').filter(segment => segment !== '');
67
+ if (segments.length === 0)
68
+ return undefined;
69
+ if (segments.some(segment => segment === '.' || segment === '..' || !PATH_SEGMENT_PATTERN.test(segment)))
70
+ return undefined;
71
+ const last = segments[segments.length - 1];
72
+ const dot = last.lastIndexOf('.');
73
+ if (dot <= 0 || !DECORATION_ENTRY_EXTENSIONS.includes(last.slice(dot)))
74
+ return undefined;
75
+ return segments.join('/');
76
+ }
77
+ /** Normalize one phase binding value; undefined (warning) on bad content. */
78
+ function normalizeSegment(raw, columns, diag) {
79
+ if (raw === 'hide')
80
+ return 'hide';
81
+ if (!isRecord(raw)) {
82
+ diag.warn('phase binding must be "hide" or { from, to }; binding dropped');
83
+ return undefined;
84
+ }
85
+ const from = finiteInt(raw.from, 0, columns - 1);
86
+ const to = finiteInt(raw.to, 0, columns - 1);
87
+ if (from === undefined || to === undefined || from > to) {
88
+ diag.warn('phase frame segment out of range; binding dropped');
89
+ return undefined;
90
+ }
91
+ return { from, to };
92
+ }
93
+ /**
94
+ * Parse and validate one decoration.json document. Fail-closed over the
95
+ * structure (types, key sets, paths, ranges); phase-binding content issues
96
+ * drop that binding only (warn-and-drop, the registry never-throw rule).
97
+ */
98
+ export function parseDecorationManifest(raw, source = 'decoration.json') {
99
+ const diag = new Diagnostics(source);
100
+ if (!isRecord(raw)) {
101
+ diag.error('descriptor must be a JSON object');
102
+ return { ok: false, diagnostics: diag.list };
103
+ }
104
+ for (const key of unknownKeys(raw, KNOWN_DECORATION_TOP_LEVEL)) {
105
+ diag.error('unknown top-level field ' + JSON.stringify(key));
106
+ }
107
+ if (raw.decorationManifestVersion !== 1) {
108
+ diag.error('decorationManifestVersion must be 1');
109
+ }
110
+ const id = typeof raw.id === 'string' ? raw.id.trim() : '';
111
+ if (!PET_ID_PATTERN.test(id)) {
112
+ diag.error('id must be a lowercase kebab id');
113
+ }
114
+ if (id.length > 64) {
115
+ diag.error('id must be at most 64 characters');
116
+ }
117
+ const license = typeof raw.license === 'string' ? raw.license.trim() : '';
118
+ if (license === '')
119
+ diag.error('license is required (asset provenance)');
120
+ if (license.length > 128) {
121
+ diag.error('license must be at most 128 characters');
122
+ }
123
+ const entry = safeDecorationEntry(raw.entry);
124
+ if (entry === undefined) {
125
+ diag.error('entry must be a safe relative PNG/WebP path');
126
+ }
127
+ const rawCell = isRecord(raw.cell) ? raw.cell : {};
128
+ for (const key of Object.keys(rawCell)) {
129
+ if (key !== 'width' && key !== 'height')
130
+ diag.warn('unknown cell field ' + JSON.stringify(key) + ' ignored');
131
+ }
132
+ const cellWidth = finiteInt(rawCell.width, 1, DECORATION_CELL_MAX);
133
+ const cellHeight = finiteInt(rawCell.height, 1, DECORATION_CELL_MAX);
134
+ if (cellWidth === undefined || cellHeight === undefined) {
135
+ diag.error('cell width/height must be integers in [1, ' + DECORATION_CELL_MAX + ']');
136
+ }
137
+ const columns = finiteInt(raw.columns, 1, DECORATION_COLUMNS_MAX);
138
+ if (columns === undefined) {
139
+ diag.error('columns must be an integer in [1, ' + DECORATION_COLUMNS_MAX + ']');
140
+ }
141
+ if (diag.hasErrors || id === '' || entry === undefined || columns === undefined) {
142
+ return { ok: false, diagnostics: diag.list };
143
+ }
144
+ const displayName = typeof raw.displayName === 'string' && raw.displayName.trim() !== ''
145
+ ? raw.displayName.trim().slice(0, DECORATION_DISPLAY_NAME_MAX)
146
+ : id;
147
+ let loop;
148
+ if (raw.loop === undefined || typeof raw.loop === 'boolean') {
149
+ loop = raw.loop ?? true;
150
+ }
151
+ else {
152
+ diag.warn('loop must be a boolean; defaulting to true');
153
+ loop = true;
154
+ }
155
+ const rawDurations = raw.durations;
156
+ let durations;
157
+ if (Array.isArray(rawDurations)) {
158
+ const usable = rawDurations.filter((v) => typeof v === 'number' && Number.isInteger(v) && v >= 1 && v <= DECORATION_DURATION_MAX_MS);
159
+ if (usable.length !== columns) {
160
+ diag.warn('durations length must equal columns; using the constant frameMs instead');
161
+ durations = [];
162
+ }
163
+ else {
164
+ durations = usable;
165
+ }
166
+ }
167
+ else if (rawDurations !== undefined) {
168
+ diag.warn('durations must be an array; using the constant frameMs instead');
169
+ durations = [];
170
+ }
171
+ else {
172
+ durations = [];
173
+ }
174
+ if (durations.length === 0) {
175
+ const frameMs = finiteInt(raw.frameMs, 1, DECORATION_DURATION_MAX_MS) ?? 120;
176
+ durations = Array.from({ length: columns }, () => frameMs);
177
+ }
178
+ const phases = {};
179
+ const rawPhases = raw.phases;
180
+ if (isRecord(rawPhases)) {
181
+ for (const [key, value] of Object.entries(rawPhases)) {
182
+ if (!PET_ACTIVITY_PHASES.includes(key)) {
183
+ diag.warn('unknown phase ' + JSON.stringify(key) + '; binding ignored');
184
+ continue;
185
+ }
186
+ const segment = normalizeSegment(value, columns, diag);
187
+ if (segment !== undefined)
188
+ phases[key] = segment;
189
+ }
190
+ }
191
+ else if (rawPhases !== undefined) {
192
+ diag.warn('phases must be an object; all phases hide');
193
+ }
194
+ const visible = Object.values(phases).some(segment => segment !== 'hide');
195
+ if (!visible)
196
+ diag.warn('no phase shows the ornament; the decoration stays hidden');
197
+ const manifest = {
198
+ decorationManifestVersion: 1,
199
+ id,
200
+ displayName,
201
+ license,
202
+ entry,
203
+ cell: { width: cellWidth, height: cellHeight },
204
+ columns,
205
+ durations,
206
+ loop,
207
+ phases,
208
+ };
209
+ return { ok: true, manifest, diagnostics: diag.list };
210
+ }
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import type { SessionEvent } from '@deepseek-ai/dsh-session';
14
14
  import type { PetStateInput } from './state.ts';
15
- import { StatusVoice, WhisperEngine } from './chatter.ts';
15
+ import { StatusVoice, WhisperEngine, type VoicePoolsProvider } from './chatter.ts';
16
16
  /** Runtime shape of the optional legacy activity event. */
17
17
  export interface ActivityStatusEventLike {
18
18
  phase?: string;
@@ -36,8 +36,13 @@ export interface PetActivityTransition {
36
36
  /** A fresh inner whisper woken by this event's model output, when any. */
37
37
  whisper?: string;
38
38
  }
39
- /** Fresh projection runtime for a newly seen session. */
40
- export declare function emptyProjectionRuntime(): ProjectionRuntime;
39
+ /**
40
+ * Fresh projection runtime for a newly seen session. The optional voice-pack
41
+ * provider (pet-center M4, issue #677) hands both chatter engines their
42
+ * pools; engines resolve overrides at draw time, so swapping the provider's
43
+ * pack re-voices live runtimes without rebuilding them.
44
+ */
45
+ export declare function emptyProjectionRuntime(pools?: VoicePoolsProvider): ProjectionRuntime;
41
46
  /** Whether a legacy phase is part of the pet's supported vocabulary. */
42
47
  export declare function isActivityPhase(phase: string): phase is PetStateInput['phase'];
43
48
  /**
@@ -1 +1 @@
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"}
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,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAE/F,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;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,CAAC,EAAE,kBAAkB,GAAG,iBAAiB,CAQpF;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"}
@@ -11,14 +11,19 @@
11
11
  * @module @linxin666/dsh-pet/event-projection
12
12
  */
13
13
  import { StatusVoice, toolArgHint, WhisperEngine } from './chatter.js';
14
- /** Fresh projection runtime for a newly seen session. */
15
- export function emptyProjectionRuntime() {
14
+ /**
15
+ * Fresh projection runtime for a newly seen session. The optional voice-pack
16
+ * provider (pet-center M4, issue #677) hands both chatter engines their
17
+ * pools; engines resolve overrides at draw time, so swapping the provider's
18
+ * pack re-voices live runtimes without rebuilding them.
19
+ */
20
+ export function emptyProjectionRuntime(pools) {
16
21
  return {
17
22
  activeTools: new Set(),
18
23
  officialEventsSeen: false,
19
24
  stepHadFailure: false,
20
- voice: new StatusVoice(),
21
- whispers: new WhisperEngine(),
25
+ voice: new StatusVoice(pools),
26
+ whispers: new WhisperEngine(pools),
22
27
  };
23
28
  }
24
29
  /** Keep tool names readable inside the compact status bubble. */
@@ -47,6 +47,7 @@ export declare function makePetSettingsSchema(fallbackPetId: string): z<Schemast
47
47
  bottom: z<number, number>;
48
48
  petId: z<string, string>;
49
49
  enabled: z<boolean, boolean>;
50
+ decorationEnabled: z<boolean, boolean>;
50
51
  }>, Schemastery.ObjectT<{
51
52
  visible: z<boolean, boolean>;
52
53
  size: z<number, number>;
@@ -54,6 +55,7 @@ export declare function makePetSettingsSchema(fallbackPetId: string): z<Schemast
54
55
  bottom: z<number, number>;
55
56
  petId: z<string, string>;
56
57
  enabled: z<boolean, boolean>;
58
+ decorationEnabled: z<boolean, boolean>;
57
59
  }>>;
58
60
  /** Register the pet service and its API + asset routes on the context. */
59
61
  export declare const apply: typeof applyImpl;
@@ -1 +1 @@
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;AAM1G,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAC9D,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,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,eAAe,EACf,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,mBAAmB,GACpB,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9E,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,eAAO,MAAM,KAAK,kBAA6C,CAAA;AAE/D,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,SAAc,GAAG,IAAI,CA+D7D"}
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;AAM1G,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAC9D,YAAY,EACV,SAAS,EACT,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,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,eAAe,EACf,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,mBAAmB,GACpB,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9E,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;;;;;;;;;;;;;;;;IAU1D;AAED,0EAA0E;AAC1E,eAAO,MAAM,KAAK,kBAA6C,CAAA;AAE/D,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,SAAc,GAAG,IAAI,CAgE7D"}
@@ -45,6 +45,7 @@ export function makePetSettingsSchema(fallbackPetId) {
45
45
  bottom: z.number().step(1).min(0).max(DISPLAY_INSET_MAX).default(20),
46
46
  petId: z.string().default(fallbackPetId),
47
47
  enabled: z.boolean().default(true),
48
+ decorationEnabled: z.boolean().default(true),
48
49
  });
49
50
  }
50
51
  /** Register the pet service and its API + asset routes on the context. */
@@ -71,6 +72,7 @@ function applyImpl(ctx, config = {}) {
71
72
  bottom: service.display().bottom,
72
73
  petId: service.selectedPetId(),
73
74
  enabled: config.enabled ?? true,
75
+ decorationEnabled: config.decorationEnabled ?? true,
74
76
  };
75
77
  // The browser half talks to the pet through same-origin JSON endpoints and
76
78
  // loads each pet's atlas from the registry's own media route (RPC domains
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Pet manifest v2 — parsing, fail-closed validation, and the v1 compat read
3
+ * (issue #623, milestone M1/M2). This module is the single entry point every
4
+ * manifest parse flows through: manifests without 'petManifestVersion' are
5
+ * compat-read as v1 sprite2d pets (with a migration hint diagnostic), v2
6
+ * manifests are validated fail-closed (unknown top-level keys, unknown
7
+ * renderer kinds, missing conditional blocks and unsafe paths all reject the
8
+ * manifest with human-readable diagnostics).
9
+ *
10
+ * Discipline split: STRUCTURE is fail-closed (types, key sets, paths,
11
+ * required fields); CONTENT stays warn-and-drop (a bad sequence entry drops
12
+ * the entry, never the pet) — matching the registry's long-standing
13
+ * never-throw philosophy. Deep normalization of sequences/remarks stays with
14
+ * the registry's existing normalizers; this module only gates structure.
15
+ *
16
+ * The JSON Schema twin lives at contracts/pet-manifest-v2.schema.json for
17
+ * documentation, the CLI, and external tooling; the hand-rolled validator
18
+ * here is authoritative (the repository ships no schema-validator runtime).
19
+ *
20
+ * This file is imported directly by scripts/ (dsh-pet-migrate-v2) under
21
+ * node's strip-only TypeScript mode: keep it erasable-syntax-only (no
22
+ * parameter properties, enums, or namespaces).
23
+ * @module @linxin666/dsh-pet/manifest-v2
24
+ */
25
+ import type { ActivityPhase, PetAnimation } from './state.ts';
26
+ /** Schema version this module validates. */
27
+ export declare const PET_MANIFEST_V2: 2;
28
+ /** Renderer kinds the pet center knows how to dispatch (M1 §2). */
29
+ export declare const PET_RENDERER_KINDS: readonly ['sprite2d', 'live2d'];
30
+ export type PetRendererKind = (typeof PET_RENDERER_KINDS)[number];
31
+ /** The seven ActivityPhase semantics (pet-center owned; M1 §1). */
32
+ export declare const PET_ACTIVITY_PHASES: readonly ActivityPhase[];
33
+ /** sprite2d renderer block (v2 nested shape of the v1 atlas contract). */
34
+ export interface PetManifestSprite2d {
35
+ /** Atlas path relative to the manifest directory (safe segments only). */
36
+ spritesheetPath: string;
37
+ /** Atlas cell size in px; defaults resolved by the registry. */
38
+ cell?: {
39
+ width?: number;
40
+ height?: number;
41
+ };
42
+ /** Columns per row; defaults to 8. */
43
+ columns?: number;
44
+ /** Total atlas rows (9 classic; 11 for v2 look-row atlases). */
45
+ atlasRows?: number;
46
+ /** Per-row used frame counts. */
47
+ frames?: number[];
48
+ /** Per-track rhythm overrides (durations/loop/fallback), v1 shape. */
49
+ tracks?: Record<string, unknown>;
50
+ }
51
+ /** live2d renderer block (Cubism Core is always user-supplied; M1 §0). */
52
+ export interface PetManifestLive2d {
53
+ /** Path of the .model3.json relative to the manifest directory. */
54
+ model: string;
55
+ /** Model scale, (0, 10]; defaults to 1. */
56
+ scale?: number;
57
+ /** Model offset in canvas space. */
58
+ translate?: {
59
+ x?: number;
60
+ y?: number;
61
+ };
62
+ /** ActivityPhase -> motion group name; idle is required, unmapped phases fall back to idle. */
63
+ motions: Partial<Record<ActivityPhase, string>> & {
64
+ idle: string;
65
+ };
66
+ /** Optional ActivityPhase -> expression name layered over the motion. */
67
+ expressions?: Partial<Record<ActivityPhase, string>>;
68
+ /** Hit area names triggering pet.interact; defaults to every model HitArea. */
69
+ hitAreas?: string[];
70
+ /** Whisper-paced lip sync (post-M3). */
71
+ lipSync?: boolean;
72
+ }
73
+ /** Normalized v2 manifest the registry consumes. */
74
+ export interface PetManifestV2 {
75
+ petManifestVersion: typeof PET_MANIFEST_V2;
76
+ id: string;
77
+ displayName: string;
78
+ description?: string;
79
+ version?: string;
80
+ author?: string;
81
+ /** Required by v2; v1 compat reads may lack it (warning, not rejection). */
82
+ license?: string;
83
+ homepage?: string;
84
+ renderer: PetRendererKind;
85
+ sprite2d?: PetManifestSprite2d;
86
+ live2d?: PetManifestLive2d;
87
+ sequences?: Partial<Record<ActivityPhase, PetAnimation[]>>;
88
+ /** Pass-through for the registry's remarks normalizer (v1 shape). */
89
+ remarks?: unknown;
90
+ }
91
+ /** One structured diagnostic emitted while parsing a manifest. */
92
+ export interface PetManifestDiagnostic {
93
+ level: 'error' | 'warning';
94
+ message: string;
95
+ }
96
+ /** Parse outcome: a usable manifest plus diagnostics, or rejection. */
97
+ export type PetManifestParse = {
98
+ ok: true;
99
+ manifest: PetManifestV2;
100
+ migrated: 'v1-compat' | undefined;
101
+ diagnostics: PetManifestDiagnostic[];
102
+ } | {
103
+ ok: false;
104
+ diagnostics: PetManifestDiagnostic[];
105
+ };
106
+ /**
107
+ * Field allow-lists mirroring contracts/pet-manifest-v2.schema.json. Exported
108
+ * so the drift test can lock the schema file and this validator together;
109
+ * the CLI reuses parsePetManifest instead of these.
110
+ */
111
+ export declare const KNOWN_TOP_LEVEL: Set<string>;
112
+ /** sprite2d block field allow-list (drift-locked to the schema file). */
113
+ export declare const KNOWN_SPRITE2D: Set<string>;
114
+ /** live2d block field allow-list (drift-locked to the schema file). */
115
+ export declare const KNOWN_LIVE2D: Set<string>;
116
+ /**
117
+ * Validate a manifest-relative asset path: no absolute paths, no backslashes,
118
+ * no traversal, plain safe segments only. Returns the normalized path.
119
+ */
120
+ export declare function safeManifestPath(raw: unknown): string | undefined;
121
+ /**
122
+ * Parse one pet manifest: v1 (no petManifestVersion) is compat-read as a
123
+ * sprite2d pet with a migration hint; v2 is validated fail-closed. The parse
124
+ * never throws — every failure comes back as structured diagnostics.
125
+ * @param raw - the parsed pet.json value.
126
+ * @param sourceLabel - human-readable origin for diagnostics (dir or file).
127
+ */
128
+ export declare function parsePetManifest(raw: unknown, sourceLabel: string): PetManifestParse;
129
+ //# sourceMappingURL=manifest-v2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-v2.d.ts","sourceRoot":"","sources":["../../src/manifest-v2.ts"],"names":[],"mappings":"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"}