redscript-mc 1.2.26 → 1.2.28

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 (243) hide show
  1. package/README.md +78 -9
  2. package/README.zh.md +72 -4
  3. package/dist/__tests__/cli.test.js +13 -13
  4. package/dist/__tests__/optimizer-advanced.test.js +4 -4
  5. package/dist/__tests__/stdlib-advanced.test.js +114 -0
  6. package/dist/__tests__/stdlib-bigint.test.d.ts +7 -0
  7. package/dist/__tests__/stdlib-bigint.test.js +428 -0
  8. package/dist/cli.js +13 -5
  9. package/dist/codegen/mcfunction/index.d.ts +4 -0
  10. package/dist/codegen/mcfunction/index.js +9 -4
  11. package/dist/compile.d.ts +4 -0
  12. package/dist/compile.js +9 -1
  13. package/dist/data/arena/function/__load.mcfunction +6 -0
  14. package/dist/data/arena/function/__tick.mcfunction +2 -0
  15. package/dist/data/arena/function/announce_leaders/else_1.mcfunction +3 -0
  16. package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +1 -0
  17. package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +3 -0
  18. package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +7 -0
  19. package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +1 -0
  20. package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +4 -0
  21. package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +6 -0
  22. package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +1 -0
  23. package/dist/data/arena/function/announce_leaders/then_0.mcfunction +4 -0
  24. package/dist/data/arena/function/announce_leaders.mcfunction +6 -0
  25. package/dist/data/arena/function/arena_tick/merge_2.mcfunction +1 -0
  26. package/dist/data/arena/function/arena_tick/then_0.mcfunction +4 -0
  27. package/dist/data/arena/function/arena_tick.mcfunction +11 -0
  28. package/dist/data/counter/function/__load.mcfunction +5 -0
  29. package/dist/data/counter/function/__tick.mcfunction +2 -0
  30. package/dist/data/counter/function/counter_tick/merge_2.mcfunction +1 -0
  31. package/dist/data/counter/function/counter_tick/then_0.mcfunction +3 -0
  32. package/dist/data/counter/function/counter_tick.mcfunction +11 -0
  33. package/dist/data/gcd2/function/__load.mcfunction +3 -0
  34. package/dist/data/gcd2/function/abs/merge_2.mcfunction +3 -0
  35. package/dist/data/gcd2/function/abs/then_0.mcfunction +5 -0
  36. package/dist/data/gcd2/function/abs.mcfunction +7 -0
  37. package/dist/data/gcd2/function/gcd/loop_body_1.mcfunction +7 -0
  38. package/dist/data/gcd2/function/gcd/loop_check_0.mcfunction +5 -0
  39. package/dist/data/gcd2/function/gcd/loop_exit_2.mcfunction +3 -0
  40. package/dist/data/gcd2/function/gcd.mcfunction +14 -0
  41. package/dist/data/gcd3/function/__load.mcfunction +3 -0
  42. package/dist/data/gcd3/function/abs/merge_2.mcfunction +3 -0
  43. package/dist/data/gcd3/function/abs/then_0.mcfunction +5 -0
  44. package/dist/data/gcd3/function/abs.mcfunction +7 -0
  45. package/dist/data/gcd3/function/gcd/loop_body_1.mcfunction +7 -0
  46. package/dist/data/gcd3/function/gcd/loop_check_0.mcfunction +5 -0
  47. package/dist/data/gcd3/function/gcd/loop_exit_2.mcfunction +3 -0
  48. package/dist/data/gcd3/function/gcd.mcfunction +14 -0
  49. package/dist/data/gcd3/function/test.mcfunction +7 -0
  50. package/dist/data/gcd3nm/function/__load.mcfunction +3 -0
  51. package/dist/data/gcd3nm/function/abs/merge_2.mcfunction +3 -0
  52. package/dist/data/gcd3nm/function/abs/then_0.mcfunction +5 -0
  53. package/dist/data/gcd3nm/function/abs.mcfunction +7 -0
  54. package/dist/data/gcd3nm/function/gcd/loop_body_1.mcfunction +7 -0
  55. package/dist/data/gcd3nm/function/gcd/loop_check_0.mcfunction +5 -0
  56. package/dist/data/gcd3nm/function/gcd/loop_exit_2.mcfunction +3 -0
  57. package/dist/data/gcd3nm/function/gcd.mcfunction +14 -0
  58. package/dist/data/gcd3nm/function/test.mcfunction +7 -0
  59. package/dist/data/gcd_test/function/__load.mcfunction +3 -0
  60. package/dist/data/gcd_test/function/abs/merge_2.mcfunction +3 -0
  61. package/dist/data/gcd_test/function/abs/then_0.mcfunction +5 -0
  62. package/dist/data/gcd_test/function/abs.mcfunction +7 -0
  63. package/dist/data/gcd_test/function/gcd/loop_body_1.mcfunction +7 -0
  64. package/dist/data/gcd_test/function/gcd/loop_check_0.mcfunction +5 -0
  65. package/dist/data/gcd_test/function/gcd/loop_exit_2.mcfunction +3 -0
  66. package/dist/data/gcd_test/function/gcd.mcfunction +14 -0
  67. package/dist/data/isqrttest/function/__load.mcfunction +6 -0
  68. package/dist/data/isqrttest/function/isqrt/loop_body_4.mcfunction +12 -0
  69. package/dist/data/isqrttest/function/isqrt/loop_check_3.mcfunction +5 -0
  70. package/dist/data/isqrttest/function/isqrt/loop_exit_5.mcfunction +3 -0
  71. package/dist/data/isqrttest/function/isqrt/merge_2.mcfunction +4 -0
  72. package/dist/data/isqrttest/function/isqrt/merge_8.mcfunction +6 -0
  73. package/dist/data/isqrttest/function/isqrt/then_0.mcfunction +3 -0
  74. package/dist/data/isqrttest/function/isqrt/then_6.mcfunction +3 -0
  75. package/dist/data/isqrttest/function/isqrt.mcfunction +7 -0
  76. package/dist/data/isqrttest/function/test.mcfunction +6 -0
  77. package/dist/data/mathtest/function/__load.mcfunction +3 -0
  78. package/dist/data/mathtest/function/abs/merge_2.mcfunction +3 -0
  79. package/dist/data/mathtest/function/abs/then_0.mcfunction +5 -0
  80. package/dist/data/mathtest/function/abs.mcfunction +6 -0
  81. package/dist/data/mathtest/function/test.mcfunction +5 -0
  82. package/dist/data/minecraft/tags/function/load.json +5 -0
  83. package/dist/data/minecraft/tags/function/tick.json +5 -0
  84. package/dist/data/mypack/function/__load.mcfunction +13 -0
  85. package/dist/data/mypack/function/_atan_init.mcfunction +2 -0
  86. package/dist/data/mypack/function/abs/merge_2.mcfunction +3 -0
  87. package/dist/data/mypack/function/abs/then_0.mcfunction +5 -0
  88. package/dist/data/mypack/function/abs.mcfunction +6 -0
  89. package/dist/data/mypack/function/atan2_fixed/__sgi_1.mcfunction +2 -0
  90. package/dist/data/mypack/function/atan2_fixed/else_34.mcfunction +3 -0
  91. package/dist/data/mypack/function/atan2_fixed/loop_body_31.mcfunction +19 -0
  92. package/dist/data/mypack/function/atan2_fixed/loop_check_30.mcfunction +5 -0
  93. package/dist/data/mypack/function/atan2_fixed/loop_exit_32.mcfunction +6 -0
  94. package/dist/data/mypack/function/atan2_fixed/merge_11.mcfunction +6 -0
  95. package/dist/data/mypack/function/atan2_fixed/merge_14.mcfunction +3 -0
  96. package/dist/data/mypack/function/atan2_fixed/merge_17.mcfunction +6 -0
  97. package/dist/data/mypack/function/atan2_fixed/merge_2.mcfunction +5 -0
  98. package/dist/data/mypack/function/atan2_fixed/merge_20.mcfunction +5 -0
  99. package/dist/data/mypack/function/atan2_fixed/merge_23.mcfunction +5 -0
  100. package/dist/data/mypack/function/atan2_fixed/merge_26.mcfunction +6 -0
  101. package/dist/data/mypack/function/atan2_fixed/merge_29.mcfunction +4 -0
  102. package/dist/data/mypack/function/atan2_fixed/merge_38.mcfunction +5 -0
  103. package/dist/data/mypack/function/atan2_fixed/merge_41.mcfunction +5 -0
  104. package/dist/data/mypack/function/atan2_fixed/merge_44.mcfunction +5 -0
  105. package/dist/data/mypack/function/atan2_fixed/merge_47.mcfunction +5 -0
  106. package/dist/data/mypack/function/atan2_fixed/merge_5.mcfunction +5 -0
  107. package/dist/data/mypack/function/atan2_fixed/merge_8.mcfunction +3 -0
  108. package/dist/data/mypack/function/atan2_fixed/then_0.mcfunction +5 -0
  109. package/dist/data/mypack/function/atan2_fixed/then_12.mcfunction +3 -0
  110. package/dist/data/mypack/function/atan2_fixed/then_15.mcfunction +5 -0
  111. package/dist/data/mypack/function/atan2_fixed/then_18.mcfunction +5 -0
  112. package/dist/data/mypack/function/atan2_fixed/then_21.mcfunction +3 -0
  113. package/dist/data/mypack/function/atan2_fixed/then_24.mcfunction +3 -0
  114. package/dist/data/mypack/function/atan2_fixed/then_27.mcfunction +6 -0
  115. package/dist/data/mypack/function/atan2_fixed/then_3.mcfunction +3 -0
  116. package/dist/data/mypack/function/atan2_fixed/then_33.mcfunction +5 -0
  117. package/dist/data/mypack/function/atan2_fixed/then_36.mcfunction +5 -0
  118. package/dist/data/mypack/function/atan2_fixed/then_39.mcfunction +5 -0
  119. package/dist/data/mypack/function/atan2_fixed/then_42.mcfunction +3 -0
  120. package/dist/data/mypack/function/atan2_fixed/then_45.mcfunction +5 -0
  121. package/dist/data/mypack/function/atan2_fixed/then_6.mcfunction +3 -0
  122. package/dist/data/mypack/function/atan2_fixed/then_9.mcfunction +5 -0
  123. package/dist/data/mypack/function/atan2_fixed.mcfunction +7 -0
  124. package/dist/data/mypack/function/my_game.mcfunction +10 -0
  125. package/dist/data/quiz/function/__load.mcfunction +16 -0
  126. package/dist/data/quiz/function/__tick.mcfunction +6 -0
  127. package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +4 -0
  128. package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +4 -0
  129. package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +4 -0
  130. package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +4 -0
  131. package/dist/data/quiz/function/answer_a.mcfunction +4 -0
  132. package/dist/data/quiz/function/answer_b.mcfunction +4 -0
  133. package/dist/data/quiz/function/answer_c.mcfunction +4 -0
  134. package/dist/data/quiz/function/ask_question/else_1.mcfunction +5 -0
  135. package/dist/data/quiz/function/ask_question/else_4.mcfunction +5 -0
  136. package/dist/data/quiz/function/ask_question/else_7.mcfunction +4 -0
  137. package/dist/data/quiz/function/ask_question/merge_2.mcfunction +1 -0
  138. package/dist/data/quiz/function/ask_question/merge_5.mcfunction +2 -0
  139. package/dist/data/quiz/function/ask_question/merge_8.mcfunction +2 -0
  140. package/dist/data/quiz/function/ask_question/then_0.mcfunction +4 -0
  141. package/dist/data/quiz/function/ask_question/then_3.mcfunction +4 -0
  142. package/dist/data/quiz/function/ask_question/then_6.mcfunction +4 -0
  143. package/dist/data/quiz/function/ask_question.mcfunction +7 -0
  144. package/dist/data/quiz/function/finish_quiz.mcfunction +6 -0
  145. package/dist/data/quiz/function/handle_answer/else_1.mcfunction +5 -0
  146. package/dist/data/quiz/function/handle_answer/else_10.mcfunction +3 -0
  147. package/dist/data/quiz/function/handle_answer/else_16.mcfunction +3 -0
  148. package/dist/data/quiz/function/handle_answer/else_4.mcfunction +3 -0
  149. package/dist/data/quiz/function/handle_answer/else_7.mcfunction +5 -0
  150. package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +2 -0
  151. package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +2 -0
  152. package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +2 -0
  153. package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +8 -0
  154. package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +2 -0
  155. package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +2 -0
  156. package/dist/data/quiz/function/handle_answer/then_0.mcfunction +5 -0
  157. package/dist/data/quiz/function/handle_answer/then_12.mcfunction +5 -0
  158. package/dist/data/quiz/function/handle_answer/then_15.mcfunction +6 -0
  159. package/dist/data/quiz/function/handle_answer/then_3.mcfunction +6 -0
  160. package/dist/data/quiz/function/handle_answer/then_6.mcfunction +5 -0
  161. package/dist/data/quiz/function/handle_answer/then_9.mcfunction +6 -0
  162. package/dist/data/quiz/function/handle_answer.mcfunction +11 -0
  163. package/dist/data/quiz/function/start_quiz.mcfunction +5 -0
  164. package/dist/data/reqtest/function/__load.mcfunction +4 -0
  165. package/dist/data/reqtest/function/_table_init.mcfunction +2 -0
  166. package/dist/data/reqtest/function/no_trig.mcfunction +3 -0
  167. package/dist/data/reqtest/function/use_table.mcfunction +4 -0
  168. package/dist/data/reqtest2/function/__load.mcfunction +3 -0
  169. package/dist/data/reqtest2/function/no_trig.mcfunction +3 -0
  170. package/dist/data/runtime/function/__load.mcfunction +5 -0
  171. package/dist/data/runtime/function/__tick.mcfunction +2 -0
  172. package/dist/data/runtime/function/counter_tick/then_0.mcfunction +3 -0
  173. package/dist/data/runtime/function/counter_tick.mcfunction +13 -0
  174. package/dist/data/shop/function/__load.mcfunction +7 -0
  175. package/dist/data/shop/function/__tick.mcfunction +3 -0
  176. package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +4 -0
  177. package/dist/data/shop/function/complete_purchase/else_1.mcfunction +5 -0
  178. package/dist/data/shop/function/complete_purchase/else_4.mcfunction +5 -0
  179. package/dist/data/shop/function/complete_purchase/else_7.mcfunction +3 -0
  180. package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +2 -0
  181. package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +2 -0
  182. package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +2 -0
  183. package/dist/data/shop/function/complete_purchase/then_0.mcfunction +4 -0
  184. package/dist/data/shop/function/complete_purchase/then_3.mcfunction +4 -0
  185. package/dist/data/shop/function/complete_purchase/then_6.mcfunction +4 -0
  186. package/dist/data/shop/function/complete_purchase.mcfunction +7 -0
  187. package/dist/data/shop/function/handle_shop_trigger.mcfunction +3 -0
  188. package/dist/data/swap_test/function/__load.mcfunction +3 -0
  189. package/dist/data/swap_test/function/gcd_old/loop_body_1.mcfunction +7 -0
  190. package/dist/data/swap_test/function/gcd_old/loop_check_0.mcfunction +5 -0
  191. package/dist/data/swap_test/function/gcd_old/loop_exit_2.mcfunction +3 -0
  192. package/dist/data/swap_test/function/gcd_old.mcfunction +8 -0
  193. package/dist/data/turret/function/__load.mcfunction +5 -0
  194. package/dist/data/turret/function/__tick.mcfunction +4 -0
  195. package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +4 -0
  196. package/dist/data/turret/function/deploy_turret.mcfunction +8 -0
  197. package/dist/data/turret/function/turret_tick/at_1.mcfunction +2 -0
  198. package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +2 -0
  199. package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +2 -0
  200. package/dist/data/turret/function/turret_tick/tick_body.mcfunction +3 -0
  201. package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +1 -0
  202. package/dist/data/turret/function/turret_tick.mcfunction +5 -0
  203. package/dist/gcd2.map.json +15 -0
  204. package/dist/gcd3.map.json +17 -0
  205. package/dist/gcd_test.map.json +15 -0
  206. package/dist/index.d.ts +4 -0
  207. package/dist/index.js +11 -6
  208. package/dist/isqrttest.map.json +15 -0
  209. package/dist/lowering/index.d.ts +3 -0
  210. package/dist/lowering/index.js +161 -64
  211. package/dist/mathtest.map.json +6 -0
  212. package/dist/mypack.map.json +27 -0
  213. package/dist/optimizer/commands.d.ts +1 -0
  214. package/dist/optimizer/commands.js +18 -11
  215. package/dist/optimizer/structure.d.ts +1 -0
  216. package/dist/optimizer/structure.js +6 -1
  217. package/dist/pack.mcmeta +6 -0
  218. package/dist/reqtest.map.json +4 -0
  219. package/dist/reqtest2.map.json +4 -0
  220. package/dist/runtime/index.js +26 -5
  221. package/dist/runtime.map.json +7 -0
  222. package/dist/swap_test.map.json +14 -0
  223. package/editors/vscode/package-lock.json +3 -3
  224. package/editors/vscode/package.json +1 -1
  225. package/examples/math-showcase.mcrs +146 -0
  226. package/examples/readme-demo.mcrs +92 -0
  227. package/examples/showcase.mcrs +505 -0
  228. package/package.json +1 -1
  229. package/src/__tests__/cli.test.ts +13 -13
  230. package/src/__tests__/optimizer-advanced.test.ts +4 -4
  231. package/src/__tests__/stdlib-advanced.test.ts +120 -0
  232. package/src/__tests__/stdlib-bigint.test.ts +427 -0
  233. package/src/cli.ts +14 -5
  234. package/src/codegen/mcfunction/index.ts +14 -5
  235. package/src/compile.ts +15 -2
  236. package/src/index.ts +17 -7
  237. package/src/lowering/index.ts +165 -63
  238. package/src/optimizer/commands.ts +18 -12
  239. package/src/optimizer/structure.ts +6 -2
  240. package/src/runtime/index.ts +27 -5
  241. package/src/stdlib/advanced.mcrs +81 -0
  242. package/src/stdlib/bigint.mcrs +205 -0
  243. package/src/stdlib/math.mcrs +19 -6
@@ -257,3 +257,123 @@ describe('julia_iter', () => {
257
257
  expect(scoreOf(rt, 'r')).toBe(0)
258
258
  })
259
259
  })
260
+
261
+ // ─── Experiment: cross-stdlib (vec + math + advanced) ────────────────────────
262
+
263
+ const VEC_SRC = fs.readFileSync(path.join(__dirname, '../../src/stdlib/vec.mcrs'), 'utf-8')
264
+
265
+ function runAll(driver: string): MCRuntime {
266
+ const result = compile(driver, {
267
+ namespace: 'exptest',
268
+ librarySources: [MATH_SRC, VEC_SRC, ADV_SRC],
269
+ })
270
+ if (!result.success) throw new Error(result.error?.message ?? 'compile failed: ' + result.error?.message)
271
+ const rt = new MCRuntime('exptest')
272
+ for (const file of result.files ?? []) {
273
+ if (!file.path.endsWith('.mcfunction')) continue
274
+ const match = file.path.match(/data\/([^/]+)\/function\/(.+)\.mcfunction$/)
275
+ if (!match) continue
276
+ rt.loadFunction(`${match[1]}:${match[2]}`, file.content.split('\n'))
277
+ }
278
+ rt.load()
279
+ return rt
280
+ }
281
+
282
+ function exp(rt: MCRuntime, key: string): number {
283
+ return rt.getScore('out', `exptest.${key}`) ?? 0
284
+ }
285
+
286
+ describe('newton_sqrt', () => {
287
+ it('newton_sqrt(25) == 5', () => {
288
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", newton_sqrt(25)); }`)
289
+ rt.execFunction('test')
290
+ expect(exp(rt, 'r')).toBe(5)
291
+ })
292
+ it('newton_sqrt(100) == 10', () => {
293
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", newton_sqrt(100)); }`)
294
+ rt.execFunction('test')
295
+ expect(exp(rt, 'r')).toBe(10)
296
+ })
297
+ it('newton_sqrt(2) == 1', () => {
298
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", newton_sqrt(2)); }`)
299
+ rt.execFunction('test')
300
+ expect(exp(rt, 'r')).toBe(1)
301
+ })
302
+ it('newton_sqrt(0) == 0', () => {
303
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", newton_sqrt(0)); }`)
304
+ rt.execFunction('test')
305
+ expect(exp(rt, 'r')).toBe(0)
306
+ })
307
+ })
308
+
309
+ describe('digital_root', () => {
310
+ it('digital_root(493) == 7', () => {
311
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", digital_root(493)); }`)
312
+ rt.execFunction('test')
313
+ expect(exp(rt, 'r')).toBe(7)
314
+ })
315
+ it('digital_root(9) == 9', () => {
316
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", digital_root(9)); }`)
317
+ rt.execFunction('test')
318
+ expect(exp(rt, 'r')).toBe(9)
319
+ })
320
+ it('digital_root(0) == 0', () => {
321
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", digital_root(0)); }`)
322
+ rt.execFunction('test')
323
+ expect(exp(rt, 'r')).toBe(0)
324
+ })
325
+ })
326
+
327
+ describe('spiral_ring', () => {
328
+ it('spiral_ring(1) == 0', () => {
329
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", spiral_ring(1)); }`)
330
+ rt.execFunction('test')
331
+ expect(exp(rt, 'r')).toBe(0)
332
+ })
333
+ it('spiral_ring(9) == 1', () => {
334
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", spiral_ring(9)); }`)
335
+ rt.execFunction('test')
336
+ expect(exp(rt, 'r')).toBe(1)
337
+ })
338
+ it('spiral_ring(25) == 2', () => {
339
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", spiral_ring(25)); }`)
340
+ rt.execFunction('test')
341
+ expect(exp(rt, 'r')).toBe(2)
342
+ })
343
+ })
344
+
345
+ describe('clamp_circle', () => {
346
+ it('point inside: clamp_circle_x(3,4,10) == 3', () => {
347
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", clamp_circle_x(3, 4, 10)); }`)
348
+ rt.execFunction('test')
349
+ expect(exp(rt, 'r')).toBe(3)
350
+ })
351
+ it('point outside: clamp_circle_x(600,0,500) == 500', () => {
352
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", clamp_circle_x(600, 0, 500)); }`)
353
+ rt.execFunction('test')
354
+ expect(exp(rt, 'r')).toBe(500)
355
+ })
356
+ it('clamp_circle_y(0,600,500) == 500', () => {
357
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", clamp_circle_y(0, 600, 500)); }`)
358
+ rt.execFunction('test')
359
+ expect(exp(rt, 'r')).toBe(500)
360
+ })
361
+ })
362
+
363
+ describe('angle_between', () => {
364
+ it('perpendicular vectors: angle_between(1000,0,0,1000) == 90', () => {
365
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", angle_between(1000, 0, 0, 1000)); }`)
366
+ rt.execFunction('test')
367
+ expect(exp(rt, 'r')).toBe(90)
368
+ })
369
+ it('parallel vectors: angle_between(1000,0,1000,0) == 0', () => {
370
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", angle_between(1000, 0, 1000, 0)); }`)
371
+ rt.execFunction('test')
372
+ expect(exp(rt, 'r')).toBe(0)
373
+ })
374
+ it('opposite vectors: angle_between(1000,0,-1000,0) == 180', () => {
375
+ const rt = runAll(`fn test() { scoreboard_set("out", "r", angle_between(1000, 0, -1000, 0)); }`)
376
+ rt.execFunction('test')
377
+ expect(exp(rt, 'r')).toBe(180)
378
+ })
379
+ })
@@ -0,0 +1,427 @@
1
+ /**
2
+ * stdlib/bigint.mcrs — runtime behavioural tests
3
+ *
4
+ * Tests basic BigInt operations (base 10000, 8 limbs = 32 decimal digits).
5
+ * All arithmetic validated against known values.
6
+ */
7
+
8
+ import * as fs from 'fs'
9
+ import * as path from 'path'
10
+ import { compile } from '../compile'
11
+ import { MCRuntime } from '../runtime'
12
+
13
+ const MATH_SRC = fs.readFileSync(path.join(__dirname, '../../src/stdlib/math.mcrs'), 'utf-8')
14
+ const BIGINT_SRC = fs.readFileSync(path.join(__dirname, '../../src/stdlib/bigint.mcrs'), 'utf-8')
15
+
16
+ function run(driver: string): MCRuntime {
17
+ const result = compile(driver, {
18
+ namespace: 'bitest',
19
+ librarySources: [MATH_SRC, BIGINT_SRC],
20
+ })
21
+ if (!result.success) throw new Error(result.error?.message ?? 'compile failed')
22
+ const rt = new MCRuntime('bitest')
23
+ for (const file of result.files ?? []) {
24
+ if (!file.path.endsWith('.mcfunction')) continue
25
+ const match = file.path.match(/data\/([^/]+)\/function\/(.+)\.mcfunction$/)
26
+ if (!match) continue
27
+ rt.loadFunction(`${match[1]}:${match[2]}`, file.content.split('\n'))
28
+ }
29
+ rt.load()
30
+ return rt
31
+ }
32
+
33
+ function sc(rt: MCRuntime, key: string): number {
34
+ return rt.getScore('out', `bitest.${key}`) ?? 0
35
+ }
36
+
37
+ // ── storage_set_int roundtrip ─────────────────────────────────────────────────
38
+
39
+ describe('storage_set_int roundtrip', () => {
40
+ it('static index: write 99 to arr[2], read back', () => {
41
+ const rt = run(`fn test() {
42
+ storage_set_array("rs:t", "arr", "[10,20,30,40]");
43
+ storage_set_int("rs:t", "arr", 2, 99);
44
+ scoreboard_set("out", "r", storage_get_int("rs:t", "arr", 2));
45
+ }`)
46
+ rt.execFunction('test')
47
+ expect(sc(rt, 'r')).toBe(99)
48
+ })
49
+
50
+ it('runtime index: write 777 to arr[idx], read back', () => {
51
+ const rt = run(`fn test() {
52
+ storage_set_array("rs:t", "arr", "[1,2,3,4,5]");
53
+ let idx: int = 3;
54
+ storage_set_int("rs:t", "arr", idx, 777);
55
+ scoreboard_set("out", "r", storage_get_int("rs:t", "arr", idx));
56
+ }`)
57
+ rt.execFunction('test')
58
+ expect(sc(rt, 'r')).toBe(777)
59
+ })
60
+
61
+ it('loop write: fill arr[0..3] with 10,20,30,40', () => {
62
+ const rt = run(`fn test() {
63
+ storage_set_array("rs:t", "arr", "[0,0,0,0]");
64
+ let i: int = 0;
65
+ while (i < 4) {
66
+ storage_set_int("rs:t", "arr", i, (i + 1) * 10);
67
+ i = i + 1;
68
+ }
69
+ scoreboard_set("out", "a", storage_get_int("rs:t", "arr", 0));
70
+ scoreboard_set("out", "b", storage_get_int("rs:t", "arr", 1));
71
+ scoreboard_set("out", "c", storage_get_int("rs:t", "arr", 2));
72
+ scoreboard_set("out", "d", storage_get_int("rs:t", "arr", 3));
73
+ }`)
74
+ rt.execFunction('test')
75
+ expect(sc(rt, 'a')).toBe(10)
76
+ expect(sc(rt, 'b')).toBe(20)
77
+ expect(sc(rt, 'c')).toBe(30)
78
+ expect(sc(rt, 'd')).toBe(40)
79
+ })
80
+ })
81
+
82
+ // ── bigint_init + from_int ────────────────────────────────────────────────────
83
+
84
+ describe('bigint init and load', () => {
85
+ it('bigint_init zeros registers', () => {
86
+ const rt = run(`fn test() {
87
+ bigint_init();
88
+ scoreboard_set("out", "r", bigint_get_a(0));
89
+ }`)
90
+ rt.execFunction('test')
91
+ expect(sc(rt, 'r')).toBe(0)
92
+ })
93
+
94
+ it('bigint_from_int_a(12345678): limb0=5678, limb1=1234', () => {
95
+ const rt = run(`fn test() {
96
+ bigint_init();
97
+ bigint_from_int_a(12345678);
98
+ scoreboard_set("out", "l0", bigint_get_a(0));
99
+ scoreboard_set("out", "l1", bigint_get_a(1));
100
+ scoreboard_set("out", "l2", bigint_get_a(2));
101
+ }`)
102
+ rt.execFunction('test')
103
+ expect(sc(rt, 'l0')).toBe(5678)
104
+ expect(sc(rt, 'l1')).toBe(1234)
105
+ expect(sc(rt, 'l2')).toBe(0)
106
+ })
107
+
108
+ it('bigint_from_int_a(2000000000): limb0=0, limb1=0, limb2=20', () => {
109
+ // 2000000000 = 20 × 10000^2 (not 2000: 20 × 10^8 = 2×10^9 ✓)
110
+ const rt = run(`fn test() {
111
+ bigint_init();
112
+ bigint_from_int_a(2000000000);
113
+ scoreboard_set("out", "l0", bigint_get_a(0));
114
+ scoreboard_set("out", "l1", bigint_get_a(1));
115
+ scoreboard_set("out", "l2", bigint_get_a(2));
116
+ }`)
117
+ rt.execFunction('test')
118
+ expect(sc(rt, 'l0')).toBe(0)
119
+ expect(sc(rt, 'l1')).toBe(0)
120
+ expect(sc(rt, 'l2')).toBe(20)
121
+ })
122
+ })
123
+
124
+ // ── bigint_add ────────────────────────────────────────────────────────────────
125
+
126
+ describe('bigint_add', () => {
127
+ it('1 + 1 = 2 (simple)', () => {
128
+ const rt = run(`fn test() {
129
+ bigint_init();
130
+ bigint_from_int_a(1);
131
+ bigint_from_int_b(1);
132
+ bigint_add();
133
+ scoreboard_set("out", "r", bigint_get_c(0));
134
+ }`)
135
+ rt.execFunction('test')
136
+ expect(sc(rt, 'r')).toBe(2)
137
+ })
138
+
139
+ it('9999 + 1 = 10000: carry across limb boundary', () => {
140
+ // a=[9999,0,...] + b=[1,0,...] = c=[0,1,0,...] (carry to limb1)
141
+ const rt = run(`fn test() {
142
+ bigint_init();
143
+ bigint_from_int_a(9999);
144
+ bigint_from_int_b(1);
145
+ bigint_add();
146
+ scoreboard_set("out", "l0", bigint_get_c(0));
147
+ scoreboard_set("out", "l1", bigint_get_c(1));
148
+ }`)
149
+ rt.execFunction('test')
150
+ expect(sc(rt, 'l0')).toBe(0)
151
+ expect(sc(rt, 'l1')).toBe(1)
152
+ })
153
+
154
+ it('99990000 + 10000 = 100000000: multi-limb carry', () => {
155
+ // a=[0,9999,...] + b=[0,1,...] = c=[0,0,1,...] (carry to limb2)
156
+ const rt = run(`fn test() {
157
+ bigint_init();
158
+ bigint_from_int_a(99990000);
159
+ bigint_from_int_b(10000);
160
+ bigint_add();
161
+ scoreboard_set("out", "l0", bigint_get_c(0));
162
+ scoreboard_set("out", "l1", bigint_get_c(1));
163
+ scoreboard_set("out", "l2", bigint_get_c(2));
164
+ }`)
165
+ rt.execFunction('test')
166
+ expect(sc(rt, 'l0')).toBe(0)
167
+ expect(sc(rt, 'l1')).toBe(0)
168
+ expect(sc(rt, 'l2')).toBe(1)
169
+ })
170
+
171
+ it('large add: 999999999 + 999999999', () => {
172
+ // 999999999 = [9999, 9999, 9, 0, ...]
173
+ // + same = [9998, 9999, 18, 0, ...] after carry
174
+ // = 1999999998: l0=9998, l1=9999, l2=19 (carry: 9+9=18, no carry from l2)
175
+ // Wait: l0=9999+9999=19998, carry=1, l0=9998
176
+ // l1=9999+9999+1=19999, carry=1, l1=9999
177
+ // l2=9+9+1=19, carry=0, l2=19
178
+ const rt = run(`fn test() {
179
+ bigint_init();
180
+ bigint_from_int_a(999999999);
181
+ bigint_from_int_b(999999999);
182
+ bigint_add();
183
+ scoreboard_set("out", "l0", bigint_get_c(0));
184
+ scoreboard_set("out", "l1", bigint_get_c(1));
185
+ scoreboard_set("out", "l2", bigint_get_c(2));
186
+ }`)
187
+ rt.execFunction('test')
188
+ expect(sc(rt, 'l0')).toBe(9998) // 1999999998 % 10000 = 9998
189
+ expect(sc(rt, 'l1')).toBe(9999) // floor(1999999998 / 10000) % 10000 = 9999
190
+ expect(sc(rt, 'l2')).toBe(19) // floor(1999999998 / 100000000) = 19
191
+ })
192
+ })
193
+
194
+ // ── bigint_sub ────────────────────────────────────────────────────────────────
195
+
196
+ describe('bigint_sub', () => {
197
+ it('10 - 3 = 7', () => {
198
+ const rt = run(`fn test() {
199
+ bigint_init();
200
+ bigint_from_int_a(10);
201
+ bigint_from_int_b(3);
202
+ bigint_sub();
203
+ scoreboard_set("out", "r", bigint_get_c(0));
204
+ }`)
205
+ rt.execFunction('test')
206
+ expect(sc(rt, 'r')).toBe(7)
207
+ })
208
+
209
+ it('10000 - 1 = 9999: borrow across limb boundary', () => {
210
+ // a=[0,1,...] - b=[1,0,...] = c=[9999,0,...] (borrow from limb1)
211
+ const rt = run(`fn test() {
212
+ bigint_init();
213
+ bigint_from_int_a(10000);
214
+ bigint_from_int_b(1);
215
+ bigint_sub();
216
+ scoreboard_set("out", "l0", bigint_get_c(0));
217
+ scoreboard_set("out", "l1", bigint_get_c(1));
218
+ }`)
219
+ rt.execFunction('test')
220
+ expect(sc(rt, 'l0')).toBe(9999)
221
+ expect(sc(rt, 'l1')).toBe(0)
222
+ })
223
+ })
224
+
225
+ // ── bigint_compare ────────────────────────────────────────────────────────────
226
+
227
+ describe('bigint_compare', () => {
228
+ it('1 == 1 → 0', () => {
229
+ const rt = run(`fn test() {
230
+ bigint_init();
231
+ bigint_from_int_a(1);
232
+ bigint_from_int_b(1);
233
+ scoreboard_set("out", "r", bigint_compare());
234
+ }`)
235
+ rt.execFunction('test')
236
+ expect(sc(rt, 'r')).toBe(0)
237
+ })
238
+
239
+ it('2 > 1 → 1', () => {
240
+ const rt = run(`fn test() {
241
+ bigint_init();
242
+ bigint_from_int_a(2);
243
+ bigint_from_int_b(1);
244
+ scoreboard_set("out", "r", bigint_compare());
245
+ }`)
246
+ rt.execFunction('test')
247
+ expect(sc(rt, 'r')).toBe(1)
248
+ })
249
+
250
+ it('1 < 2 → -1', () => {
251
+ const rt = run(`fn test() {
252
+ bigint_init();
253
+ bigint_from_int_a(1);
254
+ bigint_from_int_b(2);
255
+ scoreboard_set("out", "r", bigint_compare());
256
+ }`)
257
+ rt.execFunction('test')
258
+ expect(sc(rt, 'r')).toBe(-1)
259
+ })
260
+ })
261
+
262
+ // ── bigint_mul_small ──────────────────────────────────────────────────────────
263
+
264
+ describe('bigint_mul_small', () => {
265
+ it('12345 * 2 = 24690', () => {
266
+ const rt = run(`fn test() {
267
+ bigint_init();
268
+ bigint_from_int_a(12345);
269
+ bigint_mul_small(2);
270
+ scoreboard_set("out", "l0", bigint_get_c(0));
271
+ scoreboard_set("out", "l1", bigint_get_c(1));
272
+ }`)
273
+ rt.execFunction('test')
274
+ expect(sc(rt, 'l0')).toBe(4690)
275
+ expect(sc(rt, 'l1')).toBe(2)
276
+ })
277
+
278
+ it('9999 * 9999 = 99980001: carry', () => {
279
+ // c[0] = 99980001 % 10000 = 1
280
+ // c[1] = floor(99980001 / 10000) = 9998
281
+ const rt = run(`fn test() {
282
+ bigint_init();
283
+ bigint_from_int_a(9999);
284
+ bigint_mul_small(9999);
285
+ scoreboard_set("out", "l0", bigint_get_c(0));
286
+ scoreboard_set("out", "l1", bigint_get_c(1));
287
+ }`)
288
+ rt.execFunction('test')
289
+ expect(sc(rt, 'l0')).toBe(1) // 99980001 % 10000 = 1 (actually 0001)
290
+ expect(sc(rt, 'l1')).toBe(9998) // 9998
291
+ })
292
+ })
293
+
294
+ // ── bigint_mul ────────────────────────────────────────────────────────────────
295
+
296
+ describe('bigint_mul', () => {
297
+ it('3 * 4 = 12', () => {
298
+ const rt = run(`fn test() {
299
+ bigint_init();
300
+ bigint_from_int_a(3);
301
+ bigint_from_int_b(4);
302
+ bigint_mul();
303
+ scoreboard_set("out", "r", bigint_get_c(0));
304
+ }`)
305
+ rt.execFunction('test')
306
+ expect(sc(rt, 'r')).toBe(12)
307
+ })
308
+
309
+ it('9999 * 9999 = 99980001', () => {
310
+ const rt = run(`fn test() {
311
+ bigint_init();
312
+ bigint_from_int_a(9999);
313
+ bigint_from_int_b(9999);
314
+ bigint_mul();
315
+ scoreboard_set("out", "l0", bigint_get_c(0));
316
+ scoreboard_set("out", "l1", bigint_get_c(1));
317
+ }`)
318
+ rt.execFunction('test')
319
+ expect(sc(rt, 'l0')).toBe(1)
320
+ expect(sc(rt, 'l1')).toBe(9998)
321
+ })
322
+
323
+ it('100000 * 100000 = 10^10: spans 3 limbs', () => {
324
+ // 10^10 = [0, 0, 0, 1, 0, ...] in base 10000 (1 * 10000^3 = 10^12? no)
325
+ // 10^10 / 10000^0 % 10000 = 0
326
+ // 10^10 / 10000^1 % 10000 = 0
327
+ // 10^10 / 10000^2 % 10000 = 10000 → wait: 10^10 / 10^8 = 100, 100 % 10000 = 100
328
+ // Actually: 10^10 = 100 * 10^8 = 100 * (10^4)^2
329
+ // l0 = 10^10 % 10^4 = 0
330
+ // l1 = floor(10^10 / 10^4) % 10^4 = floor(10^6) % 10000 = 0
331
+ // l2 = floor(10^10 / 10^8) % 10^4 = 100 % 10000 = 100
332
+ const rt = run(`fn test() {
333
+ bigint_init();
334
+ bigint_from_int_a(100000);
335
+ bigint_from_int_b(100000);
336
+ bigint_mul();
337
+ scoreboard_set("out", "l0", bigint_get_c(0));
338
+ scoreboard_set("out", "l1", bigint_get_c(1));
339
+ scoreboard_set("out", "l2", bigint_get_c(2));
340
+ }`)
341
+ rt.execFunction('test')
342
+ expect(sc(rt, 'l0')).toBe(0)
343
+ expect(sc(rt, 'l1')).toBe(0)
344
+ expect(sc(rt, 'l2')).toBe(100)
345
+ })
346
+ })
347
+
348
+ // ── bigint_fib ────────────────────────────────────────────────────────────────
349
+
350
+ describe('bigint_fib', () => {
351
+ it('F(0) = 0', () => {
352
+ const rt = run(`fn test() {
353
+ bigint_fib(0);
354
+ scoreboard_set("out", "r", bigint_get_a(0));
355
+ }`)
356
+ rt.execFunction('test')
357
+ expect(sc(rt, 'r')).toBe(0)
358
+ })
359
+
360
+ it('F(1) = 1', () => {
361
+ const rt = run(`fn test() {
362
+ bigint_fib(1);
363
+ scoreboard_set("out", "r", bigint_get_a(0));
364
+ }`)
365
+ rt.execFunction('test')
366
+ expect(sc(rt, 'r')).toBe(1)
367
+ })
368
+
369
+ it('F(10) = 55', () => {
370
+ const rt = run(`fn test() {
371
+ bigint_fib(10);
372
+ scoreboard_set("out", "r", bigint_get_a(0));
373
+ }`)
374
+ rt.execFunction('test')
375
+ expect(sc(rt, 'r')).toBe(55)
376
+ })
377
+
378
+ it('F(20) = 6765', () => {
379
+ const rt = run(`fn test() {
380
+ bigint_fib(20);
381
+ scoreboard_set("out", "r", bigint_get_a(0));
382
+ }`)
383
+ rt.execFunction('test')
384
+ expect(sc(rt, 'r')).toBe(6765)
385
+ })
386
+
387
+ it('F(50) = 12586269025: limb0=9025, limb1=8626, limb2=125', () => {
388
+ // F(50) = 12,586,269,025
389
+ // 12586269025 % 10000 = 9025
390
+ // floor(12586269025 / 10000) % 10000 = 1258626 % 10000 = 8626
391
+ // floor(12586269025 / 10^8) = 125
392
+ const rt = run(`fn test() {
393
+ bigint_fib(50);
394
+ scoreboard_set("out", "l0", bigint_get_a(0));
395
+ scoreboard_set("out", "l1", bigint_get_a(1));
396
+ scoreboard_set("out", "l2", bigint_get_a(2));
397
+ }`)
398
+ rt.execFunction('test')
399
+ expect(sc(rt, 'l0')).toBe(9025)
400
+ expect(sc(rt, 'l1')).toBe(8626)
401
+ expect(sc(rt, 'l2')).toBe(125)
402
+ })
403
+
404
+ it('F(100) low limbs check', () => {
405
+ // F(100) = 354224848179261915075
406
+ // % 10000 = 5075
407
+ // floor / 10000 % 10000 = floor(35422484817926191.5075) % 10000 = ...
408
+ // Let's compute:
409
+ // 354224848179261915075 % 10000 = 5075
410
+ // floor(354224848179261915075 / 10000) = 35422484817926191507 (JS BigInt)
411
+ // 35422484817926191507 % 10000 = 1507
412
+ // floor(35422484817926191507 / 10000) = 3542248481792619 (roughly)
413
+ // % 10000 = 2619
414
+ const rt = run(`fn test() {
415
+ bigint_fib(100);
416
+ scoreboard_set("out", "l0", bigint_get_a(0));
417
+ scoreboard_set("out", "l1", bigint_get_a(1));
418
+ scoreboard_set("out", "l2", bigint_get_a(2));
419
+ }`)
420
+ rt.execFunction('test')
421
+ const f100 = BigInt('354224848179261915075')
422
+ const b = BigInt(10000)
423
+ expect(sc(rt, 'l0')).toBe(Number(f100 % b))
424
+ expect(sc(rt, 'l1')).toBe(Number((f100 / b) % b))
425
+ expect(sc(rt, 'l2')).toBe(Number((f100 / b / b) % b))
426
+ })
427
+ })
package/src/cli.ts CHANGED
@@ -29,7 +29,7 @@ function printUsage(): void {
29
29
  RedScript Compiler
30
30
 
31
31
  Usage:
32
- redscript compile <file> [-o <out>] [--output-nbt <file>] [--namespace <ns>] [--target <target>] [--no-dce]
32
+ redscript compile <file> [-o <out>] [--output-nbt <file>] [--namespace <ns>] [--scoreboard <obj>] [--target <target>] [--no-dce]
33
33
  redscript watch <dir> [-o <outdir>] [--namespace <ns>] [--hot-reload <url>]
34
34
  redscript check <file>
35
35
  redscript fmt <file.mcrs> [file2.mcrs ...]
@@ -54,6 +54,9 @@ Options:
54
54
  --target <target> Output target: datapack (default), cmdblock, or structure
55
55
  --no-dce Disable AST dead code elimination
56
56
  --no-mangle Disable variable name mangling (use readable names)
57
+ --scoreboard <obj> Scoreboard objective for variables (default: namespace).
58
+ Each datapack automatically uses its namespace as the objective
59
+ so multiple datapacks can coexist without collisions.
57
60
  --stats Print optimizer statistics
58
61
  --hot-reload <url> After each successful compile, POST to <url>/reload
59
62
  (use with redscript-testharness; e.g. http://localhost:25561)
@@ -168,6 +171,7 @@ function parseArgs(args: string[]): {
168
171
  hotReload?: string
169
172
  dce?: boolean
170
173
  mangle?: boolean
174
+ scoreboardObjective?: string
171
175
  } {
172
176
  const result: ReturnType<typeof parseArgs> = { dce: true, mangle: true }
173
177
  let i = 0
@@ -199,6 +203,9 @@ function parseArgs(args: string[]): {
199
203
  } else if (arg === '--no-mangle') {
200
204
  result.mangle = false
201
205
  i++
206
+ } else if (arg === '--scoreboard') {
207
+ result.scoreboardObjective = args[++i]
208
+ i++
202
209
  } else if (arg === '--hot-reload') {
203
210
  result.hotReload = args[++i]
204
211
  i++
@@ -262,7 +269,8 @@ function compileCommand(
262
269
  target: string = 'datapack',
263
270
  showStats = false,
264
271
  dce = true,
265
- mangle = true
272
+ mangle = true,
273
+ scoreboardObjective: string | undefined = undefined
266
274
  ): void {
267
275
  // Read source file
268
276
  if (!fs.existsSync(file)) {
@@ -274,7 +282,7 @@ function compileCommand(
274
282
 
275
283
  try {
276
284
  if (target === 'cmdblock') {
277
- const result = compile(source, { namespace, filePath: file, dce, mangle })
285
+ const result = compile(source, { namespace, filePath: file, dce, mangle, scoreboardObjective })
278
286
  printWarnings(result.warnings)
279
287
 
280
288
  // Generate command block JSON
@@ -305,7 +313,7 @@ function compileCommand(
305
313
  printOptimizationStats(structure.stats)
306
314
  }
307
315
  } else {
308
- const result = compile(source, { namespace, filePath: file, dce, mangle })
316
+ const result = compile(source, { namespace, filePath: file, dce, mangle, scoreboardObjective })
309
317
  printWarnings(result.warnings)
310
318
 
311
319
  // Default: generate datapack
@@ -508,7 +516,8 @@ async function main(): Promise<void> {
508
516
  target,
509
517
  parsed.stats,
510
518
  parsed.dce,
511
- parsed.mangle
519
+ parsed.mangle,
520
+ parsed.scoreboardObjective // undefined = derive from namespace in compile()
512
521
  )
513
522
  }
514
523
  break
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  import type { IRBlock, IRFunction, IRInstr, IRModule, Operand, Terminator } from '../../ir/types'
20
- import { optimizeCommandFunctions, type OptimizationStats, createEmptyOptimizationStats, mergeOptimizationStats } from '../../optimizer/commands'
20
+ import { optimizeCommandFunctions, setOptimizerObjective, type OptimizationStats, createEmptyOptimizationStats, mergeOptimizationStats } from '../../optimizer/commands'
21
21
  import { EVENT_TYPES, isEventTypeName, type EventTypeName } from '../../events/types'
22
22
  import { VarAllocator } from '../var-allocator'
23
23
 
@@ -25,7 +25,8 @@ import { VarAllocator } from '../var-allocator'
25
25
  // Utilities
26
26
  // ---------------------------------------------------------------------------
27
27
 
28
- const OBJ = 'rs' // scoreboard objective name
28
+ // Default scoreboard objective — overridden per-compilation via DatapackGenerationOptions.scoreboardObjective
29
+ let OBJ = 'rs'
29
30
 
30
31
  function operandToScore(op: Operand, alloc: VarAllocator): string {
31
32
  if (op.kind === 'var') return `${alloc.alloc(op.name)} ${OBJ}`
@@ -284,6 +285,10 @@ export interface DatapackGenerationResult {
284
285
  export interface DatapackGenerationOptions {
285
286
  optimizeCommands?: boolean
286
287
  mangle?: boolean
288
+ /** Scoreboard objective used for all scoreboard variables.
289
+ * Defaults to 'rs'. Override per-datapack to avoid collisions
290
+ * when multiple RedScript datapacks are loaded simultaneously. */
291
+ scoreboardObjective?: string
287
292
  }
288
293
 
289
294
  export function countMcfunctionCommands(files: DatapackFile[]): number {
@@ -354,7 +359,11 @@ export function generateDatapackWithStats(
354
359
  module: IRModule,
355
360
  options: DatapackGenerationOptions = {},
356
361
  ): DatapackGenerationResult {
357
- const { optimizeCommands = true, mangle = false } = options
362
+ const { optimizeCommands = true, mangle = false, scoreboardObjective = 'rs' } = options
363
+ // Set module-level OBJ so all helper functions in this module use the correct objective.
364
+ // This is safe because compilation is synchronous.
365
+ OBJ = scoreboardObjective
366
+ setOptimizerObjective(scoreboardObjective)
358
367
  const alloc = new VarAllocator(mangle)
359
368
  const files: DatapackFile[] = []
360
369
  const advancements: DatapackFile[] = []
@@ -402,9 +411,9 @@ export function generateDatapackWithStats(
402
411
  for (const eventType of eventTypes) {
403
412
  const detection = EVENT_TYPES[eventType].detection
404
413
  if (eventType === 'PlayerDeath') {
405
- loadLines.push('scoreboard objectives add rs.deaths deathCount')
414
+ loadLines.push(`scoreboard objectives add ${OBJ}.deaths deathCount`)
406
415
  } else if (eventType === 'EntityKill') {
407
- loadLines.push('scoreboard objectives add rs.kills totalKillCount')
416
+ loadLines.push(`scoreboard objectives add ${OBJ}.kills totalKillCount`)
408
417
  } else if (eventType === 'ItemUse') {
409
418
  loadLines.push('# ItemUse detection requires a project-specific objective/tag setup')
410
419
  } else if (detection === 'tag' || detection === 'advancement') {