@wenathlan/cadria 2.0.94 → 2.0.117
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 +33 -1
- package/docs/.gitkeep +0 -0
- package/package.json +25 -2
- package/tests/.gitkeep +0 -0
- package/tests/audiodecode.test.ts +406 -0
- package/tests/audiofeatures.test.ts +271 -0
- package/tests/audiopipeline.test.ts +193 -0
- package/tests/audiorhythm.test.ts +172 -0
- package/tests/audiospectrum.test.ts +257 -0
- package/tests/audiostructure.test.ts +292 -0
- package/tests/audiotimbre.test.ts +270 -0
- package/tests/audiotonality.test.ts +289 -0
- package/tests/blendmodes.test.ts +31 -4
- package/tests/colorconvert.test.ts +3 -4
- package/tests/easecurves.test.ts +3 -4
- package/tests/family.test.ts +73 -0
- package/tests/familyurl.test.ts +14 -5
- package/tests/gateway.test.ts +373 -0
- package/tests/generation.test.ts +228 -0
- package/tests/imageexport.test.ts +443 -0
- package/tests/imageproject.test.ts +372 -0
- package/tests/imagerender.test.ts +291 -0
- package/tests/imagerenderpaint.test.ts +119 -0
- package/tests/imagestyles.test.ts +347 -0
- package/tests/masks.test.ts +3 -3
- package/tests/matiz.test.ts +8 -5
- package/tests/mediaprobe.test.ts +66 -22
- package/tests/onboardingstate.test.ts +73 -0
- package/tests/shadercomp.test.ts +120 -0
- package/tests/synthcomposition.test.ts +257 -0
- package/tests/synthmotion.test.ts +372 -0
- package/tests/synthpalette.test.ts +247 -0
- package/tests/synthtexture.test.ts +256 -0
- package/tests/timelineedit.test.ts +3 -4
- package/tests/timelineview.test.ts +248 -0
- package/tests/timeticks.test.ts +4 -4
- package/tests/transcodeplan.test.ts +19 -8
- package/tests/versawaseanimate.test.ts +106 -0
- package/tests/versawasescheduler.test.ts +96 -0
- package/tests/versawaseslices.test.ts +75 -0
- package/tests/visualize.test.ts +6 -6
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// # versawaseanimate.test — the engine's animation sampler, runnable with vitest:
|
|
2
|
+
// pnpm vitest run tests/versawaseanimate.test.ts
|
|
3
|
+
// Every test watches the promise the sampler makes: tracks resolve by target,
|
|
4
|
+
// defaults hold for untouched channels, sampling lerps inside keys, the scene
|
|
5
|
+
// span wraps long plays, and the world transform accumulates the parent chain
|
|
6
|
+
// without hanging on a cyclic graph.
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { describe, it } from "vitest";
|
|
9
|
+
import {
|
|
10
|
+
sampleNode,
|
|
11
|
+
sampleSceneAt,
|
|
12
|
+
tracksByTarget,
|
|
13
|
+
worldTransform,
|
|
14
|
+
wrapSeconds,
|
|
15
|
+
} from "../versawaseanimate.ts";
|
|
16
|
+
import type { Scene3d } from "../versawase.ts";
|
|
17
|
+
|
|
18
|
+
const SCENE: Scene3d = {
|
|
19
|
+
id: "scene-arm",
|
|
20
|
+
name: "arm rig",
|
|
21
|
+
fps: 30,
|
|
22
|
+
nodes: [
|
|
23
|
+
{ id: "arm", kind: "group", name: "arm", parent: "" },
|
|
24
|
+
{ id: "hand", kind: "mesh", name: "hand", parent: "arm" },
|
|
25
|
+
],
|
|
26
|
+
tracks: [
|
|
27
|
+
{ id: "pos-x", target: "arm", channel: "position", keys: [0, 2], values: [0, 10], loop: true },
|
|
28
|
+
{ id: "scl-x", target: "hand", channel: "scale", keys: [0, 1], values: [1, 2], loop: false },
|
|
29
|
+
{ id: "opa", target: "hand", channel: "opacity", keys: [0, 1], values: [1, 0], loop: false },
|
|
30
|
+
{ id: "rot-y", target: "hand", channel: "rotation", keys: [0, 4], values: [0, 90], loop: false },
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
describe("versawaseanimate", () => {
|
|
35
|
+
it("collects the tracks that target one node", () => {
|
|
36
|
+
const tracks = tracksByTarget(SCENE, "hand");
|
|
37
|
+
assert.deepEqual(tracks.map((track) => track.id), ["scl-x", "opa", "rot-y"]);
|
|
38
|
+
assert.equal(tracksByTarget(SCENE, "ghost").length, 0);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("keeps untouched channels on their defaults", () => {
|
|
42
|
+
const bare = { id: "bare", kind: "mesh" as const, name: "bare", parent: "" };
|
|
43
|
+
const snapshot = sampleNode({ ...SCENE, tracks: [] }, bare, 1);
|
|
44
|
+
assert.deepEqual(snapshot.position, { x: 0, y: 0, z: 0 });
|
|
45
|
+
assert.deepEqual(snapshot.scale, { x: 1, y: 1, z: 1 });
|
|
46
|
+
assert.equal(snapshot.opacity, 1);
|
|
47
|
+
assert.equal(snapshot.morph, 0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("lerps inside the keys and rides the axis suffix", () => {
|
|
51
|
+
const arm = SCENE.nodes[0];
|
|
52
|
+
const snapshot = sampleNode(SCENE, arm, 1);
|
|
53
|
+
assert.equal(snapshot.position.x, 5);
|
|
54
|
+
assert.equal(snapshot.position.y, 0);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("samples the whole scene at one instant", () => {
|
|
58
|
+
const snapshots = sampleSceneAt(SCENE, 1);
|
|
59
|
+
assert.equal(snapshots.size, 2);
|
|
60
|
+
assert.equal(snapshots.get("arm")?.position.x, 5);
|
|
61
|
+
assert.equal(snapshots.get("hand")?.scale.x, 2);
|
|
62
|
+
assert.equal(snapshots.get("hand")?.opacity, 0);
|
|
63
|
+
assert.equal(snapshots.get("hand")?.rotation.y, 22.5);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("wraps long plays into the authored span", () => {
|
|
67
|
+
assert.equal(wrapSeconds(1, 4), 1);
|
|
68
|
+
assert.equal(wrapSeconds(6, 4), 2);
|
|
69
|
+
assert.equal(wrapSeconds(-1, 4), 0);
|
|
70
|
+
assert.equal(wrapSeconds(6, 0), 6);
|
|
71
|
+
const snapshots = sampleSceneAt(SCENE, 6);
|
|
72
|
+
// span is 4 (the rot-y keys); t=6 rides t=2, where the looping arm
|
|
73
|
+
// position returns to its first key value per the trackValueAt contract.
|
|
74
|
+
assert.equal(snapshots.get("arm")?.position.x, 0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("accumulates the parent chain into the world transform", () => {
|
|
78
|
+
const world = worldTransform(SCENE, "hand", 1);
|
|
79
|
+
assert.equal(world.position.x, 5);
|
|
80
|
+
assert.equal(world.scale.x, 2);
|
|
81
|
+
assert.equal(world.rotation.y, 22.5);
|
|
82
|
+
const root = worldTransform(SCENE, "arm", 1);
|
|
83
|
+
assert.equal(root.position.x, 5);
|
|
84
|
+
assert.equal(root.scale.x, 1);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("cuts a cyclic parent graph at the first revisit", () => {
|
|
88
|
+
const cyclic: Scene3d = {
|
|
89
|
+
...SCENE,
|
|
90
|
+
nodes: [
|
|
91
|
+
{ id: "arm", kind: "group", name: "arm", parent: "hand" },
|
|
92
|
+
{ id: "hand", kind: "mesh", name: "hand", parent: "arm" },
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
const world = worldTransform(cyclic, "arm", 1);
|
|
96
|
+
assert.equal(world.scale.x, 2);
|
|
97
|
+
assert.equal(world.position.x, 5);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("answers an empty scene with an empty snapshot map", () => {
|
|
101
|
+
const empty: Scene3d = { id: "empty", name: "empty", fps: 30, nodes: [], tracks: [] };
|
|
102
|
+
assert.equal(sampleSceneAt(empty, 5).size, 0);
|
|
103
|
+
const world = worldTransform(empty, "ghost", 5);
|
|
104
|
+
assert.deepEqual(world.scale, { x: 1, y: 1, z: 1 });
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// # versawasescheduler.test — the engine's render-job scheduler, runnable with
|
|
2
|
+
// vitest: pnpm vitest run tests/versawasescheduler.test.ts
|
|
3
|
+
// Every test watches the promise the scheduler makes: the plan skips non-queued
|
|
4
|
+
// jobs, lands every job on the least-loaded slot with ties riding the lowest
|
|
5
|
+
// index, counts 3d frames through the renderJobFrames primitive, and the status
|
|
6
|
+
// machine answers null on every illegal transition.
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { describe, it } from "vitest";
|
|
9
|
+
import {
|
|
10
|
+
completeJob,
|
|
11
|
+
failJob,
|
|
12
|
+
planSchedule,
|
|
13
|
+
queueCensus,
|
|
14
|
+
slotLoads,
|
|
15
|
+
startJob,
|
|
16
|
+
} from "../versawasescheduler.ts";
|
|
17
|
+
import type { RenderJob } from "../versawase.ts";
|
|
18
|
+
|
|
19
|
+
const job = (id: string, frames: number, status: RenderJob["status"] = "queued"): RenderJob => ({
|
|
20
|
+
id,
|
|
21
|
+
kind: "3d",
|
|
22
|
+
status,
|
|
23
|
+
frameFrom: 0,
|
|
24
|
+
frameTo: frames / 30,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const imageJob = (id: string, status: RenderJob["status"] = "queued"): RenderJob => ({
|
|
28
|
+
id,
|
|
29
|
+
kind: "image",
|
|
30
|
+
status,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("versawasescheduler", () => {
|
|
34
|
+
it("plans queued jobs onto the least-loaded slot, ties ride the lowest index", () => {
|
|
35
|
+
const plan = planSchedule([job("a", 30), job("b", 30), job("c", 30)], 30, 2);
|
|
36
|
+
assert.equal(plan.length, 3);
|
|
37
|
+
assert.deepEqual(plan.map((entry) => entry.slot), [0, 1, 0]);
|
|
38
|
+
assert.deepEqual(plan.map((entry) => entry.order), [1, 1, 2]);
|
|
39
|
+
assert.equal(plan[2].startLoad, 30);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("counts frames through the renderJobFrames primitive", () => {
|
|
43
|
+
const plan = planSchedule([job("long", 90)], 30, 1);
|
|
44
|
+
assert.equal(plan[0].frames, 90);
|
|
45
|
+
const image = planSchedule([imageJob("still")], 30, 1);
|
|
46
|
+
assert.equal(image[0].frames, 1);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("skips jobs that already left the queue", () => {
|
|
50
|
+
const plan = planSchedule(
|
|
51
|
+
[job("done", 30, "ready"), job("running", 30, "rendering"), job("broken", 30, "failed"), imageJob("next")],
|
|
52
|
+
30,
|
|
53
|
+
2,
|
|
54
|
+
);
|
|
55
|
+
assert.equal(plan.length, 1);
|
|
56
|
+
assert.equal(plan[0].job.id, "next");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("never answers an empty plan for an empty queue and guards the slot count", () => {
|
|
60
|
+
assert.equal(planSchedule([], 30, 4).length, 0);
|
|
61
|
+
const plan = planSchedule([imageJob("only")], 30, 0);
|
|
62
|
+
assert.equal(plan[0].slot, 0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("sums the slot loads after planning", () => {
|
|
66
|
+
const entries = planSchedule([job("a", 60), job("b", 30)], 30, 2);
|
|
67
|
+
assert.deepEqual(slotLoads(entries, 2), [60, 30]);
|
|
68
|
+
assert.deepEqual(slotLoads([], 3), [0, 0, 0]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("rides the pure status machine and refuses illegal transitions", () => {
|
|
72
|
+
const queued = imageJob("q");
|
|
73
|
+
const running = startJob(queued);
|
|
74
|
+
assert.equal(running?.status, "rendering");
|
|
75
|
+
assert.equal(running?.tone, "info");
|
|
76
|
+
assert.equal(completeJob(queued), null);
|
|
77
|
+
const ready = completeJob(running as RenderJob);
|
|
78
|
+
assert.equal(ready?.status, "ready");
|
|
79
|
+
assert.equal(ready?.tone, "success");
|
|
80
|
+
assert.equal(startJob(ready as RenderJob), null);
|
|
81
|
+
const failed = failJob(running as RenderJob);
|
|
82
|
+
assert.equal(failed?.status, "failed");
|
|
83
|
+
assert.equal(failJob(ready as RenderJob), null);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("censuses the queue in one pass", () => {
|
|
87
|
+
const census = queueCensus([
|
|
88
|
+
imageJob("a"),
|
|
89
|
+
imageJob("b"),
|
|
90
|
+
job("c", 30, "rendering"),
|
|
91
|
+
job("d", 30, "ready"),
|
|
92
|
+
job("e", 30, "failed"),
|
|
93
|
+
]);
|
|
94
|
+
assert.deepEqual(census, { queued: 2, rendering: 1, ready: 1, failed: 1 });
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// # versawaseslices.test — the sliced-render planner (F-CAD-027), runnable with
|
|
2
|
+
// vitest: pnpm vitest run tests/versawaseslices.test.ts
|
|
3
|
+
// Every test watches the promise the planner makes: slices land on the frame
|
|
4
|
+
// grid with exact tick windows, the last slice carries the remainder, the
|
|
5
|
+
// preview seek rides the same grid, progress weighs frames (never slice count)
|
|
6
|
+
// and the wall-clock estimate pays the slowest worker.
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { describe, it } from "vitest";
|
|
9
|
+
import { estimateWallClock, planSlices, sliceAt, sliceProgress } from "../versawaseslices.ts";
|
|
10
|
+
import { TICKS_PER_SECOND, tickofframe } from "../timeticks.ts";
|
|
11
|
+
|
|
12
|
+
describe("versawaseslices", () => {
|
|
13
|
+
it("cuts 35s at 24fps into four 10s frame-aligned slices", () => {
|
|
14
|
+
const slices = planSlices({ durationSeconds: 35, fps: 24 });
|
|
15
|
+
assert.equal(slices.length, 4);
|
|
16
|
+
assert.deepEqual(slices.map((slice) => slice.frames), [240, 240, 240, 360 - 240]);
|
|
17
|
+
assert.equal(slices[0].fromFrame, 0);
|
|
18
|
+
assert.equal(slices[0].toFrame, 240);
|
|
19
|
+
assert.equal(slices[3].toFrame, 840);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("answers exact tick windows that ride tickofframe", () => {
|
|
23
|
+
const slices = planSlices({ durationSeconds: 10, fps: 24 });
|
|
24
|
+
assert.equal(slices[0].fromTick, 0);
|
|
25
|
+
assert.equal(slices[0].toTick, tickofframe({ num: 24, den: 1 }, 240));
|
|
26
|
+
assert.ok(slices[0].toTick > 0 && slices[0].toTick < TICKS_PER_SECOND * 11);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("carries the remainder in the last slice and guards the max", () => {
|
|
30
|
+
const slices = planSlices({ durationSeconds: 12.5, fps: 30, sliceSeconds: 10 });
|
|
31
|
+
assert.equal(slices.length, 2);
|
|
32
|
+
assert.equal(slices[1].frames, 375 - 300);
|
|
33
|
+
const capped = planSlices({ durationSeconds: 60, fps: 30, maxSlices: 2 });
|
|
34
|
+
assert.equal(capped.length, 2);
|
|
35
|
+
assert.deepEqual(capped.map((slice) => slice.frames), [900, 900]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("maps a preview seek to the slice that owns the instant", () => {
|
|
39
|
+
const slices = planSlices({ durationSeconds: 35, fps: 24 });
|
|
40
|
+
assert.equal(sliceAt(slices, 0)?.index, 0);
|
|
41
|
+
assert.equal(sliceAt(slices, 12)?.index, 1);
|
|
42
|
+
assert.equal(sliceAt(slices, 34.9)?.index, 3);
|
|
43
|
+
assert.equal(sliceAt(slices, 40), undefined);
|
|
44
|
+
assert.equal(sliceAt([], 1), undefined);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("weighs progress by frames, never by slice count", () => {
|
|
48
|
+
const slices = planSlices({ durationSeconds: 35, fps: 24 });
|
|
49
|
+
const total = 840;
|
|
50
|
+
assert.equal(sliceProgress(slices, []), 0);
|
|
51
|
+
assert.ok(Math.abs(sliceProgress(slices, [0]) - 240 / total) < 1e-12);
|
|
52
|
+
assert.equal(sliceProgress(slices, [0, 1, 2, 3]), 1);
|
|
53
|
+
// the short final slice weighs less than a full one
|
|
54
|
+
const shortOnly = sliceProgress(slices, [3]);
|
|
55
|
+
const fullOnly = sliceProgress(slices, [0]);
|
|
56
|
+
assert.ok(shortOnly < fullOnly);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("pays the slowest worker in the honest estimate", () => {
|
|
60
|
+
const slices = planSlices({ durationSeconds: 40, fps: 24 });
|
|
61
|
+
// 4 slices of 240 frames; 2 workers ride 2 slices each — equal lanes.
|
|
62
|
+
assert.equal(estimateWallClock(slices, 0.01, 2), 480 * 0.01);
|
|
63
|
+
// 1 worker carries everything.
|
|
64
|
+
assert.equal(estimateWallClock(slices, 0.01, 1), 960 * 0.01);
|
|
65
|
+
assert.equal(estimateWallClock([], 0.01, 4), 0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("answers empty plans and defaults with engine defaults", () => {
|
|
69
|
+
assert.deepEqual(planSlices({ durationSeconds: 0 }), []);
|
|
70
|
+
assert.deepEqual(planSlices({ durationSeconds: 5, sliceSeconds: 0 }), []);
|
|
71
|
+
const single = planSlices({ durationSeconds: 2 });
|
|
72
|
+
assert.equal(single.length, 1);
|
|
73
|
+
assert.equal(single[0].frames, 48); // 2s at the default 24fps
|
|
74
|
+
});
|
|
75
|
+
});
|
package/tests/visualize.test.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
// # visualize.test — honest unit tests for the visualize library, runnable
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// # visualize.test — honest unit tests for the visualize library, runnable
|
|
2
|
+
// with vitest (no canvas):
|
|
3
|
+
// pnpm vitest run tests/visualize.test.ts
|
|
4
4
|
// The fixture is a synthetic grayscale gradient buffer shaped like ImageData.
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
|
-
import { describe, it } from "
|
|
6
|
+
import { describe, it } from "vitest";
|
|
7
|
+
import { type ImageInput, VisualizeError, visualize } from "../visualize.ts";
|
|
8
|
+
import { materialSummary, vertexColors } from "../visualizematerial.ts";
|
|
7
9
|
import { gridDimensions } from "../visualizemesh.ts";
|
|
8
10
|
import { boundingBox, faceNormal, normalizedScale, vertexNormals } from "../visualizenormals.ts";
|
|
9
|
-
import { materialSummary, vertexColors } from "../visualizematerial.ts";
|
|
10
|
-
import { visualize, VisualizeError, type ImageInput } from "../visualize.ts";
|
|
11
11
|
|
|
12
12
|
/** builds a w×h grayscale horizontal ramp (left black, right white, alpha full). */
|
|
13
13
|
function gradientImage(width: number, height: number): ImageInput {
|