dsh-mpkg-wallpaper 3.13.4 → 3.13.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.
package/lib/client.js CHANGED
@@ -5725,7 +5725,9 @@ function mpwThumbFail(el, why) {
5725
5725
  const c2 = canvas.getContext("2d");
5726
5726
  if (!c2) return;
5727
5727
  const layers = manifest.layers || [];
5728
- if (!layers.length) return;
5728
+ /* ①(P-184) 清单里一层都没有:也落一次**0 层**戳 —— 否则会留着上一份同 key 清单的旧戳,
5729
+ 慢判就会把"这份清单没画面"误读成"画出来了"(与"所有层加载失败"同一条口径:如实判没画出来)。 */
5730
+ if (!layers.length) { mpwScenePaintStamp(canvas, manifest.key, 0); return; }
5729
5731
  sceneImgs = layers.map(() => null);
5730
5732
  let pending = layers.length;
5731
5733
  const draw = () => {
@@ -5738,13 +5740,18 @@ function mpwThumbFail(el, why) {
5738
5740
  // 图层定位:清单坐标是场景包围盒内偏移([0,W]×[0,H]),
5739
5741
  // 场景中心 = (W/2, H/2) 对齐屏幕中心。
5740
5742
  const offX = manifest.w / 2, offY = manifest.h / 2;
5743
+ let drawn = 0;
5741
5744
  for (let i = 0; i < layers.length; i++) {
5742
5745
  const im = sceneImgs[i];
5743
5746
  const l = layers[i];
5744
5747
  if (!im || !im.width || !im.height) continue;
5745
5748
  const w = l.w * scale, h = l.h * scale;
5746
5749
  c2.drawImage(im, cx + (l.x - offX) * scale - w / 2, cy + (l.y - offY) * scale - h / 2, w, h);
5750
+ drawn++;
5747
5751
  }
5752
+ /* ①(2026-09-25 P-184) 落**画过戳**:只认"真的画了几层 + 画的是哪一份清单"(见
5753
+ `mpwScenePaintStamp` 的注释)。0 层 ⇒ 戳为 0 ⇒ 慢判如实判"没画出来"并补一次 + 告警。 */
5754
+ mpwScenePaintStamp(canvas, manifest.key, drawn);
5748
5755
  };
5749
5756
  layers.forEach((l, i) => {
5750
5757
  const im = new Image();
@@ -5858,12 +5865,36 @@ function mpwThumbFail(el, why) {
5858
5865
  · 慢判(12s):有 src 但**一直没有画面** ⇒ 强制重挂一次(同签名只做一次)。
5859
5866
  诚实边界:转码档(host 正在转)不判死 —— 它有自己的超时回退(见 showVideoEl 后面的
5860
5867
  `__mpwTransLoadingTimeout`),在后面推它只会把转码重来一遍。 */
5868
+ /* ①(2026-09-25 P-184) 场景合成的"画过"戳:**逐层计数**(`mpwScenePaintStamp`/`mpwScenePainted`)。
5869
+ 为什么必须换成戳:慢判(12s"有 src 但一直没有画面 ⇒ 强制重挂一次")原来对 scene 档用的判据是
5870
+ `canvas.width > 0` —— 而 `draw()` **一开始**就按视口设宽高、`clearRect` 之后逐层 `drawImage`,
5871
+ **所有图层都加载失败时**(真机:图层 URL 404 / token 失效 / 清单里全是坏 url)宽高照样被设上
5872
+ ⇒ 判据退化成"元素存在",慢判恒为"画出来了"、自愈永不触发,用户看到的是**永久空白且无自愈**。
5873
+ 现在只有**真的 `drawImage` 过 ≥1 层、且那一次画的正是当前 sceneKey** 才算"画出来了"。
5874
+ (抽成两个只用 canvas 普通字段的小函数,是为了让门禁能单独切出来跑真实现:
5875
+ `tools/bgpaint-heal-test.mjs`;也便于宿主/探针读同一处口径。) */
5876
+ function mpwScenePaintStamp(canvas, key, drawn) {
5877
+ try {
5878
+ if (!canvas) return 0;
5879
+ canvas.__mpwBgDrawnLayers = Number(drawn) || 0;
5880
+ canvas.__mpwBgDrawnKey = String(key == null ? "" : key);
5881
+ canvas.__mpwBgDrawnAt = (typeof Date !== "undefined" && Date.now) ? Date.now() : 0;
5882
+ return canvas.__mpwBgDrawnLayers;
5883
+ } catch { return 0 }
5884
+ }
5885
+ function mpwScenePainted(canvas, key) {
5886
+ try {
5887
+ if (!canvas) return false;
5888
+ if (!((Number(canvas.__mpwBgDrawnLayers) || 0) > 0)) return false;
5889
+ return String(canvas.__mpwBgDrawnKey || "") === String(key == null ? "" : key);
5890
+ } catch { return false }
5891
+ }
5861
5892
  function mpwBgPaintedNow(section) {
5862
5893
  try {
5863
5894
  const { img, video, canvas, wrap } = bgElements();
5864
5895
  if (!wrap) return true;
5865
5896
  if (section.webUrl) return true; // 帧内画面看不见,不越权判死
5866
- if (section.converted === "scene" && section.sceneKey) return !!(canvas && (canvas.width || 0) > 0);
5897
+ if (section.converted === "scene" && section.sceneKey) return mpwScenePainted(canvas, section.sceneKey);
5867
5898
  if (section.converted === "mp4") {
5868
5899
  if (!video) return true;
5869
5900
  let st = "";
@@ -5937,7 +5968,7 @@ function mpwThumbFail(el, why) {
5937
5968
  }
5938
5969
  try { applyFromStorageInner() } catch (e) { mpwErr("壁纸源补挂", e) }
5939
5970
  }
5940
- try { window.__mpwBgWrapState = () => ({ fixOn: mpwBgWrapFixOn(), heal: (() => { try { return window.__mpwBgSrcHeal || 0 } catch { return -1 } })(), last: __mpwBgHeal, paintedNow: (() => { try { return mpwBgPaintedNow(normalizeSection(readSection())) } catch { return null } })() }) } catch {}
5971
+ try { window.__mpwBgWrapState = () => ({ fixOn: mpwBgWrapFixOn(), heal: (() => { try { return window.__mpwBgSrcHeal || 0 } catch { return -1 } })(), last: __mpwBgHeal, paintedNow: (() => { try { return mpwBgPaintedNow(normalizeSection(readSection())) } catch { return null } })(), drawn: (() => { try { const c = bgElements().canvas; return c ? { layers: Number(c.__mpwBgDrawnLayers) || 0, key: String(c.__mpwBgDrawnKey || ""), at: Number(c.__mpwBgDrawnAt) || 0 } : null } catch { return null } })() }) } catch {}
5941
5972
  /* ═══ ①(2026-09-16 根治) 设置归一化:**必须是模块级函数** ═══
5942
5973
  历史 bug("标题栏磨砂一直没有效果"的真根因,真机 diag 实锤):
5943
5974
  `__asStr` / `normalizeSection` 原本定义在 `applyFromStorageInner()` **函数体内**
package/lib/index.js CHANGED
@@ -16,7 +16,7 @@ import { execFileSync, spawn } from 'node:child_process';
16
16
  // ①(2026-09-21 音频格式补全) 后三个导出是**唯一的**扩展名→MIME 表与嗅探入口,见 docs/AUDIO-TRACK-SPEC.md §1.2/§3:
17
17
  // 路由侧原先各自内联一份扩展名三元链 + 一份魔数嗅探,已与渲染器支持的后缀漂移(.oga/.m4a/.aac 缺失、ftyp 偏移写错)。
18
18
  // ⚠ 这条 import 必须**保持单行**:tools/bundle-equivalence-test.mjs 的具名导入解析器按行读(注释进括号会解析失败)。
19
- import { extractSceneMainImage, extractSceneMainImageFromDir, extractSceneManifest, extractSceneLayer, extractTexVideoMp4, collectSceneVideoFiles, parsePkg, readPkgEntry, scanSceneAudio, scanSceneVideo, clearSceneVideoScanCache, suffixAudioMime, audioMimeFor, AUDIO_SUFFIX_MIME } from './pkg-extract.js';
19
+ import { extractSceneMainImage, extractSceneMainImageFromDir, extractSceneManifest, extractSceneLayer, extractTexVideoMp4, collectSceneVideoFiles, parsePkg, readPkgEntry, scanSceneAudio, scanSceneVideo, clearSceneVideoScanCache, suffixAudioMime, audioMimeFor, AUDIO_SUFFIX_MIME, mpwParseWeJson, mpwWeJsonSwallowed } from './pkg-extract.js';
20
20
  // ①(新 2026-09-16 I 项) 网页(web)类壁纸:内容优先的类型判定 + WE API shim 注入 + 跨源策略
21
21
  // (MIT,本仓库自写;API 名单参考 oneincase/webwallgl,未复制其代码,见 ../docs/COPYING-RULES.md §4)
22
22
  // ①(WP-1 2026-09-19) 追加:帧内存储 facade 的持久化路由(/web-store)、宿主媒体音频控制
@@ -3830,7 +3830,18 @@ function apply(ctx) {
3830
3830
  const proj = join(p, 'project.json');
3831
3831
  if (!existsSync(proj)) continue;
3832
3832
  try {
3833
- const meta = JSON.parse(readFileSync(proj, 'utf8'));
3833
+ /* ①(2026-09-24 包内/包旁 JSON 收口线) **包旁** JSON:Steam 库里 `<壁纸目录>/project.json`
3834
+ (与 scene.pkg 同级,官方工坊布局)⇒ 走**宿主侧同一份**宽容解析(尾逗号/注释/BOM;
3835
+ 官方 `assets/effects/fluidsimulation/effect.json` 第 402 行自己就带尾逗号)。
3836
+ 语义与改动前一致:读不到/真坏 JSON ⇒ 仍然抛(下面的既有 catch 照旧跳过该壁纸并 console.warn)。
3837
+ 多出来的是**记账**:解析器计数(`mpwWeJsonStats().failed`)+ 下面 catch 里的
3838
+ `mpwWeJsonSwallowed`(计数 + 一行诊断;同 where+原因只吵一次)—— 原来只有一句 warn,
3839
+ "坏在哪一步/坏了几次"看不出来。**注意只在解析这一段**调用记账:`readdirSync`/探测
3840
+ 失败不该被记成"JSON 坏"(归因不能糊)。 */
3841
+ const meta = (() => {
3842
+ try { return mpwParseWeJson(readFileSync(proj, 'utf8')); }
3843
+ catch (e) { mpwWeJsonSwallowed('[steam-inventory] ' + proj, e); throw e; }
3844
+ })();
3834
3845
  const type = (meta.type || 'scene').toLowerCase();
3835
3846
  // ④(安全) 排除 application / exe 壁纸:这类已弃用的 .exe 格式可能被注入病毒,
3836
3847
  // 本插件绝不读取/执行任何 .exe。scene 只给预览图(无法渲染)。
@@ -154,6 +154,14 @@ function mpwWeJsonSwallowed(where, e) {
154
154
  }
155
155
  /* ═══ MPW-WEJSON-END ═══ */
156
156
 
157
+ /* ①(2026-09-24 包内/包旁 JSON 收口线) **宿主侧复用出口**:`lib/web-wallpaper.js`(WE 目录的 `project.json`)
158
+ 与 `lib/index.js`(Steam 库壁纸目录的 `project.json`)都要用**同一份**宽容解析与记账 —— 导出面只加名字,
159
+ 解析逻辑、计数口径、`swallowed` 语义**一字未动**("包内 JSON 解析点都要记账"这条纪律由
160
+ tools/we-json-tolerance-test.mjs 的静态反查看住)。客户端侧 `lib/client.js` 的同名块是**浏览器产物**的
161
+ 内联副本(页面没有模块图),两边一致性由同一条门禁的切片对拍看着 —— 所以这里不是"第三份实现",
162
+ 而是把宿主这一份**接出来给宿主模块共用**。 */
163
+ export { mpwParseWeJson, mpwWeJsonStats, mpwWeJsonSwallowed };
164
+
157
165
  /** Wallpaper Engine texture format ids (TEXI0001 header), per RePKG/lwe. */
158
166
  const TexFormat = {
159
167
  RGBA8888: 0,
@@ -31,6 +31,13 @@ import { createHash } from 'node:crypto';
31
31
  // 为什么要 import 而不是复制一份:触摸的协议形状(`op:'touch'` 的字段表)与父页整形体同在
32
32
  // lib/web-interaction.js,**唯一源**不能有两份(tools/web-interaction-test.mjs 会断言两侧一致)。
33
33
  import { WEB_TOUCH_FRAME_SOURCE } from './web-interaction.js';
34
+ // ①(2026-09-24 包内/包旁 JSON 收口线) 官方随包 JSON 的**宽容解析**:**复用宿主侧那一份**
35
+ // (`lib/pkg-extract.js` 的 MPW-WEJSON 块,P-177 同口径:先 JSON.parse → 字符串外尾逗号 → 再不行剥注释;
36
+ // BOM 吃掉;真坏 JSON 仍然抛)。为什么必须共享而不是在本文件再写一遍:本仓已有两份同语义实现
37
+ // (`lib/client.js` 浏览器内联副本 + `lib/pkg-extract.js` 宿主侧),tools/we-json-tolerance-test.mjs
38
+ // 把两处切片逐夹具对拍防漂移 —— 再加第三份就是三处漂移面。
39
+ // ⚠ 本模块是**宿主侧** ESM(Node),与 `lib/index.js` 同一个模块图(bundle 的模块表因此不变)。
40
+ import { mpwParseWeJson, mpwWeJsonSwallowed } from './pkg-extract.js';
34
41
 
35
42
  /** 类型判定结果(四态 + 排除态) */
36
43
  export const WEB_KIND = { WEB: 'web', SCENE: 'scene', VIDEO: 'video', UNKNOWN: 'unknown' };
@@ -316,15 +323,27 @@ export function listWallpaperFiles(root, opts = {}) {
316
323
  return out;
317
324
  }
318
325
 
319
- /** 读并解析目录里的 project.json(缺失/损坏 → null;损坏不抛,交给启发式兜底) */
326
+ /** 读并解析目录里的 project.json(缺失 → null;损坏 → 记账后 null,交给启发式兜底)。
327
+ * ①(2026-09-24 包内/包旁 JSON 收口线) 读的是 **WE 目录的 `project.json`**(**包旁**:官方工坊布局里与
328
+ * `scene.pkg` 同级;本模块的 `detectWallpaperDir` → `detectWebWallpaperKind` 用它做**内容优先**判定,
329
+ * `general.file` 是网页壁纸入口的第一顺位证据)⇒ 必须容忍官方允许的尾逗号/注释:官方随包发布的
330
+ * `assets/effects/fluidsimulation/effect.json` 第 402 行自己就带尾逗号(WE 用 jsoncpp 照用),
331
+ * 严格 `JSON.parse` 会把这份**包旁属性表/入口声明**整份丢掉 ⇒ 类型判定只剩文件名启发式、属性推不下去,
332
+ * 而且**日志一行都没有**(改前的 `catch { return null }` 就是这类静默)。
333
+ * 语义保持"读不到就是 null"(缺失 ⇒ null / 真坏 JSON ⇒ 记账后 null,照旧不抛 —— 调用方按启发式兜底);
334
+ * `mpwWeJsonSwallowed` 给计数 + 一行诊断(同 where+原因只吵一次)。 */
320
335
  export function readProjectJson(dir) {
336
+ const p = join(dir, 'project.json');
337
+ let text = null;
321
338
  try {
322
- const p = join(dir, 'project.json');
323
339
  if (!statSync(p).isFile()) return null;
324
- const obj = JSON.parse(readFileSync(p, 'utf8'));
340
+ text = readFileSync(p, 'utf8');
341
+ } catch { return null; } // 缺失/损坏的路径/IO 读不到 ⇒ null(原语义;**不是** JSON 问题,不记账成 JSON 坏)
342
+ try {
343
+ const obj = mpwParseWeJson(text);
325
344
  return obj && typeof obj === 'object' ? obj : null;
326
- } catch {
327
- return null;
345
+ } catch (e) {
346
+ return mpwWeJsonSwallowed('web-wallpaper:readProjectJson(' + p + ')', e);
328
347
  }
329
348
  }
330
349
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mpkg-wallpaper",
3
- "version": "3.13.4",
3
+ "version": "3.13.6",
4
4
  "description": "DSH Web 壁纸引擎 Wallpaper Engine mpkg 背景插件:浏览器内直接解析 .mpkg(preview.gif 动态背景/内嵌 mp4 视频/多时段切换),整屏统一虚化/对话框/弹层/遮罩独立虚化、镜头缩放平移、时钟、冲突检测。",
5
5
  "private": false,
6
6
  "type": "module",