redscript-mc 1.2.24 → 1.2.26

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 (58) hide show
  1. package/.github/workflows/publish-extension-on-ci.yml +1 -0
  2. package/dist/__tests__/cli.test.js +1 -1
  3. package/dist/__tests__/codegen.test.js +12 -6
  4. package/dist/__tests__/e2e.test.js +6 -6
  5. package/dist/__tests__/lowering.test.js +8 -8
  6. package/dist/__tests__/optimizer.test.js +31 -0
  7. package/dist/__tests__/stdlib-advanced.test.d.ts +4 -0
  8. package/dist/__tests__/stdlib-advanced.test.js +264 -0
  9. package/dist/__tests__/stdlib-math.test.d.ts +7 -0
  10. package/dist/__tests__/stdlib-math.test.js +352 -0
  11. package/dist/__tests__/stdlib-vec.test.d.ts +4 -0
  12. package/dist/__tests__/stdlib-vec.test.js +264 -0
  13. package/dist/ast/types.d.ts +17 -1
  14. package/dist/codegen/mcfunction/index.js +159 -18
  15. package/dist/codegen/var-allocator.d.ts +17 -0
  16. package/dist/codegen/var-allocator.js +33 -3
  17. package/dist/compile.d.ts +14 -0
  18. package/dist/compile.js +62 -5
  19. package/dist/index.js +20 -1
  20. package/dist/ir/types.d.ts +4 -0
  21. package/dist/lexer/index.d.ts +1 -1
  22. package/dist/lexer/index.js +1 -0
  23. package/dist/lowering/index.d.ts +5 -0
  24. package/dist/lowering/index.js +83 -10
  25. package/dist/optimizer/dce.js +21 -5
  26. package/dist/optimizer/passes.js +18 -6
  27. package/dist/optimizer/structure.js +7 -0
  28. package/dist/parser/index.d.ts +5 -0
  29. package/dist/parser/index.js +43 -2
  30. package/dist/runtime/index.d.ts +6 -0
  31. package/dist/runtime/index.js +109 -9
  32. package/editors/vscode/package-lock.json +3 -3
  33. package/editors/vscode/package.json +1 -1
  34. package/package.json +1 -1
  35. package/src/__tests__/cli.test.ts +1 -1
  36. package/src/__tests__/codegen.test.ts +12 -6
  37. package/src/__tests__/e2e.test.ts +6 -6
  38. package/src/__tests__/lowering.test.ts +8 -8
  39. package/src/__tests__/optimizer.test.ts +33 -0
  40. package/src/__tests__/stdlib-advanced.test.ts +259 -0
  41. package/src/__tests__/stdlib-math.test.ts +374 -0
  42. package/src/__tests__/stdlib-vec.test.ts +259 -0
  43. package/src/ast/types.ts +11 -1
  44. package/src/codegen/mcfunction/index.ts +148 -19
  45. package/src/codegen/var-allocator.ts +36 -3
  46. package/src/compile.ts +72 -5
  47. package/src/index.ts +21 -1
  48. package/src/ir/types.ts +2 -0
  49. package/src/lexer/index.ts +2 -1
  50. package/src/lowering/index.ts +96 -10
  51. package/src/optimizer/dce.ts +22 -5
  52. package/src/optimizer/passes.ts +18 -5
  53. package/src/optimizer/structure.ts +6 -1
  54. package/src/parser/index.ts +47 -2
  55. package/src/runtime/index.ts +108 -10
  56. package/src/stdlib/advanced.mcrs +249 -0
  57. package/src/stdlib/math.mcrs +259 -19
  58. package/src/stdlib/vec.mcrs +246 -0
@@ -64,6 +64,7 @@ jobs:
64
64
  git config user.email "github-actions[bot]@users.noreply.github.com"
65
65
  git add editors/vscode/package.json editors/vscode/package-lock.json
66
66
  git commit -m "chore: auto-bump vscode extension to ${NEW_VER} [skip ci]" || echo "Nothing to commit"
67
+ git pull --rebase origin main || true
67
68
  git push
68
69
  id: bump
69
70
 
@@ -176,7 +176,7 @@ describe('CLI API', () => {
176
176
  const mainFn = result.files.find(file => file.path.endsWith('/main.mcfunction'));
177
177
  expect(doneFn?.content).toContain('scoreboard players get timer_ticks rs');
178
178
  expect(doneFn?.content).toContain('return run scoreboard players get');
179
- expect(mainFn?.content).toContain('execute if score $finished rs matches 1..');
179
+ expect(mainFn?.content).toContain('execute if score $main_finished rs matches 1..');
180
180
  });
181
181
  it('Timer.tick increments', () => {
182
182
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-timer-tick-'));
@@ -17,26 +17,32 @@ describe('generateDatapack', () => {
17
17
  expect(load?.content).toContain('scoreboard players set $counter rs 0');
18
18
  });
19
19
  it('generates function file for simple add(a, b)', () => {
20
+ // IR now uses { kind: 'param', index: i } for param-copy instructions,
21
+ // matching what the lowering emits. Pass mangle:false so we can check
22
+ // readable names without worrying about sequential mangled names.
20
23
  const mod = {
21
24
  namespace: 'mypack',
22
25
  globals: [],
23
26
  functions: [{
24
27
  name: 'add',
25
- params: ['a', 'b'],
26
- locals: ['a', 'b', 'result'],
28
+ params: ['$a', '$b'],
29
+ locals: ['$a', '$b', '$result'],
27
30
  blocks: [{
28
31
  label: 'entry',
29
32
  instrs: [
30
- { op: 'binop', dst: 'result', lhs: { kind: 'var', name: 'a' }, bop: '+', rhs: { kind: 'var', name: 'b' } },
33
+ // param-copy instructions (what the lowering now emits)
34
+ { op: 'assign', dst: '$a', src: { kind: 'param', index: 0 } },
35
+ { op: 'assign', dst: '$b', src: { kind: 'param', index: 1 } },
36
+ { op: 'binop', dst: '$result', lhs: { kind: 'var', name: '$a' }, bop: '+', rhs: { kind: 'var', name: '$b' } },
31
37
  ],
32
- term: { op: 'return', value: { kind: 'var', name: 'result' } },
38
+ term: { op: 'return', value: { kind: 'var', name: '$result' } },
33
39
  }],
34
40
  }],
35
41
  };
36
- const files = (0, mcfunction_1.generateDatapack)(mod);
42
+ const files = (0, mcfunction_1.generateDatapackWithStats)(mod, { mangle: false }).files;
37
43
  const fn = files.find(f => f.path.includes('add.mcfunction'));
38
44
  expect(fn).toBeDefined();
39
- // Should have param setup
45
+ // param setup emitted from the IR
40
46
  expect(fn.content).toContain('scoreboard players operation $a rs = $p0 rs');
41
47
  expect(fn.content).toContain('scoreboard players operation $b rs = $p1 rs');
42
48
  // Should have add operation
@@ -104,7 +104,7 @@ fn main() {
104
104
  `);
105
105
  const mainFn = getFunction(files, 'main');
106
106
  expect(mainFn).toBeDefined();
107
- expect(mainFn).toContain('scoreboard players set $hp rs 105');
107
+ expect(mainFn).toContain('scoreboard players set $main_hp rs 105');
108
108
  expect(mainFn).toContain('Arena Battle');
109
109
  });
110
110
  });
@@ -1683,7 +1683,7 @@ describe('for-range loop', () => {
1683
1683
  const src = `fn test() { for i in 0..5 { say("hi"); } }`;
1684
1684
  const files = compile(src, 'forloop');
1685
1685
  const fn = getFunction(files, 'test');
1686
- expect(fn).toContain('scoreboard players set $i rs 0');
1686
+ expect(fn).toContain('scoreboard players set $test_i rs 0');
1687
1687
  });
1688
1688
  it('generates loop sub-function with increment and condition', () => {
1689
1689
  const src = `fn test() { for i in 0..5 { say("hi"); } }`;
@@ -1691,16 +1691,16 @@ describe('for-range loop', () => {
1691
1691
  const subFn = files.find(f => f.path.includes('__for_0'));
1692
1692
  expect(subFn).toBeDefined();
1693
1693
  expect(subFn?.content).toContain('say hi');
1694
- expect(subFn?.content).toContain('scoreboard players add $i rs 1');
1695
- expect(subFn?.content).toContain('execute if score $i rs matches ..4 run function forloop:test/__for_0');
1694
+ expect(subFn?.content).toContain('scoreboard players add $test_i rs 1');
1695
+ expect(subFn?.content).toContain('execute if score $test_i rs matches ..4 run function forloop:test/__for_0');
1696
1696
  });
1697
1697
  it('supports non-zero start', () => {
1698
1698
  const src = `fn test() { for x in 3..8 { say("loop"); } }`;
1699
1699
  const files = compile(src, 'forloop2');
1700
1700
  const fn = getFunction(files, 'test');
1701
- expect(fn).toContain('scoreboard players set $x rs 3');
1701
+ expect(fn).toContain('scoreboard players set $test_x rs 3');
1702
1702
  const subFn = files.find(f => f.path.includes('__for_0'));
1703
- expect(subFn?.content).toContain('execute if score $x rs matches ..7 run function forloop2:test/__for_0');
1703
+ expect(subFn?.content).toContain('execute if score $test_x rs matches ..7 run function forloop2:test/__for_0');
1704
1704
  });
1705
1705
  });
1706
1706
  // ---------------------------------------------------------------------------
@@ -42,7 +42,7 @@ describe('Lowering', () => {
42
42
  const ir = compile('fn foo(x: int) {}');
43
43
  const fn = getFunction(ir, 'foo');
44
44
  const instrs = getInstructions(fn);
45
- expect(instrs.some(i => i.op === 'assign' && i.dst === '$x' && i.src.name === '$p0')).toBe(true);
45
+ expect(instrs.some(i => i.op === 'assign' && i.dst === '$foo_x' && i.src.kind === 'param' && i.src.index === 0)).toBe(true);
46
46
  });
47
47
  it('fills in missing default arguments at call sites', () => {
48
48
  const ir = compile(`
@@ -124,14 +124,14 @@ fn foo() {
124
124
  `);
125
125
  const fn = getFunction(ir, 'foo');
126
126
  const instrs = getInstructions(fn);
127
- expect(instrs.some(i => i.op === 'assign' && i.dst === '$x' && i.src.kind === 'const' && i.src.value === 100)).toBe(true);
127
+ expect(instrs.some(i => i.op === 'assign' && i.dst === '$foo_x' && i.src.kind === 'const' && i.src.value === 100)).toBe(true);
128
128
  expect(ir.globals).not.toContain('$MAX_HP');
129
129
  });
130
130
  it('lowers let with literal', () => {
131
131
  const ir = compile('fn foo() { let x: int = 42; }');
132
132
  const fn = getFunction(ir, 'foo');
133
133
  const instrs = getInstructions(fn);
134
- expect(instrs.some(i => i.op === 'assign' && i.dst === '$x' && i.src.value === 42)).toBe(true);
134
+ expect(instrs.some(i => i.op === 'assign' && i.dst === '$foo_x' && i.src.value === 42)).toBe(true);
135
135
  });
136
136
  it('lowers let with expression', () => {
137
137
  const ir = compile('fn foo(a: int) { let x: int = a + 1; }');
@@ -496,15 +496,15 @@ fn choose(dir: Direction) {
496
496
  const ir = compile('fn test() { let score: int = 7; say("You have ${score} points"); }');
497
497
  const fn = getFunction(ir, 'test');
498
498
  const rawCmds = getRawCommands(fn);
499
- expect(rawCmds).toContain('tellraw @a ["",{"text":"You have "},{"score":{"name":"$score","objective":"rs"}},{"text":" points"}]');
499
+ expect(rawCmds).toContain('tellraw @a ["",{"text":"You have "},{"score":{"name":"$test_score","objective":"rs"}},{"text":" points"}]');
500
500
  });
501
501
  it('lowers f-string output builtins to tellraw/title JSON components', () => {
502
502
  const ir = compile('fn test() { let score: int = 7; say(f"Score: {score}"); tellraw(@a, f"Score: {score}"); actionbar(@s, f"Score: {score}"); title(@s, f"Score: {score}"); }');
503
503
  const fn = getFunction(ir, 'test');
504
504
  const rawCmds = getRawCommands(fn);
505
- expect(rawCmds).toContain('tellraw @a ["",{"text":"Score: "},{"score":{"name":"$score","objective":"rs"}}]');
506
- expect(rawCmds).toContain('title @s actionbar ["",{"text":"Score: "},{"score":{"name":"$score","objective":"rs"}}]');
507
- expect(rawCmds).toContain('title @s title ["",{"text":"Score: "},{"score":{"name":"$score","objective":"rs"}}]');
505
+ expect(rawCmds).toContain('tellraw @a ["",{"text":"Score: "},{"score":{"name":"$test_score","objective":"rs"}}]');
506
+ expect(rawCmds).toContain('title @s actionbar ["",{"text":"Score: "},{"score":{"name":"$test_score","objective":"rs"}}]');
507
+ expect(rawCmds).toContain('title @s title ["",{"text":"Score: "},{"score":{"name":"$test_score","objective":"rs"}}]');
508
508
  });
509
509
  it('lowers summon()', () => {
510
510
  const ir = compile('fn test() { summon("zombie"); }');
@@ -987,7 +987,7 @@ fn count_down() {
987
987
  const ir = compile('let count: int = 0;\nfn test() { let y: int = count; }');
988
988
  const fn = getFunction(ir, 'test');
989
989
  const instrs = getInstructions(fn);
990
- expect(instrs.some(i => i.op === 'assign' && i.dst === '$y' && i.src.kind === 'var' && i.src.name === '$count')).toBe(true);
990
+ expect(instrs.some(i => i.op === 'assign' && i.dst === '$test_y' && i.src.kind === 'var' && i.src.name === '$count')).toBe(true);
991
991
  });
992
992
  it('writes global variable in function body', () => {
993
993
  const ir = compile('let count: int = 0;\nfn inc() { count = 5; }');
@@ -95,6 +95,37 @@ describe('deadCodeElimination', () => {
95
95
  expect(opt.blocks[0].instrs[0].dst).toBe('$used_by_raw');
96
96
  });
97
97
  });
98
+ describe('copyPropagation – stale alias invalidation', () => {
99
+ it('does not propagate $tmp = $y after $y is overwritten (swap pattern)', () => {
100
+ // Simulates: let tmp = y; y = x % y; x = tmp
101
+ // The copy $tmp = $y must be invalidated when $y is reassigned.
102
+ // Before fix: x = tmp was propagated to x = y (new y, wrong value).
103
+ const fn = makeFn([
104
+ { op: 'assign', dst: '$tmp', src: { kind: 'var', name: '$y' } }, // tmp = y
105
+ { op: 'binop', dst: '$r', lhs: { kind: 'var', name: '$x' }, bop: '%', rhs: { kind: 'var', name: '$y' } }, // r = x%y
106
+ { op: 'assign', dst: '$y', src: { kind: 'var', name: '$r' } }, // y = r ← stale: tmp still points to OLD y
107
+ { op: 'assign', dst: '$x', src: { kind: 'var', name: '$tmp' } }, // x = tmp (should NOT be x = y)
108
+ ]);
109
+ const opt = (0, passes_1.copyPropagation)(fn);
110
+ const instrs = opt.blocks[0].instrs;
111
+ const xAssign = instrs.find((i) => i.dst === '$x');
112
+ // x = tmp must NOT be optimised to x = $y (stale) or x = $r (new y).
113
+ // It should stay as x = $tmp (the original copy).
114
+ expect(xAssign.src).toEqual({ kind: 'var', name: '$tmp' });
115
+ });
116
+ it('still propagates simple non-conflicting copies', () => {
117
+ // a = 5; b = a; c = b → after propagation b and c should both be const 5
118
+ const fn = makeFn([
119
+ { op: 'assign', dst: '$a', src: { kind: 'const', value: 5 } },
120
+ { op: 'assign', dst: '$b', src: { kind: 'var', name: '$a' } },
121
+ { op: 'assign', dst: '$c', src: { kind: 'var', name: '$b' } },
122
+ ]);
123
+ const opt = (0, passes_1.copyPropagation)(fn);
124
+ const instrs = opt.blocks[0].instrs;
125
+ const cAssign = instrs.find((i) => i.dst === '$c');
126
+ expect(cAssign.src).toEqual({ kind: 'const', value: 5 });
127
+ });
128
+ });
98
129
  describe('optimize pipeline', () => {
99
130
  it('combines all passes', () => {
100
131
  // t0 = 2 + 3 (→ constant fold → t0 = 5)
@@ -0,0 +1,4 @@
1
+ /**
2
+ * stdlib/advanced.mcrs — runtime behavioural tests
3
+ */
4
+ export {};
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ /**
3
+ * stdlib/advanced.mcrs — runtime behavioural tests
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const compile_1 = require("../compile");
42
+ const runtime_1 = require("../runtime");
43
+ const MATH_SRC = fs.readFileSync(path.join(__dirname, '../../src/stdlib/math.mcrs'), 'utf-8');
44
+ const ADV_SRC = fs.readFileSync(path.join(__dirname, '../../src/stdlib/advanced.mcrs'), 'utf-8');
45
+ function run(driver) {
46
+ const result = (0, compile_1.compile)(driver, {
47
+ namespace: 'advtest',
48
+ librarySources: [MATH_SRC, ADV_SRC],
49
+ });
50
+ if (!result.success)
51
+ throw new Error(result.error?.message ?? 'compile failed');
52
+ const rt = new runtime_1.MCRuntime('advtest');
53
+ for (const file of result.files ?? []) {
54
+ if (!file.path.endsWith('.mcfunction'))
55
+ continue;
56
+ const match = file.path.match(/data\/([^/]+)\/function\/(.+)\.mcfunction$/);
57
+ if (!match)
58
+ continue;
59
+ rt.loadFunction(`${match[1]}:${match[2]}`, file.content.split('\n'));
60
+ }
61
+ rt.load();
62
+ return rt;
63
+ }
64
+ function scoreOf(rt, key) {
65
+ return rt.getScore('out', `advtest.${key}`);
66
+ }
67
+ // ─── fib ─────────────────────────────────────────────────────────────────────
68
+ describe('fib', () => {
69
+ it.each([
70
+ [0, 0],
71
+ [1, 1],
72
+ [2, 1],
73
+ [5, 5],
74
+ [10, 55],
75
+ [20, 6765],
76
+ ])('fib(%d) == %d', (n, expected) => {
77
+ const rt = run(`fn test() { scoreboard_set("out", "r", fib(${n})); }`);
78
+ rt.execFunction('test');
79
+ expect(scoreOf(rt, 'r')).toBe(expected);
80
+ });
81
+ });
82
+ // ─── is_prime ─────────────────────────────────────────────────────────────────
83
+ describe('is_prime', () => {
84
+ it.each([
85
+ [0, 0], [1, 0], [2, 1], [3, 1], [4, 0],
86
+ [7, 1], [9, 0], [11, 1], [97, 1], [100, 0],
87
+ ])('is_prime(%d) == %d', (n, expected) => {
88
+ const rt = run(`fn test() { scoreboard_set("out", "r", is_prime(${n})); }`);
89
+ rt.execFunction('test');
90
+ expect(scoreOf(rt, 'r')).toBe(expected);
91
+ });
92
+ });
93
+ // ─── collatz_steps ───────────────────────────────────────────────────────────
94
+ describe('collatz_steps', () => {
95
+ it.each([
96
+ [1, 0],
97
+ [2, 1],
98
+ [4, 2],
99
+ [6, 8],
100
+ [27, 111],
101
+ ])('collatz_steps(%d) == %d', (n, expected) => {
102
+ const rt = run(`fn test() { scoreboard_set("out", "r", collatz_steps(${n})); }`);
103
+ rt.execFunction('test');
104
+ expect(scoreOf(rt, 'r')).toBe(expected);
105
+ });
106
+ });
107
+ // ─── digit helpers ───────────────────────────────────────────────────────────
108
+ describe('digit_sum', () => {
109
+ it.each([
110
+ [0, 0], [1, 1], [9, 9], [123, 6], [999, 27], [-42, 6],
111
+ ])('digit_sum(%d) == %d', (n, expected) => {
112
+ const rt = run(`fn test() { scoreboard_set("out", "r", digit_sum(${n})); }`);
113
+ rt.execFunction('test');
114
+ expect(scoreOf(rt, 'r')).toBe(expected);
115
+ });
116
+ });
117
+ describe('count_digits', () => {
118
+ it.each([
119
+ [0, 1], [9, 1], [10, 2], [100, 3], [9999, 4],
120
+ ])('count_digits(%d) == %d', (n, expected) => {
121
+ const rt = run(`fn test() { scoreboard_set("out", "r", count_digits(${n})); }`);
122
+ rt.execFunction('test');
123
+ expect(scoreOf(rt, 'r')).toBe(expected);
124
+ });
125
+ });
126
+ describe('reverse_int', () => {
127
+ it.each([
128
+ [12345, 54321],
129
+ [100, 1],
130
+ [7, 7],
131
+ ])('reverse_int(%d) == %d', (n, expected) => {
132
+ const rt = run(`fn test() { scoreboard_set("out", "r", reverse_int(${n})); }`);
133
+ rt.execFunction('test');
134
+ expect(scoreOf(rt, 'r')).toBe(expected);
135
+ });
136
+ });
137
+ // ─── mod_pow ─────────────────────────────────────────────────────────────────
138
+ describe('mod_pow', () => {
139
+ it.each([
140
+ [2, 10, 1000, 24], // 2^10 = 1024, 1024 mod 1000 = 24
141
+ [3, 4, 100, 81], // 3^4 = 81
142
+ [2, 0, 10, 1], // any^0 = 1
143
+ [5, 1, 100, 5],
144
+ [7, 3, 13, 343 % 13], // 343 mod 13 = 5
145
+ ])('mod_pow(%d,%d,%d) == %d', (b, e, m, expected) => {
146
+ const rt = run(`fn test() { scoreboard_set("out", "r", mod_pow(${b},${e},${m})); }`);
147
+ rt.execFunction('test');
148
+ expect(scoreOf(rt, 'r')).toBe(expected);
149
+ });
150
+ });
151
+ // ─── hash_int ────────────────────────────────────────────────────────────────
152
+ describe('hash_int', () => {
153
+ it('is deterministic', () => {
154
+ const rt1 = run(`fn test() { scoreboard_set("out", "r", hash_int(42)); }`);
155
+ rt1.execFunction('test');
156
+ const rt2 = run(`fn test() { scoreboard_set("out", "r", hash_int(42)); }`);
157
+ rt2.execFunction('test');
158
+ expect(scoreOf(rt1, 'r')).toBe(scoreOf(rt2, 'r'));
159
+ });
160
+ it('different inputs → different outputs', () => {
161
+ const rt = run(`fn test() {
162
+ scoreboard_set("out", "a", hash_int(0));
163
+ scoreboard_set("out", "b", hash_int(1));
164
+ scoreboard_set("out", "c", hash_int(1000));
165
+ }`);
166
+ rt.execFunction('test');
167
+ const a = scoreOf(rt, 'a');
168
+ const b = scoreOf(rt, 'b');
169
+ const c = scoreOf(rt, 'c');
170
+ expect(a).not.toBe(b);
171
+ expect(b).not.toBe(c);
172
+ });
173
+ it('output is non-negative', () => {
174
+ for (const n of [-1000, -1, 0, 1, 999, 46340]) {
175
+ const rt = run(`fn test() { scoreboard_set("out", "r", hash_int(${n})); }`);
176
+ rt.execFunction('test');
177
+ expect(scoreOf(rt, 'r')).toBeGreaterThanOrEqual(0);
178
+ }
179
+ });
180
+ });
181
+ // ─── noise1d ─────────────────────────────────────────────────────────────────
182
+ describe('noise1d', () => {
183
+ it('output in [0, 999]', () => {
184
+ for (const x of [0, 100, 500, 999, 1000, 2000]) {
185
+ const rt = run(`fn test() { scoreboard_set("out", "r", noise1d(${x})); }`);
186
+ rt.execFunction('test');
187
+ const v = scoreOf(rt, 'r');
188
+ expect(v).toBeGreaterThanOrEqual(0);
189
+ expect(v).toBeLessThan(1000);
190
+ }
191
+ });
192
+ it('is deterministic', () => {
193
+ const rt1 = run(`fn test() { scoreboard_set("out", "r", noise1d(1234)); }`);
194
+ rt1.execFunction('test');
195
+ const rt2 = run(`fn test() { scoreboard_set("out", "r", noise1d(1234)); }`);
196
+ rt2.execFunction('test');
197
+ expect(scoreOf(rt1, 'r')).toBe(scoreOf(rt2, 'r'));
198
+ });
199
+ });
200
+ // ─── bezier ──────────────────────────────────────────────────────────────────
201
+ describe('bezier_quad', () => {
202
+ it.each([
203
+ [0, 500, 1000, 0, 0], // t=0: start
204
+ [0, 500, 1000, 1000, 1000], // t=1000: end
205
+ [0, 1000, 0, 500, 500], // arch midpoint
206
+ ])('bezier_quad(%d,%d,%d,t=%d) == %d', (p0, p1, p2, t, expected) => {
207
+ const rt = run(`fn test() { scoreboard_set("out", "r", bezier_quad(${p0},${p1},${p2},${t})); }`);
208
+ rt.execFunction('test');
209
+ expect(scoreOf(rt, 'r')).toBe(expected);
210
+ });
211
+ });
212
+ describe('bezier_cubic', () => {
213
+ it('t=0: start', () => {
214
+ const rt = run(`fn test() { scoreboard_set("out", "r", bezier_cubic(0,333,667,1000,0)); }`);
215
+ rt.execFunction('test');
216
+ expect(scoreOf(rt, 'r')).toBe(0);
217
+ });
218
+ it('t=1000: end', () => {
219
+ const rt = run(`fn test() { scoreboard_set("out", "r", bezier_cubic(0,333,667,1000,1000)); }`);
220
+ rt.execFunction('test');
221
+ expect(scoreOf(rt, 'r')).toBe(1000);
222
+ });
223
+ });
224
+ // ─── mandelbrot_iter ─────────────────────────────────────────────────────────
225
+ describe('mandelbrot_iter', () => {
226
+ // c = 0 + 0i → always in set (z always 0)
227
+ it('origin (0,0) stays bounded for max_iter=20', () => {
228
+ const rt = run(`fn test() { scoreboard_set("out", "r", mandelbrot_iter(0,0,20)); }`);
229
+ rt.execFunction('test');
230
+ expect(scoreOf(rt, 'r')).toBe(20);
231
+ });
232
+ // c = 2 + 0i → escapes immediately (|z1| = 2, |z2| = 6 > 2)
233
+ it('c=(2000,0) escapes quickly', () => {
234
+ const rt = run(`fn test() { scoreboard_set("out", "r", mandelbrot_iter(2000,0,50)); }`);
235
+ rt.execFunction('test');
236
+ expect(scoreOf(rt, 'r')).toBeLessThan(5);
237
+ });
238
+ // c = -1 + 0i → stays bounded (period-2 cycle: 0 → -1 → 0 → ...)
239
+ it('c=(-1000,0) is in the set', () => {
240
+ const rt = run(`fn test() { scoreboard_set("out", "r", mandelbrot_iter(-1000,0,50)); }`);
241
+ rt.execFunction('test');
242
+ expect(scoreOf(rt, 'r')).toBe(50);
243
+ });
244
+ // c = 0.5 + 0.5i → escapes
245
+ it('c=(500,500) escapes before max_iter=100', () => {
246
+ const rt = run(`fn test() { scoreboard_set("out", "r", mandelbrot_iter(500,500,100)); }`);
247
+ rt.execFunction('test');
248
+ expect(scoreOf(rt, 'r')).toBeLessThan(100);
249
+ });
250
+ });
251
+ // ─── julia_iter ──────────────────────────────────────────────────────────────
252
+ describe('julia_iter', () => {
253
+ it('z0=(0,0) with c=(0,0) stays bounded', () => {
254
+ const rt = run(`fn test() { scoreboard_set("out", "r", julia_iter(0,0,0,0,20)); }`);
255
+ rt.execFunction('test');
256
+ expect(scoreOf(rt, 'r')).toBe(20);
257
+ });
258
+ it('z0=(3000,0) escapes immediately', () => {
259
+ const rt = run(`fn test() { scoreboard_set("out", "r", julia_iter(3000,0,0,0,20)); }`);
260
+ rt.execFunction('test');
261
+ expect(scoreOf(rt, 'r')).toBe(0);
262
+ });
263
+ });
264
+ //# sourceMappingURL=stdlib-advanced.test.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * stdlib/math.mcrs — Runtime behavioural tests
3
+ *
4
+ * Each test compiles the math stdlib together with a small driver function,
5
+ * runs it through MCRuntime, and checks scoreboard values.
6
+ */
7
+ export {};