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
package/js/test/variable.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import { Project } from 'hikkaku'
|
|
2
|
-
import {
|
|
3
|
-
add,
|
|
4
|
-
and,
|
|
5
|
-
changeVariableBy,
|
|
6
|
-
divide,
|
|
7
|
-
equals,
|
|
8
|
-
forEach,
|
|
9
|
-
getVariable,
|
|
10
|
-
gt,
|
|
11
|
-
ifElse,
|
|
12
|
-
ifThen,
|
|
13
|
-
join,
|
|
14
|
-
length,
|
|
15
|
-
multiply,
|
|
16
|
-
or,
|
|
17
|
-
repeat,
|
|
18
|
-
setVariableTo,
|
|
19
|
-
whenFlagClicked,
|
|
20
|
-
} from 'hikkaku/blocks'
|
|
21
|
-
import { describe, expect, test } from 'vite-plus/test'
|
|
22
|
-
import {
|
|
23
|
-
createVmFromProject,
|
|
24
|
-
getStageVariable,
|
|
25
|
-
getTargetByName,
|
|
26
|
-
runUntilFinished,
|
|
27
|
-
} from './helpers.ts'
|
|
28
|
-
|
|
29
|
-
describe('js/test variable handling', () => {
|
|
30
|
-
test('executes arithmetic assignment and conditionally branches with variable values', () => {
|
|
31
|
-
const project = new Project()
|
|
32
|
-
const total = project.stage.createVariable('total', 0)
|
|
33
|
-
const multiplier = project.stage.createVariable('multiplier', 0)
|
|
34
|
-
const done = project.stage.createVariable('done', 0)
|
|
35
|
-
|
|
36
|
-
project.stage.run(() => {
|
|
37
|
-
whenFlagClicked(() => {
|
|
38
|
-
setVariableTo(total, 10)
|
|
39
|
-
setVariableTo(multiplier, 2)
|
|
40
|
-
changeVariableBy(total, multiply(multiplier.get(), 3))
|
|
41
|
-
setVariableTo(multiplier, divide(total.get(), 2))
|
|
42
|
-
ifThen(gt(multiplier.get(), 2), () => {
|
|
43
|
-
setVariableTo(total, add(total.get(), 10))
|
|
44
|
-
})
|
|
45
|
-
setVariableTo(done, 1)
|
|
46
|
-
})
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
const vm = createVmFromProject(project)
|
|
50
|
-
vm.greenFlag()
|
|
51
|
-
runUntilFinished(vm)
|
|
52
|
-
|
|
53
|
-
expect(getStageVariable(vm, total.id)).toBe(26)
|
|
54
|
-
expect(getStageVariable(vm, multiplier.id)).toBe(8)
|
|
55
|
-
expect(getStageVariable(vm, done.id)).toBe(1)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
test('uses repeat and forEach to update variables deterministically', () => {
|
|
59
|
-
const project = new Project()
|
|
60
|
-
const running = project.stage.createVariable('running', 0)
|
|
61
|
-
const index = project.stage.createVariable('index', 0)
|
|
62
|
-
|
|
63
|
-
project.stage.run(() => {
|
|
64
|
-
whenFlagClicked(() => {
|
|
65
|
-
repeat(2, () => {
|
|
66
|
-
changeVariableBy(running, 1)
|
|
67
|
-
})
|
|
68
|
-
forEach(index, 4, () => {
|
|
69
|
-
changeVariableBy(running, 1)
|
|
70
|
-
})
|
|
71
|
-
})
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
const vm = createVmFromProject(project)
|
|
75
|
-
vm.greenFlag()
|
|
76
|
-
runUntilFinished(vm)
|
|
77
|
-
|
|
78
|
-
expect(getStageVariable(vm, running.id)).toBe(6)
|
|
79
|
-
expect(getStageVariable(vm, index.id)).toBe(4)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
test('checks branch behavior with logical and numeric/string variable states', () => {
|
|
83
|
-
const project = new Project()
|
|
84
|
-
const total = project.stage.createVariable('total', 0)
|
|
85
|
-
const status = project.stage.createVariable('status', '')
|
|
86
|
-
const statusVerified = project.stage.createVariable('statusVerified', 0)
|
|
87
|
-
|
|
88
|
-
project.stage.run(() => {
|
|
89
|
-
whenFlagClicked(() => {
|
|
90
|
-
setVariableTo(total, 4)
|
|
91
|
-
setVariableTo(status, 'ready')
|
|
92
|
-
ifElse(
|
|
93
|
-
gt(total.get(), 3),
|
|
94
|
-
() => {
|
|
95
|
-
setVariableTo(status, join(status.get(), '!'))
|
|
96
|
-
},
|
|
97
|
-
() => {
|
|
98
|
-
setVariableTo(status, 'low')
|
|
99
|
-
},
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
ifThen(and(equals(status.get(), 'ready!'), gt(total.get(), 3)), () => {
|
|
103
|
-
setVariableTo(statusVerified, 1)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
ifElse(
|
|
107
|
-
and(gt(total.get(), 5), equals(status.get(), 'ready!')),
|
|
108
|
-
() => {
|
|
109
|
-
changeVariableBy(total, 4)
|
|
110
|
-
},
|
|
111
|
-
() => {
|
|
112
|
-
changeVariableBy(total, -1)
|
|
113
|
-
},
|
|
114
|
-
)
|
|
115
|
-
})
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
const vm = createVmFromProject(project)
|
|
119
|
-
vm.greenFlag()
|
|
120
|
-
runUntilFinished(vm)
|
|
121
|
-
|
|
122
|
-
expect(getStageVariable(vm, total.id)).toBe(8)
|
|
123
|
-
expect(getStageVariable(vm, status.id)).toBe('ready!')
|
|
124
|
-
expect(getStageVariable(vm, statusVerified.id)).toBe(1)
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
test('derives a numeric summary from a string variable', () => {
|
|
128
|
-
const project = new Project()
|
|
129
|
-
const label = project.stage.createVariable('label', '')
|
|
130
|
-
const labelLength = project.stage.createVariable('labelLength', 0)
|
|
131
|
-
const isLong = project.stage.createVariable('isLong', 0)
|
|
132
|
-
const status = project.stage.createVariable('status', '')
|
|
133
|
-
|
|
134
|
-
project.stage.run(() => {
|
|
135
|
-
whenFlagClicked(() => {
|
|
136
|
-
setVariableTo(label, join('hello', ', world'))
|
|
137
|
-
setVariableTo(labelLength, length(label.get()))
|
|
138
|
-
ifElse(
|
|
139
|
-
gt(labelLength.get(), 8),
|
|
140
|
-
() => {
|
|
141
|
-
setVariableTo(status, 'long')
|
|
142
|
-
},
|
|
143
|
-
() => {
|
|
144
|
-
setVariableTo(status, 'short')
|
|
145
|
-
},
|
|
146
|
-
)
|
|
147
|
-
ifThen(
|
|
148
|
-
or(equals(status.get(), 'long'), equals(status.get(), 'short')),
|
|
149
|
-
() => {
|
|
150
|
-
setVariableTo(isLong, 1)
|
|
151
|
-
},
|
|
152
|
-
)
|
|
153
|
-
})
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
const vm = createVmFromProject(project)
|
|
157
|
-
vm.greenFlag()
|
|
158
|
-
runUntilFinished(vm)
|
|
159
|
-
|
|
160
|
-
expect(getStageVariable(vm, label.id)).toBe('hello, world')
|
|
161
|
-
expect(getStageVariable(vm, labelLength.id)).toBe(12)
|
|
162
|
-
expect(getStageVariable(vm, status.id)).toBe('long')
|
|
163
|
-
expect(getStageVariable(vm, isLong.id)).toBe(1)
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
test('updates sprite variables and shares values between stage and sprite scripts', () => {
|
|
167
|
-
const project = new Project()
|
|
168
|
-
const stageScore = project.stage.createVariable('stageScore', 0)
|
|
169
|
-
const sprite = project.createSprite('worker')
|
|
170
|
-
const spriteScore = sprite.createVariable('spriteScore', 0)
|
|
171
|
-
|
|
172
|
-
project.stage.run(() => {
|
|
173
|
-
whenFlagClicked(() => {
|
|
174
|
-
setVariableTo(stageScore, 2)
|
|
175
|
-
})
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
sprite.run(() => {
|
|
179
|
-
whenFlagClicked(() => {
|
|
180
|
-
setVariableTo(spriteScore, getVariable(stageScore))
|
|
181
|
-
repeat(2, () => {
|
|
182
|
-
changeVariableBy(spriteScore, getVariable(stageScore))
|
|
183
|
-
})
|
|
184
|
-
ifThen(gt(spriteScore.get(), 5), () => {
|
|
185
|
-
setVariableTo(
|
|
186
|
-
stageScore,
|
|
187
|
-
add(getVariable(stageScore), spriteScore.get()),
|
|
188
|
-
)
|
|
189
|
-
})
|
|
190
|
-
})
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
const vm = createVmFromProject(project)
|
|
194
|
-
vm.greenFlag()
|
|
195
|
-
runUntilFinished(vm)
|
|
196
|
-
|
|
197
|
-
expect(getStageVariable(vm, stageScore.id)).toBe(8)
|
|
198
|
-
expect(getTargetByName(vm, 'worker').variables[spriteScore.id]).toBe(6)
|
|
199
|
-
})
|
|
200
|
-
})
|
package/js/test/warp.test.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { Project } from 'hikkaku'
|
|
2
|
-
import {
|
|
3
|
-
callProcedure,
|
|
4
|
-
defineProcedure,
|
|
5
|
-
procedureLabel,
|
|
6
|
-
repeat,
|
|
7
|
-
say,
|
|
8
|
-
whenFlagClicked,
|
|
9
|
-
} from 'hikkaku/blocks'
|
|
10
|
-
import { describe, expect, it } from 'vite-plus/test'
|
|
11
|
-
import { createHeadlessVM, createProgramModuleFromProject } from '../vm'
|
|
12
|
-
|
|
13
|
-
describe('warp-exit', () => {
|
|
14
|
-
it('should emit warp-exit before finished', () => {
|
|
15
|
-
const project = new Project()
|
|
16
|
-
project.stage.run(() => {
|
|
17
|
-
const fn = defineProcedure(
|
|
18
|
-
[procedureLabel('myfn')],
|
|
19
|
-
() => {
|
|
20
|
-
// produce more blocks
|
|
21
|
-
for (let i = 0; i < 1000; i++) {
|
|
22
|
-
repeat(100, () => {
|
|
23
|
-
say('Hello')
|
|
24
|
-
})
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
true,
|
|
28
|
-
)
|
|
29
|
-
whenFlagClicked(() => {
|
|
30
|
-
repeat(3, () => {
|
|
31
|
-
callProcedure(fn, [])
|
|
32
|
-
})
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
const program = createProgramModuleFromProject({
|
|
36
|
-
projectJson: project.toScratch(),
|
|
37
|
-
assets: {},
|
|
38
|
-
})
|
|
39
|
-
const vm = createHeadlessVM({
|
|
40
|
-
program,
|
|
41
|
-
options: {
|
|
42
|
-
stepTimeoutTicks: 10000,
|
|
43
|
-
turbo: true,
|
|
44
|
-
},
|
|
45
|
-
})
|
|
46
|
-
vm.greenFlag()
|
|
47
|
-
|
|
48
|
-
const reasons: string[] = []
|
|
49
|
-
for (let i = 0; i < 16; i++) {
|
|
50
|
-
const frame = vm.stepFrame()
|
|
51
|
-
reasons.push(frame.stopReason)
|
|
52
|
-
if (frame.stopReason === 'finished') {
|
|
53
|
-
break
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
expect(reasons).toContain('warp-exit')
|
|
57
|
-
expect(reasons[reasons.length - 1]).toBe('finished')
|
|
58
|
-
})
|
|
59
|
-
})
|
package/js/vm/AGENTS.md
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
# js/vm
|
|
2
|
-
|
|
3
|
-
- Manages VM bindings and execution support logic.
|
|
4
|
-
- Maintains runtime/program WASM ABI bridging (`factory.ts`, `program-wasm.ts`); keep ABI checks and payload decoding explicit.
|
|
5
|
-
- Since type guards, normalization, and JSON conversion are prone to spec drift, tests are required.
|
|
6
|
-
- Use shared test data in `../test/test-projects.ts` to reduce duplication.
|
package/js/vm/README.md
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
# VM Usage
|
|
2
|
-
|
|
3
|
-
This folder provides a headless runtime API for running Scratch projects in TypeScript.
|
|
4
|
-
|
|
5
|
-
Exports are available from `moonscratch` (the package entrypoint) and from
|
|
6
|
-
`packages/moonscratch/js/vm` during local development.
|
|
7
|
-
|
|
8
|
-
## Install and import
|
|
9
|
-
|
|
10
|
-
```ts
|
|
11
|
-
import {
|
|
12
|
-
createHeadlessVM,
|
|
13
|
-
createHeadlessVMFromProject,
|
|
14
|
-
createHeadlessVMWithScratchAssets,
|
|
15
|
-
precompileProgramForRuntime,
|
|
16
|
-
createProgramModuleFromProject,
|
|
17
|
-
HeadlessVM,
|
|
18
|
-
} from 'moonscratch';
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
`createVM`, `createVMFromProject`, and `createVMWithScratchAssets` are aliases.
|
|
22
|
-
|
|
23
|
-
## Basic usage
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
import { createHeadlessVM } from 'moonscratch';
|
|
27
|
-
|
|
28
|
-
// Any Scratch 3.0 project JSON object or string.
|
|
29
|
-
const projectJson = {
|
|
30
|
-
meta: { semver: '3.0.0', vm: '0.2.0', agent: 'example' },
|
|
31
|
-
targets: [
|
|
32
|
-
{
|
|
33
|
-
isStage: true,
|
|
34
|
-
name: 'Stage',
|
|
35
|
-
currentCostume: 0,
|
|
36
|
-
variables: {},
|
|
37
|
-
lists: {},
|
|
38
|
-
broadcasts: {},
|
|
39
|
-
costumes: [],
|
|
40
|
-
sounds: [],
|
|
41
|
-
blocks: {},
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const program = createProgramModuleFromProject({ projectJson });
|
|
47
|
-
const vm = createHeadlessVM({
|
|
48
|
-
program,
|
|
49
|
-
options: {
|
|
50
|
-
turbo: true,
|
|
51
|
-
deterministic: true,
|
|
52
|
-
seed: 123,
|
|
53
|
-
compatibility_30tps: false,
|
|
54
|
-
},
|
|
55
|
-
viewerLanguage: 'en',
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
vm.start(); // initialize runtime state (optional in many cases)
|
|
59
|
-
vm.greenFlag(); // press green flag
|
|
60
|
-
vm.setTime(Date.now()); // update runtime clock explicitly
|
|
61
|
-
const frame = vm.stepFrame(); // run until rerender/finished/timeout event
|
|
62
|
-
console.log(frame.activeThreads);
|
|
63
|
-
|
|
64
|
-
// Reuse the compiled program module for many VM instances.
|
|
65
|
-
const vm2 = createHeadlessVM({ program });
|
|
66
|
-
|
|
67
|
-
// Convenience helper: compile + instantiate + create VM in one call.
|
|
68
|
-
const vm3 = createHeadlessVMFromProject({ projectJson });
|
|
69
|
-
|
|
70
|
-
// Optional: precompile runtime artifact once, then create VMs.
|
|
71
|
-
precompileProgramForRuntime({ program });
|
|
72
|
-
const vm4 = createHeadlessVM({ program });
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
- `stepFrame()` advances until one of three events happens.
|
|
76
|
-
- `stopReason: 'rerender' | 'finished' | 'timeout'`.
|
|
77
|
-
- `setTime(nowMs)` updates the runtime clock used by timer/wait behavior.
|
|
78
|
-
|
|
79
|
-
## Input state
|
|
80
|
-
|
|
81
|
-
`HeadlessVM` exposes helpers to update I/O state:
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
vm.setAnswer('Scratch');
|
|
85
|
-
vm.setMouseState({ x: 120, y: 80, isDown: true });
|
|
86
|
-
vm.setKeysDown(['space', 'arrowup']);
|
|
87
|
-
vm.setTouching({ Sprite1: ['_mouse_'] });
|
|
88
|
-
vm.setMouseTargets({ stage: true, targets: ['Sprite1'] });
|
|
89
|
-
vm.setBackdrop('bg2');
|
|
90
|
-
vm.postIO('username', 'alice'); // value as JsonValue
|
|
91
|
-
vm.postIORawJson('loudness', '12');
|
|
92
|
-
vm.broadcast('GO');
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Effects
|
|
96
|
-
|
|
97
|
-
### Pull effects manually
|
|
98
|
-
|
|
99
|
-
```ts
|
|
100
|
-
const effects = vm.takeEffects();
|
|
101
|
-
for (const effect of effects) {
|
|
102
|
-
if (effect.type === 'log') {
|
|
103
|
-
console.log(effect.level, effect.message);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Handle effects with handlers
|
|
109
|
-
|
|
110
|
-
```ts
|
|
111
|
-
await vm.handleEffects({
|
|
112
|
-
async translate({ words }) {
|
|
113
|
-
return words === 'hello' ? 'こんにちは' : null;
|
|
114
|
-
},
|
|
115
|
-
async textToSpeech(effect) {
|
|
116
|
-
await speak(effect.words, effect.voice);
|
|
117
|
-
vm.ackTextToSpeech(effect.waitKey);
|
|
118
|
-
},
|
|
119
|
-
musicNote(effect) {
|
|
120
|
-
playNote(effect.instrument, effect.note, effect.beats);
|
|
121
|
-
},
|
|
122
|
-
async musicDrum(effect) {
|
|
123
|
-
playDrum(effect.drum, effect.beats);
|
|
124
|
-
},
|
|
125
|
-
effect(effect) {
|
|
126
|
-
console.log('effect:', effect);
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
`handleEffects` consumes the current effect queue and dispatches to provided handlers.
|
|
132
|
-
|
|
133
|
-
## Snapshot and rendering
|
|
134
|
-
|
|
135
|
-
```ts
|
|
136
|
-
import { createHeadlessVM, renderWithSVG, renderWithSharp, renderWithWebGL } from 'moonscratch';
|
|
137
|
-
|
|
138
|
-
const snapshot = vm.snapshot();
|
|
139
|
-
const json = vm.snapshotJson();
|
|
140
|
-
const frame = vm.renderFrame();
|
|
141
|
-
const svg = renderWithSVG(frame);
|
|
142
|
-
const png = await renderWithSharp(frame);
|
|
143
|
-
const webgl = renderWithWebGL(frame);
|
|
144
|
-
|
|
145
|
-
// Types:
|
|
146
|
-
// snapshot: VMSnapshot
|
|
147
|
-
// frame: RenderFrame
|
|
148
|
-
// svg: SVG string
|
|
149
|
-
// png: Buffer
|
|
150
|
-
// webgl: { canvas, toImageData(), toImageElement() }
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
## Assets
|
|
154
|
-
|
|
155
|
-
Use `createHeadlessVMWithScratchAssets` to load missing Scratch costume assets
|
|
156
|
-
automatically from CDN.
|
|
157
|
-
|
|
158
|
-
```ts
|
|
159
|
-
const vm = await createHeadlessVMWithScratchAssets({
|
|
160
|
-
projectJson,
|
|
161
|
-
scratchCdnBaseUrl: 'https://cdn.scratch.mit.edu/internalapi/asset',
|
|
162
|
-
fetchAsset: (url) => fetch(url),
|
|
163
|
-
decodeImageBytes: async (bytes) => {
|
|
164
|
-
const image = await decodeToImage(bytes);
|
|
165
|
-
return {
|
|
166
|
-
width: image.width,
|
|
167
|
-
height: image.height,
|
|
168
|
-
rgbaBase64: image.rgbaBase64,
|
|
169
|
-
};
|
|
170
|
-
},
|
|
171
|
-
});
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
If you already have assets in memory, pass them via `assets` (`Record<string, JsonValue>`).
|
|
175
|
-
For missing ids, the API attempts to fetch and cache by `assetId` / `md5ext`.
|
|
176
|
-
|
|
177
|
-
## Utility methods
|
|
178
|
-
|
|
179
|
-
- `vm.raw`: underlying WASM runtime handle.
|
|
180
|
-
- `vm.stopAll()`: stop all running sounds/threads.
|
|
181
|
-
- `vm.setTime(nowMs)`: update VM clock (`sensing_timer`, waits, etc.).
|
|
182
|
-
- `vm.setTranslateResult(words, language, translated)`: seed translate cache.
|
|
183
|
-
- `vm.setTranslateCache(cache)` / `vm.clearTranslateCache()`.
|
package/js/vm/bindings.test.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vite-plus/test'
|
|
2
|
-
|
|
3
|
-
import { moonscratch } from './bindings.ts'
|
|
4
|
-
|
|
5
|
-
describe('moonscratch/js/vm/bindings.ts', () => {
|
|
6
|
-
test('exports generated vm bindings', () => {
|
|
7
|
-
expect(typeof moonscratch.vm_compile_from_json).toBe('function')
|
|
8
|
-
expect(typeof moonscratch.vm_new_from_compiled).toBe('function')
|
|
9
|
-
expect(typeof moonscratch.vm_step_frame).toBe('function')
|
|
10
|
-
expect(typeof moonscratch.vm_set_time).toBe('function')
|
|
11
|
-
expect(typeof moonscratch.vm_render_frame).toBe('function')
|
|
12
|
-
})
|
|
13
|
-
})
|
package/js/vm/bindings.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vite-plus/test'
|
|
2
|
-
import { getStageVariables } from '../test/test-projects.ts'
|
|
3
|
-
import { createHeadlessVM, createProgramModuleFromProject } from './factory.ts'
|
|
4
|
-
import type { ProjectJson } from './types.ts'
|
|
5
|
-
|
|
6
|
-
const COMPARE_OPERATOR_PROJECT: ProjectJson = {
|
|
7
|
-
targets: [
|
|
8
|
-
{
|
|
9
|
-
isStage: true,
|
|
10
|
-
name: 'Stage',
|
|
11
|
-
variables: {
|
|
12
|
-
var_lt_bad: ['lt_bad', 0],
|
|
13
|
-
var_lt_good: ['lt_good', 0],
|
|
14
|
-
var_gt_good: ['gt_good', 0],
|
|
15
|
-
},
|
|
16
|
-
lists: {},
|
|
17
|
-
blocks: {},
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
isStage: false,
|
|
21
|
-
name: 'Sprite1',
|
|
22
|
-
variables: {},
|
|
23
|
-
lists: {},
|
|
24
|
-
blocks: {
|
|
25
|
-
hat: {
|
|
26
|
-
opcode: 'event_whenflagclicked',
|
|
27
|
-
next: 'if_lt_bad',
|
|
28
|
-
parent: null,
|
|
29
|
-
inputs: {},
|
|
30
|
-
fields: {},
|
|
31
|
-
topLevel: true,
|
|
32
|
-
},
|
|
33
|
-
if_lt_bad: {
|
|
34
|
-
opcode: 'control_if',
|
|
35
|
-
next: 'if_lt_good',
|
|
36
|
-
parent: 'hat',
|
|
37
|
-
inputs: {
|
|
38
|
-
CONDITION: [2, 'lt_bad'],
|
|
39
|
-
SUBSTACK: [2, 'set_lt_bad'],
|
|
40
|
-
},
|
|
41
|
-
fields: {},
|
|
42
|
-
topLevel: false,
|
|
43
|
-
},
|
|
44
|
-
lt_bad: {
|
|
45
|
-
opcode: 'operator_lt',
|
|
46
|
-
next: null,
|
|
47
|
-
parent: 'if_lt_bad',
|
|
48
|
-
inputs: {
|
|
49
|
-
OPERAND1: [1, [4, -24]],
|
|
50
|
-
OPERAND2: [1, [4, -85]],
|
|
51
|
-
},
|
|
52
|
-
fields: {},
|
|
53
|
-
topLevel: false,
|
|
54
|
-
},
|
|
55
|
-
set_lt_bad: {
|
|
56
|
-
opcode: 'data_setvariableto',
|
|
57
|
-
next: null,
|
|
58
|
-
parent: 'if_lt_bad',
|
|
59
|
-
inputs: { VALUE: [1, [4, 1]] },
|
|
60
|
-
fields: { VARIABLE: ['lt_bad', 'var_lt_bad'] },
|
|
61
|
-
topLevel: false,
|
|
62
|
-
},
|
|
63
|
-
if_lt_good: {
|
|
64
|
-
opcode: 'control_if',
|
|
65
|
-
next: 'if_gt_good',
|
|
66
|
-
parent: 'if_lt_bad',
|
|
67
|
-
inputs: {
|
|
68
|
-
CONDITION: [2, 'lt_good'],
|
|
69
|
-
SUBSTACK: [2, 'set_lt_good'],
|
|
70
|
-
},
|
|
71
|
-
fields: {},
|
|
72
|
-
topLevel: false,
|
|
73
|
-
},
|
|
74
|
-
lt_good: {
|
|
75
|
-
opcode: 'operator_lt',
|
|
76
|
-
next: null,
|
|
77
|
-
parent: 'if_lt_good',
|
|
78
|
-
inputs: {
|
|
79
|
-
OPERAND1: [1, [4, -85]],
|
|
80
|
-
OPERAND2: [1, [4, -24]],
|
|
81
|
-
},
|
|
82
|
-
fields: {},
|
|
83
|
-
topLevel: false,
|
|
84
|
-
},
|
|
85
|
-
set_lt_good: {
|
|
86
|
-
opcode: 'data_setvariableto',
|
|
87
|
-
next: null,
|
|
88
|
-
parent: 'if_lt_good',
|
|
89
|
-
inputs: { VALUE: [1, [4, 1]] },
|
|
90
|
-
fields: { VARIABLE: ['lt_good', 'var_lt_good'] },
|
|
91
|
-
topLevel: false,
|
|
92
|
-
},
|
|
93
|
-
if_gt_good: {
|
|
94
|
-
opcode: 'control_if',
|
|
95
|
-
next: null,
|
|
96
|
-
parent: 'if_lt_good',
|
|
97
|
-
inputs: {
|
|
98
|
-
CONDITION: [2, 'gt_good'],
|
|
99
|
-
SUBSTACK: [2, 'set_gt_good'],
|
|
100
|
-
},
|
|
101
|
-
fields: {},
|
|
102
|
-
topLevel: false,
|
|
103
|
-
},
|
|
104
|
-
gt_good: {
|
|
105
|
-
opcode: 'operator_gt',
|
|
106
|
-
next: null,
|
|
107
|
-
parent: 'if_gt_good',
|
|
108
|
-
inputs: {
|
|
109
|
-
OPERAND1: [1, [4, -24]],
|
|
110
|
-
OPERAND2: [1, [4, -85]],
|
|
111
|
-
},
|
|
112
|
-
fields: {},
|
|
113
|
-
topLevel: false,
|
|
114
|
-
},
|
|
115
|
-
set_gt_good: {
|
|
116
|
-
opcode: 'data_setvariableto',
|
|
117
|
-
next: null,
|
|
118
|
-
parent: 'if_gt_good',
|
|
119
|
-
inputs: { VALUE: [1, [4, 1]] },
|
|
120
|
-
fields: { VARIABLE: ['gt_good', 'var_gt_good'] },
|
|
121
|
-
topLevel: false,
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
],
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
describe('moonscratch/js/vm compare operators', () => {
|
|
129
|
-
test('compares negative numeric operands as numbers', () => {
|
|
130
|
-
const program = createProgramModuleFromProject({
|
|
131
|
-
projectJson: COMPARE_OPERATOR_PROJECT,
|
|
132
|
-
})
|
|
133
|
-
const vm = createHeadlessVM({
|
|
134
|
-
program,
|
|
135
|
-
initialNowMs: 0,
|
|
136
|
-
})
|
|
137
|
-
vm.greenFlag()
|
|
138
|
-
vm.setTime(16)
|
|
139
|
-
vm.stepFrame()
|
|
140
|
-
|
|
141
|
-
expect(getStageVariables(vm).var_lt_bad).toBe(0)
|
|
142
|
-
expect(getStageVariables(vm).var_lt_good).toBe(1)
|
|
143
|
-
expect(getStageVariables(vm).var_gt_good).toBe(1)
|
|
144
|
-
})
|
|
145
|
-
})
|
package/js/vm/constants.test.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vite-plus/test'
|
|
2
|
-
|
|
3
|
-
import { DEFAULT_FPS, DEFAULT_FRAME_MS, DEFAULT_LANGUAGE } from './constants.ts'
|
|
4
|
-
|
|
5
|
-
describe('moonscratch/js/vm/constants.ts', () => {
|
|
6
|
-
test('exports default constants', () => {
|
|
7
|
-
expect(DEFAULT_LANGUAGE).toBe('en')
|
|
8
|
-
expect(DEFAULT_FPS).toBe(30)
|
|
9
|
-
expect(DEFAULT_FRAME_MS).toBeCloseTo(1000 / 30)
|
|
10
|
-
})
|
|
11
|
-
})
|
package/js/vm/constants.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vite-plus/test'
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
isMusicPlayDrumEffect,
|
|
5
|
-
isMusicPlayNoteEffect,
|
|
6
|
-
isTextToSpeechEffect,
|
|
7
|
-
isTranslateRequestEffect,
|
|
8
|
-
} from './effect-guards.ts'
|
|
9
|
-
|
|
10
|
-
describe('moonscratch/js/vm/effect-guards.ts', () => {
|
|
11
|
-
test('identifies translate_request effect', () => {
|
|
12
|
-
expect(
|
|
13
|
-
isTranslateRequestEffect({
|
|
14
|
-
type: 'translate_request',
|
|
15
|
-
words: 'hello',
|
|
16
|
-
language: 'ja',
|
|
17
|
-
}),
|
|
18
|
-
).toBe(true)
|
|
19
|
-
expect(
|
|
20
|
-
isTranslateRequestEffect({ type: 'translate_request', words: 'hello' }),
|
|
21
|
-
).toBe(false)
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
test('identifies text_to_speech effect', () => {
|
|
25
|
-
expect(
|
|
26
|
-
isTextToSpeechEffect({
|
|
27
|
-
type: 'text_to_speech',
|
|
28
|
-
target: 'Sprite1',
|
|
29
|
-
words: 'hello',
|
|
30
|
-
voice: 'TENOR',
|
|
31
|
-
language: 'ja',
|
|
32
|
-
waitKey: 'text2speech_done_1',
|
|
33
|
-
}),
|
|
34
|
-
).toBe(true)
|
|
35
|
-
expect(
|
|
36
|
-
isTextToSpeechEffect({ type: 'text_to_speech', words: 'hello' }),
|
|
37
|
-
).toBe(false)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
test('identifies music effects', () => {
|
|
41
|
-
expect(
|
|
42
|
-
isMusicPlayNoteEffect({
|
|
43
|
-
type: 'music_play_note',
|
|
44
|
-
target: 'Sprite1',
|
|
45
|
-
note: 60,
|
|
46
|
-
beats: 1,
|
|
47
|
-
instrument: 1,
|
|
48
|
-
tempo: 120,
|
|
49
|
-
}),
|
|
50
|
-
).toBe(true)
|
|
51
|
-
expect(
|
|
52
|
-
isMusicPlayDrumEffect({
|
|
53
|
-
type: 'music_play_drum',
|
|
54
|
-
target: 'Sprite1',
|
|
55
|
-
drum: 1,
|
|
56
|
-
beats: 0.5,
|
|
57
|
-
tempo: 120,
|
|
58
|
-
}),
|
|
59
|
-
).toBe(true)
|
|
60
|
-
expect(
|
|
61
|
-
isMusicPlayDrumEffect({
|
|
62
|
-
type: 'music_play_drum',
|
|
63
|
-
target: 'Sprite1',
|
|
64
|
-
drum: 1,
|
|
65
|
-
}),
|
|
66
|
-
).toBe(false)
|
|
67
|
-
})
|
|
68
|
-
})
|