distark-render 1.1.15 → 1.1.16

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 (184) hide show
  1. package/README.md +0 -0
  2. package/assets/tank.json +0 -0
  3. package/dist/cli/animate.d.ts +0 -0
  4. package/dist/cli/animate.d.ts.map +0 -0
  5. package/dist/cli/animate.js +0 -0
  6. package/dist/cli/animate.js.map +0 -0
  7. package/dist/cli/diff.d.ts +0 -0
  8. package/dist/cli/diff.d.ts.map +0 -0
  9. package/dist/cli/diff.js +0 -0
  10. package/dist/cli/diff.js.map +0 -0
  11. package/dist/cli/distark-check.d.ts +0 -0
  12. package/dist/cli/distark-check.d.ts.map +0 -0
  13. package/dist/cli/distark-check.js +0 -0
  14. package/dist/cli/distark-check.js.map +0 -0
  15. package/dist/cli/query.d.ts +0 -0
  16. package/dist/cli/query.d.ts.map +0 -0
  17. package/dist/cli/query.js +0 -0
  18. package/dist/cli/query.js.map +0 -0
  19. package/dist/cli/record.d.ts +0 -0
  20. package/dist/cli/record.d.ts.map +0 -0
  21. package/dist/cli/record.js +0 -0
  22. package/dist/cli/record.js.map +0 -0
  23. package/dist/cli/render.d.ts +0 -0
  24. package/dist/cli/render.d.ts.map +0 -0
  25. package/dist/cli/render.js +0 -0
  26. package/dist/cli/render.js.map +0 -0
  27. package/dist/cli/scene.d.ts +0 -0
  28. package/dist/cli/scene.d.ts.map +0 -0
  29. package/dist/cli/scene.js +16 -16
  30. package/dist/cli/scene.js.map +0 -0
  31. package/dist/cli/shared.d.ts +0 -0
  32. package/dist/cli/shared.d.ts.map +0 -0
  33. package/dist/cli/shared.js +0 -0
  34. package/dist/cli/shared.js.map +0 -0
  35. package/dist/cli/test.d.ts +0 -0
  36. package/dist/cli/test.d.ts.map +0 -0
  37. package/dist/cli/test.js +0 -0
  38. package/dist/cli/test.js.map +0 -0
  39. package/dist/cli/verify.d.ts +0 -0
  40. package/dist/cli/verify.d.ts.map +0 -0
  41. package/dist/cli/verify.js +0 -0
  42. package/dist/cli/verify.js.map +0 -0
  43. package/dist/modules/adapters/p5Renderer.d.ts +0 -0
  44. package/dist/modules/adapters/p5Renderer.d.ts.map +1 -1
  45. package/dist/modules/adapters/p5Renderer.js +54 -12
  46. package/dist/modules/adapters/p5Renderer.js.map +1 -1
  47. package/dist/modules/adapters/skiaRenderer.d.ts +0 -0
  48. package/dist/modules/adapters/skiaRenderer.d.ts.map +1 -1
  49. package/dist/modules/adapters/skiaRenderer.js +2 -1
  50. package/dist/modules/adapters/skiaRenderer.js.map +1 -1
  51. package/dist/modules/animationDiff.d.ts +0 -0
  52. package/dist/modules/animationDiff.d.ts.map +0 -0
  53. package/dist/modules/animationDiff.js +0 -0
  54. package/dist/modules/animationDiff.js.map +0 -0
  55. package/dist/modules/browserSceneRenderer.d.ts +0 -0
  56. package/dist/modules/browserSceneRenderer.d.ts.map +0 -0
  57. package/dist/modules/browserSceneRenderer.js +0 -0
  58. package/dist/modules/browserSceneRenderer.js.map +0 -0
  59. package/dist/modules/characterValidator.d.ts +2 -1
  60. package/dist/modules/characterValidator.d.ts.map +1 -1
  61. package/dist/modules/characterValidator.js +2 -41
  62. package/dist/modules/characterValidator.js.map +1 -1
  63. package/dist/modules/eyeSystem.d.ts +0 -0
  64. package/dist/modules/eyeSystem.d.ts.map +1 -1
  65. package/dist/modules/eyeSystem.js +104 -69
  66. package/dist/modules/eyeSystem.js.map +1 -1
  67. package/dist/modules/imageLoad.d.ts +50 -0
  68. package/dist/modules/imageLoad.d.ts.map +1 -1
  69. package/dist/modules/imageLoad.js +262 -18
  70. package/dist/modules/imageLoad.js.map +1 -1
  71. package/dist/modules/mouthSystem.d.ts +0 -0
  72. package/dist/modules/mouthSystem.d.ts.map +0 -0
  73. package/dist/modules/mouthSystem.js +0 -0
  74. package/dist/modules/mouthSystem.js.map +0 -0
  75. package/dist/modules/renderRig.d.ts +0 -0
  76. package/dist/modules/renderRig.d.ts.map +0 -0
  77. package/dist/modules/renderRig.js +0 -0
  78. package/dist/modules/renderRig.js.map +0 -0
  79. package/dist/modules/sceneRenderer.d.ts +0 -0
  80. package/dist/modules/sceneRenderer.d.ts.map +0 -0
  81. package/dist/modules/sceneRenderer.js +0 -0
  82. package/dist/modules/sceneRenderer.js.map +0 -0
  83. package/dist/tests/helpers.d.ts +0 -0
  84. package/dist/tests/helpers.d.ts.map +0 -0
  85. package/dist/tests/helpers.js +0 -0
  86. package/dist/tests/helpers.js.map +0 -0
  87. package/dist/tests/test-angle-swap.d.ts +0 -0
  88. package/dist/tests/test-angle-swap.d.ts.map +0 -0
  89. package/dist/tests/test-angle-swap.js +0 -0
  90. package/dist/tests/test-angle-swap.js.map +0 -0
  91. package/dist/tests/test-animation-diff-rendering.d.ts +0 -0
  92. package/dist/tests/test-animation-diff-rendering.d.ts.map +0 -0
  93. package/dist/tests/test-animation-diff-rendering.js +0 -0
  94. package/dist/tests/test-animation-diff-rendering.js.map +0 -0
  95. package/dist/tests/test-animation-diff.d.ts +0 -0
  96. package/dist/tests/test-animation-diff.d.ts.map +0 -0
  97. package/dist/tests/test-animation-diff.js +0 -0
  98. package/dist/tests/test-animation-diff.js.map +0 -0
  99. package/dist/tests/test-auto-eye-movement-e2e.d.ts +24 -0
  100. package/dist/tests/test-auto-eye-movement-e2e.d.ts.map +1 -0
  101. package/dist/tests/test-auto-eye-movement-e2e.js +514 -0
  102. package/dist/tests/test-auto-eye-movement-e2e.js.map +1 -0
  103. package/dist/tests/test-autofit.d.ts +0 -0
  104. package/dist/tests/test-autofit.d.ts.map +0 -0
  105. package/dist/tests/test-autofit.js +0 -0
  106. package/dist/tests/test-autofit.js.map +0 -0
  107. package/dist/tests/test-base-autofit.d.ts +0 -0
  108. package/dist/tests/test-base-autofit.d.ts.map +0 -0
  109. package/dist/tests/test-base-autofit.js +0 -0
  110. package/dist/tests/test-base-autofit.js.map +0 -0
  111. package/dist/tests/test-browser-scene.d.ts +0 -0
  112. package/dist/tests/test-browser-scene.d.ts.map +0 -0
  113. package/dist/tests/test-browser-scene.js +0 -0
  114. package/dist/tests/test-browser-scene.js.map +0 -0
  115. package/dist/tests/test-character-validator.d.ts +0 -0
  116. package/dist/tests/test-character-validator.d.ts.map +0 -0
  117. package/dist/tests/test-character-validator.js +0 -32
  118. package/dist/tests/test-character-validator.js.map +1 -1
  119. package/dist/tests/test-cli.d.ts +0 -0
  120. package/dist/tests/test-cli.d.ts.map +0 -0
  121. package/dist/tests/test-cli.js +0 -0
  122. package/dist/tests/test-cli.js.map +0 -0
  123. package/dist/tests/test-eye-system-reset.d.ts +15 -0
  124. package/dist/tests/test-eye-system-reset.d.ts.map +1 -0
  125. package/dist/tests/test-eye-system-reset.js +148 -0
  126. package/dist/tests/test-eye-system-reset.js.map +1 -0
  127. package/dist/tests/test-image-loading.d.ts +0 -0
  128. package/dist/tests/test-image-loading.d.ts.map +0 -0
  129. package/dist/tests/test-image-loading.js +0 -0
  130. package/dist/tests/test-image-loading.js.map +0 -0
  131. package/dist/tests/test-joint-movement.d.ts +0 -0
  132. package/dist/tests/test-joint-movement.d.ts.map +0 -0
  133. package/dist/tests/test-joint-movement.js +0 -0
  134. package/dist/tests/test-joint-movement.js.map +0 -0
  135. package/dist/tests/test-p5-image-types.d.ts +0 -0
  136. package/dist/tests/test-p5-image-types.d.ts.map +0 -0
  137. package/dist/tests/test-p5-image-types.js +0 -0
  138. package/dist/tests/test-p5-image-types.js.map +0 -0
  139. package/dist/tests/test-render-cache-visual.d.ts +0 -0
  140. package/dist/tests/test-render-cache-visual.d.ts.map +0 -0
  141. package/dist/tests/test-render-cache-visual.js +0 -0
  142. package/dist/tests/test-render-cache-visual.js.map +0 -0
  143. package/dist/tests/test-render-cache.d.ts +0 -0
  144. package/dist/tests/test-render-cache.d.ts.map +0 -0
  145. package/dist/tests/test-render-cache.js +0 -0
  146. package/dist/tests/test-render-cache.js.map +0 -0
  147. package/dist/tests/test-scene-object-svg-rerasterization.d.ts +0 -0
  148. package/dist/tests/test-scene-object-svg-rerasterization.d.ts.map +0 -0
  149. package/dist/tests/test-scene-object-svg-rerasterization.js +0 -0
  150. package/dist/tests/test-scene-object-svg-rerasterization.js.map +0 -0
  151. package/dist/tests/test-scene.d.ts +0 -0
  152. package/dist/tests/test-scene.d.ts.map +0 -0
  153. package/dist/tests/test-scene.js +0 -0
  154. package/dist/tests/test-scene.js.map +0 -0
  155. package/dist/tests/test-skia.d.ts +0 -0
  156. package/dist/tests/test-skia.d.ts.map +0 -0
  157. package/dist/tests/test-skia.js +0 -0
  158. package/dist/tests/test-skia.js.map +0 -0
  159. package/dist/tests/test-svg-parallax.d.ts +0 -0
  160. package/dist/tests/test-svg-parallax.d.ts.map +0 -0
  161. package/dist/tests/test-svg-parallax.js +0 -0
  162. package/dist/tests/test-svg-parallax.js.map +0 -0
  163. package/dist/tests/test-svg-rerasterization.d.ts +0 -0
  164. package/dist/tests/test-svg-rerasterization.d.ts.map +0 -0
  165. package/dist/tests/test-svg-rerasterization.js +194 -4
  166. package/dist/tests/test-svg-rerasterization.js.map +1 -1
  167. package/dist/tests/test-teen-tank-rig.d.ts +12 -0
  168. package/dist/tests/test-teen-tank-rig.d.ts.map +1 -0
  169. package/dist/tests/test-teen-tank-rig.js +122 -0
  170. package/dist/tests/test-teen-tank-rig.js.map +1 -0
  171. package/dist/tests/test-torso-pivot.d.ts +0 -0
  172. package/dist/tests/test-torso-pivot.d.ts.map +0 -0
  173. package/dist/tests/test-torso-pivot.js +0 -0
  174. package/dist/tests/test-torso-pivot.js.map +0 -0
  175. package/dist/tests/test-visual-verification.d.ts +0 -0
  176. package/dist/tests/test-visual-verification.d.ts.map +0 -0
  177. package/dist/tests/test-visual-verification.js +0 -0
  178. package/dist/tests/test-visual-verification.js.map +0 -0
  179. package/dist/types.d.ts +0 -0
  180. package/dist/types.d.ts.map +0 -0
  181. package/dist/types.js +0 -0
  182. package/dist/types.js.map +0 -0
  183. package/package.json +106 -106
  184. package/assets/presets/jumping-jack.json +0 -194
@@ -30,10 +30,12 @@ class MockImage {
30
30
  }, 0);
31
31
  }
32
32
  }
33
- function makeSvgDataUrl() {
34
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" fill="black" /></svg>`;
33
+ function svgDataUrl(svg) {
35
34
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
36
35
  }
36
+ function makeSvgDataUrl() {
37
+ return svgDataUrl(`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><rect x="0" y="0" width="24" height="24" fill="black" /></svg>`);
38
+ }
37
39
  function makeRig(svgPath, torsoWidth, torsoHeight) {
38
40
  return {
39
41
  imagePaths: {
@@ -71,7 +73,8 @@ async function main() {
71
73
  const initialAsset = loadedImages[svgPath];
72
74
  assert(!!initialAsset?.__distarkSvgSource, 'Initial SVG asset keeps original SVG source');
73
75
  assert(initialAsset?.width === 24 && initialAsset?.height === 24, 'Initial SVG raster matches initial rendered size');
74
- const largeRig = makeRig(svgPath, 25, 25);
76
+ // Use 50×50 a 2× jump from the 24px initial raster, well above the 5% hysteresis band.
77
+ const largeRig = makeRig(svgPath, 50, 50);
75
78
  const firstLargeRender = renderer.computeCharacterRigData(largeRig, {
76
79
  canvasWidth: 400,
77
80
  canvasHeight: 400,
@@ -81,13 +84,200 @@ async function main() {
81
84
  await loader.waitForPendingImageUpgrades();
82
85
  const upgradedAsset = loadedImages[svgPath];
83
86
  assert(upgradedAsset !== initialAsset, 'Upscaling swaps in a regenerated SVG raster asset');
84
- assert(upgradedAsset?.width >= 25 && upgradedAsset?.height >= 25, 'Any upscale immediately regenerates a larger SVG raster');
87
+ assert(upgradedAsset?.width >= 50 && upgradedAsset?.height >= 50, 'Any upscale immediately regenerates a larger SVG raster');
85
88
  const secondLargeRender = renderer.computeCharacterRigData(largeRig, {
86
89
  canvasWidth: 400,
87
90
  canvasHeight: 400,
88
91
  loadedImages,
89
92
  });
90
93
  assert(secondLargeRender.objects[0]?.imageData === upgradedAsset, 'Subsequent renders use the upgraded SVG raster');
94
+ const mmSvgPath = svgDataUrl(`<svg xmlns="http://www.w3.org/2000/svg" width="25.4mm" height="1in" viewBox="0 0 25.4 25.4"><rect x="0" y="0" width="25.4" height="25.4" fill="black" /></svg>`);
95
+ const mmLoadedImages = await loader.loadAllRigImages(makeRig(mmSvgPath, 24, 24), false);
96
+ const mmAsset = mmLoadedImages[mmSvgPath];
97
+ assert(mmAsset?.width === 96 && mmAsset?.height === 96, 'SVG physical units rasterize to CSS pixels');
98
+ const loosePartSvgPath = svgDataUrl(`<svg xmlns="http://www.w3.org/2000/svg" width="50.8mm" height="46.566666mm" viewBox="0 0 50.8 46.566665"><g transform="translate(-98.248611,-138.2007)"><ellipse cx="104.81959" cy="148.46378" rx="5.8557706" ry="9.6129332" fill="black"/><image width="13.052777" height="20.461111" x="98.248611" y="138.2007" style="display:none"/><ellipse cx="106.75509" cy="144.67407" rx="1.6606208" ry="2.1973581" fill="white"/></g></svg>`);
99
+ const partLoadedImages = await loader.loadAllRigImages(makeRig(loosePartSvgPath, 24, 24), false);
100
+ const partInitial = partLoadedImages[loosePartSvgPath];
101
+ assert(!!partInitial?.__distarkSvgSource, 'SVG part retains original raw source after initial load');
102
+ assert(!(partInitial?.__distarkSvgSource || '').includes('<g transform="matrix('), 'Raw source stored in cache has no injected fit-matrix transform');
103
+ renderer.computeCharacterRigData(makeRig(loosePartSvgPath, 24, 24), {
104
+ canvasWidth: 400,
105
+ canvasHeight: 400,
106
+ loadedImages: partLoadedImages,
107
+ });
108
+ await loader.waitForPendingImageUpgrades();
109
+ const partAsset = partLoadedImages[loosePartSvgPath];
110
+ // Guard 1 (<=): requesting 24×24 on a canvas that is already >= 24 on both sides
111
+ // serves the existing raster without a downgrade. The renderer draws it scaled
112
+ // into the 24×24 display region at the correct visual size — no re-rasterization needed.
113
+ assert((partAsset?.width ?? 0) >= 24 && (partAsset?.height ?? 0) >= 24, 'SVG part raster is at least as large as the requested rig dimensions (no downgrade)');
114
+ // ── Monotonic upgrade / anti-oscillation tests ─────────────────────────────
115
+ // Regression for commit 20182eb which changed both size guards from `<=` to
116
+ // `===` and removed the `Math.max` for __distarkRequestedRasterWidth. That
117
+ // change allowed DOWNGRADE requests: when desiredSize dipped below the cached
118
+ // raster size a new smaller canvas was triggered; the following frame needing
119
+ // the original larger size triggered another upgrade; creating perpetual
120
+ // oscillation ("shaky dimensions"). These tests pin the correct behaviour.
121
+ const oscLoader = new ImageLoader('http://example.test');
122
+ const oscSvgPath = makeSvgDataUrl();
123
+ let oscUpgradeCount = 0;
124
+ const OrigCreateSvgCanvas = oscLoader.createSvgCanvas.bind(oscLoader);
125
+ oscLoader.createSvgCanvas = async (...args) => {
126
+ oscUpgradeCount++;
127
+ return OrigCreateSvgCanvas(...args);
128
+ };
129
+ // Seed loadedImages with the SVG at its intrinsic size (24×24).
130
+ const oscImages = await oscLoader.loadAllRigImages(makeRig(oscSvgPath, 24, 24), false);
131
+ oscUpgradeCount = 0; // reset counter — we only care about requestImageForSize calls
132
+ // Frame 1: request at 100×100 → should trigger exactly ONE upgrade.
133
+ oscLoader.requestImageForSize(oscSvgPath, 100, 100, oscImages);
134
+ await oscLoader.waitForPendingImageUpgrades();
135
+ assert(oscUpgradeCount === 1, 'Upscale from intrinsic triggers exactly one rasterization');
136
+ assert(oscImages[oscSvgPath]?.width >= 100, 'Canvas after first upgrade is at least 100 wide');
137
+ oscUpgradeCount = 0;
138
+ // Frame 2: same size request → must NOT trigger a new rasterization.
139
+ oscLoader.requestImageForSize(oscSvgPath, 100, 100, oscImages);
140
+ await oscLoader.waitForPendingImageUpgrades();
141
+ assert(oscUpgradeCount === 0, 'Identical size request does not re-rasterize (Guard 1 <=)');
142
+ // Frame 3: slightly SMALLER request (95×95, 5% below current 100×100).
143
+ // Guard 1 (<=): 95 <= 100 → should return the 100×100 canvas without re-rasterizing.
144
+ oscLoader.requestImageForSize(oscSvgPath, 95, 95, oscImages);
145
+ await oscLoader.waitForPendingImageUpgrades();
146
+ assert(oscUpgradeCount === 0, 'Smaller request served by existing larger raster (no downgrade)');
147
+ assert(oscImages[oscSvgPath]?.width >= 100, 'Canvas stays at 100 after smaller request');
148
+ // Oscillation simulation: alternate 95 ↔ 110 for 10 frames.
149
+ // With the old buggy code each alternation triggered a new rasterization.
150
+ // With the fix, the raster grows to 110 on the first 110 request and stays
151
+ // there for all subsequent requests (both 95 and 110 pass Guard 1 afterwards).
152
+ const oscImages2 = await oscLoader.loadAllRigImages(makeRig(oscSvgPath, 24, 24), false);
153
+ oscUpgradeCount = 0;
154
+ for (let i = 0; i < 10; i++) {
155
+ const size = i % 2 === 0 ? 95 : 110;
156
+ oscLoader.requestImageForSize(oscSvgPath, size, size, oscImages2);
157
+ await oscLoader.waitForPendingImageUpgrades();
158
+ }
159
+ assert(oscUpgradeCount <= 2, `Oscillating size requests trigger at most 2 rasterizations (got ${oscUpgradeCount}), not one per frame`);
160
+ assert(oscImages2[oscSvgPath]?.width >= 95, 'Canvas after oscillation is still valid');
161
+ // __distarkIntrinsicWidth must survive through the upgrade so it can still be
162
+ // used as a fallback when desiredWidth is 0.
163
+ const intrLoader = new ImageLoader('http://example.test');
164
+ const intrImages = await intrLoader.loadAllRigImages(makeRig(makeSvgDataUrl(), 24, 24), false);
165
+ const intrKey = Object.keys(intrImages)[0];
166
+ const intrinsicBefore = intrImages[intrKey].__distarkIntrinsicWidth;
167
+ intrLoader.requestImageForSize(intrKey, 80, 80, intrImages);
168
+ await intrLoader.waitForPendingImageUpgrades();
169
+ const intrinsicAfter = intrImages[intrKey].__distarkIntrinsicWidth;
170
+ assert(intrinsicAfter === intrinsicBefore, `__distarkIntrinsicWidth preserved through upgrade (${intrinsicBefore} → ${intrinsicAfter})`);
171
+ // ── preserveAspectRatio="none" → non-uniform fill (no letterboxing) ────────
172
+ // When Body.js stores an Inkscape SVG it adds preserveAspectRatio="none" so
173
+ // that future upgrade rasters stretch the content to fill the target box.
174
+ // Without the fix, normalizeSvgToVisibleBounds uses uniform min-scale fit and
175
+ // centres the content with padding — causing a visible dimension jump the
176
+ // first time the raster is upgraded after the attribute is added.
177
+ //
178
+ // Geometry of this test:
179
+ // SVG viewBox 10×20 (tall, aspect 0.5), target 200×100 (wide, aspect 2.0)
180
+ // Uniform fit → fitScale = min(200/10, 100/20) = 5 → matrix(5 0 0 5 75 0)
181
+ // content is only 50 px wide inside a 200 px canvas (75 px padding each side)
182
+ // Non-uniform fill → scaleX = 200/10 = 20, scaleY = 100/20 = 5 → matrix(20 0 0 5 0 0)
183
+ // content fills the entire 200×100 canvas with no padding
184
+ class NormalizeTester extends ImageLoader {
185
+ static expose(svg, w, h) {
186
+ return ImageLoader.normalizeSvgToVisibleBounds(svg, w, h);
187
+ }
188
+ }
189
+ const tallSvgWithFill = '<svg xmlns="http://www.w3.org/2000/svg" width="10" height="20" viewBox="0 0 10 20" preserveAspectRatio="none"><rect width="10" height="20"/></svg>';
190
+ const tallSvgNoHint = '<svg xmlns="http://www.w3.org/2000/svg" width="10" height="20" viewBox="0 0 10 20"><rect width="10" height="20"/></svg>';
191
+ const wideTarget = { w: 200, h: 100 };
192
+ const normalizedFill = NormalizeTester.expose(tallSvgWithFill, wideTarget.w, wideTarget.h);
193
+ const normalizedFit = NormalizeTester.expose(tallSvgNoHint, wideTarget.w, wideTarget.h);
194
+ // With preserveAspectRatio="none": fill → scaleX 20, scaleY 5, no centering offset
195
+ assert(normalizedFill.includes('matrix(20'), `preserveAspectRatio="none" SVG should use fill scaleX=20 (got: ${normalizedFill.match(/matrix\([^)]+\)/)?.[0] ?? 'no matrix'})`);
196
+ assert(!normalizedFill.includes('matrix(5 0 0 5 75'), 'preserveAspectRatio="none" SVG must NOT produce a uniform-fit pillarboxed matrix');
197
+ // Without preserveAspectRatio: uniform fit → existing behaviour preserved
198
+ assert(normalizedFit.includes('matrix(5 0 0 5'), `SVG without preserveAspectRatio hint should still use uniform fit scaleX=scaleY=5 (got: ${normalizedFit.match(/matrix\([^)]+\)/)?.[0] ?? 'no matrix'})`);
199
+ // Verify mm SVG: after Body.js patches __distarkSvgSource with preserveAspectRatio="none",
200
+ // a subsequent requestImageForSize upgrade must produce a canvas whose content fills the
201
+ // target box without pillarboxing.
202
+ //
203
+ // Olivia head: mm SVG intrinsic ~390×525 px, dimensionValues 208×232.
204
+ // The aspect ratio mismatch (0.897 vs 0.743) would produce heavy pillarboxing under
205
+ // the old uniform-fit code. With fill mode the matrix scaleX/scaleY differ, confirming
206
+ // non-uniform (stretch) rendering.
207
+ const oliviaHeadSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="103.1875mm" height="138.90625mm" viewBox="0 0 103.1875 138.90625" preserveAspectRatio="none"><rect width="103.1875" height="138.90625"/></svg>';
208
+ const oliviaNormalized = NormalizeTester.expose(oliviaHeadSvg, 208, 232);
209
+ // scaleUU_X = 208 / 103.1875 ≈ 2.016, scaleUU_Y = 232 / 138.90625 ≈ 1.670 — must differ
210
+ const oliviaMatrixMatch = oliviaNormalized.match(/matrix\(([\d.]+)\s+0\s+0\s+([\d.]+)/);
211
+ const oliviaScaleX = parseFloat(oliviaMatrixMatch?.[1] ?? '0');
212
+ const oliviaScaleY = parseFloat(oliviaMatrixMatch?.[2] ?? '0');
213
+ assert(Math.abs(oliviaScaleX - oliviaScaleY) > 0.1, `Olivia mm SVG with preserveAspectRatio="none" must use non-uniform scaling (scaleX=${oliviaScaleX.toFixed(3)}, scaleY=${oliviaScaleY.toFixed(3)})`);
214
+ // ── prepareSvgCanvases: pre-rasterize all SVG parts at dimensionValues sizes ──
215
+ // Before playback starts, every SVG part in loadedImages must have a canvas
216
+ // that is already the exact size dictated by dimensionValues. Without this,
217
+ // the first rendered frame uses the intrinsic-size canvas (often much larger
218
+ // than the display slot), then an async requestImageForSize upgrade fires —
219
+ // causing the visible "flash then correct size" artefact.
220
+ //
221
+ // prepareSvgCanvases(rigData, loadedImages) must:
222
+ // 1. Read dimensionValues[partName] for each SVG imagePath
223
+ // 2. Create a canvas at exactly that size using the (already-normalized) source
224
+ // 3. Mark the canvas __distarkSvgNormalized = true so Body.js doesn't re-normalize
225
+ // 4. Update both imageCache and loadedImages in-place
226
+ const prepLoader = new ImageLoader('http://example.test');
227
+ // Simple SVG — full viewBox is filled, no offset transform.
228
+ // preserveAspectRatio="none" is already set so normalizeSvgToVisibleBounds fills.
229
+ const simpleSvgForPrep = `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="120" viewBox="0 0 100 120" preserveAspectRatio="none"><rect x="0" y="0" width="100" height="120" fill="black"/></svg>`;
230
+ const prepPath = svgDataUrl(simpleSvgForPrep);
231
+ const prepRig = makeRig(prepPath, 60, 80);
232
+ // Load at intrinsic size (100×120)
233
+ const prepImages = await prepLoader.loadAllRigImages(prepRig, false);
234
+ assert(prepImages[prepPath]?.width === 100 && prepImages[prepPath]?.height === 120, `Before prepareSvgCanvases: canvas at intrinsic 100×120 (got ${prepImages[prepPath]?.width}×${prepImages[prepPath]?.height})`);
235
+ // prepareSvgCanvases must exist and be callable
236
+ assert(typeof prepLoader.prepareSvgCanvases === 'function', 'prepareSvgCanvases is a public method on ImageLoader');
237
+ await prepLoader.prepareSvgCanvases(prepRig, prepImages);
238
+ // After: canvas must be at the dimensionValues size (60×80) with default pixelDensity=1
239
+ assert(prepImages[prepPath]?.width === 60 && prepImages[prepPath]?.height === 80, `prepareSvgCanvases resizes canvas to dimensionValues 60×80 (got ${prepImages[prepPath]?.width}×${prepImages[prepPath]?.height})`);
240
+ // With pixelDensity=2 the canvas must be at 120×160 (dimensionValues × 2)
241
+ const prepLoader2 = new ImageLoader('http://example.test');
242
+ const prepImages2 = await prepLoader2.loadAllRigImages(prepRig, false);
243
+ await prepLoader2.prepareSvgCanvases(prepRig, prepImages2, { pixelDensity: 2 });
244
+ assert(prepImages2[prepPath]?.width === 120 && prepImages2[prepPath]?.height === 160, `prepareSvgCanvases with pixelDensity=2 resizes to 120×160 (got ${prepImages2[prepPath]?.width}×${prepImages2[prepPath]?.height})`);
245
+ // Fractional camera scales must be preserved. Rounding 1.25 to 1 would
246
+ // leave playback undersized and trigger requestImageForSize on frame 1.
247
+ const prepLoader125 = new ImageLoader('http://example.test');
248
+ const prepImages125 = await prepLoader125.loadAllRigImages(prepRig, false);
249
+ await prepLoader125.prepareSvgCanvases(prepRig, prepImages125, { pixelDensity: 1.25 });
250
+ assert(prepImages125[prepPath]?.width === 75 && prepImages125[prepPath]?.height === 100, `prepareSvgCanvases preserves fractional scale 1.25 → 75×100 (got ${prepImages125[prepPath]?.width}×${prepImages125[prepPath]?.height})`);
251
+ // A later lower-resolution loadImages call must not downgrade a canvas that
252
+ // playback precompute already warmed at a higher camera/capture scale.
253
+ await prepLoader125.prepareSvgCanvases(prepRig, prepImages125, { pixelDensity: 1 });
254
+ assert(prepImages125[prepPath]?.width === 75 && prepImages125[prepPath]?.height === 100, `prepareSvgCanvases must not downgrade 75×100 cache back to 60×80 (got ${prepImages125[prepPath]?.width}×${prepImages125[prepPath]?.height})`);
255
+ // Canvas must be marked so Body.js rememberOriginalSvgImage skips re-normalization
256
+ assert(prepImages[prepPath]?.__distarkSvgNormalized === true, 'Canvas produced by prepareSvgCanvases is marked __distarkSvgNormalized');
257
+ // imageCache must also point to the new canvas
258
+ assert(prepLoader.getCachedImage(prepPath)?.width === 60, 'imageCache updated to the target-sized canvas after prepareSvgCanvases');
259
+ // Runtime playback lock: once the app has precomputed the fixed-size SVG
260
+ // manifest, requestImageForSize must not create/swap a larger canvas.
261
+ globalThis.__distarkSvgRuntimeUpgradeLocked = true;
262
+ const lockedBefore = prepImages[prepPath];
263
+ const lockedReturned = prepLoader.requestImageForSize(prepPath, 500, 500, prepImages);
264
+ await new Promise(resolve => setTimeout(resolve, 20));
265
+ assert(lockedReturned === lockedBefore, 'locked requestImageForSize returns existing canvas');
266
+ assert(prepImages[prepPath] === lockedBefore, 'locked requestImageForSize does not replace loadedImages entry');
267
+ assert(prepImages[prepPath]?.width === 60 && prepImages[prepPath]?.height === 80, 'locked requestImageForSize does not resize canvas');
268
+ globalThis.__distarkSvgRuntimeUpgradeLocked = false;
269
+ // ── loadImageWithCache: public alias for loadImage ──────────────────────────
270
+ // cacheVariationImages in RigCharacterControl calls loadImageWithCache().
271
+ // That method does not exist on ImageLoader/P5ImageLoader, so the pre-caching
272
+ // silently fails (TypeError). Adding it as a public alias makes pre-caching work.
273
+ const aliasLoader = new ImageLoader('http://example.test');
274
+ assert(typeof aliasLoader.loadImageWithCache === 'function', 'loadImageWithCache is a public method on ImageLoader (alias for loadImage)');
275
+ let aliasCalled = false;
276
+ aliasLoader.loadImageWithCache(prepPath, prepPath, (_key, _img) => { aliasCalled = true; }, (_key, _err) => { });
277
+ // Allow async loadImage to complete
278
+ await new Promise(resolve => setTimeout(resolve, 50));
279
+ assert(aliasCalled, 'loadImageWithCache triggers loadImage and fires onLoad callback');
280
+ assert(aliasLoader.getCachedImage(prepPath) !== undefined, 'loadImageWithCache populates imageCache');
91
281
  printSummary();
92
282
  }
93
283
  main().catch((error) => {
@@ -1 +1 @@
1
- {"version":3,"file":"test-svg-rerasterization.js","sourceRoot":"","sources":["../../tests/test-svg-rerasterization.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"test-svg-rerasterization.js","sourceRoot":"","sources":["../../tests/test-svg-rerasterization.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Integration test: Teen Tank SVG rig
3
+ *
4
+ * Verifies:
5
+ * 1. All expected body parts load with images
6
+ * 2. Every part's rendered bounding box stays within the canvas (no clipping)
7
+ * 3. Every part's rendered dimensions match dimensionValues (no squash/stretch)
8
+ * 4. Every joint-connected part pair has overlapping or adjacent bounding boxes
9
+ * (ensures joints connect visually, not floating apart)
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=test-teen-tank-rig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-teen-tank-rig.d.ts","sourceRoot":"","sources":["../../tests/test-teen-tank-rig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Integration test: Teen Tank SVG rig
3
+ *
4
+ * Verifies:
5
+ * 1. All expected body parts load with images
6
+ * 2. Every part's rendered bounding box stays within the canvas (no clipping)
7
+ * 3. Every part's rendered dimensions match dimensionValues (no squash/stretch)
8
+ * 4. Every joint-connected part pair has overlapping or adjacent bounding boxes
9
+ * (ensures joints connect visually, not floating apart)
10
+ */
11
+ import { readFile } from 'fs/promises';
12
+ import { SkiaRenderer } from '../modules/adapters/skiaRenderer.js';
13
+ import { assert, printSummary } from './helpers.js';
14
+ // The parts we expect to be present and have loaded images
15
+ const EXPECTED_BODY_PARTS = [
16
+ 'head',
17
+ 'torso',
18
+ 'leftUpperArm', 'leftForearm', 'leftHand',
19
+ 'rightUpperArm', 'rightForearm', 'rightHand',
20
+ 'leftThigh', 'leftLeg',
21
+ 'rightThigh', 'rightLeg',
22
+ ];
23
+ // Joint connections: [parent, child] — their bounding boxes must touch or overlap
24
+ const JOINT_CONNECTIONS = [
25
+ ['torso', 'head'],
26
+ ['torso', 'leftUpperArm'],
27
+ ['leftUpperArm', 'leftForearm'],
28
+ ['leftForearm', 'leftHand'],
29
+ ['torso', 'rightUpperArm'],
30
+ ['rightUpperArm', 'rightForearm'],
31
+ ['rightForearm', 'rightHand'],
32
+ ['torso', 'leftThigh'],
33
+ ['leftThigh', 'leftLeg'],
34
+ ['torso', 'rightThigh'],
35
+ ['rightThigh', 'rightLeg'],
36
+ ];
37
+ // Tolerance in pixels for joint proximity check (parts can be slightly apart due to anchor math)
38
+ const JOINT_PROXIMITY_PX = 80;
39
+ // Tolerance ratio: rendered dimension must be within this fraction of the declared dimension
40
+ const DIMENSION_TOLERANCE = 0.02; // 2%
41
+ function getBounds(obj) {
42
+ const left = obj.x - obj.width * obj.anchorX;
43
+ const top = obj.y - obj.height * obj.anchorY;
44
+ return { left, top, right: left + obj.width, bottom: top + obj.height };
45
+ }
46
+ function boxesTouching(a, b, tolerance) {
47
+ return (a.right + tolerance >= b.left &&
48
+ b.right + tolerance >= a.left &&
49
+ a.bottom + tolerance >= b.top &&
50
+ b.bottom + tolerance >= a.top);
51
+ }
52
+ async function main() {
53
+ console.log('\n=== Teen Tank SVG Rig Integration Tests ===\n');
54
+ const rigPath = 'C:\\Users\\kirag\\Downloads\\teen_tank_Svg.json';
55
+ const rigData = JSON.parse(await readFile(rigPath, 'utf-8'));
56
+ const dimensions = rigData.dimensionValues || {};
57
+ const origLog = console.log;
58
+ console.log = (...a) => process.stderr.write(a.join(' ') + '\n');
59
+ const renderer = new SkiaRenderer();
60
+ await renderer.loadImages(rigData);
61
+ const canvasWidth = 1200;
62
+ const canvasHeight = 2000;
63
+ const renderData = renderer.compute(rigData, {
64
+ canvasWidth,
65
+ canvasHeight,
66
+ });
67
+ console.log = origLog;
68
+ const objects = renderData.objects;
69
+ const byName = new Map(objects.map(o => [o.name, o]));
70
+ // ── 1. All expected parts loaded with images ───────────────────────────────
71
+ console.log('--- Part presence & image loading ---');
72
+ for (const part of EXPECTED_BODY_PARTS) {
73
+ const obj = byName.get(part);
74
+ assert(!!obj, `Part '${part}' is present in render data`);
75
+ assert(!!obj?.imageData, `Part '${part}' has a loaded image`);
76
+ }
77
+ // ── 2. No part clips outside canvas bounds ─────────────────────────────────
78
+ console.log('\n--- Canvas bounds (no clipping) ---');
79
+ for (const part of EXPECTED_BODY_PARTS) {
80
+ const obj = byName.get(part);
81
+ if (!obj)
82
+ continue;
83
+ const b = getBounds(obj);
84
+ const withinCanvas = b.left >= -1 &&
85
+ b.top >= -1 &&
86
+ b.right <= canvasWidth + 1 &&
87
+ b.bottom <= canvasHeight + 1;
88
+ assert(withinCanvas, `Part '${part}' stays within canvas (left=${Math.round(b.left)} top=${Math.round(b.top)} right=${Math.round(b.right)} bottom=${Math.round(b.bottom)})`);
89
+ }
90
+ // ── 3. Rendered dimensions match dimensionValues ───────────────────────────
91
+ console.log('\n--- Rendered dimensions match dimensionValues ---');
92
+ for (const part of EXPECTED_BODY_PARTS) {
93
+ const obj = byName.get(part);
94
+ const decl = dimensions[part];
95
+ if (!obj || !decl)
96
+ continue;
97
+ const wDiff = Math.abs(obj.width - decl.width) / decl.width;
98
+ const hDiff = Math.abs(obj.height - decl.height) / decl.height;
99
+ assert(wDiff <= DIMENSION_TOLERANCE, `Part '${part}' width ${Math.round(obj.width)} ≈ declared ${decl.width} (diff ${(wDiff * 100).toFixed(1)}%)`);
100
+ assert(hDiff <= DIMENSION_TOLERANCE, `Part '${part}' height ${Math.round(obj.height)} ≈ declared ${decl.height} (diff ${(hDiff * 100).toFixed(1)}%)`);
101
+ }
102
+ // ── 4. Joint connections — parts must touch or nearly overlap ──────────────
103
+ console.log('\n--- Joint connectivity ---');
104
+ for (const [parentName, childName] of JOINT_CONNECTIONS) {
105
+ const parent = byName.get(parentName);
106
+ const child = byName.get(childName);
107
+ if (!parent || !child) {
108
+ assert(false, `Cannot check joint ${parentName}→${childName}: part missing`);
109
+ continue;
110
+ }
111
+ const pb = getBounds(parent);
112
+ const cb = getBounds(child);
113
+ const touching = boxesTouching(pb, cb, JOINT_PROXIMITY_PX);
114
+ assert(touching, `Joint ${parentName}→${childName} bounding boxes are adjacent/overlapping (within ${JOINT_PROXIMITY_PX}px)`);
115
+ }
116
+ printSummary();
117
+ }
118
+ main().catch(err => {
119
+ console.error(err);
120
+ process.exit(1);
121
+ });
122
+ //# sourceMappingURL=test-teen-tank-rig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-teen-tank-rig.js","sourceRoot":"","sources":["../../tests/test-teen-tank-rig.ts"],"names":[],"mappings":"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"}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/dist/types.d.ts CHANGED
File without changes
File without changes
package/dist/types.js CHANGED
File without changes
package/dist/types.js.map CHANGED
File without changes