{"_id":"@empty-sekai/sekai-custom-profile-sdk","_rev":"7-2c2cd99d76f70ca060818d1bcad52ada","name":"@empty-sekai/sekai-custom-profile-sdk","dist-tags":{"latest":"0.6.0"},"versions":{"0.3.0":{"name":"@empty-sekai/sekai-custom-profile-sdk","version":"0.3.0","license":"AGPL-3.0-only","_id":"@empty-sekai/sekai-custom-profile-sdk@0.3.0","maintainers":[{"name":"nichinichisou","email":"nichinichisou0609@outlook.com"}],"homepage":"https://github.com/empty-sekai/sekai-custom-profile-sdk#readme","bugs":{"url":"https://github.com/empty-sekai/sekai-custom-profile-sdk/issues"},"dist":{"shasum":"f22d5384aecf7b5eb918ae12ae34b143ecce9650","tarball":"https://registry.npmjs.org/@empty-sekai/sekai-custom-profile-sdk/-/sekai-custom-profile-sdk-0.3.0.tgz","fileCount":130,"integrity":"sha512-UcE7iGnszYfpMiEHn6b6qZXVzS5ojF1IfK/llPdzWb+bUvVDS0yVOpvEU76t1hsGc8IrueXryo3+sfXRoUpqHw==","signatures":[{"sig":"MEYCIQDbVOUb/+RG82zilMYm/wbHkklqgv0/e2dX+L9BezAvCwIhALkRzkI678p/dFg/Nxc0P2ETYZbO5nC9dGmOJQOHbWq1","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":3343645},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"build":"npm run build:wasm && npm run build:ts","build:ts":"tsc -p tsconfig.json && node ./scripts/copy-artifacts.mjs","typecheck":"tsc -p tsconfig.json --noEmit","build:wasm":"bash ./build.sh","demo:serve":"node scripts/serve-demo.mjs","test:gates":"node --experimental-strip-types --test scripts/test/*.test.mjs","audit:public":"node scripts/audit-public-package.mjs","measure:wasm:size":"node scripts/measure-wasm-size.mjs","verify:wasm:runtime":"node scripts/verify-wasm-runtime.mjs"},"_npmUser":{"name":"nichinichisou","email":"nichinichisou0609@outlook.com"},"repository":{"url":"git+https://github.com/empty-sekai/sekai-custom-profile-sdk.git","type":"git"},"_npmVersion":"10.9.0","description":"Unofficial Project SEKAI custom profile SDK with authoring, native-compatible scenes, WebGL2 rendering, and WASM.","directories":{},"_nodeVersion":"22.17.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.0"},"_npmOperationalInternal":{"tmp":"tmp/sekai-custom-profile-sdk_0.3.0_1784436585439_0.25730414431166193","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@empty-sekai/sekai-custom-profile-sdk","version":"0.3.1","license":"AGPL-3.0-only","_id":"@empty-sekai/sekai-custom-profile-sdk@0.3.1","maintainers":[{"name":"nichinichisou","email":"nichinichisou0609@outlook.com"}],"homepage":"https://github.com/empty-sekai/sekai-custom-profile-sdk#readme","bugs":{"url":"https://github.com/empty-sekai/sekai-custom-profile-sdk/issues"},"dist":{"shasum":"0cca7487236cfa40bd9057a751ec14780fb0eb08","tarball":"https://registry.npmjs.org/@empty-sekai/sekai-custom-profile-sdk/-/sekai-custom-profile-sdk-0.3.1.tgz","fileCount":130,"integrity":"sha512-WI0PR7zIl0Tz0bShUnhmV9lU1VIgZhe7dF/x5lhOl3bvenDElHijTFup4QUylhixI3NjI7ElqFs/+gDiS+9aoA==","signatures":[{"sig":"MEUCIBYqZdzYHKgxQrfkh49wOzxYe9Izb/VL1Q9pOknjhQ9YAiEA703J7LyeiyUzAmKPxGSwIPwes4wB3tU4L8wYXfnTFa4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@empty-sekai%2fsekai-custom-profile-sdk@0.3.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":3349830},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"726a5602ff0cfd790e0f0d339ee6080345005245","scripts":{"build":"npm run build:wasm && npm run build:ts","build:ts":"tsc -p tsconfig.json && node ./scripts/copy-artifacts.mjs","typecheck":"tsc -p tsconfig.json --noEmit","build:wasm":"bash ./build.sh","demo:serve":"node scripts/serve-demo.mjs","test:gates":"node --experimental-strip-types --test scripts/test/*.test.mjs","audit:public":"node scripts/audit-public-package.mjs","measure:wasm:size":"node scripts/measure-wasm-size.mjs","verify:wasm:runtime":"node scripts/verify-wasm-runtime.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b707d474-0949-4445-92c4-0939c322f3cf"}},"repository":{"url":"git+https://github.com/empty-sekai/sekai-custom-profile-sdk.git","type":"git"},"_npmVersion":"12.0.1","description":"Unofficial Project SEKAI custom profile SDK with authoring, native-compatible scenes, WebGL2 rendering, and WASM.","directories":{},"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.0"},"_npmOperationalInternal":{"tmp":"tmp/sekai-custom-profile-sdk_0.3.1_1784696067082_0.004441020557184094","host":"s3://npm-registry-packages-npm-production"}},"0.3.2":{"name":"@empty-sekai/sekai-custom-profile-sdk","version":"0.3.2","license":"AGPL-3.0-only","_id":"@empty-sekai/sekai-custom-profile-sdk@0.3.2","maintainers":[{"name":"nichinichisou","email":"nichinichisou0609@outlook.com"}],"homepage":"https://github.com/empty-sekai/sekai-custom-profile-sdk#readme","bugs":{"url":"https://github.com/empty-sekai/sekai-custom-profile-sdk/issues"},"dist":{"shasum":"8d37579ec8d72ab3b0ca918f127107fd990215a1","tarball":"https://registry.npmjs.org/@empty-sekai/sekai-custom-profile-sdk/-/sekai-custom-profile-sdk-0.3.2.tgz","fileCount":130,"integrity":"sha512-6VjqvvBuvmZGn96L95cU2CxL9b9MVp8Dmsr+Pt3yk7bwOmcv2y6N+0Xa4hmgLilpZywDRFH88ZsSXk/RqY3vOw==","signatures":[{"sig":"MEQCIHY+M3gvdmfnAIDDhP5Re0cVJ3kmvf1moetiR7orqZ4fAiAKElSU21e9CmZveHXF/hlFMj3JJtRMSMnW/VPNYJ7sng==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@empty-sekai%2fsekai-custom-profile-sdk@0.3.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":3356843},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"828bed9c614abad72a1a026e8e7a1aa2fa684172","scripts":{"build":"npm run build:wasm && npm run build:ts","build:ts":"tsc -p tsconfig.json && node ./scripts/copy-artifacts.mjs","typecheck":"tsc -p tsconfig.json --noEmit","build:wasm":"bash ./build.sh","demo:serve":"node scripts/serve-demo.mjs","test:gates":"node --experimental-strip-types --test scripts/test/*.test.mjs","audit:public":"node scripts/audit-public-package.mjs","measure:wasm:size":"node scripts/measure-wasm-size.mjs","verify:wasm:runtime":"node scripts/verify-wasm-runtime.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b707d474-0949-4445-92c4-0939c322f3cf"}},"repository":{"url":"git+https://github.com/empty-sekai/sekai-custom-profile-sdk.git","type":"git"},"_npmVersion":"12.0.1","description":"Unofficial Project SEKAI custom profile SDK with authoring, native-compatible scenes, WebGL2 rendering, and WASM.","directories":{},"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.0"},"_npmOperationalInternal":{"tmp":"tmp/sekai-custom-profile-sdk_0.3.2_1784817517812_0.8017372460658811","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@empty-sekai/sekai-custom-profile-sdk","version":"0.4.0","license":"AGPL-3.0-only","_id":"@empty-sekai/sekai-custom-profile-sdk@0.4.0","maintainers":[{"name":"nichinichisou","email":"nichinichisou0609@outlook.com"}],"homepage":"https://github.com/empty-sekai/sekai-custom-profile-sdk#readme","bugs":{"url":"https://github.com/empty-sekai/sekai-custom-profile-sdk/issues"},"dist":{"shasum":"00ef3c9ec0c4415c1a8ffbf6044c77e185145d8c","tarball":"https://registry.npmjs.org/@empty-sekai/sekai-custom-profile-sdk/-/sekai-custom-profile-sdk-0.4.0.tgz","fileCount":130,"integrity":"sha512-8fDH8hz1T9EgTMmqqgpe3ikooSyXhf9fdknXYFPyjIMw6YhP6dRsDgMz1LdM0CqVpdfeybu1T+5ArCbDvJOFJg==","signatures":[{"sig":"MEUCIGUAn7Wc3iiFkYYQfLerhdmlUXA1LQi1T8vqnEdgfeyPAiEAyKvO+0IYtDFqOJDgMgKeg89GkRCXruuP36FK9AY2MVo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@empty-sekai%2fsekai-custom-profile-sdk@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":3358376},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"9ded4be3c9c6555a62e7ecd288c98c172f9d1dbf","scripts":{"build":"npm run build:wasm && npm run build:ts","build:ts":"tsc -p tsconfig.json && node ./scripts/copy-artifacts.mjs","typecheck":"tsc -p tsconfig.json --noEmit","build:wasm":"bash ./build.sh","demo:serve":"node scripts/serve-demo.mjs","test:gates":"node --experimental-strip-types --test scripts/test/*.test.mjs","audit:public":"node scripts/audit-public-package.mjs","measure:wasm:size":"node scripts/measure-wasm-size.mjs","verify:wasm:runtime":"node scripts/verify-wasm-runtime.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b707d474-0949-4445-92c4-0939c322f3cf"}},"repository":{"url":"git+https://github.com/empty-sekai/sekai-custom-profile-sdk.git","type":"git"},"_npmVersion":"12.0.2","description":"Unofficial Project SEKAI custom profile SDK with authoring, native-compatible scenes, WebGL2 rendering, and WASM.","directories":{},"_nodeVersion":"24.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.0"},"_npmOperationalInternal":{"tmp":"tmp/sekai-custom-profile-sdk_0.4.0_1785916453729_0.4902532215973654","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@empty-sekai/sekai-custom-profile-sdk","version":"0.5.0","license":"AGPL-3.0-only","_id":"@empty-sekai/sekai-custom-profile-sdk@0.5.0","maintainers":[{"name":"nichinichisou","email":"nichinichisou0609@outlook.com"}],"homepage":"https://github.com/empty-sekai/sekai-custom-profile-sdk#readme","bugs":{"url":"https://github.com/empty-sekai/sekai-custom-profile-sdk/issues"},"dist":{"shasum":"ccca7f5e656af10008e83d2b14eb3d7408a7f219","tarball":"https://registry.npmjs.org/@empty-sekai/sekai-custom-profile-sdk/-/sekai-custom-profile-sdk-0.5.0.tgz","fileCount":130,"integrity":"sha512-QcMZddAa9DGo68DCfFcQdJFsxkSb0b1DgbkzyB9tp5cGYkMnaVix76FMwM6KOUlQm+oZMJs9h0GYotmSxW3wJg==","signatures":[{"sig":"MEUCIFnCKPvUDDc9s+cA/pMVkungkuUh+S1x6qgTDkHElzzlAiEAmyMn+lvJvNQPe3O9uT7VIn9JcFNGqiUJJm57vFOe+ls=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@empty-sekai%2fsekai-custom-profile-sdk@0.5.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":3358880},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"ef7167b2f74d62530663cd886807d1bbf6120f55","scripts":{"build":"npm run build:wasm && npm run build:ts","build:ts":"tsc -p tsconfig.json && node ./scripts/copy-artifacts.mjs","typecheck":"tsc -p tsconfig.json --noEmit","build:wasm":"bash ./build.sh","demo:serve":"node scripts/serve-demo.mjs","test:gates":"node --experimental-strip-types --test scripts/test/*.test.mjs","audit:public":"node scripts/audit-public-package.mjs","measure:wasm:size":"node scripts/measure-wasm-size.mjs","verify:wasm:runtime":"node scripts/verify-wasm-runtime.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b707d474-0949-4445-92c4-0939c322f3cf"}},"repository":{"url":"git+https://github.com/empty-sekai/sekai-custom-profile-sdk.git","type":"git"},"_npmVersion":"12.0.2","description":"Unofficial Project SEKAI custom profile SDK with authoring, native-compatible scenes, WebGL2 rendering, and WASM.","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.0"},"_npmOperationalInternal":{"tmp":"tmp/sekai-custom-profile-sdk_0.5.0_1788424221950_0.46797865190782995","host":"s3://npm-registry-packages-npm-production"}},"0.5.1":{"name":"@empty-sekai/sekai-custom-profile-sdk","version":"0.5.1","license":"AGPL-3.0-only","_id":"@empty-sekai/sekai-custom-profile-sdk@0.5.1","maintainers":[{"name":"nichinichisou","email":"nichinichisou0609@outlook.com"}],"homepage":"https://github.com/empty-sekai/sekai-custom-profile-sdk#readme","bugs":{"url":"https://github.com/empty-sekai/sekai-custom-profile-sdk/issues"},"dist":{"shasum":"2a4d8f5ec689c32414e051c8ee25b79f1bbe9e4a","tarball":"https://registry.npmjs.org/@empty-sekai/sekai-custom-profile-sdk/-/sekai-custom-profile-sdk-0.5.1.tgz","fileCount":127,"integrity":"sha512-basevuRs2yvJQ75AlE7QvnjKNMHQDDZooWTBvnFOS8BkeLYDgVd+zsm00yVqw+myMw/dQEijEVPiBChRb/NWSA==","signatures":[{"sig":"MEYCIQCKKKLN1FocFhpDGz2soaUmh+ASiiTBzwFU6LS3gRMcgAIhAIU8+ZQQqxTJ1mbbKONVR/zrrpb0/pFURvwXfMvO5F2v","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIAOb9XD3YeREoUETUIbtv2Nn1sjicjc5/fVKB5YNZBXSAiEAsQrnf0tGt4E62C3LQERQyURkOwzDmMRdHe1Ua3qedsA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@empty-sekai%2fsekai-custom-profile-sdk@0.5.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":3375446},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"6b796ba2667f356a32defd024c934eccfd197217","scripts":{"build":"npm run build:wasm && npm run build:ts","build:ts":"tsc -p tsconfig.json && node ./scripts/copy-artifacts.mjs","typecheck":"tsc -p tsconfig.json --noEmit","build:wasm":"bash ./build.sh","demo:serve":"node scripts/serve-demo.mjs","test:gates":"node --experimental-strip-types --test scripts/test/*.test.mjs","audit:public":"node scripts/audit-public-package.mjs","measure:wasm:size":"node scripts/measure-wasm-size.mjs","verify:wasm:runtime":"node scripts/verify-wasm-runtime.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b707d474-0949-4445-92c4-0939c322f3cf"}},"repository":{"url":"git+https://github.com/empty-sekai/sekai-custom-profile-sdk.git","type":"git"},"_npmVersion":"12.0.2","description":"Unofficial Project SEKAI custom profile SDK with authoring, native-compatible scenes, WebGL2 rendering, and WASM.","directories":{},"_nodeVersion":"24.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.0"},"_npmOperationalInternal":{"tmp":"tmp/sekai-custom-profile-sdk_0.5.1_1789311013933_0.1909093041345007","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"_id":"@empty-sekai/sekai-custom-profile-sdk@0.6.0","bugs":{"url":"https://github.com/empty-sekai/sekai-custom-profile-sdk/issues"},"dist":{"shasum":"b11617eabeda37e486df6a2c7c20f133d21030fe","tarball":"https://registry.npmjs.org/@empty-sekai/sekai-custom-profile-sdk/-/sekai-custom-profile-sdk-0.6.0.tgz","fileCount":134,"integrity":"sha512-9OEnpGLtQvYgLvpWiDC/G93yKWsK53dmiKA59brXLPolgtjLnvf128YICWWVbg9Jbh4/GrRQ40Oa4e0SMAVszQ==","signatures":[{"sig":"MEUCIArXsTT7bozpSytYS09UvFpk8qQRmAIDG2z/8tCRucAhAiEAh3B0Eo1mSPwUuUwEkTQLOa6BuAri+ehVpc6K3OslyMo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEKnf7zO5OWpC2FH0R6kHylqlEMRsOg8vPIe6HIB8OBhAiB85RL13JnyETMlAE9GqcbPpv7lFR4O86LR23pRc42eXg=="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@empty-sekai%2fsekai-custom-profile-sdk@0.6.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":3887597},"main":"./dist/index.js","name":"@empty-sekai/sekai-custom-profile-sdk","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"cd8b00d3a07c0d068054be32ff0119471e96616a","license":"AGPL-3.0-only","scripts":{"build":"npm run build:wasm && npm run build:ts","build:ts":"tsc -p tsconfig.json && node ./scripts/copy-artifacts.mjs","typecheck":"tsc -p tsconfig.json --noEmit","build:wasm":"bash ./build.sh","demo:serve":"node scripts/serve-demo.mjs","test:gates":"node --experimental-strip-types --test scripts/test/*.test.mjs","audit:public":"node scripts/audit-public-package.mjs","measure:wasm:size":"node scripts/measure-wasm-size.mjs","verify:wasm:runtime":"node scripts/verify-wasm-runtime.mjs"},"version":"0.6.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:b707d474-0949-4445-92c4-0939c322f3cf"}},"homepage":"https://github.com/empty-sekai/sekai-custom-profile-sdk#readme","repository":{"url":"git+https://github.com/empty-sekai/sekai-custom-profile-sdk.git","type":"git"},"_npmVersion":"12.1.0","description":"Unofficial Project SEKAI custom profile SDK with authoring, native-compatible scenes, WebGL2 rendering, and WASM.","directories":{},"maintainers":[{"name":"nichinichisou","email":"nichinichisou0609@outlook.com"}],"_nodeVersion":"24.21.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sekai-custom-profile-sdk_0.6.0_1790274539433_0.8571730470277825"}}},"time":{"created":"2026-07-19T04:49:45.226Z","modified":"2026-09-24T18:28:59.954Z","0.3.0":"2026-07-19T04:49:45.605Z","0.3.1":"2026-07-22T04:54:27.292Z","0.3.2":"2026-07-23T14:38:37.973Z","0.4.0":"2026-08-05T07:54:13.940Z","0.5.0":"2026-09-03T08:30:22.135Z","0.5.1":"2026-09-13T14:50:14.057Z","0.6.0":"2026-09-24T18:28:59.586Z"},"bugs":{"url":"https://github.com/empty-sekai/sekai-custom-profile-sdk/issues"},"license":"AGPL-3.0-only","homepage":"https://github.com/empty-sekai/sekai-custom-profile-sdk#readme","repository":{"url":"git+https://github.com/empty-sekai/sekai-custom-profile-sdk.git","type":"git"},"description":"Unofficial Project SEKAI custom profile SDK with authoring, native-compatible scenes, WebGL2 rendering, and WASM.","maintainers":[{"name":"nichinichisou","email":"nichinichisou0609@outlook.com"}],"readme":"# @empty-sekai/sekai-custom-profile-sdk\n\n[简体中文](README.md) | [English](README.en.md)\n\n`@empty-sekai/sekai-custom-profile-sdk` 是面向 Project SEKAI（PJSK）自定义名片的非官方 browser SDK，统一提供 authoring、场景解析、WebGL2 渲染、交互、导出与字体 atlas 能力。\n\nTMP 富文本与排版针对 PJSK 使用的 Unity TextMesh Pro 数据和行为提供兼容模型，覆盖当前已经建模和验证的标签、排版、材质与动态语义。尚未建模的游戏行为或后续游戏更新可能与客户端不同，因此不承诺完整复刻游戏的渲染逻辑或最终像素。\n\nRust/WASM 负责 profile resolution、TMP 富文本、layout、动态公式、稳定语义 ID、glyph demand、FreeType 字体度量、glyph SDF 与 atlas placement；TypeScript 负责 worker、异步资源调度、缓存 I/O 和 GPU resource orchestration；WebGL2 消费 semantic command stream 和紧凑状态表完成绘制。\n\n0.2 提供 stateful scene API，执行路径由 Rust/WASM semantic runtime、dedicated worker、FreeType/SDF atlas 与 WebGL2 renderer 组成。\n\n## 运行要求\n\n- WebGL2；\n- ES modules 与 Web Workers；\n- 可选 IndexedDB，用于持久化不透明 glyph record；\n- 调用方提供的 profile API 响应 JSON（名片文档由 SDK 从中解析；不再单独传 `card`）、masterdata、字体 provider 或预注册字体，以及 `ResourceProvider`。\n\n字体、玩家数据、masterdata 和图片素材由调用方提供。字体度量、富文本 layout 与 glyph 像素由调用方提供并锁定 source hash 的字体 bytes、FreeType、TMP parser 和 SDF pipeline 共同确定。\n\n## 安装\n\n```sh\nnpm install @empty-sekai/sekai-custom-profile-sdk\n```\n\n默认情况下，worker、Emscripten glue 和 WASM 文件从 package entry 相邻的 `dist/` 路径加载。若 bundler 或部署结构不同，可显式传入 `workerUrl`、`moduleUrl` 和 `wasmUrl`。\n\n## 最小接入\n\n```ts\nimport {\n  BrowserRenderer,\n  type FontProvider,\n  type ResourceProvider,\n} from \"@empty-sekai/sekai-custom-profile-sdk\";\n\nconst fontProvider: FontProvider = {\n  async provide({ region, family }, { signal }) {\n    const bytes = await loadApplicationFont({ region, family, signal });\n    return bytes ? { bytes } : null;\n  },\n};\n\nconst resourceProvider: ResourceProvider = {\n  async provide(descriptor, { signal }) {\n    const request = await resolveResourceRequest(descriptor);\n    if (!request) return null;\n\n    const response = await fetch(request, { signal, cache: \"default\" });\n    if (!response.ok) return null;\n    return { source: await response.blob() };\n  },\n};\n\nconst renderer = await BrowserRenderer.create({\n  canvas: document.querySelector(\"canvas\")!,\n  region: \"en\",\n  resourceProvider,\n  fontProvider,\n});\n\nconst masterData = await renderer.loadMasterData(\n  \"latest\",\n  ({ table, region, revision }, { signal }) =>\n    loadApplicationMasterData({ table, region, revision, signal }),\n);\nconst scene = await renderer.createProfileScene({\n  masterData,\n  documentKey: \"profile-preview\",\n  // 完整的 profile API 响应；名片文档取自 userCustomProfileCards\n  //（按 seq 升序的页序，默认第一页，可用 pageIndex 选择），与 profile 数据同源。\n  profile,\n  frameMode: \"animate\",\n});\n\nscene.draw();\n\n// 始终导出游戏名片原始尺寸 1830×812；内部会先绘制并同步复制当前帧。\nconst png = await scene.exportPng();\n```\n\n## 自定义名片编辑文档\n\n`BrowserAuthoringClient` 在 dedicated worker 中持有游戏兼容的编辑文档。导入完整公开 Profile 时只提取 `userCustomProfileCards`；导出不包含 worker handle、稳定元素 ID、选中状态或其他浏览器元数据。\n\n```ts\nimport { BrowserAuthoringClient, type AuthoringCommand } from \"@empty-sekai/sekai-custom-profile-sdk\";\n\nconst authoring = await BrowserAuthoringClient.create();\nconst document = profile\n  ? await authoring.importProfile(profile)\n  : await authoring.createBlank();\n\nconst command: AuthoringCommand = {\n  kind: \"set_transform\",\n  id: selectedElementId,\n  position: [120, -40, 0],\n};\nconst delta = await document.apply(command);\n\nawait document.undo();\nawait document.redo();\nconst gameDocument = await document.export();\n\nawait document.destroy();\nauthoring.destroy();\n```\n\n编辑核心最多保留 150 个历史事务，并校验每页 150 个元素的上限、12 个游戏数组、有限数值、图层顺序以及 `objectData` 边界。创建、复制、删除、变换、锁定、可见性、参数和图层命令都返回增量变化；调用方应把这些变化用于现有 scene 和界面状态，而不是在 TypeScript 中维护第二套命令历史。\n\n## 完整执行流程\n\n`createProfileScene()` 会依次完成以下语义解析、资源准备与 GPU 初始化流程：\n\n1. WASM 从 `profile.userCustomProfileCards` 按 `seq` 升序取出 `pageIndex` 指定的页（默认第一页），与 profile、locale 和 masterdata session 一起解析；也兼容已废弃的独立 `card` 入参（此时无 profile 数据）；\n2. shared semantic core 收集本场景的 localization demand，调用方 provider 返回不可变文本快照；\n3. WASM 用该文本快照解析 authored elements 与 components，并输出实际使用的 font family demand；\n4. 主线程通过有界队列调用可选 `FontProvider`，对返回 bytes 计算 hash，并把 family→hash 锁定在 renderer 生命周期内；调用方也可以在创建 scene 前直接 `registerFont()`；\n5. WASM 输出完整 glyph demand、layout request 与稳定 resource descriptor；\n6. 主线程按 descriptor stable ID 去重，并通过有界队列调用 `ResourceProvider`；\n7. WASM 使用已注册字体执行 FreeType measurement、TMP layout、glyph SDF generation 和 atlas placement；\n8. core 编译 authored layer tree、semantic commands、control bindings、interaction regions 与初始动态状态；\n9. WebGL2 上传 atlas pages、decoded images、geometry、command state 与 layer mask buffer；\n10. scene 返回后，调用方决定何时 `draw()`、推进 timeline、切换 layer、处理交互或导出 dump。\n\nglyph atlas 按实际 glyph demand 创建。单个图片资源缺失、provider 抛错或图片解码失败会产生 warning，并使用透明占位继续构建其余 scene；schema、ABI、内存预算和 scene contract 错误会显式失败。\n\n## ResourceProvider\n\nrenderer 输出并消费语义 descriptor；调用方负责把 descriptor 映射到 URL、CDN、对象存储、文件、manifest、鉴权请求或其他资源系统。\n\n```ts\nexport type ResourceDescriptor = {\n  id: string;\n  namespace: string;\n  key: string;\n  role: string;\n  provenance: Record<string, unknown>;\n  expectedSize?: { width: number; height: number };\n};\n\nexport interface ResourceProvider {\n  provide(\n    descriptor: ResourceDescriptor,\n    context: { signal: AbortSignal },\n  ): Promise<{\n    source: Blob | ArrayBuffer | Uint8Array | TexImageSource;\n  } | null>;\n\n  cacheIdentity?(descriptor: ResourceDescriptor): string | null;\n}\n```\n\n`namespace`、`key` 和 `role` 表达 renderer 语义；调用方可将其映射到任意资源命名和存储规则。\n\n罐徽章收藏品（`customProfileResourceCollectionType` 为 `can_badge`）除自身图片外还请求法线贴图 `namespace: \"static\"`、`key: \"ui/sekai_badge_normal\"`。`omikuji` 收藏品按元素 `targetId` 指向的 `omikujis` 行绘制签：请求签面图与运势图两张 `assets` 资源，签文以 FreeType 覆盖率（而非 SDF 图集）绘制，所需字体经 `FontProvider` 按字体资源名 `FOT-Omikuji`（2022 年签）或 `FOT-UDMinchoPro-B`（2023–2025 年签）请求字体文件。该字体没有字形的字符取自第一个有该字形的后备 face：先 `Roboto`，再按 master data 区服选取的 Noto Sans CJK face。`FontProvider` 会被请求这三个 family，返回完整文件，由渲染器打开下表所列 face。这些 family 不走预生成 SDF 图集，缺任何一个时场景创建失败，与其他已声明字体一致。\n\n| Family | 文件 | Face |\n|---|---|---|\n| `FOT-Omikuji`（2022 年签） | `FOT-Omikuji.otf` | 0 |\n| `FOT-UDMinchoPro-B`（2023–2025 年签） | `FOT-UDMinchoPro-B.otf` | 0 |\n| `Roboto` | `Roboto-Regular.ttf` | 0 |\n| `Noto Sans CJK SC`（区服 `cn`、`en` 及其他） | `NotoSansCJK-Regular.ttc` | 2 |\n| `Noto Sans CJK TC`（区服 `tw`） | `NotoSansCJK-Regular.ttc` | 3 |\n| `Noto Sans CJK JP`（区服 `jp`） | `NotoSansCJK-Regular.ttc` | 0 |\n| `Noto Sans CJK KR`（区服 `kr`） | `NotoSansCJK-Regular.ttc` | 1 |\n\n两个后备字体文件为 Android 14 原生系统字体：\n\n- `Roboto-Regular.ttf`，SHA-256 `9ca9debb09459bf4e3e7f826f5cd0f35f253902b85684921fce2ba3f28dd0f50`；\n- `NotoSansCJK-Regular.ttc`，SHA-256 `39fb47c543da50618ab99e8b9e5529e54566bdbef41719308165975f627d5c93`。\n\n### 任意异步来源\n\n一个 provider 可以独立组合任意资源规则：\n\n```ts\nconst provider: ResourceProvider = {\n  cacheIdentity(descriptor) {\n    return `${assetRevision}:${descriptor.id}`;\n  },\n\n  async provide(descriptor, { signal }) {\n    const memoryHit = memoryImages.get(descriptor.id);\n    if (memoryHit) return { source: memoryHit };\n\n    const stored = await assetDatabase.get(assetRevision, descriptor.id);\n    if (stored) return { source: stored };\n\n    const request = await applicationAssetRouter.resolve(descriptor);\n    if (!request) return null;\n\n    const response = await authenticatedFetch(request, { signal });\n    if (!response.ok) return null;\n\n    const blob = await response.blob();\n    await assetDatabase.put(assetRevision, descriptor.id, blob);\n    return { source: blob };\n  },\n};\n```\n\n同一个 provider 可以从本地静态文件、HTTP cache、Cache Storage、IndexedDB、用户选择的文件、Service Worker、签名 URL、GraphQL、私有鉴权接口或已经解码的 `ImageBitmap` 返回资源。路径解释与失效策略始终由 provider 掌握。\n\n### 并发、去重与失败语义\n\n- `resourceConcurrency` 默认为 8，必须是正整数；\n- descriptor 在单次 acquire 中按 stable `id` 去重；\n- 同一 renderer 内的并发 scene 请求共享 in-flight singleflight；\n- 每个等待者独立响应自己的取消信号；仅当全部等待者都取消时才取消底层 provider 请求；\n- decoded cache hit 直接复用 session lease，provider 并发槽继续服务实际加载请求；\n- `AbortSignal` 会传给 provider；\n- provider 返回 `null`、抛错或图片解码失败时，该项变为透明占位并记录 warning；\n- pinned lease 耗尽 decoded image hard budget 时会产生显式资源错误。\n\n`cacheIdentity()` 用于声明 decoded session cache 的身份。它应包含会改变图片内容的 catalog revision、region、用户或鉴权域；若省略则使用 descriptor stable ID。encoded asset 是否持久化、保存多久以及如何失效，完全由 provider 决定。\n\n## LocalizationProvider\n\nGeneral 标题、tab、难度名等 renderer-owned UI 文本通过稳定 localization key 请求；玩家名称、签名和其他用户内容保持 profile 原文。调用方可以用任意规则解析 `region + locale + key`：\n\n```ts\nconst renderer = await BrowserRenderer.create({\n  canvas,\n  region: \"tw\",\n  resourceProvider,\n  localizationProvider: {\n    async provide({ region, locale, key }, { signal }) {\n      return applicationMessages.resolve({ region, locale, key, signal });\n    },\n  },\n  localizationConcurrency: 8,\n});\n```\n\nWASM 先收集当前 scene 实际使用且已去重的 key，主线程有界并发调用 provider，并把完整 `key → UTF-8 value` 作为不可变快照送回 WASM。缺少必需 key 时 scene 创建显式失败，不跨区服猜测文案。未提供 `LocalizationProvider` 时使用 masterdata/runtime 自带的本地化来源。\n\n## Masterdata\n\n`loadMasterData()` 接受调用方实现的任意异步 table loader。调用方完整定义 table 的命名、位置、传输、鉴权与缓存方式：\n\n```ts\nconst masterData = await renderer.loadMasterData(\n  \"catalog-2026-07\",\n  async ({ table, region, revision }, { signal }) => {\n    const request = await applicationMasterDataRouter.resolve({ table, region, revision });\n    return applicationMasterDataLoader.load(request, { signal });\n  },\n  { concurrency: 4 },\n);\n```\n\n`masterData.optionalTables` 列出表集合可以没有的表（只有部分区服提供的 `customProfileCharacterIconResources`、`customProfileMaterialResources`、`customProfileUserInterfaceIconResources`，以及御神签收藏品读取的 `omikujis`）。loader 以 `optional: true` 被调用；返回 `null` 或 `undefined` 表示该区服没有这张表，可选表加载失败同样跳过。引用缺失表的元素不绘制，名片其余部分照常渲染。\n\n需要逐表控制生命周期时，可直接驱动 session：\n\n```ts\nconst masterData = await renderer.createMasterData(\"catalog-2026-07\");\n\nfor (const table of masterData.requiredTables) {\n  await masterData.putTable(table, await applicationMasterData.load(table));\n}\nfor (const table of masterData.optionalTables) {\n  const value = await applicationMasterData.loadIfPresent(table);\n  if (value != null) await masterData.putTable(table, value);\n}\n\nawait masterData.seal();\n```\n\n`seal()` 后该 session 才能创建 scene。销毁 renderer 前应销毁不再使用的 masterdata session。\n\n## 字体\n\n字体 bytes 始终由调用方提供。`family` 是 masterdata 和文本 layer 使用的 opaque 逻辑身份，并原样传给调用方的字体解析规则。\n\n推荐在 `BrowserRenderer.create()` 中提供 `FontProvider`。WASM 只请求当前 scene 实际使用的 family，主线程按 `fontConcurrency`（默认 4）有界并发调用 provider：\n\n```ts\nconst renderer = await BrowserRenderer.create({\n  canvas,\n  region: \"cn\",\n  resourceProvider,\n  fontProvider: {\n    async provide({ region, family }, { signal }) {\n      const bytes = await applicationFonts.resolve({ region, family, signal });\n      return bytes ? { bytes } : null;\n    },\n  },\n  fontConcurrency: 4,\n});\n```\n\nprovider 可以使用用户选择的文件、内存、IndexedDB、应用资源包、网络请求或任意其他实现。返回值会复制成该次解析的不可变 byte snapshot；缺少当前 scene 必需的 family 时 scene 创建显式失败。\n\n需要完全手动管理时，也可以在创建 scene 前直接注册：\n\n```ts\nconst bytes = await file.arrayBuffer();\n\nawait renderer.registerFont({ family: \"FOT-RodinNTLGPro-DB\", bytes });\nawait renderer.registerFont({ family: \"Application Alias\", bytes });\n```\n\n同一字体文件可以注册多个逻辑 alias。renderer 会对 source bytes 计算 hash，并把 region、family、source hash 和字符共同纳入 glyph identity。\n\n同一 renderer 生命周期内，首次成功注册会锁定一个 family 对应的 source hash。用相同 bytes 重复注册是幂等操作；用不同 bytes 覆盖同一 family 会返回 `FONT_IDENTITY_CONFLICT`。需要切换字体版本时应创建新的 renderer，使 font snapshot、glyph identity、atlas 和持久缓存边界保持明确。\n\n## 可选预生成 Atlas 包（0.3）\n\n默认仍按 scene 的实际 glyph demand 生成 SDF，并使用同源 IndexedDB glyph cache。预生成 atlas 不会自动下载。调用方可以直接使用本地/HTTP provider，也可以通过显式用户操作把完整 atlas 安装到同源 IndexedDB，随后让多个 renderer、编辑器和查看页共享同一份安装结果。\n\n```ts\nimport {\n  BrowserRenderer,\n  createHttpPrebuiltSdfAtlasProvider,\n  createOriginPrebuiltSdfAtlasPackage,\n} from \"@empty-sekai/sekai-custom-profile-sdk\";\n\nconst source = createHttpPrebuiltSdfAtlasProvider(\"/font-atlases\");\nconst atlasPackage = createOriginPrebuiltSdfAtlasPackage({\n  namespace: \"cn-6.0.0-font-atlas-v1\",\n  source,\n});\n\n// 只应从明确的用户操作调用。manifest 在全部页写入成功后才可见。\nawait atlasPackage.install([\n  \"FZLanTingHei-DB-GBK\",\n  \"FZZhengHei-EB-GBK\",\n  \"FZShaoEr-M11-JF\",\n], {\n  concurrency: 4,\n  requestPersistence: true,\n  onProgress(progress) {\n    updateDownloadProgress(progress.completedPages / progress.totalPages);\n  },\n});\n\nconst renderer = await BrowserRenderer.create({\n  canvas,\n  region: \"cn\",\n  resourceProvider,\n  fontProvider,\n  prebuiltSdfAtlasProvider: atlasPackage.provider,\n});\n```\n\n`atlasPackage.provider` 只读取已经完整安装的 family；未安装、安装中、IndexedDB 不可用或已移除时，manifest 返回 `null`，scene 自动回到按需 glyph 生成。安装前会检查浏览器报告的剩余 quota，每个页校验 SHA-256，失败时清理本次未完成 family。`requestPersistence` 默认关闭；renderer 不会在没有用户操作时请求持久存储权限。atlas package 不保存 profile、文本、用户 ID、layout 或 scene dump。\n\n不需要浏览器持久化时，可以把 `createHttpPrebuiltSdfAtlasProvider()` 的结果直接传给 `BrowserRenderer.create()`，由应用自己的本地文件、Service Worker 或 HTTP cache 管理生命周期。\n\natlas 包即 native crate 自带 `build-sdf-atlas` 工具的输出目录，原样发布为 `<baseUrl>/<family>/manifest.json` 及其页文件：\n\n```sh\nFONT_DIR=/path/to/fonts cargo run -p sekai-profile-renderer --features dev --release \\\n  --bin build-sdf-atlas -- --font-family FZLanTingHei-DB-GBK \\\n  --output font-atlases/FZLanTingHei-DB-GBK\n```\n\n目录名与 manifest 的 `font_family` 必须等于 scene 请求的 family 名。每页的 `file_sha256` 针对解压后的页内容，因此页文件可以预压缩为 `page-NNN.r8swz.br`（manifest 中的文件名同步改名），前提是服务端以 `Content-Encoding: br` 返回。页尺寸最大 2048×2048。同一 scene 用到的各 family 必须使用相同的 `--point-size`、`--spread` 与 `--page-size`，否则该 scene 改为按需生成 glyph。每个 scene 都会重新向 provider 查询 manifest，安装或移除从下一个 scene 起生效。\n\n## Scene 状态\n\n```ts\nawait scene.advance(tick);\nawait scene.setLayerVisible(layerId, false);\nawait scene.setLayerMasks(layerTableRevision, [\n  { layerId, visible: true },\n]);\nawait scene.setTab(controlId, value);\nawait scene.setScrollOffset(controlId, offset);\nawait scene.scrollBy(controlId, delta);\nscene.draw();\n```\n\n这些 mutation 复用 authored layer table、timeline、layout、glyph atlas 与 persistent glyph cache，只更新 WASM scene state 和紧凑 GPU buffer。layer 显隐作用于 render mask，并保持当前动态播放位置。\n\n公开 layer 对应游戏名片 authored element。shape、glyph、mask 和其他 draw primitive 作为 command 归属于各自 authored layer。\n\n## Dump、分层与交互\n\n```ts\nconst dump = await scene.dump();\n```\n\ndump 包含：\n\n- stable layer/glyph/command/control/region ID；\n- layer table 与 parent tree；\n- source content 和 resolved parameters；\n- authored visibility、render mask 与动态状态；\n- bounds、quad、matrix、clip 与 hit geometry；\n- semantic commands 与 component controls；\n- interaction regions 和剥离 TMP 后识别的连续数字区域。\n\n滚动组件分别暴露固定 viewport、随 offset 移动的 content 和随比例移动的 thumb region。调用方可在 viewport/content/thumb 上处理滚轮，并根据 thumb 拖动结果调用 `setScrollOffset()`；renderer 负责 clamp、状态 patch 与命中几何更新，不接管指针行为。\n\n产品交互由调用方实现；renderer 提供 `capabilities`、`resolved_data`、control binding 和 geometry，用于构建：\n\n- 称号、角色、卡面、活动、歌曲或剧情资料跳转；\n- tab 切换与滚动；\n- 数字复制；\n- hover card、selection、editing；\n- DOM/SVG accessibility overlay。\n\n动画过程中保持稳定的 layer tree DOM 与 layer row，并按 tick 更新画布、overlay 和必要的动态详情。\n\n## 缓存模型\n\n| 层级 | 生命周期 | 所有者 | 内容 |\n| --- | --- | --- | --- |\n| provider persistent cache | 由应用决定 | 调用方 | encoded image 或应用自定义记录 |\n| decoded image cache | renderer session | TypeScript runtime | 有界 decoded `TexImageSource` lease |\n| glyph persistent cache | browser origin | renderer | 不透明、版本校验的 glyph record |\n| glyph session atlas | worker session | WASM | atlas pages、placement、lease 与 revision |\n| GPU texture/buffer | WebGL context | WebGL2 runtime | atlas、image、geometry 与状态 buffer |\n\n`sdf.persistence` 默认为 `\"origin\"`，使用 IndexedDB；`\"memory-only\"` 保留 session cache。IndexedDB record 由 opaque glyph identity、版本信息、metrics 与 R8 SDF payload 组成。\n\n```ts\nconst scene = await renderer.createProfileScene({\n  masterData,\n  documentKey: \"preview\",\n  profile,\n  sdf: { persistence: \"memory-only\" },\n});\n```\n\nWebGL context restore 会重传保留的 atlas/image/buffer state，并复用已完成的 TMP parsing、layout 与 SDF generation 结果。\n\n## Debug 与 telemetry\n\n```ts\nconst renderer = await BrowserRenderer.create({\n  canvas,\n  region: \"en\",\n  resourceProvider,\n  telemetry: { level: \"trace\", maxSamples: 240 },\n});\n\nconst sceneStats = scene.stats();\nconst rendererStats = await renderer.stats();\n```\n\ntelemetry 覆盖 worker request；字体 provider 的请求、并发峰值、bytes、失败和耗时；本地化 provider 的请求、解析数、并发峰值、失败和耗时；resource provider 的实际调用数、并发峰值、已知 encoded bytes、失败、取消与累计解析耗时，并同时报告 decoded session cache 的 entries、bytes、pin、hit、load 和 eviction。它还覆盖 glyph generation、persistent cache hit/miss、atlas pages、texture uploads、GPU buffers、frame timing 与 context recovery。`summary` 保留聚合值；`trace` 在固定上限内保留 raw samples；`off` 仅提供即时运行状态。\n\ntelemetry 采用 privacy-safe schema，仅记录运行时计数、耗时、容量与恢复状态。GPU timing 在 unavailable 或 disjoint 状态下使用 `null` 表达测量状态。\n\n## 生命周期与取消\n\n```ts\nconst controller = new AbortController();\n\nconst pendingScene = renderer.createProfileScene({\n  masterData,\n  documentKey: \"preview\",\n  profile,\n  signal: controller.signal,\n});\n\ncontroller.abort();\n\nawait pendingScene; // rejects if creation was still in progress\n\nconst scene = await renderer.createProfileScene({ masterData, documentKey: \"preview\", profile });\nawait scene.destroy();\nawait masterData.destroy();\nrenderer.destroy();\n```\n\n`signal` 只取消仍在进行的 scene creation；已创建 scene 通过 `destroy()` 释放。销毁 scene 会释放图片 lease、atlas lease、GPU texture 与 core scene。`renderer.destroy()` 会终止 worker，并取消仍在进行的资源获取。\n\n## 从源码构建\n\n使用仓库提供的 container toolchain；它固定 FreeType、Emscripten 与 Rust target 环境。\n\n```sh\nnpm run build\nnpm run typecheck\nnpm run test:gates\nnpm run verify:wasm:runtime\nnpm run measure:wasm:size\nnpm run audit:public\n```\n\nFreeType 只启用 runtime 所需的 TrueType、CFF、SFNT、PS auxiliary/name 和 smooth raster modules。CPU EDT 是 production glyph SDF backend；analytic backend 仅用于显式 debug 与 parity 分析。\n\n## License\n\nAGPL-3.0-only，并附带 `LICENSE-EXCEPTION` 中的 browser linking exception。exception 适用于未修改 package 的浏览器使用；修改 renderer、服务端使用和非浏览器使用仍受完整 AGPL 约束，包括网络交互场景下的源代码提供义务。\n","readmeFilename":"README.md"}