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/render/webgl.ts
DELETED
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import type { CanvasLike } from '../assets/types.ts'
|
|
2
|
-
import type {
|
|
3
|
-
RenderFrame,
|
|
4
|
-
RenderImageData,
|
|
5
|
-
RenderWithWebGLOptions,
|
|
6
|
-
RenderWithWebGLResult,
|
|
7
|
-
} from './types.ts'
|
|
8
|
-
import { normalizeRenderFrame } from './utils.ts'
|
|
9
|
-
|
|
10
|
-
const normalizeCanvas = (width: number, height: number): CanvasLike | null => {
|
|
11
|
-
const global = globalThis as {
|
|
12
|
-
OffscreenCanvas?: new (width: number, height: number) => CanvasLike
|
|
13
|
-
document?: { createElement(tag: string): CanvasLike }
|
|
14
|
-
}
|
|
15
|
-
if (global.OffscreenCanvas) {
|
|
16
|
-
return new global.OffscreenCanvas(width, height)
|
|
17
|
-
}
|
|
18
|
-
if (global.document?.createElement) {
|
|
19
|
-
return global.document.createElement('canvas')
|
|
20
|
-
}
|
|
21
|
-
return null
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const createWebGLContext = (canvas: CanvasLike): unknown => {
|
|
25
|
-
const raw = canvas.getContext as (type: string, options?: unknown) => unknown
|
|
26
|
-
return (
|
|
27
|
-
raw('webgl2', { preserveDrawingBuffer: true }) ??
|
|
28
|
-
raw('webgl', { preserveDrawingBuffer: true })
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const isWebGLContext = (
|
|
33
|
-
context: unknown,
|
|
34
|
-
): context is WebGLRenderingContextLike =>
|
|
35
|
-
typeof context === 'object' &&
|
|
36
|
-
context !== null &&
|
|
37
|
-
'createTexture' in context &&
|
|
38
|
-
'createBuffer' in context &&
|
|
39
|
-
'createProgram' in context &&
|
|
40
|
-
'drawArrays' in context
|
|
41
|
-
|
|
42
|
-
type WebGLRenderingContextLike = {
|
|
43
|
-
ARRAY_BUFFER: number
|
|
44
|
-
CLAMP_TO_EDGE: number
|
|
45
|
-
COLOR_BUFFER_BIT: number
|
|
46
|
-
COMPILE_STATUS: number
|
|
47
|
-
FRAGMENT_SHADER: number
|
|
48
|
-
FLOAT: number
|
|
49
|
-
LINK_STATUS: number
|
|
50
|
-
NEAREST: number
|
|
51
|
-
RGBA: number
|
|
52
|
-
STATIC_DRAW: number
|
|
53
|
-
TEXTURE0: number
|
|
54
|
-
TEXTURE_WRAP_S: number
|
|
55
|
-
TEXTURE_WRAP_T: number
|
|
56
|
-
TEXTURE_2D: number
|
|
57
|
-
TEXTURE_MIN_FILTER: number
|
|
58
|
-
TEXTURE_MAG_FILTER: number
|
|
59
|
-
TRIANGLE_STRIP: number
|
|
60
|
-
UNSIGNED_BYTE: number
|
|
61
|
-
VERTEX_SHADER: number
|
|
62
|
-
activeTexture(texture: number): void
|
|
63
|
-
attachShader(program: unknown, shader: unknown): void
|
|
64
|
-
bindBuffer(target: number, buffer: unknown): void
|
|
65
|
-
bindTexture(target: number, texture: unknown): void
|
|
66
|
-
clear(mask: number): void
|
|
67
|
-
clearColor(r: number, g: number, b: number, a: number): void
|
|
68
|
-
compileShader(shader: unknown): void
|
|
69
|
-
createBuffer(): unknown
|
|
70
|
-
createProgram(): unknown
|
|
71
|
-
createShader(type: number): unknown
|
|
72
|
-
createTexture(): unknown
|
|
73
|
-
createFramebuffer?(): unknown
|
|
74
|
-
drawArrays(mode: number, first: number, count: number): void
|
|
75
|
-
enableVertexAttribArray(location: number): void
|
|
76
|
-
bufferData(target: number, data: ArrayBufferView, usage: number): void
|
|
77
|
-
getAttribLocation(program: unknown, name: string): number
|
|
78
|
-
getError?(): number
|
|
79
|
-
getProgramInfoLog(program: unknown): string | null
|
|
80
|
-
getProgramParameter(program: unknown, pname: number): unknown
|
|
81
|
-
getShaderInfoLog(shader: unknown): string | null
|
|
82
|
-
getShaderParameter(shader: unknown, pname: number): unknown
|
|
83
|
-
getUniformLocation(program: unknown, name: string): unknown
|
|
84
|
-
linkProgram(program: unknown): void
|
|
85
|
-
pixelStorei(...args: unknown[]): void
|
|
86
|
-
readPixels(
|
|
87
|
-
x: number,
|
|
88
|
-
y: number,
|
|
89
|
-
width: number,
|
|
90
|
-
height: number,
|
|
91
|
-
format: number,
|
|
92
|
-
type: number,
|
|
93
|
-
pixels: Uint8Array,
|
|
94
|
-
): void
|
|
95
|
-
shaderSource(shader: unknown, source: string): void
|
|
96
|
-
uniform1i(location: unknown, value: number): void
|
|
97
|
-
useProgram(program: unknown): void
|
|
98
|
-
vertexAttribPointer(
|
|
99
|
-
index: number,
|
|
100
|
-
size: number,
|
|
101
|
-
type: number,
|
|
102
|
-
normalized: boolean,
|
|
103
|
-
stride: number,
|
|
104
|
-
offset: number,
|
|
105
|
-
): void
|
|
106
|
-
viewport(x: number, y: number, width: number, height: number): void
|
|
107
|
-
texParameteri(target: number, pname: number, param: number): void
|
|
108
|
-
texImage2D(
|
|
109
|
-
target: number,
|
|
110
|
-
level: number,
|
|
111
|
-
internalformat: number,
|
|
112
|
-
width: number,
|
|
113
|
-
height: number,
|
|
114
|
-
border: number,
|
|
115
|
-
format: number,
|
|
116
|
-
type: number,
|
|
117
|
-
pixels: Uint8Array | null,
|
|
118
|
-
): void
|
|
119
|
-
bindVertexArray?(vertexArray: unknown): void
|
|
120
|
-
createVertexArray?(): unknown
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const createProgram = (gl: WebGLRenderingContextLike): unknown => {
|
|
124
|
-
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
|
|
125
|
-
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
|
|
126
|
-
if (!vertexShader || !fragmentShader) {
|
|
127
|
-
throw new Error('Unable to create WebGL shaders')
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const vertexSource = `
|
|
131
|
-
attribute vec2 a_position;
|
|
132
|
-
attribute vec2 a_tex_coord;
|
|
133
|
-
varying vec2 v_tex_coord;
|
|
134
|
-
void main() {
|
|
135
|
-
v_tex_coord = a_tex_coord;
|
|
136
|
-
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
137
|
-
}`
|
|
138
|
-
const fragmentSource = `
|
|
139
|
-
precision mediump float;
|
|
140
|
-
varying vec2 v_tex_coord;
|
|
141
|
-
uniform sampler2D u_texture;
|
|
142
|
-
void main() {
|
|
143
|
-
gl_FragColor = texture2D(u_texture, v_tex_coord);
|
|
144
|
-
}`
|
|
145
|
-
|
|
146
|
-
gl.shaderSource(vertexShader, vertexSource)
|
|
147
|
-
gl.compileShader(vertexShader)
|
|
148
|
-
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
|
|
149
|
-
throw new Error('Unable to compile WebGL vertex shader')
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
gl.shaderSource(fragmentShader, fragmentSource)
|
|
153
|
-
gl.compileShader(fragmentShader)
|
|
154
|
-
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
|
|
155
|
-
throw new Error('Unable to compile WebGL fragment shader')
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const program = gl.createProgram()
|
|
159
|
-
if (!program) {
|
|
160
|
-
throw new Error('Unable to create WebGL program')
|
|
161
|
-
}
|
|
162
|
-
gl.attachShader(program, vertexShader)
|
|
163
|
-
gl.attachShader(program, fragmentShader)
|
|
164
|
-
gl.linkProgram(program)
|
|
165
|
-
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
166
|
-
const message = gl.getProgramInfoLog(program) ?? 'linking failed'
|
|
167
|
-
throw new Error(`Unable to link WebGL program: ${message}`)
|
|
168
|
-
}
|
|
169
|
-
return program
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export const renderWithWebGL = (
|
|
173
|
-
input: RenderFrame,
|
|
174
|
-
options: RenderWithWebGLOptions = {},
|
|
175
|
-
): RenderWithWebGLResult => {
|
|
176
|
-
const frame = normalizeRenderFrame(input)
|
|
177
|
-
const canvas = options.canvas ?? normalizeCanvas(frame.width, frame.height)
|
|
178
|
-
if (!canvas) {
|
|
179
|
-
throw new Error(
|
|
180
|
-
'renderWithWebGL requires a canvas and an environment that supports canvas',
|
|
181
|
-
)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
canvas.width = frame.width
|
|
185
|
-
canvas.height = frame.height
|
|
186
|
-
const rawContext = createWebGLContext(canvas)
|
|
187
|
-
if (!isWebGLContext(rawContext)) {
|
|
188
|
-
throw new Error('renderWithWebGL requires a webgl-capable canvas')
|
|
189
|
-
}
|
|
190
|
-
const gl = rawContext
|
|
191
|
-
|
|
192
|
-
const program = createProgram(gl)
|
|
193
|
-
gl.useProgram(program)
|
|
194
|
-
|
|
195
|
-
const positions = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1])
|
|
196
|
-
const texCoords = new Float32Array([0, 1, 1, 1, 0, 0, 1, 0])
|
|
197
|
-
const buffer = gl.createBuffer()
|
|
198
|
-
const texBuffer = gl.createBuffer()
|
|
199
|
-
if (!buffer || !texBuffer) {
|
|
200
|
-
throw new Error('Unable to allocate WebGL buffers')
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
|
|
204
|
-
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW)
|
|
205
|
-
const positionLocation = gl.getAttribLocation(program, 'a_position')
|
|
206
|
-
gl.enableVertexAttribArray(positionLocation)
|
|
207
|
-
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0)
|
|
208
|
-
|
|
209
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, texBuffer)
|
|
210
|
-
gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW)
|
|
211
|
-
const texCoordLocation = gl.getAttribLocation(program, 'a_tex_coord')
|
|
212
|
-
gl.enableVertexAttribArray(texCoordLocation)
|
|
213
|
-
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0)
|
|
214
|
-
|
|
215
|
-
const texture = gl.createTexture()
|
|
216
|
-
gl.activeTexture(gl.TEXTURE0)
|
|
217
|
-
gl.bindTexture(gl.TEXTURE_2D, texture)
|
|
218
|
-
gl.pixelStorei(0x9240, true)
|
|
219
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
|
220
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
|
221
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
|
222
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
|
223
|
-
gl.texImage2D(
|
|
224
|
-
gl.TEXTURE_2D,
|
|
225
|
-
0,
|
|
226
|
-
gl.RGBA,
|
|
227
|
-
frame.width,
|
|
228
|
-
frame.height,
|
|
229
|
-
0,
|
|
230
|
-
gl.RGBA,
|
|
231
|
-
gl.UNSIGNED_BYTE,
|
|
232
|
-
frame.pixels,
|
|
233
|
-
)
|
|
234
|
-
const sampler = gl.getUniformLocation(program, 'u_texture')
|
|
235
|
-
gl.uniform1i(sampler, 0)
|
|
236
|
-
|
|
237
|
-
gl.viewport(0, 0, frame.width, frame.height)
|
|
238
|
-
gl.clearColor(0, 0, 0, 0)
|
|
239
|
-
gl.clear(gl.COLOR_BUFFER_BIT)
|
|
240
|
-
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
|
|
241
|
-
|
|
242
|
-
let imageDataCache: RenderImageData | null = null
|
|
243
|
-
const readback = (): RenderImageData => {
|
|
244
|
-
if (imageDataCache !== null) {
|
|
245
|
-
return imageDataCache
|
|
246
|
-
}
|
|
247
|
-
const out = new Uint8Array(frame.width * frame.height * 4)
|
|
248
|
-
if (frame.width > 0 && frame.height > 0) {
|
|
249
|
-
gl.readPixels(
|
|
250
|
-
0,
|
|
251
|
-
0,
|
|
252
|
-
frame.width,
|
|
253
|
-
frame.height,
|
|
254
|
-
gl.RGBA,
|
|
255
|
-
gl.UNSIGNED_BYTE,
|
|
256
|
-
out,
|
|
257
|
-
)
|
|
258
|
-
}
|
|
259
|
-
imageDataCache = {
|
|
260
|
-
width: frame.width,
|
|
261
|
-
height: frame.height,
|
|
262
|
-
data: new Uint8ClampedArray(out),
|
|
263
|
-
}
|
|
264
|
-
return imageDataCache
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const imageElement = () => canvas
|
|
268
|
-
|
|
269
|
-
return {
|
|
270
|
-
canvas,
|
|
271
|
-
toImageData: readback,
|
|
272
|
-
toImageElement: imageElement,
|
|
273
|
-
}
|
|
274
|
-
}
|
package/js/sharp-optional.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
declare module 'sharp' {
|
|
2
|
-
type SharpPipeline = {
|
|
3
|
-
ensureAlpha(): SharpPipeline
|
|
4
|
-
raw(): SharpPipeline
|
|
5
|
-
toBuffer(options: { resolveWithObject: true }): Promise<{
|
|
6
|
-
data: Uint8Array
|
|
7
|
-
info: {
|
|
8
|
-
width: number
|
|
9
|
-
height: number
|
|
10
|
-
}
|
|
11
|
-
}>
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const sharp: (input?: unknown) => SharpPipeline
|
|
15
|
-
export default sharp
|
|
16
|
-
}
|
package/js/test/helpers.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import type { Project } from 'hikkaku'
|
|
2
|
-
import { createHeadlessVMFromProject } from '../index.ts'
|
|
3
|
-
import type {
|
|
4
|
-
FrameReport,
|
|
5
|
-
HeadlessVM,
|
|
6
|
-
JsonValue,
|
|
7
|
-
RenderFrame,
|
|
8
|
-
VMSnapshotTarget,
|
|
9
|
-
} from '../vm/index.ts'
|
|
10
|
-
|
|
11
|
-
export const createVmFromProject = (project: Project): HeadlessVM => {
|
|
12
|
-
return createHeadlessVMFromProject({
|
|
13
|
-
projectJson: project.toScratch(),
|
|
14
|
-
initialNowMs: 0,
|
|
15
|
-
})
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const stepMany = (
|
|
19
|
-
vm: HeadlessVM,
|
|
20
|
-
count: number,
|
|
21
|
-
frameMs = 33,
|
|
22
|
-
): FrameReport => {
|
|
23
|
-
if (count <= 0) {
|
|
24
|
-
throw new Error('count must be positive')
|
|
25
|
-
}
|
|
26
|
-
let report: FrameReport | null = null
|
|
27
|
-
let nowMs = vm.snapshot().nowMs
|
|
28
|
-
for (let index = 0; index < count; index += 1) {
|
|
29
|
-
nowMs += frameMs
|
|
30
|
-
vm.setTime(nowMs)
|
|
31
|
-
report = vm.stepFrame()
|
|
32
|
-
}
|
|
33
|
-
if (!report) {
|
|
34
|
-
throw new Error('stepMany did not execute any frames')
|
|
35
|
-
}
|
|
36
|
-
return report
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export const runUntilFinished = (
|
|
40
|
-
vm: HeadlessVM,
|
|
41
|
-
maxFrames = 180,
|
|
42
|
-
frameMs = 33,
|
|
43
|
-
): FrameReport => {
|
|
44
|
-
let nowMs = vm.snapshot().nowMs
|
|
45
|
-
for (let index = 0; index < maxFrames; index += 1) {
|
|
46
|
-
nowMs += frameMs
|
|
47
|
-
vm.setTime(nowMs)
|
|
48
|
-
const report = vm.stepFrame()
|
|
49
|
-
if (report.stopReason === 'finished') {
|
|
50
|
-
return report
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
throw new Error(`VM did not finish within ${String(maxFrames)} frames`)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export const getTargetByName = (
|
|
57
|
-
vm: HeadlessVM,
|
|
58
|
-
name: string,
|
|
59
|
-
): VMSnapshotTarget => {
|
|
60
|
-
const target = vm.snapshot().targets.find((item) => item.name === name)
|
|
61
|
-
if (!target) {
|
|
62
|
-
throw new Error(`target "${name}" was not found in snapshot`)
|
|
63
|
-
}
|
|
64
|
-
return target
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export const getStageTarget = (vm: HeadlessVM): VMSnapshotTarget => {
|
|
68
|
-
const stage = vm.snapshot().targets.find((target) => target.isStage)
|
|
69
|
-
if (!stage) {
|
|
70
|
-
throw new Error('stage target was not found in snapshot')
|
|
71
|
-
}
|
|
72
|
-
return stage
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export const getStageVariable = (vm: HeadlessVM, id: string): JsonValue => {
|
|
76
|
-
const value = getStageTarget(vm).variables[id]
|
|
77
|
-
if (value === undefined) {
|
|
78
|
-
throw new Error(`stage variable "${id}" was not found in snapshot`)
|
|
79
|
-
}
|
|
80
|
-
return value
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export const countNonTransparentPixels = (frame: RenderFrame): number => {
|
|
84
|
-
let count = 0
|
|
85
|
-
for (let index = 0; index < frame.pixels.length; index += 4) {
|
|
86
|
-
if ((frame.pixels[index + 3] ?? 0) > 0) {
|
|
87
|
-
count += 1
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return count
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export const hasApproxColor = (
|
|
94
|
-
frame: RenderFrame,
|
|
95
|
-
rgb: readonly [number, number, number],
|
|
96
|
-
tolerance = 24,
|
|
97
|
-
minAlpha = 32,
|
|
98
|
-
): boolean => {
|
|
99
|
-
for (let index = 0; index < frame.pixels.length; index += 4) {
|
|
100
|
-
const r = frame.pixels[index] ?? 0
|
|
101
|
-
const g = frame.pixels[index + 1] ?? 0
|
|
102
|
-
const b = frame.pixels[index + 2] ?? 0
|
|
103
|
-
const a = frame.pixels[index + 3] ?? 0
|
|
104
|
-
if (a < minAlpha) {
|
|
105
|
-
continue
|
|
106
|
-
}
|
|
107
|
-
if (
|
|
108
|
-
Math.abs(r - rgb[0]) <= tolerance &&
|
|
109
|
-
Math.abs(g - rgb[1]) <= tolerance &&
|
|
110
|
-
Math.abs(b - rgb[2]) <= tolerance
|
|
111
|
-
) {
|
|
112
|
-
return true
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return false
|
|
116
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { Project } from 'hikkaku'
|
|
2
|
-
import { setVariableTo, whenFlagClicked } from 'hikkaku/blocks'
|
|
3
|
-
import { describe, expect, test } from 'vite-plus/test'
|
|
4
|
-
import { createHeadlessVM, createProgramModuleFromProject } from '../index.ts'
|
|
5
|
-
|
|
6
|
-
describe('moonscratch/js/test/hikkaku-sample.test.ts', () => {
|
|
7
|
-
test('runs a project generated with hikkaku', () => {
|
|
8
|
-
const project = new Project()
|
|
9
|
-
const score = project.stage.createVariable('score', 0)
|
|
10
|
-
|
|
11
|
-
project.stage.run(() => {
|
|
12
|
-
whenFlagClicked(() => {
|
|
13
|
-
setVariableTo(score, 42)
|
|
14
|
-
})
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
const program = createProgramModuleFromProject({
|
|
18
|
-
projectJson: project.toScratch(),
|
|
19
|
-
})
|
|
20
|
-
const vm = createHeadlessVM({
|
|
21
|
-
program,
|
|
22
|
-
initialNowMs: 0,
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
vm.greenFlag()
|
|
26
|
-
const report = vm.stepFrame()
|
|
27
|
-
|
|
28
|
-
expect(report.stopReason).toBe('finished')
|
|
29
|
-
|
|
30
|
-
const stage = vm.snapshot().targets.find((target) => target.isStage)
|
|
31
|
-
if (!stage) {
|
|
32
|
-
throw new Error('stage target was not found in snapshot')
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
expect(stage.variables[score.id]).toBe(42)
|
|
36
|
-
})
|
|
37
|
-
})
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { Project } from 'hikkaku'
|
|
2
|
-
import {
|
|
3
|
-
getKeyPressed,
|
|
4
|
-
getMouseDown,
|
|
5
|
-
getMouseX,
|
|
6
|
-
getMouseY,
|
|
7
|
-
gotoXY,
|
|
8
|
-
ifThen,
|
|
9
|
-
setDragMode,
|
|
10
|
-
setVariableTo,
|
|
11
|
-
whenFlagClicked,
|
|
12
|
-
} from 'hikkaku/blocks'
|
|
13
|
-
import { describe, expect, test } from 'vite-plus/test'
|
|
14
|
-
import {
|
|
15
|
-
createVmFromProject,
|
|
16
|
-
getStageVariable,
|
|
17
|
-
getTargetByName,
|
|
18
|
-
runUntilFinished,
|
|
19
|
-
} from './helpers.ts'
|
|
20
|
-
|
|
21
|
-
describe('rubik components: input and motion', () => {
|
|
22
|
-
test('reads key/mouse input and moves sprite with motion_gotoxy', () => {
|
|
23
|
-
const project = new Project()
|
|
24
|
-
const sprite = project.createSprite('pointer')
|
|
25
|
-
const mouseX = project.stage.createVariable('mouseX', 0)
|
|
26
|
-
const mouseY = project.stage.createVariable('mouseY', 0)
|
|
27
|
-
const mouseDown = project.stage.createVariable('mouseDown', 0)
|
|
28
|
-
const keyPressed = project.stage.createVariable('keyPressed', 0)
|
|
29
|
-
|
|
30
|
-
sprite.run(() => {
|
|
31
|
-
whenFlagClicked(() => {
|
|
32
|
-
setDragMode('draggable')
|
|
33
|
-
setVariableTo(mouseX, getMouseX())
|
|
34
|
-
setVariableTo(mouseY, getMouseY())
|
|
35
|
-
ifThen(getMouseDown(), () => {
|
|
36
|
-
setVariableTo(mouseDown, 1)
|
|
37
|
-
})
|
|
38
|
-
ifThen(getKeyPressed('space'), () => {
|
|
39
|
-
setVariableTo(keyPressed, 1)
|
|
40
|
-
})
|
|
41
|
-
gotoXY(getMouseX(), getMouseY())
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
const vm = createVmFromProject(project)
|
|
46
|
-
vm.greenFlag()
|
|
47
|
-
vm.setMouseState({ x: 40, y: -30, isDown: true })
|
|
48
|
-
vm.setKeysDown(['space'])
|
|
49
|
-
runUntilFinished(vm)
|
|
50
|
-
|
|
51
|
-
expect(getStageVariable(vm, mouseX.id)).toBe(40)
|
|
52
|
-
expect(getStageVariable(vm, mouseY.id)).toBe(-30)
|
|
53
|
-
expect(getStageVariable(vm, mouseDown.id)).toBe(1)
|
|
54
|
-
expect(getStageVariable(vm, keyPressed.id)).toBe(1)
|
|
55
|
-
|
|
56
|
-
const moved = getTargetByName(vm, 'pointer')
|
|
57
|
-
expect(moved.x).toBe(40)
|
|
58
|
-
expect(moved.y).toBe(-30)
|
|
59
|
-
})
|
|
60
|
-
})
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { Project } from 'hikkaku'
|
|
2
|
-
import {
|
|
3
|
-
changeVariableBy,
|
|
4
|
-
getItemOfList,
|
|
5
|
-
getVariable,
|
|
6
|
-
lengthOfList,
|
|
7
|
-
replaceItemOfList,
|
|
8
|
-
setVariableTo,
|
|
9
|
-
whenFlagClicked,
|
|
10
|
-
} from 'hikkaku/blocks'
|
|
11
|
-
import { describe, expect, test } from 'vite-plus/test'
|
|
12
|
-
import {
|
|
13
|
-
createVmFromProject,
|
|
14
|
-
getStageTarget,
|
|
15
|
-
getStageVariable,
|
|
16
|
-
runUntilFinished,
|
|
17
|
-
} from './helpers.ts'
|
|
18
|
-
|
|
19
|
-
describe('rubik components: lists', () => {
|
|
20
|
-
test('reads, replaces and measures list values', () => {
|
|
21
|
-
const project = new Project()
|
|
22
|
-
const values = project.stage.createList('values', [10, 20, 30])
|
|
23
|
-
const picked = project.stage.createVariable('picked', 0)
|
|
24
|
-
const length = project.stage.createVariable('length', 0)
|
|
25
|
-
const total = project.stage.createVariable('total', 0)
|
|
26
|
-
|
|
27
|
-
project.stage.run(() => {
|
|
28
|
-
whenFlagClicked(() => {
|
|
29
|
-
setVariableTo(picked, getItemOfList(values, 2))
|
|
30
|
-
replaceItemOfList(values, 2, 99)
|
|
31
|
-
changeVariableBy(picked, getItemOfList(values, 2))
|
|
32
|
-
setVariableTo(length, lengthOfList(values))
|
|
33
|
-
setVariableTo(total, getVariable(length))
|
|
34
|
-
changeVariableBy(total, getVariable(picked))
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
const vm = createVmFromProject(project)
|
|
39
|
-
vm.greenFlag()
|
|
40
|
-
runUntilFinished(vm)
|
|
41
|
-
|
|
42
|
-
expect(getStageVariable(vm, picked.id)).toBe(119)
|
|
43
|
-
expect(getStageVariable(vm, length.id)).toBe(3)
|
|
44
|
-
expect(getStageVariable(vm, total.id)).toBe(122)
|
|
45
|
-
|
|
46
|
-
const stage = getStageTarget(vm)
|
|
47
|
-
expect(stage.lists[values.id]).toEqual([10, 99, 30])
|
|
48
|
-
})
|
|
49
|
-
})
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { Project } from 'hikkaku'
|
|
2
|
-
import {
|
|
3
|
-
add,
|
|
4
|
-
and,
|
|
5
|
-
divide,
|
|
6
|
-
equals,
|
|
7
|
-
gt,
|
|
8
|
-
ifThen,
|
|
9
|
-
lt,
|
|
10
|
-
mathop,
|
|
11
|
-
multiply,
|
|
12
|
-
not,
|
|
13
|
-
or,
|
|
14
|
-
setVariableTo,
|
|
15
|
-
subtract,
|
|
16
|
-
whenFlagClicked,
|
|
17
|
-
} from 'hikkaku/blocks'
|
|
18
|
-
import { describe, expect, test } from 'vite-plus/test'
|
|
19
|
-
import {
|
|
20
|
-
createVmFromProject,
|
|
21
|
-
getStageVariable,
|
|
22
|
-
runUntilFinished,
|
|
23
|
-
} from './helpers.ts'
|
|
24
|
-
|
|
25
|
-
describe('rubik components: operators', () => {
|
|
26
|
-
test('evaluates arithmetic and boolean operator chains', () => {
|
|
27
|
-
const project = new Project()
|
|
28
|
-
const arithmetic = project.stage.createVariable('arithmetic', 0)
|
|
29
|
-
const sine = project.stage.createVariable('sine', 0)
|
|
30
|
-
const logic = project.stage.createVariable('logic', 0)
|
|
31
|
-
|
|
32
|
-
project.stage.run(() => {
|
|
33
|
-
whenFlagClicked(() => {
|
|
34
|
-
setVariableTo(arithmetic, add(10, 5))
|
|
35
|
-
setVariableTo(arithmetic, subtract(arithmetic.get(), 3))
|
|
36
|
-
setVariableTo(arithmetic, multiply(arithmetic.get(), 4))
|
|
37
|
-
setVariableTo(arithmetic, divide(arithmetic.get(), 6))
|
|
38
|
-
setVariableTo(sine, mathop('sin', 30))
|
|
39
|
-
ifThen(and(or(true, false), not(false)), () => {
|
|
40
|
-
setVariableTo(logic, 1)
|
|
41
|
-
})
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
const vm = createVmFromProject(project)
|
|
46
|
-
vm.greenFlag()
|
|
47
|
-
runUntilFinished(vm)
|
|
48
|
-
|
|
49
|
-
expect(getStageVariable(vm, arithmetic.id)).toBe(8)
|
|
50
|
-
expect(getStageVariable(vm, logic.id)).toBe(1)
|
|
51
|
-
expect(Number(getStageVariable(vm, sine.id))).toBeCloseTo(0.5, 6)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test('uses Scratch compare semantics for lt/gt/equals', () => {
|
|
55
|
-
const project = new Project()
|
|
56
|
-
const leftA = project.stage.createVariable('leftA', 0)
|
|
57
|
-
const rightA = project.stage.createVariable('rightA', 0)
|
|
58
|
-
const leftB = project.stage.createVariable('leftB', 0)
|
|
59
|
-
const rightB = project.stage.createVariable('rightB', 0)
|
|
60
|
-
const leftC = project.stage.createVariable('leftC', '')
|
|
61
|
-
const rightC = project.stage.createVariable('rightC', '')
|
|
62
|
-
const leftD = project.stage.createVariable('leftD', '')
|
|
63
|
-
const rightD = project.stage.createVariable('rightD', 0)
|
|
64
|
-
const numericLt = project.stage.createVariable('numericLt', 0)
|
|
65
|
-
const numericGt = project.stage.createVariable('numericGt', 0)
|
|
66
|
-
const stringLt = project.stage.createVariable('stringLt', 0)
|
|
67
|
-
const whitespaceEq = project.stage.createVariable('whitespaceEq', 0)
|
|
68
|
-
|
|
69
|
-
project.stage.run(() => {
|
|
70
|
-
whenFlagClicked(() => {
|
|
71
|
-
setVariableTo(leftA, -1)
|
|
72
|
-
setVariableTo(rightA, -0.05)
|
|
73
|
-
setVariableTo(leftB, 2)
|
|
74
|
-
setVariableTo(rightB, 1)
|
|
75
|
-
setVariableTo(leftC, 'apple')
|
|
76
|
-
setVariableTo(rightC, 'Banana')
|
|
77
|
-
setVariableTo(leftD, ' ')
|
|
78
|
-
setVariableTo(rightD, 0)
|
|
79
|
-
|
|
80
|
-
ifThen(lt(leftA.get(), rightA.get()), () => {
|
|
81
|
-
setVariableTo(numericLt, 1)
|
|
82
|
-
})
|
|
83
|
-
ifThen(gt(leftB.get(), rightB.get()), () => {
|
|
84
|
-
setVariableTo(numericGt, 1)
|
|
85
|
-
})
|
|
86
|
-
ifThen(lt(leftC.get(), rightC.get()), () => {
|
|
87
|
-
setVariableTo(stringLt, 1)
|
|
88
|
-
})
|
|
89
|
-
ifThen(equals(leftD.get(), rightD.get()), () => {
|
|
90
|
-
setVariableTo(whitespaceEq, 1)
|
|
91
|
-
})
|
|
92
|
-
})
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
const vm = createVmFromProject(project)
|
|
96
|
-
vm.greenFlag()
|
|
97
|
-
runUntilFinished(vm)
|
|
98
|
-
|
|
99
|
-
expect(getStageVariable(vm, numericLt.id)).toBe(1)
|
|
100
|
-
expect(getStageVariable(vm, numericGt.id)).toBe(1)
|
|
101
|
-
expect(getStageVariable(vm, stringLt.id)).toBe(1)
|
|
102
|
-
expect(getStageVariable(vm, whitespaceEq.id)).toBe(0)
|
|
103
|
-
})
|
|
104
|
-
})
|