spine-rigc 0.33.1 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -578,11 +578,11 @@ is in no field of the skeleton.
578
578
  **And over twelve skeletons nobody here wrote.** The same run ingests every editor
579
579
  export in the fetched example corpus, rebuilds it through the pack beside it and
580
580
  `diff`s the result against the source: **12 of 12, no blockers, 1.000 on every measure
581
- the report carries.** Byte identity is not the claim there and the reason is the input
582
- rather than the round trip — an editor header carries `hash` and `audio`, which the rig
583
- spec has no field for — so the contract is the structural one `diff` measures.
584
- [INGEST.md §2.3](docs/INGEST.md) has the three kinds of difference that remain, with
585
- what each is worth.
581
+ the report carries** — and each rebuild is its export's own text in canonical form,
582
+ apart from the header's `hash` and `spine`: the editor's project hash and the runtime
583
+ version rigc stamps, the two keys the rig spec has no field for by design (`IG83`,
584
+ 12 of 12). [INGEST.md §2.3](docs/INGEST.md) states that pass line and why those two
585
+ are the exceptions.
586
586
 
587
587
  **What it reads is skeleton JSON and nothing else** — no `.spine` project, no binary
588
588
  `.skel`, no atlas, no art. So it never invents, and the things it cannot get out of
package/cli.ts CHANGED
@@ -86,7 +86,8 @@ import {
86
86
  type AtlasRegion,
87
87
  } from './src/atlas.ts';
88
88
  import { parseJsonWithPosition } from './src/json-position.ts';
89
- import { KEY_TIME_EPSILON } from './src/timelines.ts';
89
+ import { PARSER_DEFAULTS, parserReading } from './src/keyorder.ts';
90
+ import { float32Step } from './src/timelines.ts';
90
91
  import { findRung, RUNG_IDS, type RungSkeleton } from './src/ladder.ts';
91
92
  import {
92
93
  DEFAULT_MAX_RESIDUAL,
@@ -623,8 +624,30 @@ function meshFit(m: CompileResult['meshes'][number]): string {
623
624
  // why, in the place the figures stood, and prints no figure.
624
625
  if (m.fitWithheld !== undefined) return ` fit not measured: ${m.fitWithheld}. ${PAGE_GRID_UNLOCATED}`;
625
626
  if (m.coverage === undefined) return '';
626
- const hole = m.holePixels ? `, enclosing ${m.holePixels}px of hole` : '';
627
- return ` covers ${(m.coverage * 100).toFixed(2)}% of the art, reaching ${m.overshoot?.toFixed(2) ?? '?'}px past it${hole}`;
627
+ // A count of the plate's own cells, so on a `scale:` page it is texels and
628
+ // says so rather than borrowing the overshoot's unit beside it (issue #762).
629
+ const hole = m.holePixels ? `, enclosing ${m.holePixels}${m.pageScale === undefined ? 'px' : ' texel(s)'} of hole` : '';
630
+ return ` covers ${(m.coverage * 100).toFixed(2)}% of the art, reaching ${m.overshoot?.toFixed(2) ?? '?'}px past it${meshFitGrid(m)}${hole}`;
631
+ }
632
+
633
+ /**
634
+ * The grid a fit was taken on, when it is not the drawing's own (issue #762).
635
+ *
636
+ * The overshoot is stated in the drawing's pixels on every route — the unit an
637
+ * attachment's size is in — and on a page that declares a `scale:` other than
638
+ * 1 it was measured on the page's texels and divided by that scale. So it
639
+ * carries the coarser grid's step: on `scale: 0.5` a figure moves in steps of
640
+ * 2.00px of the drawing, and it need not equal the figure the page it was
641
+ * packed from reads except where the distance falls on whole texels. Said
642
+ * beside the figure, and nothing at all on a loose part or a page at scale 1,
643
+ * where the line is the one it always was.
644
+ */
645
+ function meshFitGrid(m: CompileResult['meshes'][number]): string {
646
+ if (m.pageScale === undefined) return '';
647
+ return (
648
+ ` (the drawing's pixels, measured on the page's texels at scale: ${m.pageScale} — a texel is ` +
649
+ `${(1 / m.pageScale).toFixed(2)}px of the drawing)`
650
+ );
628
651
  }
629
652
 
630
653
  /**
@@ -739,12 +762,14 @@ function deformKeyName(key: DeformKeyMeasure): string {
739
762
  * The report's is the spec's own `t`; the survey's came back through
740
763
  * `Float32Array`, because that is what `spine-core` reads a timeline's frames
741
764
  * into — a key written `0.62` arrives as `0.6200000047683716`. So the tolerance
742
- * is the compiler's own key-time grid plus one float32 ulp at this magnitude,
743
- * which is narrower than any key spacing the format can hold and wide enough for
744
- * both roundings.
765
+ * is one float32 step at this magnitude (`float32Step`): the compiler emits a
766
+ * key time as a float's name — the spec's own time when it names one, the float
767
+ * below it when it does not (issue #716) — so the loaded float is within one
768
+ * step of the spec's `t` either way, which is narrower than any key spacing the
769
+ * format can hold.
745
770
  */
746
771
  function sameKeyTime(specTime: number, loaded: number): boolean {
747
- return Math.abs(specTime - loaded) <= KEY_TIME_EPSILON + Math.abs(loaded) * 2 ** -23;
772
+ return Math.abs(specTime - loaded) <= float32Step(loaded);
748
773
  }
749
774
 
750
775
  /**
@@ -1159,6 +1184,30 @@ const SCALE_PRODUCT_NOTE =
1159
1184
  * foreign data. There is no honest SKIP either: an absent declaration is not
1160
1185
  * "nothing to measure", it is "no way to know what was meant".
1161
1186
  */
1187
+ /**
1188
+ * An emitted key as the 4.3 parser reads it: every field its kind's
1189
+ * `PARSER_DEFAULTS` row lists and the key leaves out, at the row's value, then
1190
+ * the key's own fields. The emitter leaves a field at its parser default out
1191
+ * (issue #716), so a report that reads the file needs the row to say what the
1192
+ * runtime reads there — and reads it from the one table the emitter used.
1193
+ */
1194
+ function asParsed(kind: string, key: Record<string, unknown>): Record<string, unknown> {
1195
+ const row = PARSER_DEFAULTS[kind];
1196
+ if (row === undefined) return key;
1197
+ const site = { object: key, previous: () => null };
1198
+ const filled: Record<string, unknown> = {};
1199
+ for (const field of Object.keys(row)) {
1200
+ const value = parserReading(row, site, field);
1201
+ if (value !== undefined) filled[field] = value;
1202
+ }
1203
+ return { ...filled, ...key };
1204
+ }
1205
+
1206
+ /** An emitted key's time as the parser reads it — `0` where the key leaves it out. */
1207
+ function keyTimeOf(kind: string, key: Record<string, unknown>): unknown {
1208
+ return asParsed(kind, key).time;
1209
+ }
1210
+
1162
1211
  function scaleProduct(timelineName: string, key: Record<string, unknown>): string {
1163
1212
  if (timelineName !== 'scale') return '';
1164
1213
  const x = key.x;
@@ -2806,7 +2855,11 @@ function cmdExplain(flags: Record<string, string>): void {
2806
2855
  for (const [timelineName, keys] of Object.entries(timelines)) {
2807
2856
  console.log(` ${boneName}.${timelineName} ${keys.length} key(s)${drives}`);
2808
2857
  if (timelineName === 'scale') console.log(` ${SCALE_PRODUCT_NOTE}`);
2809
- for (const key of keys) {
2858
+ for (const emitted of keys) {
2859
+ // Every channel, as the parser reads it: a channel at its parser
2860
+ // default is not in the file (issue #716), and this list shows the
2861
+ // value a reader has to reason about rather than the bytes.
2862
+ const key = asParsed(`bone ${timelineName} key`, emitted);
2810
2863
  const fields = Object.entries(key)
2811
2864
  .filter(([k]) => k !== 'time' && k !== 'curve')
2812
2865
  .map(([k, v]) => `${k}=${String(v)}`)
@@ -2823,7 +2876,8 @@ function cmdExplain(flags: Record<string, string>): void {
2823
2876
  for (const [slotName, timelines] of Object.entries(anim.slots ?? {})) {
2824
2877
  for (const [timelineName, keys] of Object.entries(timelines)) {
2825
2878
  console.log(` ${slotName}.${timelineName} ${keys.length} key(s)`);
2826
- for (const key of keys) {
2879
+ for (const emitted of keys) {
2880
+ const key = asParsed(`slot ${timelineName} key`, emitted);
2827
2881
  const curve = key.curve;
2828
2882
  const shape = Array.isArray(curve)
2829
2883
  ? `bezier[${curve.length}] ${curve.slice(12).join(', ')} <- alpha channel, absolute (t,v)`
@@ -2851,7 +2905,7 @@ function cmdExplain(flags: Record<string, string>): void {
2851
2905
  .map(([k, v]) => `${k}=${String(v)}`)
2852
2906
  .join(' ');
2853
2907
  const curve = Array.isArray(key.curve) ? `bezier[${key.curve.length}]` : key.curve === 'stepped' ? 'stepped' : 'linear';
2854
- console.log(` t=${String(key.time).padEnd(7)} ${(fields || '(all defaults)').padEnd(46)} ${curve}`);
2908
+ console.log(` t=${String(keyTimeOf(`${group} key`, key)).padEnd(7)} ${(fields || '(all defaults)').padEnd(46)} ${curve}`);
2855
2909
  }
2856
2910
  }
2857
2911
  }
@@ -2868,7 +2922,7 @@ function cmdExplain(flags: Record<string, string>): void {
2868
2922
  .map(([k, v]) => `${k}=${String(v)}`)
2869
2923
  .join(' ');
2870
2924
  const curve = Array.isArray(key.curve) ? `bezier[${key.curve.length}]` : key.curve === 'stepped' ? 'stepped' : 'linear';
2871
- console.log(` t=${String(key.time).padEnd(7)} ${(fields || '(all defaults)').padEnd(46)} ${curve}`);
2925
+ console.log(` t=${String(keyTimeOf(`${group} ${timelineName} key`, key)).padEnd(7)} ${(fields || '(all defaults)').padEnd(46)} ${curve}`);
2872
2926
  }
2873
2927
  }
2874
2928
  }
@@ -2882,7 +2936,8 @@ function cmdExplain(flags: Record<string, string>): void {
2882
2936
  for (const [attName, timelines] of Object.entries(attMap)) {
2883
2937
  for (const [timelineName, keys] of Object.entries(timelines)) {
2884
2938
  console.log(` ${skinName}/${slotName}/${attName}.${timelineName} ${keys.length} key(s)`);
2885
- for (const key of keys) {
2939
+ for (const emitted of keys) {
2940
+ const key = asParsed(`attachment ${timelineName} key`, emitted);
2886
2941
  const run = Array.isArray(key.vertices) ? (key.vertices as number[]) : null;
2887
2942
  const offset = typeof key.offset === 'number' ? key.offset : 0;
2888
2943
  const span = run
@@ -2948,7 +3003,7 @@ function cmdExplain(flags: Record<string, string>): void {
2948
3003
  .map((o) => `${o.slot}${o.offset >= 0 ? '+' : ''}${o.offset}`)
2949
3004
  .join(' ')
2950
3005
  : 'back to the setup order';
2951
- console.log(` t=${String(key.time).padEnd(7)} ${offsets}`);
3006
+ console.log(` t=${String(keyTimeOf('drawOrder key', key)).padEnd(7)} ${offsets}`);
2952
3007
  }
2953
3008
  }
2954
3009
  }