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.
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/index.d.mts +1173 -0
- package/dist/index.mjs +27135 -0
- package/package.json +6 -1
- package/.agents/skills/moonbit-agent-guide/LICENSE +0 -202
- package/.agents/skills/moonbit-agent-guide/SKILL.mbt.md +0 -1126
- package/.agents/skills/moonbit-agent-guide/SKILL.md +0 -1126
- package/.agents/skills/moonbit-agent-guide/ide.md +0 -116
- package/.agents/skills/moonbit-agent-guide/references/advanced-moonbit-build.md +0 -106
- package/.agents/skills/moonbit-agent-guide/references/moonbit-language-fundamentals.mbt.md +0 -422
- package/.agents/skills/moonbit-agent-guide/references/moonbit-language-fundamentals.md +0 -422
- package/.agents/skills/moonbit-practice/SKILL.md +0 -258
- package/.agents/skills/moonbit-practice/assets/ci.yaml +0 -25
- package/.agents/skills/moonbit-practice/reference/agents.md +0 -1469
- package/.agents/skills/moonbit-practice/reference/configuration.md +0 -228
- package/.agents/skills/moonbit-practice/reference/ffi.md +0 -229
- package/.agents/skills/moonbit-practice/reference/ide.md +0 -189
- package/.agents/skills/moonbit-practice/reference/performance.md +0 -217
- package/.agents/skills/moonbit-practice/reference/refactor.md +0 -154
- package/.agents/skills/moonbit-practice/reference/stdlib.md +0 -351
- package/.agents/skills/moonbit-practice/reference/testing.md +0 -228
- package/.agents/skills/moonbit-refactoring/LICENSE +0 -21
- package/.agents/skills/moonbit-refactoring/SKILL.md +0 -323
- package/.githooks/README.md +0 -23
- package/.githooks/pre-commit +0 -3
- package/.github/workflows/copilot-setup-steps.yml +0 -40
- package/AGENTS.md +0 -91
- package/PLAN.md +0 -64
- package/TODO.md +0 -120
- package/benchmarks/calc.bench.ts +0 -144
- package/benchmarks/draw.bench.ts +0 -215
- package/benchmarks/load.bench.ts +0 -28
- package/benchmarks/render.bench.ts +0 -53
- package/benchmarks/run.bench.ts +0 -8
- package/benchmarks/types.d.ts +0 -15
- package/docs/scratch-vm-specs/eventloop.md +0 -103
- package/docs/scratch-vm-specs/moonscratch-time-separation.md +0 -50
- package/index.html +0 -91
- package/js/AGENTS.md +0 -5
- package/js/a.ts +0 -52
- package/js/assets/AGENTS.md +0 -5
- package/js/assets/base64.test.ts +0 -14
- package/js/assets/base64.ts +0 -21
- package/js/assets/build-asset.test.ts +0 -26
- package/js/assets/build-asset.ts +0 -28
- package/js/assets/create.test.ts +0 -142
- package/js/assets/create.ts +0 -122
- package/js/assets/index.test.ts +0 -15
- package/js/assets/index.ts +0 -2
- package/js/assets/types.ts +0 -26
- package/js/assets/validation.test.ts +0 -34
- package/js/assets/validation.ts +0 -25
- package/js/assets.test.ts +0 -14
- package/js/assets.ts +0 -1
- package/js/index.test.ts +0 -26
- package/js/index.ts +0 -3
- package/js/render/index.test.ts +0 -65
- package/js/render/index.ts +0 -13
- package/js/render/sharp.ts +0 -87
- package/js/render/svg.ts +0 -68
- package/js/render/types.ts +0 -35
- package/js/render/utils.ts +0 -108
- package/js/render/webgl.ts +0 -274
- package/js/sharp-optional.d.ts +0 -16
- package/js/test/helpers.ts +0 -116
- package/js/test/hikkaku-sample.test.ts +0 -37
- package/js/test/rubik-components.input-motion.test.ts +0 -60
- package/js/test/rubik-components.lists.test.ts +0 -49
- package/js/test/rubik-components.operators.test.ts +0 -104
- package/js/test/rubik-components.pen.test.ts +0 -112
- package/js/test/rubik-components.procedures-loops.test.ts +0 -72
- package/js/test/rubik-components.variables-branches.test.ts +0 -57
- package/js/test/rubik-components.visibility-entry.test.ts +0 -31
- package/js/test/test-projects.ts +0 -598
- package/js/test/variable.ts +0 -200
- package/js/test/warp.test.ts +0 -59
- package/js/vm/AGENTS.md +0 -6
- package/js/vm/README.md +0 -183
- package/js/vm/bindings.test.ts +0 -13
- package/js/vm/bindings.ts +0 -5
- package/js/vm/compare-operators.test.ts +0 -145
- package/js/vm/constants.test.ts +0 -11
- package/js/vm/constants.ts +0 -4
- package/js/vm/effect-guards.test.ts +0 -68
- package/js/vm/effect-guards.ts +0 -44
- package/js/vm/factory.test.ts +0 -486
- package/js/vm/factory.ts +0 -615
- package/js/vm/headless-vm.test.ts +0 -131
- package/js/vm/headless-vm.ts +0 -342
- package/js/vm/index.test.ts +0 -28
- package/js/vm/index.ts +0 -5
- package/js/vm/internal-types.ts +0 -32
- package/js/vm/json.test.ts +0 -40
- package/js/vm/json.ts +0 -273
- package/js/vm/normalize.test.ts +0 -48
- package/js/vm/normalize.ts +0 -65
- package/js/vm/options.test.ts +0 -30
- package/js/vm/options.ts +0 -55
- package/js/vm/pen-transparency.test.ts +0 -115
- package/js/vm/program-wasm.ts +0 -322
- package/js/vm/scheduler-render.test.ts +0 -401
- package/js/vm/scratch-assets.test.ts +0 -136
- package/js/vm/scratch-assets.ts +0 -202
- package/js/vm/types.ts +0 -358
- package/js/vm/value-guards.test.ts +0 -25
- package/js/vm/value-guards.ts +0 -18
- package/moon.mod.json +0 -10
- package/scripts/preinstall.ts +0 -4
- package/src/AGENTS.md +0 -6
- package/src/api.mbt +0 -161
- package/src/api_aot_commands.mbt +0 -184
- package/src/api_effects_json.mbt +0 -72
- package/src/api_options.mbt +0 -60
- package/src/api_program_wasm.mbt +0 -1647
- package/src/api_program_wat.mbt +0 -2206
- package/src/api_snapshot_json.mbt +0 -44
- package/src/cmd/AGENTS.md +0 -5
- package/src/cmd/main/AGENTS.md +0 -5
- package/src/cmd/main/main.mbt +0 -29
- package/src/cmd/main/moon.pkg +0 -7
- package/src/cmd/main/pkg.generated.mbti +0 -13
- package/src/json_helpers.mbt +0 -176
- package/src/moon.pkg +0 -65
- package/src/moonscratch.mbt +0 -3
- package/src/moonscratch_wbtest.mbt +0 -40
- package/src/parser_sb3.mbt +0 -890
- package/src/pkg.generated.mbti +0 -479
- package/src/runtime_eval.mbt +0 -2844
- package/src/runtime_exec.mbt +0 -3850
- package/src/runtime_render.mbt +0 -2550
- package/src/runtime_state.mbt +0 -870
- package/src/test/AGENTS.md +0 -3
- package/src/test/projects/AGENTS.md +0 -6
- package/src/test/projects/moon.pkg +0 -4
- package/src/test/projects/moonscratch_compat_test.mbt +0 -642
- package/src/test/projects/moonscratch_core_test.mbt +0 -1332
- package/src/test/projects/moonscratch_runtime_test.mbt +0 -1087
- package/src/test/projects/pkg.generated.mbti +0 -13
- package/src/test/projects/test_support.mbt +0 -35
- package/src/types_effects.mbt +0 -20
- package/src/types_error.mbt +0 -4
- package/src/types_options.mbt +0 -31
- package/src/types_runtime_structs.mbt +0 -254
- package/src/types_vm.mbt +0 -109
- package/tsconfig.json +0 -29
- package/viewer/index.ts +0 -399
- package/viewer/vite.d.ts +0 -1
- package/viewer/worker.ts +0 -161
- 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
|
-
}
|
package/benchmarks/run.bench.ts
DELETED
package/benchmarks/types.d.ts
DELETED
|
@@ -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
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
|
-
}
|
package/js/assets/AGENTS.md
DELETED
package/js/assets/base64.test.ts
DELETED
|
@@ -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
|
-
})
|
package/js/assets/base64.ts
DELETED
|
@@ -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
|
-
})
|
package/js/assets/build-asset.ts
DELETED
|
@@ -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
|
-
}
|
package/js/assets/create.test.ts
DELETED
|
@@ -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
|
-
})
|