moonscratch 0.1.0 → 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 (150) 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/.turbo/turbo-typecheck.log +0 -2
  28. package/AGENTS.md +0 -91
  29. package/PLAN.md +0 -64
  30. package/TODO.md +0 -120
  31. package/benchmarks/calc.bench.ts +0 -144
  32. package/benchmarks/draw.bench.ts +0 -215
  33. package/benchmarks/load.bench.ts +0 -28
  34. package/benchmarks/render.bench.ts +0 -53
  35. package/benchmarks/run.bench.ts +0 -8
  36. package/benchmarks/types.d.ts +0 -15
  37. package/docs/scratch-vm-specs/eventloop.md +0 -103
  38. package/docs/scratch-vm-specs/moonscratch-time-separation.md +0 -50
  39. package/index.html +0 -91
  40. package/js/AGENTS.md +0 -5
  41. package/js/a.ts +0 -52
  42. package/js/assets/AGENTS.md +0 -5
  43. package/js/assets/base64.test.ts +0 -14
  44. package/js/assets/base64.ts +0 -21
  45. package/js/assets/build-asset.test.ts +0 -26
  46. package/js/assets/build-asset.ts +0 -28
  47. package/js/assets/create.test.ts +0 -142
  48. package/js/assets/create.ts +0 -122
  49. package/js/assets/index.test.ts +0 -15
  50. package/js/assets/index.ts +0 -2
  51. package/js/assets/types.ts +0 -26
  52. package/js/assets/validation.test.ts +0 -34
  53. package/js/assets/validation.ts +0 -25
  54. package/js/assets.test.ts +0 -14
  55. package/js/assets.ts +0 -1
  56. package/js/index.test.ts +0 -26
  57. package/js/index.ts +0 -3
  58. package/js/render/index.test.ts +0 -65
  59. package/js/render/index.ts +0 -13
  60. package/js/render/sharp.ts +0 -87
  61. package/js/render/svg.ts +0 -68
  62. package/js/render/types.ts +0 -35
  63. package/js/render/utils.ts +0 -108
  64. package/js/render/webgl.ts +0 -274
  65. package/js/sharp-optional.d.ts +0 -16
  66. package/js/test/helpers.ts +0 -116
  67. package/js/test/hikkaku-sample.test.ts +0 -37
  68. package/js/test/rubik-components.input-motion.test.ts +0 -60
  69. package/js/test/rubik-components.lists.test.ts +0 -49
  70. package/js/test/rubik-components.operators.test.ts +0 -104
  71. package/js/test/rubik-components.pen.test.ts +0 -112
  72. package/js/test/rubik-components.procedures-loops.test.ts +0 -72
  73. package/js/test/rubik-components.variables-branches.test.ts +0 -57
  74. package/js/test/rubik-components.visibility-entry.test.ts +0 -31
  75. package/js/test/test-projects.ts +0 -598
  76. package/js/test/variable.ts +0 -200
  77. package/js/test/warp.test.ts +0 -59
  78. package/js/vm/AGENTS.md +0 -6
  79. package/js/vm/README.md +0 -183
  80. package/js/vm/bindings.test.ts +0 -13
  81. package/js/vm/bindings.ts +0 -5
  82. package/js/vm/compare-operators.test.ts +0 -145
  83. package/js/vm/constants.test.ts +0 -11
  84. package/js/vm/constants.ts +0 -4
  85. package/js/vm/effect-guards.test.ts +0 -68
  86. package/js/vm/effect-guards.ts +0 -44
  87. package/js/vm/factory.test.ts +0 -486
  88. package/js/vm/factory.ts +0 -615
  89. package/js/vm/headless-vm.test.ts +0 -131
  90. package/js/vm/headless-vm.ts +0 -342
  91. package/js/vm/index.test.ts +0 -28
  92. package/js/vm/index.ts +0 -5
  93. package/js/vm/internal-types.ts +0 -32
  94. package/js/vm/json.test.ts +0 -40
  95. package/js/vm/json.ts +0 -273
  96. package/js/vm/normalize.test.ts +0 -48
  97. package/js/vm/normalize.ts +0 -65
  98. package/js/vm/options.test.ts +0 -30
  99. package/js/vm/options.ts +0 -55
  100. package/js/vm/pen-transparency.test.ts +0 -115
  101. package/js/vm/program-wasm.ts +0 -322
  102. package/js/vm/scheduler-render.test.ts +0 -401
  103. package/js/vm/scratch-assets.test.ts +0 -136
  104. package/js/vm/scratch-assets.ts +0 -202
  105. package/js/vm/types.ts +0 -358
  106. package/js/vm/value-guards.test.ts +0 -25
  107. package/js/vm/value-guards.ts +0 -18
  108. package/moon.mod.json +0 -10
  109. package/scripts/preinstall.ts +0 -4
  110. package/src/AGENTS.md +0 -6
  111. package/src/api.mbt +0 -161
  112. package/src/api_aot_commands.mbt +0 -184
  113. package/src/api_effects_json.mbt +0 -72
  114. package/src/api_options.mbt +0 -60
  115. package/src/api_program_wasm.mbt +0 -1647
  116. package/src/api_program_wat.mbt +0 -2206
  117. package/src/api_snapshot_json.mbt +0 -44
  118. package/src/cmd/AGENTS.md +0 -5
  119. package/src/cmd/main/AGENTS.md +0 -5
  120. package/src/cmd/main/main.mbt +0 -29
  121. package/src/cmd/main/moon.pkg +0 -7
  122. package/src/cmd/main/pkg.generated.mbti +0 -13
  123. package/src/json_helpers.mbt +0 -176
  124. package/src/moon.pkg +0 -65
  125. package/src/moonscratch.mbt +0 -3
  126. package/src/moonscratch_wbtest.mbt +0 -40
  127. package/src/parser_sb3.mbt +0 -890
  128. package/src/pkg.generated.mbti +0 -479
  129. package/src/runtime_eval.mbt +0 -2844
  130. package/src/runtime_exec.mbt +0 -3850
  131. package/src/runtime_render.mbt +0 -2550
  132. package/src/runtime_state.mbt +0 -870
  133. package/src/test/AGENTS.md +0 -3
  134. package/src/test/projects/AGENTS.md +0 -6
  135. package/src/test/projects/moon.pkg +0 -4
  136. package/src/test/projects/moonscratch_compat_test.mbt +0 -642
  137. package/src/test/projects/moonscratch_core_test.mbt +0 -1332
  138. package/src/test/projects/moonscratch_runtime_test.mbt +0 -1087
  139. package/src/test/projects/pkg.generated.mbti +0 -13
  140. package/src/test/projects/test_support.mbt +0 -35
  141. package/src/types_effects.mbt +0 -20
  142. package/src/types_error.mbt +0 -4
  143. package/src/types_options.mbt +0 -31
  144. package/src/types_runtime_structs.mbt +0 -254
  145. package/src/types_vm.mbt +0 -109
  146. package/tsconfig.json +0 -29
  147. package/viewer/index.ts +0 -399
  148. package/viewer/vite.d.ts +0 -1
  149. package/viewer/worker.ts +0 -161
  150. package/vite.config.ts +0 -11
@@ -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
- }