moonscratch 0.1.1 → 0.1.2

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 (149) hide show
  1. package/dist/chunk-DQk6qfdC.mjs +18 -0
  2. package/dist/index.d.mts +1173 -0
  3. package/dist/index.mjs +27135 -0
  4. package/package.json +6 -1
  5. package/.agents/skills/moonbit-agent-guide/LICENSE +0 -202
  6. package/.agents/skills/moonbit-agent-guide/SKILL.mbt.md +0 -1126
  7. package/.agents/skills/moonbit-agent-guide/SKILL.md +0 -1126
  8. package/.agents/skills/moonbit-agent-guide/ide.md +0 -116
  9. package/.agents/skills/moonbit-agent-guide/references/advanced-moonbit-build.md +0 -106
  10. package/.agents/skills/moonbit-agent-guide/references/moonbit-language-fundamentals.mbt.md +0 -422
  11. package/.agents/skills/moonbit-agent-guide/references/moonbit-language-fundamentals.md +0 -422
  12. package/.agents/skills/moonbit-practice/SKILL.md +0 -258
  13. package/.agents/skills/moonbit-practice/assets/ci.yaml +0 -25
  14. package/.agents/skills/moonbit-practice/reference/agents.md +0 -1469
  15. package/.agents/skills/moonbit-practice/reference/configuration.md +0 -228
  16. package/.agents/skills/moonbit-practice/reference/ffi.md +0 -229
  17. package/.agents/skills/moonbit-practice/reference/ide.md +0 -189
  18. package/.agents/skills/moonbit-practice/reference/performance.md +0 -217
  19. package/.agents/skills/moonbit-practice/reference/refactor.md +0 -154
  20. package/.agents/skills/moonbit-practice/reference/stdlib.md +0 -351
  21. package/.agents/skills/moonbit-practice/reference/testing.md +0 -228
  22. package/.agents/skills/moonbit-refactoring/LICENSE +0 -21
  23. package/.agents/skills/moonbit-refactoring/SKILL.md +0 -323
  24. package/.githooks/README.md +0 -23
  25. package/.githooks/pre-commit +0 -3
  26. package/.github/workflows/copilot-setup-steps.yml +0 -40
  27. package/AGENTS.md +0 -91
  28. package/PLAN.md +0 -64
  29. package/TODO.md +0 -120
  30. package/benchmarks/calc.bench.ts +0 -144
  31. package/benchmarks/draw.bench.ts +0 -215
  32. package/benchmarks/load.bench.ts +0 -28
  33. package/benchmarks/render.bench.ts +0 -53
  34. package/benchmarks/run.bench.ts +0 -8
  35. package/benchmarks/types.d.ts +0 -15
  36. package/docs/scratch-vm-specs/eventloop.md +0 -103
  37. package/docs/scratch-vm-specs/moonscratch-time-separation.md +0 -50
  38. package/index.html +0 -91
  39. package/js/AGENTS.md +0 -5
  40. package/js/a.ts +0 -52
  41. package/js/assets/AGENTS.md +0 -5
  42. package/js/assets/base64.test.ts +0 -14
  43. package/js/assets/base64.ts +0 -21
  44. package/js/assets/build-asset.test.ts +0 -26
  45. package/js/assets/build-asset.ts +0 -28
  46. package/js/assets/create.test.ts +0 -142
  47. package/js/assets/create.ts +0 -122
  48. package/js/assets/index.test.ts +0 -15
  49. package/js/assets/index.ts +0 -2
  50. package/js/assets/types.ts +0 -26
  51. package/js/assets/validation.test.ts +0 -34
  52. package/js/assets/validation.ts +0 -25
  53. package/js/assets.test.ts +0 -14
  54. package/js/assets.ts +0 -1
  55. package/js/index.test.ts +0 -26
  56. package/js/index.ts +0 -3
  57. package/js/render/index.test.ts +0 -65
  58. package/js/render/index.ts +0 -13
  59. package/js/render/sharp.ts +0 -87
  60. package/js/render/svg.ts +0 -68
  61. package/js/render/types.ts +0 -35
  62. package/js/render/utils.ts +0 -108
  63. package/js/render/webgl.ts +0 -274
  64. package/js/sharp-optional.d.ts +0 -16
  65. package/js/test/helpers.ts +0 -116
  66. package/js/test/hikkaku-sample.test.ts +0 -37
  67. package/js/test/rubik-components.input-motion.test.ts +0 -60
  68. package/js/test/rubik-components.lists.test.ts +0 -49
  69. package/js/test/rubik-components.operators.test.ts +0 -104
  70. package/js/test/rubik-components.pen.test.ts +0 -112
  71. package/js/test/rubik-components.procedures-loops.test.ts +0 -72
  72. package/js/test/rubik-components.variables-branches.test.ts +0 -57
  73. package/js/test/rubik-components.visibility-entry.test.ts +0 -31
  74. package/js/test/test-projects.ts +0 -598
  75. package/js/test/variable.ts +0 -200
  76. package/js/test/warp.test.ts +0 -59
  77. package/js/vm/AGENTS.md +0 -6
  78. package/js/vm/README.md +0 -183
  79. package/js/vm/bindings.test.ts +0 -13
  80. package/js/vm/bindings.ts +0 -5
  81. package/js/vm/compare-operators.test.ts +0 -145
  82. package/js/vm/constants.test.ts +0 -11
  83. package/js/vm/constants.ts +0 -4
  84. package/js/vm/effect-guards.test.ts +0 -68
  85. package/js/vm/effect-guards.ts +0 -44
  86. package/js/vm/factory.test.ts +0 -486
  87. package/js/vm/factory.ts +0 -615
  88. package/js/vm/headless-vm.test.ts +0 -131
  89. package/js/vm/headless-vm.ts +0 -342
  90. package/js/vm/index.test.ts +0 -28
  91. package/js/vm/index.ts +0 -5
  92. package/js/vm/internal-types.ts +0 -32
  93. package/js/vm/json.test.ts +0 -40
  94. package/js/vm/json.ts +0 -273
  95. package/js/vm/normalize.test.ts +0 -48
  96. package/js/vm/normalize.ts +0 -65
  97. package/js/vm/options.test.ts +0 -30
  98. package/js/vm/options.ts +0 -55
  99. package/js/vm/pen-transparency.test.ts +0 -115
  100. package/js/vm/program-wasm.ts +0 -322
  101. package/js/vm/scheduler-render.test.ts +0 -401
  102. package/js/vm/scratch-assets.test.ts +0 -136
  103. package/js/vm/scratch-assets.ts +0 -202
  104. package/js/vm/types.ts +0 -358
  105. package/js/vm/value-guards.test.ts +0 -25
  106. package/js/vm/value-guards.ts +0 -18
  107. package/moon.mod.json +0 -10
  108. package/scripts/preinstall.ts +0 -4
  109. package/src/AGENTS.md +0 -6
  110. package/src/api.mbt +0 -161
  111. package/src/api_aot_commands.mbt +0 -184
  112. package/src/api_effects_json.mbt +0 -72
  113. package/src/api_options.mbt +0 -60
  114. package/src/api_program_wasm.mbt +0 -1647
  115. package/src/api_program_wat.mbt +0 -2206
  116. package/src/api_snapshot_json.mbt +0 -44
  117. package/src/cmd/AGENTS.md +0 -5
  118. package/src/cmd/main/AGENTS.md +0 -5
  119. package/src/cmd/main/main.mbt +0 -29
  120. package/src/cmd/main/moon.pkg +0 -7
  121. package/src/cmd/main/pkg.generated.mbti +0 -13
  122. package/src/json_helpers.mbt +0 -176
  123. package/src/moon.pkg +0 -65
  124. package/src/moonscratch.mbt +0 -3
  125. package/src/moonscratch_wbtest.mbt +0 -40
  126. package/src/parser_sb3.mbt +0 -890
  127. package/src/pkg.generated.mbti +0 -479
  128. package/src/runtime_eval.mbt +0 -2844
  129. package/src/runtime_exec.mbt +0 -3850
  130. package/src/runtime_render.mbt +0 -2550
  131. package/src/runtime_state.mbt +0 -870
  132. package/src/test/AGENTS.md +0 -3
  133. package/src/test/projects/AGENTS.md +0 -6
  134. package/src/test/projects/moon.pkg +0 -4
  135. package/src/test/projects/moonscratch_compat_test.mbt +0 -642
  136. package/src/test/projects/moonscratch_core_test.mbt +0 -1332
  137. package/src/test/projects/moonscratch_runtime_test.mbt +0 -1087
  138. package/src/test/projects/pkg.generated.mbti +0 -13
  139. package/src/test/projects/test_support.mbt +0 -35
  140. package/src/types_effects.mbt +0 -20
  141. package/src/types_error.mbt +0 -4
  142. package/src/types_options.mbt +0 -31
  143. package/src/types_runtime_structs.mbt +0 -254
  144. package/src/types_vm.mbt +0 -109
  145. package/tsconfig.json +0 -29
  146. package/viewer/index.ts +0 -399
  147. package/viewer/vite.d.ts +0 -1
  148. package/viewer/worker.ts +0 -161
  149. package/vite.config.ts +0 -61
@@ -1,53 +0,0 @@
1
- import { Project } from 'hikkaku'
2
- import {
3
- changeXBy,
4
- changeYBy,
5
- clear,
6
- gotoXY,
7
- penDown,
8
- penUp,
9
- repeat,
10
- setY,
11
- whenFlagClicked,
12
- } from 'hikkaku/blocks'
13
- import { bench, run } from 'mitata'
14
- import { createHeadlessVM, createProgramModuleFromProject } from '../js'
15
-
16
- const emptyProject = new Project()
17
- const emptyCompiled = createProgramModuleFromProject({
18
- projectJson: emptyProject.toScratch(),
19
- })
20
- bench('render empty/moonscratch', () => {
21
- const emptyVM = createHeadlessVM({ program: emptyCompiled })
22
- emptyVM.renderFrame()
23
- })
24
-
25
- const filledProject = new Project()
26
- filledProject.createSprite('mysprite').run(() => {
27
- whenFlagClicked(() => {
28
- clear()
29
- gotoXY(-240, -180)
30
- repeat(420, () => {
31
- changeXBy(1)
32
- setY(-180)
33
-
34
- penDown()
35
- repeat(360, () => {
36
- changeYBy(1)
37
- })
38
- penUp()
39
- })
40
- })
41
- })
42
-
43
- const filledCompiled = createProgramModuleFromProject({
44
- projectJson: filledProject.toScratch(),
45
- })
46
- bench('render filled/moonscratch', () => {
47
- const filledVM = createHeadlessVM({ program: filledCompiled })
48
- filledVM.renderFrame()
49
- })
50
-
51
- if (import.meta.main) {
52
- await run()
53
- }
@@ -1,8 +0,0 @@
1
- import { run } from 'mitata'
2
-
3
- import './calc.bench.ts'
4
- import './draw.bench.ts'
5
- import './load.bench.ts'
6
- import './render.bench.ts'
7
-
8
- await run()
@@ -1,15 +0,0 @@
1
- /// <reference types="@turbowarp/types" />
2
-
3
- declare module '@scratch/scratch-vm' {
4
- import type VM from 'scratch-vm'
5
- export { VM }
6
- const VMClass: typeof VM
7
- export default VMClass
8
- }
9
-
10
- declare module '@scratch/scratch-render' {
11
- import type Render from 'scratch-render'
12
- export { Render }
13
- const RenderClass: typeof Render
14
- export default RenderClass
15
- }
@@ -1,103 +0,0 @@
1
- # Scratch VM Event Loop Spec
2
-
3
- This document records how `scratch-vm` controls frame stepping, thread ticks, and time,
4
- and how `moonscratch` mirrors that behavior.
5
-
6
- ## Terminology
7
-
8
- - `frame`
9
- - One call to `Runtime._step()`.
10
- - In browser mode, called by `setInterval` from `Runtime.start()`.
11
- - `tick`
12
- - One inner pass of `Sequencer.stepThreads()` while-loop.
13
- - A tick attempts to run each runnable thread once.
14
- - `ms`
15
- - Millisecond time base used by timer/wait behavior.
16
- - `redrawRequested`
17
- - Runtime flag set by visible operations, used to decide if the current frame
18
- should continue ticking.
19
- - `yield`
20
- - Pause current thread until the next scheduler turn.
21
- - `yieldTick`
22
- - Pause current thread until the next sequencer tick.
23
-
24
- ## Observed Scratch VM Behavior
25
-
26
- ## Frame loop
27
-
28
- - `Runtime.start()` sets `currentStepTime` then schedules `_step()`:
29
- - `THREAD_STEP_INTERVAL = 1000 / 60`
30
- - `THREAD_STEP_INTERVAL_COMPATIBILITY = 1000 / 30`
31
- - Compatibility mode changes the interval; turbo mode does not.
32
-
33
- ## What happens in one frame (`Runtime._step`)
34
-
35
- 1. Clean killed threads.
36
- 2. Start edge-activated hats.
37
- 3. Reset `redrawRequested = false`.
38
- 4. Call `sequencer.stepThreads()`.
39
- 5. Update glows/monitor state.
40
- 6. Draw renderer frame once.
41
-
42
- ## Tick loop (`Sequencer.stepThreads`)
43
-
44
- - `WORK_TIME = 0.75 * runtime.currentStepTime`.
45
- - Continue ticking while:
46
- 1. there are threads,
47
- 2. at least one active thread exists,
48
- 3. elapsed work time is below `WORK_TIME`,
49
- 4. `runtime.turboMode || !runtime.redrawRequested`.
50
- - Each tick:
51
- - Iterate through thread list.
52
- - Skip done threads.
53
- - Run runnable threads (`stepThread`).
54
- - Clean up done threads.
55
-
56
- ## `yieldTick` semantics
57
-
58
- - `util.yieldTick()` sets `thread.status = STATUS_YIELD_TICK`.
59
- - At the first tick of the next `stepThreads` call, this status is cleared and thread resumes.
60
- - This is "next tick", not "after 1 ms".
61
-
62
- ## Relationship: 1 tick, 1 frame, 1 ms
63
-
64
- - `1 frame` is a scheduler/render boundary (`_step`).
65
- - `1 frame` contains `N` ticks, where `N` is dynamic.
66
- - `1 ms` is timer/wait time axis and does not map 1:1 to ticks.
67
- - Therefore:
68
- - frame count controls pacing,
69
- - tick count controls execution throughput,
70
- - ms controls timer predicates.
71
-
72
- ## Turbo Mode (important)
73
-
74
- - Turbo mode does not change frame interval directly.
75
- - Turbo mode changes the tick continuation condition:
76
- - without turbo: redraw request can stop additional ticks in the same frame,
77
- - with turbo: redraw request does not stop same-frame ticking.
78
- - Practical effect: same FPS, more work per frame.
79
-
80
- ## Moonscratch Design Rules
81
-
82
- ## Time model
83
-
84
- - Runtime clock is virtual: `nowMs`.
85
- - Time is updated only via explicit `setTime(nowMs)`.
86
- - `stepFrame` does not advance clock time.
87
-
88
- ## Frame API policy
89
-
90
- - Canonical API:
91
- - `setTime(nowMs)`
92
- - `stepFrame(frameCount = 1)`
93
- - `runFrames(...)`
94
- - `runUntilIdle(...)`
95
- - Recommended viewer loop:
96
- 1. call `setTime(...)` once per RAF/tick,
97
- 2. run one or more `stepFrame(...)` calls within a work budget,
98
- 3. render once after stepping.
99
-
100
- ## Compatibility note
101
-
102
- - `scratch-vm` uses one draw per `Runtime._step()` and uses `redrawRequested` to decide whether to continue stepping in the same frame.
103
- - `moonscratch` should preserve this shape: avoid exposing intermediate script progress by rendering between partial step chunks.
@@ -1,50 +0,0 @@
1
- # Moonscratch Time Separation Notes
2
-
3
- This document summarizes the investigation around `examples/3d-cube` rendering behavior
4
- and the design choice to separate VM time updates from stepping.
5
-
6
- ## Problem observed
7
-
8
- - In `scratch-gui` / `scratch-vm`, the cube appears to render as a single visual update
9
- (or in a gap users do not perceive).
10
- - In the old `moonscratch` viewer loop, users could see pen drawing progress line-by-line.
11
- - This was not primarily a raw performance issue. It was caused by scheduling boundaries
12
- becoming visible.
13
-
14
- ## Key findings
15
-
16
- - Both runtimes are incremental internally.
17
- - The visible difference came from how stepping and rendering were orchestrated:
18
- - `scratch-vm`:
19
- - one `Runtime._step()` per interval,
20
- - `Sequencer.stepThreads()` runs until work budget / redraw constraints,
21
- - renderer draws once at end of `_step`.
22
- - old `moonscratch` viewer:
23
- - stepped in small chunks tied to RAF pacing,
24
- - intermediate VM progress was rendered more directly.
25
-
26
- ## Design change
27
-
28
- - `stepFrame` now executes scheduler work only.
29
- - `setTime(nowMs)` is the only API that moves VM clock time.
30
- - This makes time ownership explicit and lets callers control:
31
- - when timer/wait predicates advance,
32
- - how much execution is done before rendering.
33
-
34
- ## Viewer policy
35
-
36
- Recommended loop:
37
-
38
- 1. `setTime(now)` once per display tick.
39
- 2. Run `stepFrame(1)` repeatedly within a bounded work window.
40
- 3. Render exactly once after stepping.
41
-
42
- This preserves responsiveness while reducing visible intermediate pen progress.
43
-
44
- ## Compatibility and migration notes
45
-
46
- - Breaking API changes:
47
- - removed: `stepFrame(frameCount, frameMs)`
48
- - added/renamed: `setTime(nowMs)` (formerly `setNowMs`)
49
- - removed from `FrameReport`: `nowMs` and frame-duration-derived fields
50
- - Time-dependent scripts (`wait`, `timer`) require caller-driven `setTime` updates.
package/index.html DELETED
@@ -1,91 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>MoonScratch Viewer</title>
7
- <style>
8
- :root {
9
- color-scheme: dark;
10
- font-family: 'Trebuchet MS', 'Segoe UI', 'Hiragino Kaku Gothic ProN',
11
- 'Yu Gothic', 'Meiryo', sans-serif;
12
- }
13
-
14
- body {
15
- margin: 0;
16
- min-height: 100vh;
17
- background:
18
- radial-gradient(circle at 10% 20%, #24344d 0%, transparent 35%),
19
- radial-gradient(circle at 90% 20%, #3d2549 0%, transparent 35%),
20
- linear-gradient(180deg, #111827, #0d1117);
21
- color: #f8fafc;
22
- display: flex;
23
- justify-content: center;
24
- align-items: flex-start;
25
- padding: 20px;
26
- }
27
-
28
- main {
29
- width: min(920px, 100%);
30
- background: rgba(15, 23, 42, 0.75);
31
- border: 1px solid rgba(148, 163, 184, 0.35);
32
- border-radius: 12px;
33
- padding: 20px;
34
- box-shadow:
35
- 0 20px 45px rgba(0, 0, 0, 0.35),
36
- inset 0 1px 0 rgba(255, 255, 255, 0.05);
37
- backdrop-filter: blur(3px);
38
- }
39
-
40
- h1 {
41
- margin: 0 0 12px;
42
- font-size: 1.45rem;
43
- letter-spacing: 0.02em;
44
- }
45
-
46
- .controls {
47
- display: flex;
48
- gap: 12px;
49
- align-items: center;
50
- flex-wrap: wrap;
51
- margin-bottom: 16px;
52
- }
53
-
54
- label {
55
- font-weight: 700;
56
- color: #e2e8f0;
57
- }
58
-
59
- select {
60
- border: 1px solid #334155;
61
- background: #0f172a;
62
- color: #f8fafc;
63
- padding: 8px 10px;
64
- border-radius: 8px;
65
- min-width: 260px;
66
- }
67
-
68
- .status {
69
- margin: 8px 0 14px;
70
- color: #94a3b8;
71
- min-height: 1.4em;
72
- }
73
-
74
- canvas {
75
- width: 100%;
76
- max-width: 100%;
77
- aspect-ratio: 4 / 3;
78
- background: #0f172a;
79
- border: 1px solid rgba(148, 163, 184, 0.45);
80
- border-radius: 8px;
81
- display: block;
82
- image-rendering: pixelated;
83
- margin-top: 12px;
84
- }
85
- </style>
86
- </head>
87
- <body>
88
- <main id="app"></main>
89
- <script src="./viewer/index.ts" type="module"></script>
90
- </body>
91
- </html>
package/js/AGENTS.md DELETED
@@ -1,5 +0,0 @@
1
- # js
2
-
3
- - Public APIs and modules for Bun/TypeScript.
4
- - Prioritize running `bun typecheck` and `bun run test` when making changes.
5
- - Keep boundary types and conversion logic explicit to stay aligned with MoonBit.
package/js/a.ts DELETED
@@ -1,52 +0,0 @@
1
- import {
2
- callProcedure,
3
- defineProcedure,
4
- procedureLabel,
5
- repeat,
6
- say,
7
- whenFlagClicked,
8
- } from '../../hikkaku/src/blocks'
9
- import { Project } from '../../hikkaku/src/index'
10
- import { createHeadlessVM, createProgramModuleFromProject } from '.'
11
-
12
- const project = new Project()
13
- project.stage.run(() => {
14
- const a = defineProcedure(
15
- [procedureLabel('a')],
16
- () => {
17
- repeat(100000, () => {
18
- say('Hello, World!')
19
- })
20
- },
21
- true,
22
- )
23
- whenFlagClicked(() => {
24
- repeat(10, () => {
25
- callProcedure(a, [])
26
- })
27
- })
28
- })
29
-
30
- const program = createProgramModuleFromProject({
31
- projectJson: await Bun.file(
32
- new URL('../../../examples/tesseract/dist/project.json', import.meta.url),
33
- ).json(), //project.toScratch(),
34
- })
35
-
36
- const vm = createHeadlessVM({
37
- program,
38
- options: {
39
- stepTimeoutTicks: 100,
40
- },
41
- })
42
-
43
- vm.greenFlag()
44
-
45
- while (true) {
46
- const report = vm.stepFrame()
47
- console.log('Stopped:', report.stopReason)
48
- if (report.stopReason === 'finished' || report.stopReason === 'warp-exit') {
49
- console.log(report.stopReason)
50
- break
51
- }
52
- }
@@ -1,5 +0,0 @@
1
- # js/assets
2
-
3
- - Manages asset generation, validation, and conversion logic.
4
- - Add corresponding `*.test.ts` files with new features to fix input variation coverage.
5
- - For breaking changes, verify compatibility through the public surface in `index.ts`.
@@ -1,14 +0,0 @@
1
- import { describe, expect, test } from 'vite-plus/test'
2
-
3
- import { encodeBase64 } from './base64.ts'
4
-
5
- describe('moonscratch/js/assets/base64.ts', () => {
6
- test('encodes rgba bytes as base64', () => {
7
- const encoded = encodeBase64(new Uint8Array([0, 255, 0, 255]))
8
- expect(encoded).toBe('AP8A/w==')
9
- })
10
-
11
- test('encodes empty bytes', () => {
12
- expect(encodeBase64(new Uint8Array([]))).toBe('')
13
- })
14
- })
@@ -1,21 +0,0 @@
1
- interface BufferLike {
2
- from(input: Uint8Array): { toString(encoding: string): string }
3
- }
4
-
5
- export const encodeBase64 = (bytes: Uint8Array): string => {
6
- const maybeBuffer = (globalThis as { Buffer?: BufferLike }).Buffer
7
- if (maybeBuffer) {
8
- return maybeBuffer.from(bytes).toString('base64')
9
- }
10
-
11
- const maybeBtoa = (globalThis as { btoa?: (binary: string) => string }).btoa
12
- if (typeof maybeBtoa === 'function') {
13
- let binary = ''
14
- for (let i = 0; i < bytes.length; i += 1) {
15
- binary += String.fromCharCode(bytes[i] ?? 0)
16
- }
17
- return maybeBtoa(binary)
18
- }
19
-
20
- throw new Error('No base64 encoder found in this runtime')
21
- }
@@ -1,26 +0,0 @@
1
- import { describe, expect, test } from 'vite-plus/test'
2
-
3
- import { buildAsset } from './build-asset.ts'
4
-
5
- describe('moonscratch/js/assets/build-asset.ts', () => {
6
- test('builds rgba asset from flat bytes', () => {
7
- const asset = buildAsset(2, 1, [0, 255, 0, 255, 255, 0, 0, 255])
8
- expect(asset).toEqual({
9
- width: 2,
10
- height: 1,
11
- rgbaBase64: 'AP8A//8AAP8=',
12
- })
13
- })
14
-
15
- test('throws when rgba length is shorter than expected', () => {
16
- expect(() => buildAsset(2, 1, [255, 0, 0, 255])).toThrow(
17
- 'rgba length must be at least 8',
18
- )
19
- })
20
-
21
- test('throws when rgba contains invalid byte values', () => {
22
- expect(() => buildAsset(1, 1, [300, 0, 0, 255])).toThrow(
23
- 'rgba[0] must be between 0 and 255',
24
- )
25
- })
26
- })
@@ -1,28 +0,0 @@
1
- import { encodeBase64 } from './base64.ts'
2
- import type { RgbaAsset } from './types.ts'
3
- import { toByte, toPositiveInt } from './validation.ts'
4
-
5
- export const buildAsset = (
6
- width: number,
7
- height: number,
8
- rgba: ArrayLike<number>,
9
- ): RgbaAsset => {
10
- const safeWidth = toPositiveInt(width, 'width')
11
- const safeHeight = toPositiveInt(height, 'height')
12
- const expectedLength = safeWidth * safeHeight * 4
13
-
14
- if (rgba.length < expectedLength) {
15
- throw new Error(`rgba length must be at least ${expectedLength}`)
16
- }
17
-
18
- const bytes = new Uint8Array(expectedLength)
19
- for (let i = 0; i < expectedLength; i += 1) {
20
- bytes[i] = toByte(rgba[i], `rgba[${i}]`)
21
- }
22
-
23
- return {
24
- width: safeWidth,
25
- height: safeHeight,
26
- rgbaBase64: encodeBase64(bytes),
27
- }
28
- }
@@ -1,142 +0,0 @@
1
- import { describe, expect, test, vi } from 'vite-plus/test'
2
-
3
- import {
4
- fromCanvas,
5
- fromImageBytes,
6
- fromImageData,
7
- fromImageFile,
8
- fromRgbaBytes,
9
- fromRgbaMatrix,
10
- } from './create.ts'
11
-
12
- const sharpCalls: unknown[] = []
13
-
14
- vi.mock('sharp', () => ({
15
- default: vi.fn((input?: unknown) => {
16
- sharpCalls.push(input)
17
-
18
- return {
19
- ensureAlpha() {
20
- return this
21
- },
22
- raw() {
23
- return this
24
- },
25
- async toBuffer() {
26
- return {
27
- data: new Uint8Array([0, 255, 0, 255]),
28
- info: {
29
- width: 1,
30
- height: 1,
31
- },
32
- }
33
- },
34
- }
35
- }),
36
- }))
37
-
38
- describe('moonscratch/js/assets/create.ts', () => {
39
- test('builds an asset from image data', () => {
40
- const asset = fromImageData({
41
- width: 1,
42
- height: 1,
43
- data: new Uint8Array([0, 255, 0, 255]),
44
- })
45
-
46
- expect(asset).toEqual({
47
- width: 1,
48
- height: 1,
49
- rgbaBase64: 'AP8A/w==',
50
- })
51
- })
52
-
53
- test('builds an asset from flat rgba bytes', () => {
54
- const asset = fromRgbaBytes(
55
- 2,
56
- 1,
57
- new Uint8Array([0, 255, 0, 255, 255, 0, 0, 255]),
58
- )
59
-
60
- expect(asset).toEqual({
61
- width: 2,
62
- height: 1,
63
- rgbaBase64: 'AP8A//8AAP8=',
64
- })
65
- })
66
-
67
- test('builds an asset from H x W x 4 matrix', () => {
68
- const asset = fromRgbaMatrix([
69
- [
70
- [0, 255, 0, 255],
71
- [255, 0, 0, 255],
72
- ],
73
- [
74
- [0, 0, 255, 255],
75
- [255, 255, 255, 255],
76
- ],
77
- ])
78
-
79
- expect(asset).toEqual({
80
- width: 2,
81
- height: 2,
82
- rgbaBase64: 'AP8A//8AAP8AAP///////w==',
83
- })
84
- })
85
-
86
- test('builds an asset from canvas API', () => {
87
- const getImageData = vi.fn(() => ({
88
- width: 1,
89
- height: 1,
90
- data: new Uint8Array([255, 0, 0, 255]),
91
- }))
92
-
93
- const getContext = vi.fn(() => ({ getImageData }))
94
-
95
- const asset = fromCanvas({
96
- width: 1,
97
- height: 1,
98
- getContext,
99
- })
100
-
101
- expect(getContext).toHaveBeenCalledWith('2d')
102
- expect(getImageData).toHaveBeenCalledWith(0, 0, 1, 1)
103
- expect(asset.rgbaBase64).toBe('/wAA/w==')
104
- })
105
-
106
- test('throws when matrix row widths are inconsistent', () => {
107
- expect(() =>
108
- fromRgbaMatrix([
109
- [
110
- [0, 0, 0, 255],
111
- [255, 255, 255, 255],
112
- ],
113
- [[0, 0, 0, 255]],
114
- ]),
115
- ).toThrow('pixels[1] must contain exactly 2 columns')
116
- })
117
-
118
- test('loads an image asset from bytes via sharp', async () => {
119
- const asset = await fromImageBytes(new Uint8Array([1, 2, 3]))
120
-
121
- expect(sharpCalls).toEqual([new Uint8Array([1, 2, 3])])
122
- expect(asset).toEqual({
123
- width: 1,
124
- height: 1,
125
- rgbaBase64: 'AP8A/w==',
126
- })
127
- })
128
-
129
- test('loads an image asset from file via sharp', async () => {
130
- const asset = await fromImageFile('fixtures/example.png')
131
-
132
- expect(sharpCalls).toEqual([
133
- new Uint8Array([1, 2, 3]),
134
- 'fixtures/example.png',
135
- ])
136
- expect(asset).toEqual({
137
- width: 1,
138
- height: 1,
139
- rgbaBase64: 'AP8A/w==',
140
- })
141
- })
142
- })