@linxin666/dsh-pet 0.3.5 → 0.3.6

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 (50) hide show
  1. package/contracts/voice-pack-v1.schema.json +30 -0
  2. package/cordis.patch.yml +1 -1
  3. package/lib/client.js +144 -16
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +56 -17
  6. package/lib/invariant.js +1 -1
  7. package/lib/{state-DrMX22GL.js → state-NvDEoCln.js} +5 -2
  8. package/lib/types/access.d.ts +5 -4
  9. package/lib/types/access.d.ts.map +1 -1
  10. package/lib/types/access.js +2 -12
  11. package/lib/types/affinity.d.ts +2 -0
  12. package/lib/types/affinity.d.ts.map +1 -1
  13. package/lib/types/affinity.js +12 -0
  14. package/lib/types/client/PetSprite.d.ts.map +1 -1
  15. package/lib/types/client/PetSprite.js +7 -1
  16. package/lib/types/client/locales.d.ts +18 -0
  17. package/lib/types/client/locales.d.ts.map +1 -1
  18. package/lib/types/client/locales.js +18 -0
  19. package/lib/types/client/renderers/frames2d.d.ts +12 -0
  20. package/lib/types/client/renderers/frames2d.d.ts.map +1 -1
  21. package/lib/types/client/renderers/frames2d.js +143 -18
  22. package/lib/types/ledger.d.ts +4 -2
  23. package/lib/types/ledger.d.ts.map +1 -1
  24. package/lib/types/ledger.js +4 -4
  25. package/lib/types/pair-access.d.ts +35 -0
  26. package/lib/types/pair-access.d.ts.map +1 -0
  27. package/lib/types/pair-access.js +19 -0
  28. package/lib/types/remarks.d.ts +1 -1
  29. package/lib/types/remarks.d.ts.map +1 -1
  30. package/lib/types/remarks.js +11 -2
  31. package/lib/types/service.d.ts.map +1 -1
  32. package/lib/types/service.js +4 -1
  33. package/lib/types/voice-pack.d.ts +5 -0
  34. package/lib/types/voice-pack.d.ts.map +1 -1
  35. package/lib/types/voice-pack.js +36 -2
  36. package/package.json +1 -1
  37. package/src/access.ts +7 -27
  38. package/src/affinity.ts +13 -0
  39. package/src/client/PetSprite.test.tsx +18 -0
  40. package/src/client/PetSprite.tsx +9 -1
  41. package/src/client/locales.ts +18 -0
  42. package/src/client/renderers/frames2d.test.ts +101 -0
  43. package/src/client/renderers/frames2d.ts +138 -18
  44. package/src/ledger.ts +6 -4
  45. package/src/pair-access.ts +49 -0
  46. package/src/remarks.test.ts +10 -0
  47. package/src/remarks.ts +9 -2
  48. package/src/service.ts +4 -1
  49. package/src/voice-pack.test.ts +39 -0
  50. package/src/voice-pack.ts +37 -2
@@ -206,6 +206,36 @@
206
206
  "description": "Subset of actions to render in canonical order; absent = all three, [] = none."
207
207
  }
208
208
  }
209
+ },
210
+ "remarks": {
211
+ "type": "object",
212
+ "additionalProperties": false,
213
+ "description": "Pat/feed interaction remark pools (issue #1226). Keyed by pet/petCooldown/feed/feedCooldown/noTreats.",
214
+ "properties": {
215
+ "pet": {
216
+ "$ref": "#/definitions/pool"
217
+ },
218
+ "petCooldown": {
219
+ "$ref": "#/definitions/pool"
220
+ },
221
+ "feed": {
222
+ "$ref": "#/definitions/pool"
223
+ },
224
+ "feedCooldown": {
225
+ "$ref": "#/definitions/pool"
226
+ },
227
+ "noTreats": {
228
+ "$ref": "#/definitions/pool"
229
+ }
230
+ }
231
+ },
232
+ "ranks": {
233
+ "type": "object",
234
+ "additionalProperties": {
235
+ "type": "string",
236
+ "maxLength": 80
237
+ },
238
+ "description": "Affinity rank name overrides (issue #1226), keyed by tier minimum points or original rank name."
209
239
  }
210
240
  },
211
241
  "definitions": {
package/cordis.patch.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # dsh-pet bundle patch: inserts its plugin row into the web plugin roster.
2
2
  # The bundle is the official dsh plugin shape — install it into a dsh profile
3
- # (`dsh plugin --profile <name> add link:/Users/zcl/code/dsh-web-ui/packages/dsh-pet`). The single
3
+ # (`dsh plugin --profile <name> add link:/Users/zcl/code/dsh-web/packages/dsh-pet`). The single
4
4
  # row mounts BOTH halves: the host side (pet registry, state machine,
5
5
  # affinity score, pet.* RPC service) and the browser side (the modules node
6
6
  # half scans dsh.client packages into the web plugin roster, served from this
package/lib/client.js CHANGED
@@ -712,7 +712,12 @@ window.__ModuleLoader__.load({
712
712
  children: displayName
713
713
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
714
714
  className: pet_module_css_default.statRank,
715
- children: panelStat("rank", "pet.rank", { rank: snapshot?.affinity.rank ?? "?" })
715
+ children: (() => {
716
+ const rawRank = snapshot?.affinity.rank ?? "?";
717
+ const rankKey = `pet.rank.name.${rawRank}`;
718
+ const localized = props.t(rankKey);
719
+ return panelStat("rank", "pet.rank", { rank: localized !== rankKey ? localized : rawRank });
720
+ })()
716
721
  })]
717
722
  }),
718
723
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -1811,6 +1816,18 @@ window.__ModuleLoader__.load({
1811
1816
  * phase-mapped track. Rendering never throws: a broken track list degrades
1812
1817
  * to the first decodable frame, and the 1.2 s stall watchdog re-kicks the
1813
1818
  * playback chain after timer throttling.
1819
+ *
1820
+ * Frame presentation has two modes picked once at mount by capability
1821
+ * probing:
1822
+ * - Canvas bitmap buffer (default where createImageBitmap/fetch/2D context
1823
+ * exist): every frame is decoded exactly once into an ImageBitmap during
1824
+ * the warm pass and drawn onto one <canvas> - steady-state playback issues
1825
+ * zero DOM mutations and zero re-decodes (measured hotspot: swapping
1826
+ * <img>.src per frame drove image decode + invalidation every tick).
1827
+ * - Classic <img> fallback (jsdom/tests or missing APIs): identical to the
1828
+ * historical behavior - cache-warm Image elements plus guarded src swaps,
1829
+ * so environments without modern decoding keep working unchanged.
1830
+ *
1814
1831
  * @module @linxin666/dsh-pet/client/renderers/frames2d
1815
1832
  */
1816
1833
  /** Stall watchdog period: a looping track stuck longer re-kicks its chain. */
@@ -1844,19 +1861,79 @@ window.__ModuleLoader__.load({
1844
1861
  validateConfig: validateFrames2dConfig,
1845
1862
  mount(ctx, config) {
1846
1863
  const reducedMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1847
- const img = document.createElement("img");
1848
- img.dataset.dshPetFrames2d = ctx.petId;
1849
- img.alt = "";
1850
- img.draggable = false;
1851
- img.style.width = "100%";
1852
- img.style.height = "100%";
1853
- img.style.objectFit = "contain";
1854
- img.style.pointerEvents = "none";
1855
- ctx.container.appendChild(img);
1856
- for (const track of Object.values(config.tracks)) for (const url of track.frames) {
1857
- const pre = new Image();
1858
- pre.src = url;
1864
+ let canvas = null;
1865
+ let context2d = null;
1866
+ let img = null;
1867
+ try {
1868
+ if (typeof createImageBitmap === "function" && typeof fetch === "function") {
1869
+ const probe = document.createElement("canvas");
1870
+ const c2d = probe.getContext("2d");
1871
+ if (c2d !== null) {
1872
+ canvas = probe;
1873
+ context2d = c2d;
1874
+ }
1875
+ }
1876
+ } catch {
1877
+ canvas = null;
1878
+ context2d = null;
1859
1879
  }
1880
+ if (canvas !== null && context2d !== null) {
1881
+ canvas.dataset.dshPetFrames2d = ctx.petId;
1882
+ canvas.draggable = false;
1883
+ canvas.style.width = "100%";
1884
+ canvas.style.height = "100%";
1885
+ canvas.style.objectFit = "contain";
1886
+ canvas.style.pointerEvents = "none";
1887
+ ctx.container.appendChild(canvas);
1888
+ } else {
1889
+ img = document.createElement("img");
1890
+ img.dataset.dshPetFrames2d = ctx.petId;
1891
+ img.alt = "";
1892
+ img.draggable = false;
1893
+ img.style.width = "100%";
1894
+ img.style.height = "100%";
1895
+ img.style.objectFit = "contain";
1896
+ img.style.pointerEvents = "none";
1897
+ ctx.container.appendChild(img);
1898
+ }
1899
+ const decoding = /* @__PURE__ */ new Map();
1900
+ const decodedAll = [];
1901
+ const loadFrame = (url) => {
1902
+ const cached = decoding.get(url);
1903
+ if (cached !== void 0) return cached;
1904
+ const job = (async () => {
1905
+ try {
1906
+ const response = await fetch(url);
1907
+ if (!response.ok) throw new Error("http " + response.status);
1908
+ const bitmap = await createImageBitmap(await response.blob());
1909
+ return {
1910
+ source: bitmap,
1911
+ width: bitmap.width,
1912
+ height: bitmap.height
1913
+ };
1914
+ } catch {
1915
+ return await new Promise((resolve) => {
1916
+ try {
1917
+ const pre = new Image();
1918
+ pre.onload = () => {
1919
+ resolve(pre.naturalWidth > 0 ? {
1920
+ source: pre,
1921
+ width: pre.naturalWidth,
1922
+ height: pre.naturalHeight
1923
+ } : void 0);
1924
+ };
1925
+ pre.onerror = () => resolve(void 0);
1926
+ pre.src = url;
1927
+ } catch {
1928
+ resolve(void 0);
1929
+ }
1930
+ });
1931
+ }
1932
+ })();
1933
+ decoding.set(url, job);
1934
+ decodedAll.push(job.then(() => void 0, () => void 0));
1935
+ return job;
1936
+ };
1860
1937
  let disposed = false;
1861
1938
  let timer;
1862
1939
  let watchdog;
@@ -1864,13 +1941,35 @@ window.__ModuleLoader__.load({
1864
1941
  let frameIndex = 0;
1865
1942
  let lastAdvance = Date.now();
1866
1943
  let override;
1944
+ let drawToken = 0;
1945
+ let lastDrawnUrl;
1867
1946
  const trackForPhase = (phase) => {
1868
1947
  const mapped = config.phases[phase];
1869
1948
  return mapped !== void 0 && config.tracks[mapped] !== void 0 ? mapped : config.phases.idle;
1870
1949
  };
1950
+ /** Canvas path: paints the newest requested frame; stale draws drop out. */
1951
+ const paintCanvas = (url) => {
1952
+ if (context2d === null || canvas === null) return;
1953
+ const myToken = ++drawToken;
1954
+ loadFrame(url).then((frame) => {
1955
+ if (disposed || frame === void 0 || myToken !== drawToken) return;
1956
+ if (lastDrawnUrl === url) return;
1957
+ lastDrawnUrl = url;
1958
+ if (canvas.width !== frame.width || canvas.height !== frame.height) {
1959
+ canvas.width = frame.width;
1960
+ canvas.height = frame.height;
1961
+ } else context2d.clearRect(0, 0, canvas.width, canvas.height);
1962
+ context2d.drawImage(frame.source, 0, 0);
1963
+ }).catch(() => {});
1964
+ };
1871
1965
  const show = (trackId, index) => {
1872
1966
  const url = config.tracks[trackId]?.frames[index];
1873
- if (url !== void 0 && img.getAttribute("src") !== url) img.src = url;
1967
+ if (url === void 0) return;
1968
+ if (img !== null) {
1969
+ if (img.getAttribute("src") !== url) img.src = url;
1970
+ return;
1971
+ }
1972
+ paintCanvas(url);
1874
1973
  };
1875
1974
  const schedule = (ms) => {
1876
1975
  if (disposed) return;
@@ -1922,6 +2021,7 @@ window.__ModuleLoader__.load({
1922
2021
  const expected = (def.durations[frameIndex] ?? 200) + WATCHDOG_MS;
1923
2022
  if (Date.now() - lastAdvance > expected) tick();
1924
2023
  }, WATCHDOG_MS);
2024
+ for (const warmTrack of Object.values(config.tracks)) for (const warmUrl of warmTrack.frames) loadFrame(warmUrl);
1925
2025
  play(track);
1926
2026
  let disposedOnce = false;
1927
2027
  const dispose = () => {
@@ -1931,7 +2031,17 @@ window.__ModuleLoader__.load({
1931
2031
  unsubscribe();
1932
2032
  if (timer !== void 0) clearTimeout(timer);
1933
2033
  if (watchdog !== void 0) clearInterval(watchdog);
1934
- img.remove();
2034
+ Promise.allSettled(decodedAll).then(() => {
2035
+ for (const job of decoding.values()) job.then((frame) => {
2036
+ try {
2037
+ const maybeClose = (frame?.source)?.close;
2038
+ if (typeof maybeClose === "function" && frame !== void 0) maybeClose.call(frame.source);
2039
+ } catch {}
2040
+ }).catch(() => {});
2041
+ decoding.clear();
2042
+ });
2043
+ canvas?.remove();
2044
+ img?.remove();
1935
2045
  };
1936
2046
  ctx.onCleanup(dispose);
1937
2047
  return {
@@ -3081,6 +3191,15 @@ window.__ModuleLoader__.load({
3081
3191
  "pet.namePlaceholder": "输入新名字",
3082
3192
  "pet.summon": "召唤{name}",
3083
3193
  "pet.rank": "亲密度 {rank}",
3194
+ "pet.rank.name.幼鲸": "幼鲸",
3195
+ "pet.rank.name.伙伴": "伙伴",
3196
+ "pet.rank.name.挚友": "挚友",
3197
+ "pet.rank.name.深海羁绊": "深海羁绊",
3198
+ "pet.rank.name.心有灵犀": "心有灵犀",
3199
+ "pet.rank.name.传说羁绊": "传说羁绊",
3200
+ "pet.rank.name.神话羁绊": "神话羁绊",
3201
+ "pet.rank.name.永恒之契": "永恒之契",
3202
+ "pet.rank.name.鲸生共渡": "鲸生共渡",
3084
3203
  "pet.points": "{points} 点",
3085
3204
  "pet.treats": "小鱼干 ×{n}",
3086
3205
  "pet.state.loading": "宠物正在赶来…",
@@ -3151,6 +3270,15 @@ window.__ModuleLoader__.load({
3151
3270
  "pet.namePlaceholder": "Enter a new name",
3152
3271
  "pet.summon": "Summon {name}",
3153
3272
  "pet.rank": "Affinity {rank}",
3273
+ "pet.rank.name.幼鲸": "Baby Whale",
3274
+ "pet.rank.name.伙伴": "Companion",
3275
+ "pet.rank.name.挚友": "Close Friend",
3276
+ "pet.rank.name.深海羁绊": "Deep Sea Bond",
3277
+ "pet.rank.name.心有灵犀": "Kindred Spirit",
3278
+ "pet.rank.name.传说羁绊": "Legendary Bond",
3279
+ "pet.rank.name.神话羁绊": "Mythic Bond",
3280
+ "pet.rank.name.永恒之契": "Eternal Covenant",
3281
+ "pet.rank.name.鲸生共渡": "Lifelong Companion",
3154
3282
  "pet.points": "{points} pts",
3155
3283
  "pet.treats": "Treats ×{n}",
3156
3284
  "pet.state.loading": "The pet is on its way…",
@@ -3242,7 +3370,7 @@ window.__ModuleLoader__.load({
3242
3370
  /** The building package's version, when the bundle carries it. */
3243
3371
  function bakedVersion() {
3244
3372
  try {
3245
- return "0.3.5";
3373
+ return "0.3.6";
3246
3374
  } catch {
3247
3375
  return;
3248
3376
  }