refactor(mask): [PKG-MASK-04] migrate strict types and preserve public consumers

This commit is contained in:
Harvey Zhao committed 2026-09-14 00:05:14 +08:00
1 parent aba125262a
commit aca4bc1cd2
30 files changed
+1848 -72

No files matched your search

File diff suppressed because it is too large. Load diff
@@ -0,0 +1,53 @@
# PKG-MASK-04 严格TS与公开类型兼容
## 实现与公开接口
五个自有运行模块迁为TS,types.ts明确最小核心宿主、原始可选参数/完整配置、SDK
额外历史传参、输出画布及每轮可空资源。类字段declare不产生新字段;局部断言说明
对应初始化、Promise resolver或RGBA长度约束。实际Artplayer宿主fixture通过严格检查。
相对MASK03提交aba125262,五模块归一化JS和三种正常构建产物保持一致。
公开声明与npm1.0.0/1.1.0保持字节一致(忽略换行)。原参数可选、同步注册、
Promise<void> start、void stop、固定name、完整工厂双向赋值及类型提取继续保留。
这些返回值本就准确,不为形式统一额外增加/runtime入口或暴露内部SDK类型。
## 两项类型工具修复
实际严格消费者先复现旧Node模块解析下/legacy报2307,现以精确typesVersions路径
映射修复;默认工厂声明与其他入口未变,历史直接声明路径仍验证。npm两版在相同
模式均记录旧失败,候选只允许消除此特定行的2307;NodeNext ESM根命名空间诊断
保持历史结果。根CommonJS最新是函数无default属性,1.0.0对象包装单独验证。
旧在线编辑器生成器把export default和export=混在一起,实际旧文件在两个编译器
均报2309。接入现有语义生成器,生成文件正例及三项非法调用反例通过。私有Option/
Result不改为新增公开类型;消费者用Parameters/ReturnType提取,生成内容校验防漂移。
SDK严格声明另复现TF4.22.0的hash_util.d.ts缺少Long import。声明专用sdk-ambient.d.ts
引用已安装官方@types/long4.0.2,不产生运行时导入、没有重写第三方类型,也不改
types:[]或skipLibCheck:false。包src和tsconfig均不进入tarball。
## 验证
Node24.21.0下联合99项通过:36候选生命周期、54历史缺陷、6历史契约、3公共/编辑器
类型测试。定向lint、全仓404生产TS及当前/兼容编译器流程通过。三种产物正常重建,
docs副本一致,生成编辑器声明通过正反例和文件一致性验证。
最终Yarn实际pack将旧npm1.0.0/1.1.0和候选分别安装到仓库外,复制根锁作依赖种子,
离线安装后再次frozen安装并逐字节核对包内容/锁文件。每包TS5.9.3的Node10、NodeNext
CJS/ESM、Bundler及TS4.3.5的Node10,共15组;每组8项非法输入/返回值独立报错。
实际安装main和legacy执行同步注册/stop/destroy,但不启动模型;不称为播放或SDK
验收。旧Node10/legacy失败用既有直接声明路径核实工厂,其原错误保留在报告。
复跑:yarn test:danmuku-mask、node --test refactor/scripts/danmuku-mask-types.test.mjs、
yarn test:danmuku-mask-types-package、yarn typecheck、yarn build:ts artplayer-plugin-danmuku-mask。
新增安装检查脚本复用现有Yarn/TS版本,无新增依赖或锁文件改动。包内架构及types/README
解释维护路径和历史边界。详见[类型验证](../baselines/danmuku-mask-types-validation.json)。
同步更新风险、SDK来源与影响映射中的当前源码路径,冻结历史JS夹具不改;风险和影响
检查通过,没有因迁移路径失效而忽略SDK或插件组合验证。
## 剩余工作与回退
05仍负责真实模型、原支持范围核心、浏览器/设备/切源/布局/后端与GPU资源;06负责
完整分发、许可、示例和发布验收。WebKit/Chrome设备能力和SDK的close完成仍未证明。
本任务不关闭这些风险、不改变各包独立major发布政策,不推送或发布。
回退此任务源码/类型解析及生成器选择后正常重建即可;03生命周期修复是独立提交。
@@ -0,0 +1,6 @@
import type Artplayer from 'artplayer'
import factory from '../../../packages/artplayer-plugin-danmuku-mask/src'
declare const art: Artplayer
const registration: { name: string, start: () => Promise<void>, stop: () => void } = factory({ opacity: 0, smoothSegmentation: false })(art)
export { registration }
+1 -1
View File
@@ -123,7 +123,7 @@
"consumer": "artplayer-plugin-danmuku-mask",
"reason": "Mask and Danmuku share the core-owned $danmuku layer; retain combination validation for root identity, styles and lifecycle",
"evidence": [
"packages/artplayer-plugin-danmuku-mask/src/index.js",
"packages/artplayer-plugin-danmuku-mask/src/index.ts",
"docs/assets/example/danmuku.mask.js"
]
}
+3 -2
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 231 项,范围 22 个包及工作区/示例。
状态:todo 67 / doing 14 / blocked 0 / done 150 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 66 / doing 14 / blocked 0 / done 151 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -243,7 +243,7 @@
| PKG-MASK-01 | artplayer-plugin-danmuku-mask<br>核对包契约与历史用法 | BASE-05 | 模型参数、start/stop、默认下载路径及 mask 样式 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结;接续 SDK-08,分别核实 Yarn 解析版本、未固定版本的模型 solutionPath 和资源来源 | H | done |
| PKG-MASK-02 | artplayer-plugin-danmuku-mask<br>建立特有行为与错误测试 | PKG-MASK-01, ENG-03, ENG-05 | 加载期间停止/销毁、重复启动、推理失败、WebGL/CPU 边界 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
| PKG-MASK-03 | artplayer-plugin-danmuku-mask<br>整理内部职责与资源 | PKG-MASK-02, CORE-18, PKG-DANMUKU-07 | 模型加载/推理/画布输出分离,阻止重叠推理与过期写入 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
| PKG-MASK-04 | artplayer-plugin-danmuku-mask<br>迁移自有源码和公开类型 | PKG-MASK-03, ENG-04, ENG-06, CORE-07 | 模型 adapter、canvas 和选项的精确类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
| PKG-MASK-04 | artplayer-plugin-danmuku-mask<br>迁移自有源码和公开类型 | PKG-MASK-03, ENG-04, ENG-06, CORE-07 | 模型 adapter、canvas 和选项的精确类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
| PKG-MASK-05 | artplayer-plugin-danmuku-mask<br>验证新旧核心和组合 | PKG-MASK-04, CORE-22 | 真实模型和 danmuku/seek/全屏组合,GPU 资源释放 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
| PKG-MASK-06 | artplayer-plugin-danmuku-mask<br>验证分发并同步文档 | PKG-MASK-05, ENG-07 | danmuku.mask.js、资源版本/许可、CPU fallback 和包体积证据 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
@@ -535,6 +535,7 @@
- PKG-MASK-01: [记录](baselines/danmuku-mask-release.json) [记录](baselines/danmuku-mask-registry.json) [记录](baselines/danmuku-mask-contract.md) [记录](baselines/danmuku-mask-contract-validation.json) [记录](changes/2026-09-13-PKG-MASK-01-contract.md)
- PKG-MASK-02: [记录](changes/2026-09-13-PKG-MASK-02-failures.md) [记录](baselines/danmuku-mask-failures-validation.json)
- PKG-MASK-03: [记录](changes/2026-09-13-PKG-MASK-03-lifecycle.md) [记录](baselines/danmuku-mask-lifecycle-validation.json)
- PKG-MASK-04: [记录](changes/2026-09-14-PKG-MASK-04-types.md) [记录](baselines/danmuku-mask-types-validation.json)
- PKG-ASR-01: [记录](changes/2026-09-13-PKG-ASR-01-contracts.md) [记录](baselines/asr-contract.md) [记录](baselines/asr-release.json)
- PKG-ASR-02: [记录](changes/2026-09-13-PKG-ASR-02-audio-baseline.md) [记录](baselines/asr-audio-validation.json)
- PKG-ASR-03: [记录](changes/2026-09-13-PKG-ASR-03-audio-ownership.md) [记录](baselines/asr-ownership-validation.json)
+9
View File
@@ -1,5 +1,14 @@
# 进度与证据
## PKG-MASK-04 严格TS及公开类型完成
五个自有模块迁TS且运行产物保持MASK03字节;真实核心宿主、SDK声明桥接和私有资源
类型通过严格检查。修复旧Node解析/legacy类型路径与编辑器混合导出,保留根工厂。
联合99项、安装包15编译器模式和全仓404生产TS通过;[详细记录](changes/2026-09-14-PKG-MASK-04-types.md)。
16插件中源码与类型迁移未完成的剩AutoThumbnail/VAST/JASSUB三包;所有后续组合/发布
门槛继续按tasks.json执行,不能把迁移完成当作已可发布。Mask05/06与实际SDK资源风险待验收。
## PKG-MASK-03 生命周期拆分完成
五个职责模块和单运行归属控制器落地;停止/销毁、迟到回调、并发启动、释放等待和
+1 -1
View File
@@ -751,7 +751,7 @@
"PKG-MASK-06"
],
"evidence": [
"packages/artplayer-plugin-danmuku-mask/src/index.js",
"packages/artplayer-plugin-danmuku-mask/src/index.ts",
"refactor/baselines/danmuku-mask-contract.md",
"refactor/baselines/danmuku-mask-contract-validation.json",
"refactor/baselines/danmuku-mask-release.json"
@@ -0,0 +1,22 @@
import fs from 'node:fs'
export const maskInvalidStatements = [
'mask({ modelSelection: "general" })',
'mask({ smoothSegmentation: 1 })',
'mask({ solutionPath: 42 })',
'mask({ opacity: "1" })',
'const invalidName: "other" = result.name',
'const invalidStart: void = result.start()',
'const invalidStop: Promise<void> = result.stop()',
'mask().then(() => {})',
]
export function maskConsumerSource(mode, raw = false) {
let source = fs.readFileSync(new URL('../../test/types/danmuku-mask-public.ts', import.meta.url), 'utf8')
if (mode === 'nodenext-esm' && !raw) {
source = source
.replace('import mask from \'artplayer-plugin-danmuku-mask\'', 'import maskModule from \'artplayer-plugin-danmuku-mask\'\nconst mask = maskModule.default')
.replace('import legacy from \'artplayer-plugin-danmuku-mask/legacy\'', 'import legacyModule from \'artplayer-plugin-danmuku-mask/legacy\'\nconst legacy = legacyModule.default')
}
return source
}
@@ -0,0 +1,108 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import ts from 'typescript'
import compat from 'typescript-compat'
import { checkFiles, packedFiles } from '../../scripts/package-check.mjs'
import { consumerDirectory, removeConsumer, run, workspace, writeJson } from '../../scripts/package-consumer.mjs'
import { maskConsumerSource, maskInvalidStatements } from './danmuku-mask-consumer.mjs'
import { verifyDanmukuMaskContract } from './danmuku-mask-contract.mjs'
import { hash, readMember } from './releases.mjs'
async function main() {
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use yarn test:danmuku-mask-types-package')
const yarn = process.env.npm_execpath
assert(yarn && fs.existsSync(yarn))
const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/danmuku-mask-package-types-'))
console.log(`Mask installed consumer evidence: ${output}`)
const contract = await verifyDanmukuMaskContract()
const packages = []
for (const name of ['artplayer', 'artplayer-plugin-danmuku-mask']) {
const archive = path.join(output, `${name}.tgz`)
fs.writeFileSync(path.join(output, `${name}-pack.log`), run([yarn, 'pack', '--filename', archive], path.join(workspace, 'packages', name)))
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
const files = packedFiles(archive)
checkFiles(manifest, files, name === contract.baseline.release.name ? Object.keys(contract.baseline.release.files) : [])
packages.push({ name, archive, sha256: hash(fs.readFileSync(archive)), files: Object.fromEntries(files.map(member => [member, hash(readMember(archive, member))])) })
}
const core = packages[0]
const candidates = [
...[...contract.baseline.previous, contract.baseline.release].map(release => ({ ...release, archive: contract.archives.get(release.version), label: `published-${release.version}` })),
{ ...packages[1], label: 'candidate' },
]
const matrix = []
const priorDiagnostics = new Map()
for (const plugin of candidates) {
const consumer = consumerDirectory()
try {
writeJson(path.join(consumer, 'package.json'), { name: 'mask-isolated-consumer', private: true, dependencies: Object.fromEntries([core, plugin].map(pkg => [pkg.name, `file:${pkg.archive.replaceAll('\\', '/')}`])) })
fs.copyFileSync(path.join(workspace, 'yarn.lock'), path.join(consumer, 'yarn.lock'))
fs.writeFileSync(path.join(output, `${plugin.label}-install.log`), run([yarn, 'install', '--offline', '--ignore-scripts', '--non-interactive'], consumer))
const lock = fs.readFileSync(path.join(consumer, 'yarn.lock'))
fs.writeFileSync(path.join(output, `${plugin.label}-frozen-install.log`), run([yarn, 'install', '--offline', '--frozen-lockfile', '--ignore-scripts', '--non-interactive'], consumer))
assert.deepEqual(fs.readFileSync(path.join(consumer, 'yarn.lock')), lock)
fs.writeFileSync(path.join(output, `${plugin.label}-yarn.lock`), lock)
for (const pkg of [core, plugin]) {
const root = path.join(consumer, 'node_modules', pkg.name)
assert.equal(fs.realpathSync(root), root, 'Installed package is a workspace link')
for (const [member, expected] of Object.entries(pkg.files))
assert.equal(hash(fs.readFileSync(path.join(root, member.slice(8)))), expected, `Installed bytes differ: ${member}`)
}
const runtime = path.join(consumer, 'exports.cjs')
fs.writeFileSync(runtime, `const assert = require('node:assert/strict'); global.self = global;
for (const name of ['artplayer-plugin-danmuku-mask', 'artplayer-plugin-danmuku-mask/legacy']) {
const value = require(name); const old = ${plugin.version === '1.0.0'};
assert.equal(typeof value, old ? 'object' : 'function');
assert.equal(typeof value.default, old ? 'function' : 'undefined');
const factory = old ? value.default : value;
const style = {}; const handlers = new Map();
const result = factory()({ template: { $video: {}, $danmuku: {style} }, on(name, fn) { handlers.set(name, fn); }, off(name, fn) { if (handlers.get(name) === fn) handlers.delete(name); } });
assert.deepEqual(Object.keys(result), ['name', 'start', 'stop']);
assert.equal(result.name, 'artplayerPluginDanmukuMask'); assert.equal(typeof result.start, 'function');
assert.equal(result.stop(), undefined); assert.equal(style.maskImage, 'none');
handlers.get('destroy')();
}
console.log('Installed CJS/legacy registration and stop/destroy passed; no model startup.');`)
fs.writeFileSync(path.join(output, `${plugin.label}-runtime.log`), run([runtime], consumer))
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]) {
const next = mode.startsWith('nodenext')
const filename = path.join(consumer, `consumer.${next ? mode.endsWith('-cjs') ? 'cts' : 'mts' : 'ts'}`)
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: true, target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: next ? compiler.ModuleKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleKind.ESNext : compiler.ModuleKind.CommonJS, moduleResolution: next ? compiler.ModuleResolutionKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleResolutionKind.Bundler : compiler.ModuleResolutionKind.NodeJs }
const compile = (source) => {
fs.writeFileSync(filename, source)
const program = compiler.createProgram([filename], options)
for (const file of program.getSourceFiles()) {
const actual = fs.realpathSync(file.fileName)
assert(actual.startsWith(fs.realpathSync(consumer) + path.sep) || (program.isSourceFileDefaultLibrary(file) && path.dirname(actual) === fs.realpathSync(path.dirname(compiler.sys.getExecutingFilePath()))), `Type escaped consumer: ${actual}`)
}
return compiler.getPreEmitDiagnostics(program).map(d => ({ code: d.code, file: d.file ? path.relative(consumer, d.file.fileName).replaceAll('\\', '/') : null, line: d.file && d.start !== undefined ? d.file.getLineAndCharacterOfPosition(d.start).line + 1 : null, message: compiler.flattenDiagnosticMessageText(d.messageText, '\n').replaceAll(consumer.replaceAll('\\', '/'), '<consumer>') }))
}
const raw = compile(maskConsumerSource(mode, true))
const key = `${compiler.version}:${mode}`
if (plugin.label === 'published-1.1.0')
priorDiagnostics.set(key, raw)
if (plugin.label === 'candidate')
assert.deepEqual(raw, priorDiagnostics.get(key).filter(item => !(mode === 'node10-commonjs' && item.code === 2307 && item.line === 3)), 'Only the recorded old legacy-path resolution failure may be corrected')
let source = maskConsumerSource(mode)
if (plugin.label !== 'candidate' && mode === 'node10-commonjs') {
assert.deepEqual(raw.map(item => [item.code, item.line]), [[2307, 3]], 'Historical legacy path failure must stay precise')
source = source.replace('from \'artplayer-plugin-danmuku-mask/legacy\'', 'from \'artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\'')
}
const diagnostics = compile(source)
assert.deepEqual(diagnostics, [], `${plugin.label} ${compiler.version} ${mode}`)
const invalid = compile(`${source}\n${maskInvalidStatements.join('\n')}\n`)
assert.equal(invalid.length, maskInvalidStatements.length)
assert.deepEqual(invalid.map(item => item.line), maskInvalidStatements.map((_, index) => source.split('\n').length + 1 + index))
matrix.push({ plugin: plugin.label, compiler: compiler.version, mode, raw, diagnostics, invalid })
}
}
finally { removeConsumer(consumer) }
}
writeJson(path.join(output, 'report.json'), { task: 'PKG-MASK-04', node: process.version, packages, published: candidates.slice(0, 2).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: hash(fs.readFileSync(pkg.archive)) })), matrix, scope: 'Actual published and candidate packages installed outside workspace, offline/frozen lock stability, byte checks, strict types and CJS/legacy registration. No model startup, native inference, ESM browser or GPU acceptance.' })
console.log(`Mask installed consumers passed ${matrix.length} compiler modes: ${output}`)
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
@@ -0,0 +1,57 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
// eslint-disable-next-line test/no-import-node-test -- Public compatibility uses the repository Node runner.
import test from 'node:test'
import { ESLint } from 'eslint'
import ts from 'typescript'
import compat from 'typescript-compat'
import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from '../../scripts/plugin-editor-types.mjs'
import { checkConsumer } from '../../scripts/typecheck.mjs'
import { maskInvalidStatements } from './danmuku-mask-consumer.mjs'
import { verifyDanmukuMaskContract } from './danmuku-mask-contract.mjs'
import { readMember } from './releases.mjs'
test('Mask public declaration keeps both actual published factory and result shapes', async () => {
const contract = await verifyDanmukuMaskContract()
const current = fs.readFileSync('packages/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask.d.ts', 'utf8').replaceAll('\r\n', '\n')
for (const archive of contract.archives.values())
assert.equal(current, readMember(archive, 'package/types/artplayer-plugin-danmuku-mask.d.ts').toString().replaceAll('\r\n', '\n'))
})
test('Mask strict public consumers preserve optional arguments, function replacement and reject invalid results/options', () => {
const source = fs.readFileSync('test/types/danmuku-mask-public.ts', 'utf8')
for (const compiler of [ts, compat]) {
assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', source), [])
assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', source.replace('from \'artplayer-plugin-danmuku-mask\'', 'from \'artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask\'')), [], 'Legacy mapping must preserve historical direct declaration paths')
const invalid = checkConsumer(compiler, 'node10-commonjs', `${source}\n${maskInvalidStatements.join('\n')}\n`)
assert.equal(invalid.length, maskInvalidStatements.length)
assert.deepEqual(invalid.map(item => item.line), maskInvalidStatements.map((_, index) => source.split('\n').length + 1 + index))
}
})
test('Mask editor declaration is generated from the unchanged public contract and accepts optional registration', async () => {
const source = fs.readFileSync('packages/artplayer-plugin-danmuku-mask/types/artplayer-plugin-danmuku-mask.d.ts', 'utf8')
const generated = generatePluginEditorDeclaration(source, 'artplayerPluginDanmukuMask')
const file = 'docs/assets/ts/artplayer-plugin-danmuku-mask.d.ts'
const [formatted] = await new ESLint({ fix: true, fixTypes: ['layout'] }).lintText(generated, { filePath: file })
assert.equal(formatted.errorCount, 0)
assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), formatted.output || generated)
const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')
const consumer = `const option: NonNullable<Parameters<typeof artplayerPluginDanmukuMask>[0]> = {opacity: 0};
const art = new Artplayer({container: '#player', url: '/video.mp4'});
const result = artplayerPluginDanmukuMask(option)(art);
const started: Promise<void> = result.start();
const stopped: void = result.stop();
artplayerPluginDanmukuMask();
const replacement: typeof artplayerPluginDanmukuMask = () => () => ({name: 'artplayerPluginDanmukuMask', async start() {}, stop() {}});
// @ts-expect-error Synchronous registration has no then method.
artplayerPluginDanmukuMask().then(() => {});
// @ts-expect-error The runtime factory has no default property.
artplayerPluginDanmukuMask.default();
// @ts-expect-error Opacity remains numeric.
artplayerPluginDanmukuMask({opacity: '1'});`
for (const compiler of [ts, compat]) {
assert.deepEqual(checkPluginEditorDeclaration(generated, core, consumer, compiler), [])
assert.equal(checkPluginEditorDeclaration(generated, core, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''), compiler).length, 3)
}
})
+5 -2
View File
@@ -2585,11 +2585,14 @@
"ENG-06",
"CORE-07"
],
"status": "todo",
"status": "done",
"risk": "H",
"deliverable": "模型 adapter、canvas 和选项的精确类型",
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
"evidence": []
"evidence": [
"changes/2026-09-14-PKG-MASK-04-types.md",
"baselines/danmuku-mask-types-validation.json"
]
},
{
"id": "PKG-MASK-05",
+5 -1
View File
@@ -1228,7 +1228,11 @@
"PKG-MASK-06"
],
"sourceFiles": [
"packages/artplayer-plugin-danmuku-mask/src/index.js"
"packages/artplayer-plugin-danmuku-mask/src/index.ts",
"packages/artplayer-plugin-danmuku-mask/src/config.ts",
"packages/artplayer-plugin-danmuku-mask/src/sdk.ts",
"packages/artplayer-plugin-danmuku-mask/src/controller.ts",
"packages/artplayer-plugin-danmuku-mask/src/output.ts"
],
"contract": "Npm dependency versions are separate from unversioned solutionPath CDN assets; keep option override, backend fallback, model result disposal and masks.",
"validationStatus": "MASK01 verified two actual releases, six frozen Yarn/installed SDK versions, adapter source hashes and 12 existing demo resource byte matches. The default solutionPath remains an unversioned CDN. Controlled export/default/pixel probes do not establish real models, fallback, browser/GPU cleanup or notice completeness; MASK05/06 remain open.",