redscript-mc 1.2.25 → 1.2.27

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 (265) hide show
  1. package/README.md +67 -9
  2. package/README.zh.md +61 -4
  3. package/dist/__tests__/cli.test.js +1 -1
  4. package/dist/__tests__/codegen.test.js +12 -6
  5. package/dist/__tests__/e2e.test.js +6 -6
  6. package/dist/__tests__/lowering.test.js +8 -8
  7. package/dist/__tests__/optimizer.test.js +31 -0
  8. package/dist/__tests__/stdlib-advanced.test.d.ts +4 -0
  9. package/dist/__tests__/stdlib-advanced.test.js +378 -0
  10. package/dist/__tests__/stdlib-bigint.test.d.ts +7 -0
  11. package/dist/__tests__/stdlib-bigint.test.js +428 -0
  12. package/dist/__tests__/stdlib-math.test.d.ts +7 -0
  13. package/dist/__tests__/stdlib-math.test.js +352 -0
  14. package/dist/__tests__/stdlib-vec.test.d.ts +4 -0
  15. package/dist/__tests__/stdlib-vec.test.js +264 -0
  16. package/dist/ast/types.d.ts +17 -1
  17. package/dist/codegen/mcfunction/index.js +154 -18
  18. package/dist/codegen/var-allocator.d.ts +17 -0
  19. package/dist/codegen/var-allocator.js +26 -0
  20. package/dist/compile.d.ts +14 -0
  21. package/dist/compile.js +62 -5
  22. package/dist/data/arena/function/__load.mcfunction +6 -0
  23. package/dist/data/arena/function/__tick.mcfunction +2 -0
  24. package/dist/data/arena/function/announce_leaders/else_1.mcfunction +3 -0
  25. package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +1 -0
  26. package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +3 -0
  27. package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +7 -0
  28. package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +1 -0
  29. package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +4 -0
  30. package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +6 -0
  31. package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +1 -0
  32. package/dist/data/arena/function/announce_leaders/then_0.mcfunction +4 -0
  33. package/dist/data/arena/function/announce_leaders.mcfunction +6 -0
  34. package/dist/data/arena/function/arena_tick/merge_2.mcfunction +1 -0
  35. package/dist/data/arena/function/arena_tick/then_0.mcfunction +4 -0
  36. package/dist/data/arena/function/arena_tick.mcfunction +11 -0
  37. package/dist/data/counter/function/__load.mcfunction +5 -0
  38. package/dist/data/counter/function/__tick.mcfunction +2 -0
  39. package/dist/data/counter/function/counter_tick/merge_2.mcfunction +1 -0
  40. package/dist/data/counter/function/counter_tick/then_0.mcfunction +3 -0
  41. package/dist/data/counter/function/counter_tick.mcfunction +11 -0
  42. package/dist/data/gcd2/function/__load.mcfunction +3 -0
  43. package/dist/data/gcd2/function/abs/merge_2.mcfunction +3 -0
  44. package/dist/data/gcd2/function/abs/then_0.mcfunction +5 -0
  45. package/dist/data/gcd2/function/abs.mcfunction +7 -0
  46. package/dist/data/gcd2/function/gcd/loop_body_1.mcfunction +7 -0
  47. package/dist/data/gcd2/function/gcd/loop_check_0.mcfunction +5 -0
  48. package/dist/data/gcd2/function/gcd/loop_exit_2.mcfunction +3 -0
  49. package/dist/data/gcd2/function/gcd.mcfunction +14 -0
  50. package/dist/data/gcd3/function/__load.mcfunction +3 -0
  51. package/dist/data/gcd3/function/abs/merge_2.mcfunction +3 -0
  52. package/dist/data/gcd3/function/abs/then_0.mcfunction +5 -0
  53. package/dist/data/gcd3/function/abs.mcfunction +7 -0
  54. package/dist/data/gcd3/function/gcd/loop_body_1.mcfunction +7 -0
  55. package/dist/data/gcd3/function/gcd/loop_check_0.mcfunction +5 -0
  56. package/dist/data/gcd3/function/gcd/loop_exit_2.mcfunction +3 -0
  57. package/dist/data/gcd3/function/gcd.mcfunction +14 -0
  58. package/dist/data/gcd3/function/test.mcfunction +7 -0
  59. package/dist/data/gcd3nm/function/__load.mcfunction +3 -0
  60. package/dist/data/gcd3nm/function/abs/merge_2.mcfunction +3 -0
  61. package/dist/data/gcd3nm/function/abs/then_0.mcfunction +5 -0
  62. package/dist/data/gcd3nm/function/abs.mcfunction +7 -0
  63. package/dist/data/gcd3nm/function/gcd/loop_body_1.mcfunction +7 -0
  64. package/dist/data/gcd3nm/function/gcd/loop_check_0.mcfunction +5 -0
  65. package/dist/data/gcd3nm/function/gcd/loop_exit_2.mcfunction +3 -0
  66. package/dist/data/gcd3nm/function/gcd.mcfunction +14 -0
  67. package/dist/data/gcd3nm/function/test.mcfunction +7 -0
  68. package/dist/data/gcd_test/function/__load.mcfunction +3 -0
  69. package/dist/data/gcd_test/function/abs/merge_2.mcfunction +3 -0
  70. package/dist/data/gcd_test/function/abs/then_0.mcfunction +5 -0
  71. package/dist/data/gcd_test/function/abs.mcfunction +7 -0
  72. package/dist/data/gcd_test/function/gcd/loop_body_1.mcfunction +7 -0
  73. package/dist/data/gcd_test/function/gcd/loop_check_0.mcfunction +5 -0
  74. package/dist/data/gcd_test/function/gcd/loop_exit_2.mcfunction +3 -0
  75. package/dist/data/gcd_test/function/gcd.mcfunction +14 -0
  76. package/dist/data/isqrttest/function/__load.mcfunction +6 -0
  77. package/dist/data/isqrttest/function/isqrt/loop_body_4.mcfunction +12 -0
  78. package/dist/data/isqrttest/function/isqrt/loop_check_3.mcfunction +5 -0
  79. package/dist/data/isqrttest/function/isqrt/loop_exit_5.mcfunction +3 -0
  80. package/dist/data/isqrttest/function/isqrt/merge_2.mcfunction +4 -0
  81. package/dist/data/isqrttest/function/isqrt/merge_8.mcfunction +6 -0
  82. package/dist/data/isqrttest/function/isqrt/then_0.mcfunction +3 -0
  83. package/dist/data/isqrttest/function/isqrt/then_6.mcfunction +3 -0
  84. package/dist/data/isqrttest/function/isqrt.mcfunction +7 -0
  85. package/dist/data/isqrttest/function/test.mcfunction +6 -0
  86. package/dist/data/mathtest/function/__load.mcfunction +3 -0
  87. package/dist/data/mathtest/function/abs/merge_2.mcfunction +3 -0
  88. package/dist/data/mathtest/function/abs/then_0.mcfunction +5 -0
  89. package/dist/data/mathtest/function/abs.mcfunction +6 -0
  90. package/dist/data/mathtest/function/test.mcfunction +5 -0
  91. package/dist/data/minecraft/tags/function/load.json +5 -0
  92. package/dist/data/minecraft/tags/function/tick.json +5 -0
  93. package/dist/data/mypack/function/__load.mcfunction +13 -0
  94. package/dist/data/mypack/function/_atan_init.mcfunction +2 -0
  95. package/dist/data/mypack/function/abs/merge_2.mcfunction +3 -0
  96. package/dist/data/mypack/function/abs/then_0.mcfunction +5 -0
  97. package/dist/data/mypack/function/abs.mcfunction +6 -0
  98. package/dist/data/mypack/function/atan2_fixed/__sgi_1.mcfunction +2 -0
  99. package/dist/data/mypack/function/atan2_fixed/else_34.mcfunction +3 -0
  100. package/dist/data/mypack/function/atan2_fixed/loop_body_31.mcfunction +19 -0
  101. package/dist/data/mypack/function/atan2_fixed/loop_check_30.mcfunction +5 -0
  102. package/dist/data/mypack/function/atan2_fixed/loop_exit_32.mcfunction +6 -0
  103. package/dist/data/mypack/function/atan2_fixed/merge_11.mcfunction +6 -0
  104. package/dist/data/mypack/function/atan2_fixed/merge_14.mcfunction +3 -0
  105. package/dist/data/mypack/function/atan2_fixed/merge_17.mcfunction +6 -0
  106. package/dist/data/mypack/function/atan2_fixed/merge_2.mcfunction +5 -0
  107. package/dist/data/mypack/function/atan2_fixed/merge_20.mcfunction +5 -0
  108. package/dist/data/mypack/function/atan2_fixed/merge_23.mcfunction +5 -0
  109. package/dist/data/mypack/function/atan2_fixed/merge_26.mcfunction +6 -0
  110. package/dist/data/mypack/function/atan2_fixed/merge_29.mcfunction +4 -0
  111. package/dist/data/mypack/function/atan2_fixed/merge_38.mcfunction +5 -0
  112. package/dist/data/mypack/function/atan2_fixed/merge_41.mcfunction +5 -0
  113. package/dist/data/mypack/function/atan2_fixed/merge_44.mcfunction +5 -0
  114. package/dist/data/mypack/function/atan2_fixed/merge_47.mcfunction +5 -0
  115. package/dist/data/mypack/function/atan2_fixed/merge_5.mcfunction +5 -0
  116. package/dist/data/mypack/function/atan2_fixed/merge_8.mcfunction +3 -0
  117. package/dist/data/mypack/function/atan2_fixed/then_0.mcfunction +5 -0
  118. package/dist/data/mypack/function/atan2_fixed/then_12.mcfunction +3 -0
  119. package/dist/data/mypack/function/atan2_fixed/then_15.mcfunction +5 -0
  120. package/dist/data/mypack/function/atan2_fixed/then_18.mcfunction +5 -0
  121. package/dist/data/mypack/function/atan2_fixed/then_21.mcfunction +3 -0
  122. package/dist/data/mypack/function/atan2_fixed/then_24.mcfunction +3 -0
  123. package/dist/data/mypack/function/atan2_fixed/then_27.mcfunction +6 -0
  124. package/dist/data/mypack/function/atan2_fixed/then_3.mcfunction +3 -0
  125. package/dist/data/mypack/function/atan2_fixed/then_33.mcfunction +5 -0
  126. package/dist/data/mypack/function/atan2_fixed/then_36.mcfunction +5 -0
  127. package/dist/data/mypack/function/atan2_fixed/then_39.mcfunction +5 -0
  128. package/dist/data/mypack/function/atan2_fixed/then_42.mcfunction +3 -0
  129. package/dist/data/mypack/function/atan2_fixed/then_45.mcfunction +5 -0
  130. package/dist/data/mypack/function/atan2_fixed/then_6.mcfunction +3 -0
  131. package/dist/data/mypack/function/atan2_fixed/then_9.mcfunction +5 -0
  132. package/dist/data/mypack/function/atan2_fixed.mcfunction +7 -0
  133. package/dist/data/mypack/function/my_game.mcfunction +10 -0
  134. package/dist/data/quiz/function/__load.mcfunction +16 -0
  135. package/dist/data/quiz/function/__tick.mcfunction +6 -0
  136. package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +4 -0
  137. package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +4 -0
  138. package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +4 -0
  139. package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +4 -0
  140. package/dist/data/quiz/function/answer_a.mcfunction +4 -0
  141. package/dist/data/quiz/function/answer_b.mcfunction +4 -0
  142. package/dist/data/quiz/function/answer_c.mcfunction +4 -0
  143. package/dist/data/quiz/function/ask_question/else_1.mcfunction +5 -0
  144. package/dist/data/quiz/function/ask_question/else_4.mcfunction +5 -0
  145. package/dist/data/quiz/function/ask_question/else_7.mcfunction +4 -0
  146. package/dist/data/quiz/function/ask_question/merge_2.mcfunction +1 -0
  147. package/dist/data/quiz/function/ask_question/merge_5.mcfunction +2 -0
  148. package/dist/data/quiz/function/ask_question/merge_8.mcfunction +2 -0
  149. package/dist/data/quiz/function/ask_question/then_0.mcfunction +4 -0
  150. package/dist/data/quiz/function/ask_question/then_3.mcfunction +4 -0
  151. package/dist/data/quiz/function/ask_question/then_6.mcfunction +4 -0
  152. package/dist/data/quiz/function/ask_question.mcfunction +7 -0
  153. package/dist/data/quiz/function/finish_quiz.mcfunction +6 -0
  154. package/dist/data/quiz/function/handle_answer/else_1.mcfunction +5 -0
  155. package/dist/data/quiz/function/handle_answer/else_10.mcfunction +3 -0
  156. package/dist/data/quiz/function/handle_answer/else_16.mcfunction +3 -0
  157. package/dist/data/quiz/function/handle_answer/else_4.mcfunction +3 -0
  158. package/dist/data/quiz/function/handle_answer/else_7.mcfunction +5 -0
  159. package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +2 -0
  160. package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +2 -0
  161. package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +2 -0
  162. package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +8 -0
  163. package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +2 -0
  164. package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +2 -0
  165. package/dist/data/quiz/function/handle_answer/then_0.mcfunction +5 -0
  166. package/dist/data/quiz/function/handle_answer/then_12.mcfunction +5 -0
  167. package/dist/data/quiz/function/handle_answer/then_15.mcfunction +6 -0
  168. package/dist/data/quiz/function/handle_answer/then_3.mcfunction +6 -0
  169. package/dist/data/quiz/function/handle_answer/then_6.mcfunction +5 -0
  170. package/dist/data/quiz/function/handle_answer/then_9.mcfunction +6 -0
  171. package/dist/data/quiz/function/handle_answer.mcfunction +11 -0
  172. package/dist/data/quiz/function/start_quiz.mcfunction +5 -0
  173. package/dist/data/reqtest/function/__load.mcfunction +4 -0
  174. package/dist/data/reqtest/function/_table_init.mcfunction +2 -0
  175. package/dist/data/reqtest/function/no_trig.mcfunction +3 -0
  176. package/dist/data/reqtest/function/use_table.mcfunction +4 -0
  177. package/dist/data/reqtest2/function/__load.mcfunction +3 -0
  178. package/dist/data/reqtest2/function/no_trig.mcfunction +3 -0
  179. package/dist/data/runtime/function/__load.mcfunction +5 -0
  180. package/dist/data/runtime/function/__tick.mcfunction +2 -0
  181. package/dist/data/runtime/function/counter_tick/then_0.mcfunction +3 -0
  182. package/dist/data/runtime/function/counter_tick.mcfunction +13 -0
  183. package/dist/data/shop/function/__load.mcfunction +7 -0
  184. package/dist/data/shop/function/__tick.mcfunction +3 -0
  185. package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +4 -0
  186. package/dist/data/shop/function/complete_purchase/else_1.mcfunction +5 -0
  187. package/dist/data/shop/function/complete_purchase/else_4.mcfunction +5 -0
  188. package/dist/data/shop/function/complete_purchase/else_7.mcfunction +3 -0
  189. package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +2 -0
  190. package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +2 -0
  191. package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +2 -0
  192. package/dist/data/shop/function/complete_purchase/then_0.mcfunction +4 -0
  193. package/dist/data/shop/function/complete_purchase/then_3.mcfunction +4 -0
  194. package/dist/data/shop/function/complete_purchase/then_6.mcfunction +4 -0
  195. package/dist/data/shop/function/complete_purchase.mcfunction +7 -0
  196. package/dist/data/shop/function/handle_shop_trigger.mcfunction +3 -0
  197. package/dist/data/swap_test/function/__load.mcfunction +3 -0
  198. package/dist/data/swap_test/function/gcd_old/loop_body_1.mcfunction +7 -0
  199. package/dist/data/swap_test/function/gcd_old/loop_check_0.mcfunction +5 -0
  200. package/dist/data/swap_test/function/gcd_old/loop_exit_2.mcfunction +3 -0
  201. package/dist/data/swap_test/function/gcd_old.mcfunction +8 -0
  202. package/dist/data/turret/function/__load.mcfunction +5 -0
  203. package/dist/data/turret/function/__tick.mcfunction +4 -0
  204. package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +4 -0
  205. package/dist/data/turret/function/deploy_turret.mcfunction +8 -0
  206. package/dist/data/turret/function/turret_tick/at_1.mcfunction +2 -0
  207. package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +2 -0
  208. package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +2 -0
  209. package/dist/data/turret/function/turret_tick/tick_body.mcfunction +3 -0
  210. package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +1 -0
  211. package/dist/data/turret/function/turret_tick.mcfunction +5 -0
  212. package/dist/gcd2.map.json +15 -0
  213. package/dist/gcd3.map.json +17 -0
  214. package/dist/gcd_test.map.json +15 -0
  215. package/dist/index.js +20 -1
  216. package/dist/ir/types.d.ts +4 -0
  217. package/dist/isqrttest.map.json +15 -0
  218. package/dist/lexer/index.d.ts +1 -1
  219. package/dist/lexer/index.js +1 -0
  220. package/dist/lowering/index.d.ts +5 -0
  221. package/dist/lowering/index.js +154 -14
  222. package/dist/mathtest.map.json +6 -0
  223. package/dist/mypack.map.json +27 -0
  224. package/dist/optimizer/dce.js +21 -5
  225. package/dist/optimizer/passes.js +18 -6
  226. package/dist/optimizer/structure.js +7 -0
  227. package/dist/pack.mcmeta +6 -0
  228. package/dist/parser/index.d.ts +5 -0
  229. package/dist/parser/index.js +43 -2
  230. package/dist/reqtest.map.json +4 -0
  231. package/dist/reqtest2.map.json +4 -0
  232. package/dist/runtime/index.d.ts +6 -0
  233. package/dist/runtime/index.js +130 -9
  234. package/dist/runtime.map.json +7 -0
  235. package/dist/swap_test.map.json +14 -0
  236. package/editors/vscode/package-lock.json +3 -3
  237. package/editors/vscode/package.json +1 -1
  238. package/examples/showcase.mcrs +505 -0
  239. package/package.json +1 -1
  240. package/src/__tests__/cli.test.ts +1 -1
  241. package/src/__tests__/codegen.test.ts +12 -6
  242. package/src/__tests__/e2e.test.ts +6 -6
  243. package/src/__tests__/lowering.test.ts +8 -8
  244. package/src/__tests__/optimizer.test.ts +33 -0
  245. package/src/__tests__/stdlib-advanced.test.ts +379 -0
  246. package/src/__tests__/stdlib-bigint.test.ts +427 -0
  247. package/src/__tests__/stdlib-math.test.ts +374 -0
  248. package/src/__tests__/stdlib-vec.test.ts +259 -0
  249. package/src/ast/types.ts +11 -1
  250. package/src/codegen/mcfunction/index.ts +143 -19
  251. package/src/codegen/var-allocator.ts +29 -0
  252. package/src/compile.ts +72 -5
  253. package/src/index.ts +21 -1
  254. package/src/ir/types.ts +2 -0
  255. package/src/lexer/index.ts +2 -1
  256. package/src/lowering/index.ts +171 -14
  257. package/src/optimizer/dce.ts +22 -5
  258. package/src/optimizer/passes.ts +18 -5
  259. package/src/optimizer/structure.ts +6 -1
  260. package/src/parser/index.ts +47 -2
  261. package/src/runtime/index.ts +130 -10
  262. package/src/stdlib/advanced.mcrs +330 -0
  263. package/src/stdlib/bigint.mcrs +205 -0
  264. package/src/stdlib/math.mcrs +274 -21
  265. package/src/stdlib/vec.mcrs +246 -0
@@ -266,6 +266,9 @@ export class MCRuntime {
266
266
  // Flag to stop function execution (for return)
267
267
  private shouldReturn: boolean = false
268
268
 
269
+ // Current MC macro context: key → value (set by 'function ... with storage')
270
+ private currentMacroContext: Record<string, any> | null = null
271
+
269
272
  constructor(namespace: string) {
270
273
  this.namespace = namespace
271
274
  // Initialize default objective
@@ -363,6 +366,13 @@ export class MCRuntime {
363
366
  cmd = cmd.trim()
364
367
  if (!cmd || cmd.startsWith('#')) return true
365
368
 
369
+ // MC macro command: line starts with '$'.
370
+ // Expand $(key) placeholders from currentMacroContext, then execute.
371
+ if (cmd.startsWith('$')) {
372
+ const expanded = this.expandMacro(cmd.slice(1))
373
+ return this.execCommand(expanded, executor)
374
+ }
375
+
366
376
  // Parse command
367
377
  if (cmd.startsWith('scoreboard ')) {
368
378
  return this.execScoreboard(cmd)
@@ -542,7 +552,10 @@ export class MCRuntime {
542
552
  // Track execute state
543
553
  let currentExecutor = executor
544
554
  let condition: boolean = true
545
- let storeTarget: { player: string; objective: string; type: 'result' | 'success' } | null = null
555
+ let storeTarget:
556
+ | { player: string; objective: string; type: 'result' | 'success' }
557
+ | { storagePath: string; field: string; type: 'result' }
558
+ | null = null
546
559
 
547
560
  while (rest.length > 0) {
548
561
  rest = rest.trimStart()
@@ -557,7 +570,11 @@ export class MCRuntime {
557
570
  const value = storeTarget.type === 'result'
558
571
  ? (this.returnValue ?? (result ? 1 : 0))
559
572
  : (result ? 1 : 0)
560
- this.setScore(storeTarget.player, storeTarget.objective, value)
573
+ if ('storagePath' in storeTarget) {
574
+ this.setStorageField(storeTarget.storagePath, storeTarget.field, value)
575
+ } else {
576
+ this.setScore(storeTarget.player, storeTarget.objective, value)
577
+ }
561
578
  }
562
579
 
563
580
  return result
@@ -630,15 +647,34 @@ export class MCRuntime {
630
647
  // Handle 'unless score ...'
631
648
  if (rest.startsWith('unless score ')) {
632
649
  rest = rest.slice(13)
633
- const scoreParts = rest.match(/^(\S+)\s+(\S+)\s+matches\s+(\S+)(.*)$/)
634
- if (scoreParts) {
635
- const [, player, obj, rangeStr, remaining] = scoreParts
650
+ // unless score <player> <obj> matches <range>
651
+ const matchesParts = rest.match(/^(\S+)\s+(\S+)\s+matches\s+(\S+)(.*)$/)
652
+ if (matchesParts) {
653
+ const [, player, obj, rangeStr, remaining] = matchesParts
636
654
  const range = parseRange(rangeStr)
637
655
  const score = this.getScore(player, obj)
638
656
  condition = condition && !matchesRange(score, range)
639
657
  rest = remaining.trim()
640
658
  continue
641
659
  }
660
+ // unless score <p1> <o1> <op> <p2> <o2>
661
+ const compareMatch = rest.match(/^(\S+)\s+(\S+)\s+([<>=]+)\s+(\S+)\s+(\S+)(.*)$/)
662
+ if (compareMatch) {
663
+ const [, p1, o1, op, p2, o2, remaining] = compareMatch
664
+ const v1 = this.getScore(p1, o1)
665
+ const v2 = this.getScore(p2, o2)
666
+ let matches = false
667
+ switch (op) {
668
+ case '=': matches = v1 === v2; break
669
+ case '<': matches = v1 < v2; break
670
+ case '<=': matches = v1 <= v2; break
671
+ case '>': matches = v1 > v2; break
672
+ case '>=': matches = v1 >= v2; break
673
+ }
674
+ condition = condition && !matches // unless = negate
675
+ rest = remaining.trim()
676
+ continue
677
+ }
642
678
  }
643
679
 
644
680
  // Handle 'if entity <selector>'
@@ -661,6 +697,29 @@ export class MCRuntime {
661
697
  continue
662
698
  }
663
699
 
700
+ // Handle 'store result storage <ns:path> <field>[<idx>] <type> <scale>' (array element)
701
+ if (rest.startsWith('store result storage ')) {
702
+ const sliced = rest.slice(21)
703
+ // Try array-index form first: <ns:path> <field>[<idx>] <type> <scale> <run-cmd>
704
+ // Use [^\[\s]+ for field (no brackets or spaces) so that \[ matches correctly.
705
+ const arrParts = sliced.match(/^(\S+)\s+([^\[\s]+)\[(\d+)\]\s+(\S+)\s+([\d.]+)\s+(.*)$/)
706
+ if (arrParts) {
707
+ const [, storagePath, field, indexStr, , , remaining] = arrParts
708
+ storeTarget = { storagePath, field: `${field}[${indexStr}]`, type: 'result' }
709
+ rest = remaining.trim()
710
+ continue
711
+ }
712
+ // Plain form: <ns:path> <field> <type> <scale> <run-cmd>
713
+ const storageParts = sliced.match(/^(\S+)\s+(\S+)\s+(\S+)\s+([\d.]+)\s+(.*)$/)
714
+ if (storageParts) {
715
+ const [, storagePath, field, , , remaining] = storageParts
716
+ storeTarget = { storagePath, field, type: 'result' }
717
+ rest = remaining.trim()
718
+ continue
719
+ }
720
+ rest = sliced
721
+ }
722
+
664
723
  // Handle 'store result score <player> <obj>'
665
724
  if (rest.startsWith('store result score ')) {
666
725
  rest = rest.slice(19)
@@ -695,7 +754,11 @@ export class MCRuntime {
695
754
  const value = storeTarget.type === 'result'
696
755
  ? (this.returnValue ?? (condition ? 1 : 0))
697
756
  : (condition ? 1 : 0)
698
- this.setScore(storeTarget.player, storeTarget.objective, value)
757
+ if ('storagePath' in storeTarget) {
758
+ this.setStorageField(storeTarget.storagePath, storeTarget.field, value)
759
+ } else {
760
+ this.setScore(storeTarget.player, storeTarget.objective, value)
761
+ }
699
762
  }
700
763
 
701
764
  return condition
@@ -721,13 +784,39 @@ export class MCRuntime {
721
784
  // -------------------------------------------------------------------------
722
785
 
723
786
  private execFunctionCmd(cmd: string, executor?: Entity): boolean {
724
- const fnName = cmd.slice(9).trim() // remove 'function '
787
+ let fnRef = cmd.slice(9).trim() // remove 'function '
788
+
789
+ // Handle 'function ns:name with storage ns:path' — MC macro calling convention.
790
+ // The called function may have $( ) placeholders that need to be expanded
791
+ // using the provided storage compound. We execute the function after
792
+ // expanding its macro context.
793
+ const withStorageMatch = fnRef.match(/^(\S+)\s+with\s+storage\s+(\S+)$/)
794
+ if (withStorageMatch) {
795
+ const [, actualFnName, storagePath] = withStorageMatch
796
+ const macroContext = this.getStorageCompound(storagePath) ?? {}
797
+ const outerShouldReturn = this.shouldReturn
798
+ const outerMacroCtx = this.currentMacroContext
799
+ this.currentMacroContext = macroContext
800
+ this.execFunction(actualFnName, executor)
801
+ this.currentMacroContext = outerMacroCtx
802
+ this.shouldReturn = outerShouldReturn
803
+ return true
804
+ }
805
+
725
806
  const outerShouldReturn = this.shouldReturn
726
- this.execFunction(fnName, executor)
807
+ this.execFunction(fnRef, executor)
727
808
  this.shouldReturn = outerShouldReturn
728
809
  return true
729
810
  }
730
811
 
812
+ /** Expand MC macro placeholders: $(key) → value from currentMacroContext */
813
+ private expandMacro(cmd: string): string {
814
+ return cmd.replace(/\$\(([^)]+)\)/g, (_, key) => {
815
+ const val = this.currentMacroContext?.[key]
816
+ return val !== undefined ? String(val) : `$(${key})`
817
+ })
818
+ }
819
+
731
820
  // -------------------------------------------------------------------------
732
821
  // Data Commands
733
822
  // -------------------------------------------------------------------------
@@ -755,8 +844,31 @@ export class MCRuntime {
755
844
  return true
756
845
  }
757
846
 
758
- // data get storage <ns:path> <field>
759
- const getMatch = cmd.match(/^data get storage (\S+) (\S+)$/)
847
+ // data modify storage <ns:path> <field>[<index>] set value <val> (array element write)
848
+ const setArrMatch = cmd.match(/^data modify storage (\S+) ([^\[\s]+)\[(\d+)\] set value (.+)$/)
849
+ if (setArrMatch) {
850
+ const [, storagePath, field, indexStr, valueStr] = setArrMatch
851
+ const arr = this.getStorageField(storagePath, field)
852
+ const idx = parseInt(indexStr, 10)
853
+ if (Array.isArray(arr) && idx >= 0 && idx < arr.length) {
854
+ arr[idx] = this.parseDataValue(valueStr)
855
+ }
856
+ return true
857
+ }
858
+
859
+ // data get storage <ns:path> <field>[<index>] [scale] (array element access)
860
+ const getArrMatch = cmd.match(/^data get storage (\S+) ([^\[\s]+)\[(\d+)\](?:\s+[\d.]+)?$/)
861
+ if (getArrMatch) {
862
+ const [, storagePath, field, indexStr] = getArrMatch
863
+ const arr = this.getStorageField(storagePath, field)
864
+ const idx = parseInt(indexStr, 10)
865
+ const value = Array.isArray(arr) ? arr[idx] : undefined
866
+ this.returnValue = typeof value === 'number' ? value : 0
867
+ return true
868
+ }
869
+
870
+ // data get storage <ns:path> <field> [scale]
871
+ const getMatch = cmd.match(/^data get storage (\S+) (\S+)(?:\s+[\d.]+)?$/)
760
872
  if (getMatch) {
761
873
  const [, storagePath, field] = getMatch
762
874
  const value = this.getStorageField(storagePath, field)
@@ -803,6 +915,14 @@ export class MCRuntime {
803
915
  }
804
916
  }
805
917
 
918
+ /** Return the whole storage compound at storagePath as a flat key→value map.
919
+ * Used by 'function ... with storage' to provide macro context. */
920
+ private getStorageCompound(storagePath: string): Record<string, any> | null {
921
+ const data = this.storage.get(storagePath)
922
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return null
923
+ return data as Record<string, any>
924
+ }
925
+
806
926
  private getStorageField(storagePath: string, field: string): any {
807
927
  const data = this.storage.get(storagePath) ?? {}
808
928
  const segments = this.parseStoragePath(field)
@@ -0,0 +1,330 @@
1
+ // advanced.mcrs — Higher-order integer math and "fun" algorithms.
2
+ //
3
+ // Requires: math.mcrs (for lerp, smoothstep, mulfix, abs, isqrt, sqrt_fixed)
4
+ //
5
+ // Category 1: Number theory — fib, is_prime, collatz_steps, digit_sum, reverse_int, mod_pow
6
+ // Category 2: Hashing/noise — hash_int, noise1d
7
+ // Category 3: Curves — bezier_quad, bezier_cubic
8
+ // Category 4: Fractals 🤯 — mandelbrot_iter (fixed-point complex arithmetic)
9
+
10
+ module library;
11
+
12
+ // ─── Category 1: Number theory ───────────────────────────────────────────────
13
+
14
+ // Fibonacci number F(n) using simple iteration.
15
+ // Overflow: F(46) = 1836311903 ≈ INT_MAX; use n ≤ 46.
16
+ // fib(0) == 0, fib(1) == 1, fib(10) == 55
17
+ fn fib(n: int) -> int {
18
+ if (n <= 0) { return 0; }
19
+ if (n == 1) { return 1; }
20
+ let a: int = 0;
21
+ let b: int = 1;
22
+ let i: int = 2;
23
+ while (i <= n) {
24
+ let c: int = a + b;
25
+ a = b;
26
+ b = c;
27
+ i = i + 1;
28
+ }
29
+ return b;
30
+ }
31
+
32
+ // Primality test by trial division up to √n.
33
+ // Returns 1 if n is prime, 0 otherwise.
34
+ // is_prime(2) == 1, is_prime(4) == 0, is_prime(97) == 1
35
+ fn is_prime(n: int) -> int {
36
+ if (n < 2) { return 0; }
37
+ if (n == 2) { return 1; }
38
+ if (n % 2 == 0) { return 0; }
39
+ let i: int = 3;
40
+ while (i * i <= n) {
41
+ if (n % i == 0) { return 0; }
42
+ i = i + 2;
43
+ }
44
+ return 1;
45
+ }
46
+
47
+ // Number of steps in the Collatz sequence starting at n until reaching 1.
48
+ // collatz_steps(1) == 0
49
+ // collatz_steps(6) == 8
50
+ // collatz_steps(27) == 111 (world record among small numbers)
51
+ fn collatz_steps(n: int) -> int {
52
+ if (n <= 1) { return 0; }
53
+ let x: int = n;
54
+ let steps: int = 0;
55
+ while (x != 1) {
56
+ if (x % 2 == 0) {
57
+ x = x / 2;
58
+ } else {
59
+ x = 3 * x + 1;
60
+ }
61
+ steps = steps + 1;
62
+ }
63
+ return steps;
64
+ }
65
+
66
+ // Sum of decimal digits. Negative input uses absolute value.
67
+ // digit_sum(123) == 6, digit_sum(0) == 0
68
+ fn digit_sum(n: int) -> int {
69
+ let x: int = n;
70
+ if (x < 0) { x = 0 - x; }
71
+ if (x == 0) { return 0; }
72
+ let sum: int = 0;
73
+ while (x > 0) {
74
+ sum = sum + x % 10;
75
+ x = x / 10;
76
+ }
77
+ return sum;
78
+ }
79
+
80
+ // Count decimal digits. 0 has 1 digit. Negative: counts absolute digits.
81
+ // count_digits(0) == 1, count_digits(100) == 3
82
+ fn count_digits(n: int) -> int {
83
+ let x: int = n;
84
+ if (x < 0) { x = 0 - x; }
85
+ if (x == 0) { return 1; }
86
+ let cnt: int = 0;
87
+ while (x > 0) {
88
+ cnt = cnt + 1;
89
+ x = x / 10;
90
+ }
91
+ return cnt;
92
+ }
93
+
94
+ // Reverse the decimal digits of an integer. Sign is preserved.
95
+ // reverse_int(12345) == 54321, reverse_int(-42) == -24
96
+ fn reverse_int(n: int) -> int {
97
+ let x: int = n;
98
+ let neg: int = 0;
99
+ if (x < 0) { x = 0 - x; neg = 1; }
100
+ let result: int = 0;
101
+ while (x > 0) {
102
+ result = result * 10 + x % 10;
103
+ x = x / 10;
104
+ }
105
+ if (neg == 1) { return 0 - result; }
106
+ return result;
107
+ }
108
+
109
+ // Modular exponentiation: (base ^ exp) mod m using fast O(log exp) squaring.
110
+ // IMPORTANT: m must be ≤ 46340 to avoid b*b overflow (46340² < INT_MAX).
111
+ // mod_pow(2, 10, 1000) == 24 (1024 mod 1000)
112
+ fn mod_pow(base: int, exp: int, m: int) -> int {
113
+ if (m == 1) { return 0; }
114
+ let result: int = 1;
115
+ let b: int = base % m;
116
+ if (b < 0) { b = b + m; }
117
+ let e: int = exp;
118
+ while (e > 0) {
119
+ if (e % 2 == 1) {
120
+ result = result * b % m;
121
+ }
122
+ b = b * b % m;
123
+ e = e / 2;
124
+ }
125
+ return result;
126
+ }
127
+
128
+ // ─── Category 2: Hashing & noise ─────────────────────────────────────────────
129
+
130
+ // Integer hash function. Output is non-negative, range [0, ~2 × 10⁹).
131
+ // Deterministic, no randomness — same input always produces the same output.
132
+ // Suitable as a seeded pseudo-random value for procedural generation.
133
+ // hash_int(0) != hash_int(1), repeatable across runs.
134
+ fn hash_int(n: int) -> int {
135
+ let h: int = n;
136
+ if (h < 0) { h = 0 - h; }
137
+ h = h % 46340; // Clamp so h² fits in int32
138
+ h = h * 46337 + 97; // 46337 is prime; h*46337 ≤ 46340*46337 < INT_MAX
139
+ if (h < 0) { h = 0 - h; }
140
+ h = h % 46340;
141
+ h = h * 46337 + 211;
142
+ if (h < 0) { h = 0 - h; }
143
+ return h;
144
+ }
145
+
146
+ // 1D value noise. Input x in fixed-point (scale = 1000).
147
+ // Output in [0, 999] — smoothly interpolated between hashed lattice points.
148
+ // noise1d(0) == noise1d(0), noise1d(500) is between noise1d(0) and noise1d(1000).
149
+ fn noise1d(x: int) -> int {
150
+ let ix: int = x / 1000;
151
+ let frac: int = x % 1000;
152
+ // Handle negative x so frac is always in [0, 999]
153
+ if (frac < 0) {
154
+ ix = ix - 1;
155
+ frac = frac + 1000;
156
+ }
157
+ let h0: int = hash_int(ix) % 1000;
158
+ let h1: int = hash_int(ix + 1) % 1000;
159
+ if (h0 < 0) { h0 = 0 - h0; }
160
+ if (h1 < 0) { h1 = 0 - h1; }
161
+ // Smoothstep for C1 continuity
162
+ let t: int = smoothstep(0, 1000, frac);
163
+ return lerp(h0, h1, t);
164
+ }
165
+
166
+ // ─── Category 3: Curves ──────────────────────────────────────────────────────
167
+
168
+ // Quadratic Bezier: B(t) = lerp(lerp(p0,p1,t), lerp(p1,p2,t), t)
169
+ // De Casteljau's algorithm — numerically stable, safe for large coordinates.
170
+ // t in [0, 1000] (fixed-point).
171
+ //
172
+ // bezier_quad(0, 500, 1000, 0) == 0 (t=0: start)
173
+ // bezier_quad(0, 500, 1000, 500) == 500 (t=500: midpoint of curve)
174
+ // bezier_quad(0, 500, 1000, 1000) == 1000 (t=1000: end)
175
+ // bezier_quad(0, 1000, 0, 500) == 500 (arch at midpoint)
176
+ fn bezier_quad(p0: int, p1: int, p2: int, t: int) -> int {
177
+ let m0: int = lerp(p0, p1, t);
178
+ let m1: int = lerp(p1, p2, t);
179
+ return lerp(m0, m1, t);
180
+ }
181
+
182
+ // Cubic Bezier: 4-point curve using De Casteljau's algorithm.
183
+ // t in [0, 1000].
184
+ // bezier_cubic(0, 333, 667, 1000, 0) == 0
185
+ // bezier_cubic(0, 333, 667, 1000, 1000) == 1000
186
+ fn bezier_cubic(p0: int, p1: int, p2: int, p3: int, t: int) -> int {
187
+ let m0: int = lerp(p0, p1, t);
188
+ let m1: int = lerp(p1, p2, t);
189
+ let m2: int = lerp(p2, p3, t);
190
+ let n0: int = lerp(m0, m1, t);
191
+ let n1: int = lerp(m1, m2, t);
192
+ return lerp(n0, n1, t);
193
+ }
194
+
195
+ // ─── Category 4: Fractals 🤯 ─────────────────────────────────────────────────
196
+
197
+ // Mandelbrot set iteration count.
198
+ //
199
+ // cx, cy: fixed-point coordinates of the complex number c = cx/1000 + i*cy/1000
200
+ // cx = -2000..1000 (i.e. real part -2.0..1.0)
201
+ // cy = -1000..1000 (imaginary part -1.0..1.0)
202
+ //
203
+ // Returns the number of iterations before |z| > 2 (escape), or max_iter if
204
+ // the point is in the Mandelbrot set. Use the return value to colour blocks!
205
+ //
206
+ // Points in the set: mandelbrot_iter(-1000, 0, 100) == 100 (c = -1+0i)
207
+ // Points outside: mandelbrot_iter(1000, 0, 100) == 0 (c = 1+0i, escapes immediately)
208
+ // Boundary region: mandelbrot_iter(-500, 500, 50) → varies
209
+ //
210
+ // Algorithm: z₀ = 0, z_{n+1} = z_n² + c
211
+ // z_n = (zr + zi·i), z_n² = zr²−zi² + 2·zr·zi·i
212
+ // Escape when |z|² = (zr²+zi²)/10⁶ > 4 ↔ mulfix(zr,zr)+mulfix(zi,zi) > 4000
213
+ fn mandelbrot_iter(cx: int, cy: int, max_iter: int) -> int {
214
+ let zr: int = 0;
215
+ let zi: int = 0;
216
+ let i: int = 0;
217
+ while (i < max_iter) {
218
+ let zr2: int = mulfix(zr, zr) - mulfix(zi, zi) + cx;
219
+ let zi2: int = 2 * mulfix(zr, zi) + cy;
220
+ zr = zr2;
221
+ zi = zi2;
222
+ if (mulfix(zr, zr) + mulfix(zi, zi) > 4000) {
223
+ return i;
224
+ }
225
+ i = i + 1;
226
+ }
227
+ return max_iter;
228
+ }
229
+
230
+ // Julia set iteration count (generalised Mandelbrot with fixed c and variable z₀).
231
+ // z0r, z0i: starting point (fixed-point, scale=1000)
232
+ // cr, ci: constant c (fixed-point, scale=1000)
233
+ // Same escape condition as mandelbrot_iter.
234
+ fn julia_iter(z0r: int, z0i: int, cr: int, ci: int, max_iter: int) -> int {
235
+ let zr: int = z0r;
236
+ let zi: int = z0i;
237
+ let i: int = 0;
238
+ while (i < max_iter) {
239
+ let zr2: int = mulfix(zr, zr) - mulfix(zi, zi) + cr;
240
+ let zi2: int = 2 * mulfix(zr, zi) + ci;
241
+ zr = zr2;
242
+ zi = zi2;
243
+ if (mulfix(zr, zr) + mulfix(zi, zi) > 4000) {
244
+ return i;
245
+ }
246
+ i = i + 1;
247
+ }
248
+ return max_iter;
249
+ }
250
+
251
+ // ─── Category 5: Geometry experiments ────────────────────────────────────────
252
+
253
+ // Angle between two 2D vectors in degrees (×1 = degrees, unsigned 0..180).
254
+ // Strategy: normalize both vectors to unit (×1000 components), then
255
+ // angle = atan2(|cross|, dot) where cross,dot are from the unit vectors.
256
+ // Unit vector components are ≤ 1000, so cross/dot ≤ 10^6 (no overflow).
257
+ // After dividing by 1000 to re-scale, pass to atan2_fixed.
258
+ //
259
+ // angle_between(1000, 0, 0, 1000) == 90
260
+ // angle_between(1000, 0, 1000, 0) == 0
261
+ // angle_between(1000, 0, -1000, 0) == 180
262
+ fn angle_between(x1: int, y1: int, x2: int, y2: int) -> int {
263
+ let nx1: int = normalize2d_x(x1, y1);
264
+ let ny1: int = normalize2d_y(x1, y1);
265
+ let nx2: int = normalize2d_x(x2, y2);
266
+ let ny2: int = normalize2d_y(x2, y2);
267
+ if (nx1 == 0 && ny1 == 0) { return 0; }
268
+ if (nx2 == 0 && ny2 == 0) { return 0; }
269
+ // dot and cross of unit vectors (components ×1000 → result ×1000000)
270
+ let d: int = dot2d(nx1, ny1, nx2, ny2) / 1000; // cos × 1000
271
+ let c: int = abs(cross2d(nx1, ny1, nx2, ny2)) / 1000; // |sin| × 1000
272
+ return atan2_fixed(c, d);
273
+ }
274
+
275
+ // Clamp a 2D point to lie within a circle of radius r centred at origin.
276
+ // r is in the same units as x, y (raw block coords, NOT fixed-point).
277
+ // Returns the clamped x component.
278
+ // Note: keep x, y < ~2000 to avoid overflow in normalize2d_x (x * 10^6).
279
+ //
280
+ // clamp_circle_x(3, 4, 10) == 3 (point at dist 5, inside r=10)
281
+ // clamp_circle_x(600, 0, 500) == 500
282
+ fn clamp_circle_x(x: int, y: int, r: int) -> int {
283
+ // length2d_fixed returns dist × 1000; compare with r × 1000
284
+ let dist: int = length2d_fixed(x, y);
285
+ if (dist <= r * 1000) { return x; }
286
+ return normalize2d_x(x, y) * r / 1000;
287
+ }
288
+
289
+ // Y component of circle clamp.
290
+ // clamp_circle_y(0, 600, 500) == 500
291
+ fn clamp_circle_y(x: int, y: int, r: int) -> int {
292
+ let dist: int = length2d_fixed(x, y);
293
+ if (dist <= r * 1000) { return y; }
294
+ return normalize2d_y(x, y) * r / 1000;
295
+ }
296
+
297
+ // Newton's method integer square root (alternative to isqrt).
298
+ // Converges quadratically; validates our while loop + division.
299
+ // newton_sqrt(25) == 5, newton_sqrt(100) == 10, newton_sqrt(2) == 1
300
+ fn newton_sqrt(n: int) -> int {
301
+ if (n <= 0) { return 0; }
302
+ if (n == 1) { return 1; }
303
+ let x: int = n / 2;
304
+ let prev: int = 0;
305
+ while (x != prev) {
306
+ prev = x;
307
+ x = (x + n / x) / 2;
308
+ }
309
+ return x;
310
+ }
311
+
312
+ // Digital root: repeatedly sum digits until single digit.
313
+ // digital_root(493) == 7 (4+9+3=16 → 1+6=7)
314
+ // digital_root(9) == 9
315
+ // digital_root(0) == 0
316
+ fn digital_root(n: int) -> int {
317
+ if (n == 0) { return 0; }
318
+ let r: int = n % 9;
319
+ if (r == 0) { return 9; }
320
+ return r;
321
+ }
322
+
323
+ // Ulam spiral ring number: which concentric square ring is n on?
324
+ // Ring 0: n=1; Ring 1: n=2..9 (3×3 square); Ring 2: n=10..25 (5×5); etc.
325
+ // Formula: floor((isqrt(n-1) + 1) / 2)
326
+ // spiral_ring(1) == 0, spiral_ring(9) == 1, spiral_ring(25) == 2
327
+ fn spiral_ring(n: int) -> int {
328
+ if (n <= 1) { return 0; }
329
+ return (isqrt(n - 1) + 1) / 2;
330
+ }