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,3 +0,0 @@
1
- # test
2
-
3
- - Use `projects` for large project-level behavior test cases.
@@ -1,6 +0,0 @@
1
- # src/test/projects
2
-
3
- - Place macro project-level tests here.
4
- - Prefer adding cases close to real-world operation, and keep duplicate fine-grained checks in unit tests under `src/`.
5
- - For changes that update expected output, consider running `moon test --update`.
6
- - For project-level tests, use `hikkaku` to generate `project.json` and keep the generated artifact under test expectations.
@@ -1,4 +0,0 @@
1
- import {
2
- "nakasyou/moonscratch" @moonscratch,
3
- "moonbitlang/core/json" @json,
4
- }
@@ -1,642 +0,0 @@
1
- ///|
2
- let music_project =
3
- #|{
4
- #| "targets": [
5
- #| {
6
- #| "isStage": true,
7
- #| "name": "Stage",
8
- #| "variables": {
9
- #| "var_tempo": ["tempo_var", 0]
10
- #| },
11
- #| "lists": {},
12
- #| "blocks": {}
13
- #| },
14
- #| {
15
- #| "isStage": false,
16
- #| "name": "Sprite1",
17
- #| "variables": {},
18
- #| "lists": {},
19
- #| "blocks": {
20
- #| "hat_flag": {
21
- #| "opcode": "event_whenflagclicked",
22
- #| "next": "set_tempo",
23
- #| "parent": null,
24
- #| "inputs": {},
25
- #| "fields": {},
26
- #| "topLevel": true
27
- #| },
28
- #| "set_tempo": {
29
- #| "opcode": "music_setTempo",
30
- #| "next": "change_tempo",
31
- #| "parent": "hat_flag",
32
- #| "inputs": {
33
- #| "TEMPO": [1, [4, 100]]
34
- #| },
35
- #| "fields": {},
36
- #| "topLevel": false
37
- #| },
38
- #| "change_tempo": {
39
- #| "opcode": "music_changeTempo",
40
- #| "next": "set_tempo_var",
41
- #| "parent": "set_tempo",
42
- #| "inputs": {
43
- #| "TEMPO": [1, [4, -10]]
44
- #| },
45
- #| "fields": {},
46
- #| "topLevel": false
47
- #| },
48
- #| "set_tempo_var": {
49
- #| "opcode": "data_setvariableto",
50
- #| "next": "set_instrument",
51
- #| "parent": "change_tempo",
52
- #| "inputs": {
53
- #| "VALUE": [2, "tempo_reporter"]
54
- #| },
55
- #| "fields": {
56
- #| "VARIABLE": ["tempo_var", "var_tempo"]
57
- #| },
58
- #| "topLevel": false
59
- #| },
60
- #| "tempo_reporter": {
61
- #| "opcode": "music_getTempo",
62
- #| "next": null,
63
- #| "parent": "set_tempo_var",
64
- #| "inputs": {},
65
- #| "fields": {},
66
- #| "topLevel": false
67
- #| },
68
- #| "set_instrument": {
69
- #| "opcode": "music_setInstrument",
70
- #| "next": "play_note",
71
- #| "parent": "set_tempo_var",
72
- #| "inputs": {
73
- #| "INSTRUMENT": [1, [4, 2]]
74
- #| },
75
- #| "fields": {},
76
- #| "topLevel": false
77
- #| },
78
- #| "play_note": {
79
- #| "opcode": "music_playNoteForBeats",
80
- #| "next": "play_drum",
81
- #| "parent": "set_instrument",
82
- #| "inputs": {
83
- #| "NOTE": [1, [4, 60]],
84
- #| "BEATS": [1, [4, 0.1]]
85
- #| },
86
- #| "fields": {},
87
- #| "topLevel": false
88
- #| },
89
- #| "play_drum": {
90
- #| "opcode": "music_playDrumForBeats",
91
- #| "next": "rest",
92
- #| "parent": "play_note",
93
- #| "inputs": {
94
- #| "DRUM": [1, [4, 1]],
95
- #| "BEATS": [1, [4, 0.1]]
96
- #| },
97
- #| "fields": {},
98
- #| "topLevel": false
99
- #| },
100
- #| "rest": {
101
- #| "opcode": "music_restForBeats",
102
- #| "next": null,
103
- #| "parent": "play_drum",
104
- #| "inputs": {
105
- #| "BEATS": [1, [4, 0.1]]
106
- #| },
107
- #| "fields": {},
108
- #| "topLevel": false
109
- #| }
110
- #| }
111
- #| }
112
- #| ]
113
- #|}
114
-
115
- ///|
116
- test "music extension opcodes update tempo and emit note/drum effects" {
117
- let vm = unwrap_result(
118
- try? vm_new_precompiled_from_json(music_project),
119
- "vm init failed",
120
- )
121
- @moonscratch.vm_green_flag(vm)
122
- let mut now_ms = 0
123
- for _ in 0..<40 {
124
- now_ms += 16
125
- @moonscratch.vm_set_time(vm, now_ms)
126
- ignore(@moonscratch.vm_step_frame(vm))
127
- }
128
-
129
- let snapshot = unwrap_result(
130
- try? @json.parse(@moonscratch.vm_snapshot_json(vm)),
131
- "snapshot parse failed",
132
- )
133
- let root_obj = match snapshot {
134
- Object(obj) => obj
135
- _ => fail("snapshot must be object")
136
- }
137
- let targets = match root_obj["targets"] {
138
- Array(items) => items
139
- _ => fail("targets must be array")
140
- }
141
- let stage = match targets[0] {
142
- Object(obj) => obj
143
- _ => fail("stage must be object")
144
- }
145
- let vars = match stage["variables"] {
146
- Object(obj) => obj
147
- _ => fail("variables must be object")
148
- }
149
- assert_eq(vars["var_tempo"], Json::number(90.0))
150
-
151
- let effects = unwrap_result(
152
- try? @json.parse(@moonscratch.vm_take_effects_json(vm)),
153
- "effects parse failed",
154
- )
155
- let effect_items = match effects {
156
- Array(items) => items
157
- _ => fail("effects must be array")
158
- }
159
- let mut has_note = false
160
- let mut has_drum = false
161
- for effect in effect_items {
162
- match effect {
163
- Object(obj) =>
164
- if obj["type"] == Json::string("music_play_note") {
165
- has_note = true
166
- assert_eq(obj["note"], Json::number(60.0))
167
- assert_eq(obj["instrument"], Json::number(2.0))
168
- assert_eq(obj["tempo"], Json::number(90.0))
169
- } else if obj["type"] == Json::string("music_play_drum") {
170
- has_drum = true
171
- assert_eq(obj["drum"], Json::number(1.0))
172
- assert_eq(obj["tempo"], Json::number(90.0))
173
- }
174
- _ => ()
175
- }
176
- }
177
- assert_true(has_note)
178
- assert_true(has_drum)
179
- }
180
-
181
- ///|
182
- let text2speech_translate_project =
183
- #|{
184
- #| "targets": [
185
- #| {
186
- #| "isStage": true,
187
- #| "name": "Stage",
188
- #| "variables": {
189
- #| "var_viewer": ["viewer", ""],
190
- #| "var_trans": ["translated", ""],
191
- #| "var_done": ["done", 0]
192
- #| },
193
- #| "lists": {},
194
- #| "blocks": {}
195
- #| },
196
- #| {
197
- #| "isStage": false,
198
- #| "name": "Sprite1",
199
- #| "variables": {},
200
- #| "lists": {},
201
- #| "blocks": {
202
- #| "hat_flag": {
203
- #| "opcode": "event_whenflagclicked",
204
- #| "next": "set_viewer",
205
- #| "parent": null,
206
- #| "inputs": {},
207
- #| "fields": {},
208
- #| "topLevel": true
209
- #| },
210
- #| "set_viewer": {
211
- #| "opcode": "data_setvariableto",
212
- #| "next": "set_translate",
213
- #| "parent": "hat_flag",
214
- #| "inputs": {
215
- #| "VALUE": [2, "viewer_reporter"]
216
- #| },
217
- #| "fields": {
218
- #| "VARIABLE": ["viewer", "var_viewer"]
219
- #| },
220
- #| "topLevel": false
221
- #| },
222
- #| "viewer_reporter": {
223
- #| "opcode": "translate_getViewerLanguage",
224
- #| "next": null,
225
- #| "parent": "set_viewer",
226
- #| "inputs": {},
227
- #| "fields": {},
228
- #| "topLevel": false
229
- #| },
230
- #| "set_translate": {
231
- #| "opcode": "data_setvariableto",
232
- #| "next": "set_voice",
233
- #| "parent": "set_viewer",
234
- #| "inputs": {
235
- #| "VALUE": [2, "translate_reporter"]
236
- #| },
237
- #| "fields": {
238
- #| "VARIABLE": ["translated", "var_trans"]
239
- #| },
240
- #| "topLevel": false
241
- #| },
242
- #| "translate_reporter": {
243
- #| "opcode": "translate_getTranslate",
244
- #| "next": null,
245
- #| "parent": "set_translate",
246
- #| "inputs": {
247
- #| "WORDS": [1, [10, "hello"]],
248
- #| "LANGUAGE": [1, [10, "ja"]]
249
- #| },
250
- #| "fields": {},
251
- #| "topLevel": false
252
- #| },
253
- #| "set_voice": {
254
- #| "opcode": "text2speech_setVoice",
255
- #| "next": "set_language",
256
- #| "parent": "set_translate",
257
- #| "inputs": {
258
- #| "VOICE": [1, [10, "TENOR"]]
259
- #| },
260
- #| "fields": {},
261
- #| "topLevel": false
262
- #| },
263
- #| "set_language": {
264
- #| "opcode": "text2speech_setLanguage",
265
- #| "next": "speak",
266
- #| "parent": "set_voice",
267
- #| "inputs": {
268
- #| "LANGUAGE": [1, [10, "ja"]]
269
- #| },
270
- #| "fields": {},
271
- #| "topLevel": false
272
- #| },
273
- #| "speak": {
274
- #| "opcode": "text2speech_speakAndWait",
275
- #| "next": "set_done",
276
- #| "parent": "set_language",
277
- #| "inputs": {
278
- #| "WORDS": [1, [10, "hello"]]
279
- #| },
280
- #| "fields": {},
281
- #| "topLevel": false
282
- #| },
283
- #| "set_done": {
284
- #| "opcode": "data_setvariableto",
285
- #| "next": null,
286
- #| "parent": "speak",
287
- #| "inputs": {
288
- #| "VALUE": [1, [4, 1]]
289
- #| },
290
- #| "fields": {
291
- #| "VARIABLE": ["done", "var_done"]
292
- #| },
293
- #| "topLevel": false
294
- #| }
295
- #| }
296
- #| }
297
- #| ]
298
- #|}
299
-
300
- ///|
301
- test "text2speech and translate extension opcodes use JS-provided API data" {
302
- let vm = unwrap_result(
303
- try? vm_new_precompiled_from_json(text2speech_translate_project),
304
- "vm init failed",
305
- )
306
- @moonscratch.vm_green_flag(vm)
307
- @moonscratch.vm_post_io_json(vm, "viewer_language", "\"ja\"")
308
- @moonscratch.vm_post_io_json(
309
- vm, "translate_cache", "{\"ja\":{\"hello\":\"こんにちは\"}}",
310
- )
311
-
312
- for _ in 0..<6 {
313
- ignore(@moonscratch.vm_step_frame(vm))
314
- }
315
- @moonscratch.vm_post_io_json(vm, "text2speech_done_1", "true")
316
- for _ in 0..<10 {
317
- ignore(@moonscratch.vm_step_frame(vm))
318
- }
319
-
320
- let snapshot = unwrap_result(
321
- try? @json.parse(@moonscratch.vm_snapshot_json(vm)),
322
- "snapshot parse failed",
323
- )
324
- let root_obj = match snapshot {
325
- Object(obj) => obj
326
- _ => fail("snapshot must be object")
327
- }
328
- let targets = match root_obj["targets"] {
329
- Array(items) => items
330
- _ => fail("targets must be array")
331
- }
332
- let stage = match targets[0] {
333
- Object(obj) => obj
334
- _ => fail("stage must be object")
335
- }
336
- let vars = match stage["variables"] {
337
- Object(obj) => obj
338
- _ => fail("variables must be object")
339
- }
340
- assert_eq(vars["var_viewer"], Json::string("ja"))
341
- assert_eq(vars["var_trans"], Json::string("こんにちは"))
342
- assert_eq(vars["var_done"], Json::number(1.0))
343
-
344
- let effects = unwrap_result(
345
- try? @json.parse(@moonscratch.vm_take_effects_json(vm)),
346
- "effects parse failed",
347
- )
348
- let effect_items = match effects {
349
- Array(items) => items
350
- _ => fail("effects must be array")
351
- }
352
- let mut has_tts = false
353
- let mut has_translate_request = false
354
- for effect in effect_items {
355
- match effect {
356
- Object(obj) =>
357
- if obj["type"] == Json::string("text_to_speech") {
358
- has_tts = true
359
- assert_eq(obj["voice"], Json::string("TENOR"))
360
- assert_eq(obj["language"], Json::string("ja"))
361
- assert_eq(obj["waitKey"], Json::string("text2speech_done_1"))
362
- } else if obj["type"] == Json::string("translate_request") {
363
- has_translate_request = true
364
- }
365
- _ => ()
366
- }
367
- }
368
- assert_true(has_tts)
369
- assert_true(!has_translate_request)
370
- }
371
-
372
- ///|
373
- let pen_touching_project =
374
- #|{
375
- #| "targets": [
376
- #| {
377
- #| "isStage": true,
378
- #| "name": "Stage",
379
- #| "variables": {
380
- #| "var_touch_bg": ["touch_bg", false],
381
- #| "var_touch_mask": ["touch_mask", false],
382
- #| "var_touch_pen": ["touch_pen", false]
383
- #| },
384
- #| "lists": {},
385
- #| "costumes": [
386
- #| {
387
- #| "name": "backdrop1",
388
- #| "assetId": "bg_green",
389
- #| "bitmapResolution": 1,
390
- #| "rotationCenterX": 0,
391
- #| "rotationCenterY": 0
392
- #| }
393
- #| ],
394
- #| "blocks": {}
395
- #| },
396
- #| {
397
- #| "isStage": false,
398
- #| "name": "Sprite1",
399
- #| "x": 0,
400
- #| "y": 0,
401
- #| "direction": 90,
402
- #| "size": 100,
403
- #| "visible": true,
404
- #| "variables": {},
405
- #| "lists": {},
406
- #| "costumes": [
407
- #| {
408
- #| "name": "costume1",
409
- #| "assetId": "sprite_red",
410
- #| "bitmapResolution": 1,
411
- #| "rotationCenterX": 0,
412
- #| "rotationCenterY": 0
413
- #| }
414
- #| ],
415
- #| "blocks": {
416
- #| "hat_flag": {
417
- #| "opcode": "event_whenflagclicked",
418
- #| "next": "set_touch_bg",
419
- #| "parent": null,
420
- #| "inputs": {},
421
- #| "fields": {},
422
- #| "topLevel": true
423
- #| },
424
- #| "set_touch_bg": {
425
- #| "opcode": "data_setvariableto",
426
- #| "next": "set_touch_mask",
427
- #| "parent": "hat_flag",
428
- #| "inputs": {
429
- #| "VALUE": [2, "rep_touch_bg"]
430
- #| },
431
- #| "fields": {
432
- #| "VARIABLE": ["touch_bg", "var_touch_bg"]
433
- #| },
434
- #| "topLevel": false
435
- #| },
436
- #| "rep_touch_bg": {
437
- #| "opcode": "sensing_touchingcolor",
438
- #| "next": null,
439
- #| "parent": "set_touch_bg",
440
- #| "inputs": {
441
- #| "COLOR": [1, [10, "#00ff00"]]
442
- #| },
443
- #| "fields": {},
444
- #| "topLevel": false
445
- #| },
446
- #| "set_touch_mask": {
447
- #| "opcode": "data_setvariableto",
448
- #| "next": "pen_color",
449
- #| "parent": "set_touch_bg",
450
- #| "inputs": {
451
- #| "VALUE": [2, "rep_touch_mask"]
452
- #| },
453
- #| "fields": {
454
- #| "VARIABLE": ["touch_mask", "var_touch_mask"]
455
- #| },
456
- #| "topLevel": false
457
- #| },
458
- #| "rep_touch_mask": {
459
- #| "opcode": "sensing_coloristouchingcolor",
460
- #| "next": null,
461
- #| "parent": "set_touch_mask",
462
- #| "inputs": {
463
- #| "COLOR": [1, [10, "#ff0000"]],
464
- #| "COLOR2": [1, [10, "#00ff00"]]
465
- #| },
466
- #| "fields": {},
467
- #| "topLevel": false
468
- #| },
469
- #| "pen_color": {
470
- #| "opcode": "pen_setPenColorToColor",
471
- #| "next": "pen_down",
472
- #| "parent": "set_touch_mask",
473
- #| "inputs": {
474
- #| "COLOR": [1, [10, "#0000ff"]]
475
- #| },
476
- #| "fields": {},
477
- #| "topLevel": false
478
- #| },
479
- #| "pen_down": {
480
- #| "opcode": "pen_penDown",
481
- #| "next": "move_steps",
482
- #| "parent": "pen_color",
483
- #| "inputs": {},
484
- #| "fields": {},
485
- #| "topLevel": false
486
- #| },
487
- #| "move_steps": {
488
- #| "opcode": "motion_movesteps",
489
- #| "next": "set_touch_pen",
490
- #| "parent": "pen_down",
491
- #| "inputs": {
492
- #| "STEPS": [1, [4, 20]]
493
- #| },
494
- #| "fields": {},
495
- #| "topLevel": false
496
- #| },
497
- #| "set_touch_pen": {
498
- #| "opcode": "data_setvariableto",
499
- #| "next": null,
500
- #| "parent": "move_steps",
501
- #| "inputs": {
502
- #| "VALUE": [2, "rep_touch_pen"]
503
- #| },
504
- #| "fields": {
505
- #| "VARIABLE": ["touch_pen", "var_touch_pen"]
506
- #| },
507
- #| "topLevel": false
508
- #| },
509
- #| "rep_touch_pen": {
510
- #| "opcode": "sensing_touchingcolor",
511
- #| "next": null,
512
- #| "parent": "set_touch_pen",
513
- #| "inputs": {
514
- #| "COLOR": [1, [10, "#0000ff"]]
515
- #| },
516
- #| "fields": {},
517
- #| "topLevel": false
518
- #| }
519
- #| }
520
- #| }
521
- #| ]
522
- #|}
523
-
524
- ///|
525
- let pen_touching_assets =
526
- #|{
527
- #| "bg_green": {
528
- #| "width": 1,
529
- #| "height": 1,
530
- #| "rgbaBase64": "AP8A/w=="
531
- #| },
532
- #| "sprite_red": {
533
- #| "width": 1,
534
- #| "height": 1,
535
- #| "rgbaBase64": "/wAA/w=="
536
- #| }
537
- #|}
538
-
539
- ///|
540
- test "pen opcodes draw into headless buffer and touching color reporters read it" {
541
- let vm = unwrap_result(
542
- try? vm_new_precompiled_from_json(
543
- pen_touching_project,
544
- assets_json=pen_touching_assets,
545
- ),
546
- "vm init failed",
547
- )
548
- @moonscratch.vm_green_flag(vm)
549
- for _ in 0..<60 {
550
- ignore(@moonscratch.vm_step_frame(vm))
551
- }
552
-
553
- let snapshot = unwrap_result(
554
- try? @json.parse(@moonscratch.vm_snapshot_json(vm)),
555
- "snapshot parse failed",
556
- )
557
- let root_obj = match snapshot {
558
- Object(obj) => obj
559
- _ => fail("snapshot must be object")
560
- }
561
- let targets = match root_obj["targets"] {
562
- Array(items) => items
563
- _ => fail("targets must be array")
564
- }
565
- let stage = match targets[0] {
566
- Object(obj) => obj
567
- _ => fail("stage must be object")
568
- }
569
- let vars = match stage["variables"] {
570
- Object(obj) => obj
571
- _ => fail("variables must be object")
572
- }
573
- assert_eq(vars["var_touch_bg"], Json::boolean(true))
574
- assert_eq(vars["var_touch_mask"], Json::boolean(true))
575
- assert_eq(vars["var_touch_pen"], Json::boolean(true))
576
-
577
- let effects = unwrap_result(
578
- try? @json.parse(@moonscratch.vm_take_effects_json(vm)),
579
- "effects parse failed",
580
- )
581
- let effect_items = match effects {
582
- Array(items) => items
583
- _ => fail("effects must be array")
584
- }
585
- for effect in effect_items {
586
- match effect {
587
- Object(obj) =>
588
- if obj["type"] == Json::string("log") {
589
- match obj["message"] {
590
- String(message) => assert_true(!message.contains("pen_"))
591
- _ => ()
592
- }
593
- }
594
- _ => ()
595
- }
596
- }
597
- }
598
-
599
- ///|
600
- test "render_frame outputs composited scene colors" {
601
- let vm = unwrap_result(
602
- try? vm_new_precompiled_from_json(
603
- pen_touching_project,
604
- assets_json=pen_touching_assets,
605
- ),
606
- "vm init failed",
607
- )
608
- @moonscratch.vm_green_flag(vm)
609
- for _ in 0..<60 {
610
- ignore(@moonscratch.vm_step_frame(vm))
611
- }
612
-
613
- let frame = @moonscratch.vm_render_frame(vm)
614
- assert_true(frame.width > 0)
615
- assert_true(frame.height > 0)
616
- assert_eq(frame.width * frame.height * 4, frame.pixels.length())
617
-
618
- let mut has_green = false
619
- let mut has_red = false
620
- let mut has_blue = false
621
- for i in 0..<(frame.pixels.length() / 4) {
622
- let base = i * 4
623
- let r = frame.pixels[base].to_int()
624
- let g = frame.pixels[base + 1].to_int()
625
- let b = frame.pixels[base + 2].to_int()
626
- let a = frame.pixels[base + 3].to_int()
627
- if a > 0 {
628
- if r == 0 && g >= 150 && b == 0 {
629
- has_green = true
630
- }
631
- if r >= 150 && g == 0 && b == 0 {
632
- has_red = true
633
- }
634
- if r == 0 && g == 0 && b >= 150 {
635
- has_blue = true
636
- }
637
- }
638
- }
639
- assert_true(has_green)
640
- assert_true(has_red)
641
- assert_true(has_blue)
642
- }