redscript-mc 2.2.1 → 2.4.0

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 (82) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +18 -2
  3. package/dist/src/__tests__/array-dynamic.test.d.ts +12 -0
  4. package/dist/src/__tests__/array-dynamic.test.js +131 -0
  5. package/dist/src/__tests__/array-write.test.d.ts +11 -0
  6. package/dist/src/__tests__/array-write.test.js +149 -0
  7. package/dist/src/__tests__/tuner/engine.test.d.ts +4 -0
  8. package/dist/src/__tests__/tuner/engine.test.js +232 -0
  9. package/dist/src/ast/types.d.ts +7 -0
  10. package/dist/src/emit/modules.js +5 -0
  11. package/dist/src/hir/lower.js +29 -0
  12. package/dist/src/hir/monomorphize.js +2 -0
  13. package/dist/src/hir/types.d.ts +9 -2
  14. package/dist/src/lir/lower.js +131 -0
  15. package/dist/src/mir/lower.js +73 -3
  16. package/dist/src/mir/macro.js +5 -0
  17. package/dist/src/mir/types.d.ts +12 -0
  18. package/dist/src/mir/verify.js +7 -0
  19. package/dist/src/optimizer/copy_prop.js +5 -0
  20. package/dist/src/optimizer/coroutine.js +12 -0
  21. package/dist/src/optimizer/dce.js +9 -0
  22. package/dist/src/optimizer/unroll.js +3 -0
  23. package/dist/src/parser/index.js +5 -0
  24. package/dist/src/tuner/adapters/ln-polynomial.d.ts +23 -0
  25. package/dist/src/tuner/adapters/ln-polynomial.js +142 -0
  26. package/dist/src/tuner/adapters/sqrt-newton.d.ts +28 -0
  27. package/dist/src/tuner/adapters/sqrt-newton.js +125 -0
  28. package/dist/src/tuner/cli.d.ts +5 -0
  29. package/dist/src/tuner/cli.js +168 -0
  30. package/dist/src/tuner/engine.d.ts +17 -0
  31. package/dist/src/tuner/engine.js +215 -0
  32. package/dist/src/tuner/metrics.d.ts +15 -0
  33. package/dist/src/tuner/metrics.js +51 -0
  34. package/dist/src/tuner/simulator.d.ts +35 -0
  35. package/dist/src/tuner/simulator.js +78 -0
  36. package/dist/src/tuner/types.d.ts +32 -0
  37. package/dist/src/tuner/types.js +6 -0
  38. package/dist/src/typechecker/index.js +5 -0
  39. package/docs/STDLIB_ROADMAP.md +142 -0
  40. package/editors/vscode/package-lock.json +3 -3
  41. package/editors/vscode/package.json +1 -1
  42. package/package.json +1 -1
  43. package/src/__tests__/array-dynamic.test.ts +147 -0
  44. package/src/__tests__/array-write.test.ts +169 -0
  45. package/src/__tests__/tuner/engine.test.ts +260 -0
  46. package/src/ast/types.ts +1 -0
  47. package/src/emit/modules.ts +5 -0
  48. package/src/hir/lower.ts +30 -0
  49. package/src/hir/monomorphize.ts +2 -0
  50. package/src/hir/types.ts +3 -1
  51. package/src/lir/lower.ts +151 -0
  52. package/src/mir/lower.ts +75 -3
  53. package/src/mir/macro.ts +5 -0
  54. package/src/mir/types.ts +2 -0
  55. package/src/mir/verify.ts +7 -0
  56. package/src/optimizer/copy_prop.ts +5 -0
  57. package/src/optimizer/coroutine.ts +9 -0
  58. package/src/optimizer/dce.ts +6 -0
  59. package/src/optimizer/unroll.ts +3 -0
  60. package/src/parser/index.ts +9 -0
  61. package/src/stdlib/bigint.mcrs +155 -192
  62. package/src/stdlib/bits.mcrs +158 -0
  63. package/src/stdlib/color.mcrs +160 -0
  64. package/src/stdlib/geometry.mcrs +124 -0
  65. package/src/stdlib/list.mcrs +96 -0
  66. package/src/stdlib/math.mcrs +227 -0
  67. package/src/stdlib/math_hp.mcrs +65 -0
  68. package/src/stdlib/random.mcrs +67 -0
  69. package/src/stdlib/signal.mcrs +112 -0
  70. package/src/stdlib/timer.mcrs +32 -0
  71. package/src/stdlib/vec.mcrs +27 -0
  72. package/src/tuner/adapters/ln-polynomial.ts +147 -0
  73. package/src/tuner/adapters/sqrt-newton.ts +135 -0
  74. package/src/tuner/cli.ts +158 -0
  75. package/src/tuner/engine.ts +272 -0
  76. package/src/tuner/metrics.ts +66 -0
  77. package/src/tuner/simulator.ts +69 -0
  78. package/src/tuner/types.ts +44 -0
  79. package/src/typechecker/index.ts +6 -0
  80. package/docs/ARCHITECTURE.zh.md +0 -1088
  81. package/docs/COMPILATION_STATS.md +0 -142
  82. package/docs/IMPLEMENTATION_GUIDE.md +0 -512
package/CHANGELOG.md CHANGED
@@ -2,6 +2,37 @@
2
2
 
3
3
  All notable changes to RedScript will be documented in this file.
4
4
 
5
+ ## [2.4.0] - 2026-03-17
6
+
7
+ ### Added
8
+ - Dynamic array index read: `arr[i]` where `i` is a variable (MC Function Macro, MC 1.20.2+)
9
+ - Dynamic array index write: `arr[i] = val`, `arr[i] += val` compound assignment
10
+ - `list_push(arr, val)` / `list_pop(arr)` / `list_len(arr)` builtins for NBT array manipulation
11
+
12
+ ### Known Limitations
13
+ - Array parameters in function calls do not pass the array by reference yet; use `while` loops with dynamic index directly in the calling scope
14
+ - `for` loops with dynamic array access may incorrectly inline when loop bounds are constants; use `while` loops instead
15
+
16
+ ## [2.3.0] - 2026-03-17
17
+
18
+ ### Added
19
+ - `stdlib/math.mcrs`: `ln` (SA-tuned atanh series, max_error < 0.0006), `sqrt_fx` (×10000 scale), `exp_fx` (Horner Taylor + 2^k scaling)
20
+ - `stdlib/math_hp.mcrs`: `sin_hp`/`cos_hp` using MC entity rotation trick (double precision), `init_trig()` bootstrap
21
+ - `stdlib/random.mcrs`: LCG (`next_lcg`, `random_range`, `random_bool`) + PCG (`pcg_next_lo/hi`, `pcg_output`)
22
+ - `stdlib/color.mcrs`: RGB packing/unpacking, `rgb_lerp`, HSL↔RGB conversion (`hsl_to_r/g/b`, `rgb_to_h/s/l`)
23
+ - `stdlib/bits.mcrs`: bitwise AND/OR/XOR/NOT, left/right shift, bit get/set/clear/toggle, popcount (all integer-simulated)
24
+ - `stdlib/list.mcrs`: `sort3`, min/max/avg for 3 and 5 values, weighted choice utilities
25
+ - `stdlib/geometry.mcrs`: AABB/sphere/cylinder contains checks, parabola physics, grid/tile helpers, angle normalization, MC sun angle
26
+ - `stdlib/signal.mcrs`: uniform, normal (12-sample approximation), exponential distribution, bernoulli trial, weighted2/3 choice
27
+ - `stdlib/bigint.mcrs`: 96-bit base-10000 arithmetic (add, sub, mul, div, cmp, int32↔bigint3 conversion)
28
+ - `src/tuner/`: hyperparameter search framework (Nelder-Mead + Simulated Annealing) for stdlib coefficient optimization
29
+ - `adapters/ln-polynomial.ts`: atanh series adapter
30
+ - `adapters/sqrt-newton.ts`: Newton iteration adapter
31
+ - CLI: `redscript tune --adapter <name> [--strategy nm|sa] [--budget N] [--out path]`
32
+
33
+ ### Changed
34
+ - `stdlib/vec.mcrs`: added 2D/3D add/sub/scale/neg component helpers
35
+
5
36
  ## [2.1.1] - 2026-03-16
6
37
 
7
38
  ### Added
package/README.md CHANGED
@@ -306,8 +306,16 @@ redscript validate <file> Validate MC commands
306
306
  RedScript ships a built-in standard library. Use the short form — no path needed:
307
307
 
308
308
  ```rs
309
- import "stdlib/math" // fixed-point math
310
- import "stdlib/vec" // 2D/3D vector geometry
309
+ import "stdlib/math" // fixed-point math: ln, sqrt_fx, exp_fx, sin_fixed, cos_fixed...
310
+ import "stdlib/math_hp" // high-precision trig via entity rotation (init_trig required)
311
+ import "stdlib/vec" // 2D/3D vector: dot, cross, length, distance, atan2, rotate...
312
+ import "stdlib/random" // LCG & PCG random number generators
313
+ import "stdlib/color" // RGB/HSL color packing, blending, conversion
314
+ import "stdlib/bits" // bitwise AND/OR/XOR/NOT/shift/popcount (integer-simulated)
315
+ import "stdlib/list" // sort3, min/max/avg, weighted utilities
316
+ import "stdlib/geometry" // AABB/sphere contains, parabola physics, angle helpers
317
+ import "stdlib/signal" // normal/exponential distributions, bernoulli, weighted choice
318
+ import "stdlib/bigint" // 96-bit base-10000 arithmetic (add/sub/mul/div/cmp)
311
319
  import "stdlib/combat" // damage, kill-check helpers
312
320
  import "stdlib/player" // health, alive check, range
313
321
  import "stdlib/cooldown" // per-player cooldown tracking
@@ -319,6 +327,8 @@ Custom library paths can be added with `--include <dir>` so your own modules wor
319
327
 
320
328
  All stdlib files use `module library;` — only the functions you actually call are compiled in.
321
329
 
330
+ > Parts of the standard library are inspired by [kaer-3058/large_number](https://github.com/kaer-3058/large_number), a comprehensive math library for Minecraft datapacks.
331
+
322
332
  ```rs
323
333
  import "stdlib/math" // abs, sign, min, max, clamp, lerp, isqrt, sqrt_fixed,
324
334
  // pow_int, gcd, lcm, sin_fixed, cos_fixed, map, ceil_div,
@@ -444,6 +454,12 @@ See [CHANGELOG.md](./CHANGELOG.md) for the full release notes.
444
454
 
445
455
  ---
446
456
 
457
+ ## Acknowledgements
458
+
459
+ Parts of the standard library are inspired by [kaer-3058/large_number](https://github.com/kaer-3058/large_number), a comprehensive math library for Minecraft datapacks. RedScript provides a higher-level, type-safe API over similar algorithms.
460
+
461
+ ---
462
+
447
463
  <div align="center">
448
464
 
449
465
  MIT License · Copyright © 2026 [bkmashiro](https://github.com/bkmashiro)
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Tests for dynamic array index read: arr[i] where i is a variable.
3
+ *
4
+ * Covers:
5
+ * - MIR: nbt_read_dynamic instruction is emitted instead of falling back to
6
+ * copy(obj) (which returned the array length, not the value)
7
+ * - LIR/Emit: generates a macro helper function and calls it with
8
+ * `function ns:__dyn_idx_... with storage rs:macro_args`
9
+ * - The generated .mcfunction contains 'with storage' (function macro call)
10
+ * - The helper function contains the $return macro line
11
+ */
12
+ export {};
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for dynamic array index read: arr[i] where i is a variable.
4
+ *
5
+ * Covers:
6
+ * - MIR: nbt_read_dynamic instruction is emitted instead of falling back to
7
+ * copy(obj) (which returned the array length, not the value)
8
+ * - LIR/Emit: generates a macro helper function and calls it with
9
+ * `function ns:__dyn_idx_... with storage rs:macro_args`
10
+ * - The generated .mcfunction contains 'with storage' (function macro call)
11
+ * - The helper function contains the $return macro line
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ const compile_1 = require("../emit/compile");
15
+ // Helper: find file in compiled output by path substring
16
+ function getFile(files, pathSubstr) {
17
+ const f = files.find(f => f.path.includes(pathSubstr));
18
+ return f?.content;
19
+ }
20
+ // Helper: get the content of the function file for `fnName` in namespace
21
+ function getFunctionBody(files, fnName, ns = 'test') {
22
+ const content = getFile(files, `${fnName}.mcfunction`);
23
+ if (!content) {
24
+ // list files for debug
25
+ const paths = files.map(f => f.path).join('\n');
26
+ throw new Error(`Function '${fnName}' not found in output. Files:\n${paths}`);
27
+ }
28
+ return content;
29
+ }
30
+ describe('Dynamic array index read: arr[i]', () => {
31
+ const src = `
32
+ fn test() {
33
+ let nums: int[] = [10, 20, 30, 40, 50];
34
+ let i: int = 2;
35
+ i = i + 1;
36
+ let v: int = nums[i];
37
+ scoreboard_set("#out", "test", v);
38
+ }
39
+ `;
40
+ let files;
41
+ beforeAll(() => {
42
+ const result = (0, compile_1.compile)(src, { namespace: 'test' });
43
+ files = result.files;
44
+ });
45
+ test('compiles without error', () => {
46
+ // beforeAll would have thrown if compilation failed
47
+ expect(files.length).toBeGreaterThan(0);
48
+ });
49
+ test('test function contains "with storage" (macro call)', () => {
50
+ const body = getFunctionBody(files, 'test');
51
+ expect(body).toContain('with storage');
52
+ });
53
+ test('test function does NOT contain fallback "scoreboard players set #out test 5"', () => {
54
+ // Old fallback would copy the array length (5 elements) as the result
55
+ const body = getFunctionBody(files, 'test');
56
+ expect(body).not.toContain('scoreboard players set #out test 5');
57
+ });
58
+ test('a macro helper function is generated for the array', () => {
59
+ // Should have a function file matching __dyn_idx_
60
+ const helperFile = files.find(f => f.path.includes('__dyn_idx_'));
61
+ expect(helperFile).toBeDefined();
62
+ });
63
+ test('macro helper function contains $return run data get', () => {
64
+ const helperFile = files.find(f => f.path.includes('__dyn_idx_'));
65
+ expect(helperFile).toBeDefined();
66
+ expect(helperFile.content).toContain('$return run data get');
67
+ expect(helperFile.content).toContain('$(arr_idx)');
68
+ });
69
+ test('macro helper function references the correct array path (nums)', () => {
70
+ const helperFile = files.find(f => f.path.includes('__dyn_idx_'));
71
+ expect(helperFile).toBeDefined();
72
+ expect(helperFile.content).toContain('nums[$(arr_idx)]');
73
+ });
74
+ test('test function stores index to rs:macro_args', () => {
75
+ const body = getFunctionBody(files, 'test');
76
+ // Should store the index value into rs:macro_args arr_idx
77
+ expect(body).toContain('rs:macro_args');
78
+ });
79
+ });
80
+ describe('Dynamic array index: constant index still uses direct nbt_read', () => {
81
+ const src = `
82
+ fn test_const() {
83
+ let nums: int[] = [10, 20, 30];
84
+ let v: int = nums[1];
85
+ scoreboard_set("#out", "test", v);
86
+ }
87
+ `;
88
+ let files;
89
+ beforeAll(() => {
90
+ const result = (0, compile_1.compile)(src, { namespace: 'test' });
91
+ files = result.files;
92
+ });
93
+ test('constant index does NOT generate macro call (uses direct data get)', () => {
94
+ const body = getFunctionBody(files, 'test_const');
95
+ // Direct nbt_read emits store_nbt_to_score → execute store result score ... run data get ...
96
+ // without 'with storage'
97
+ expect(body).not.toContain('with storage');
98
+ expect(body).toContain('data get storage');
99
+ expect(body).toContain('nums[1]');
100
+ });
101
+ });
102
+ describe('Dynamic array index: multiple arrays, separate helpers', () => {
103
+ const src = `
104
+ fn test_multi() {
105
+ let a: int[] = [1, 2, 3];
106
+ let b: int[] = [10, 20, 30];
107
+ let i: int = 1;
108
+ i = i + 0;
109
+ let va: int = a[i];
110
+ let vb: int = b[i];
111
+ scoreboard_set("#va", "test", va);
112
+ scoreboard_set("#vb", "test", vb);
113
+ }
114
+ `;
115
+ let files;
116
+ beforeAll(() => {
117
+ const result = (0, compile_1.compile)(src, { namespace: 'test' });
118
+ files = result.files;
119
+ });
120
+ test('two separate macro helpers are generated for arrays a and b', () => {
121
+ const helperFiles = files.filter(f => f.path.includes('__dyn_idx_'));
122
+ expect(helperFiles.length).toBe(2);
123
+ });
124
+ test('each helper references its respective array path', () => {
125
+ const helperFiles = files.filter(f => f.path.includes('__dyn_idx_'));
126
+ const contents = helperFiles.map(f => f.content).join('\n');
127
+ expect(contents).toContain('a[$(arr_idx)]');
128
+ expect(contents).toContain('b[$(arr_idx)]');
129
+ });
130
+ });
131
+ //# sourceMappingURL=array-dynamic.test.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tests for array index write: arr[i] = val (constant and dynamic index).
3
+ *
4
+ * Covers:
5
+ * - Parser: arr[i] = val parses as index_assign (no "Expected ';'" error)
6
+ * - MIR: constant index → nbt_write, dynamic index → nbt_write_dynamic
7
+ * - LIR/Emit: constant index uses store_score_to_nbt to path[N]
8
+ * dynamic index generates a macro helper function for write
9
+ * - Compound assignments: arr[i] += 5 desugars to read + write
10
+ */
11
+ export {};
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for array index write: arr[i] = val (constant and dynamic index).
4
+ *
5
+ * Covers:
6
+ * - Parser: arr[i] = val parses as index_assign (no "Expected ';'" error)
7
+ * - MIR: constant index → nbt_write, dynamic index → nbt_write_dynamic
8
+ * - LIR/Emit: constant index uses store_score_to_nbt to path[N]
9
+ * dynamic index generates a macro helper function for write
10
+ * - Compound assignments: arr[i] += 5 desugars to read + write
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ const compile_1 = require("../emit/compile");
14
+ // Helper: find file in compiled output by path substring
15
+ function getFile(files, pathSubstr) {
16
+ const f = files.find(f => f.path.includes(pathSubstr));
17
+ return f?.content;
18
+ }
19
+ // Helper: get the content of the function file for `fnName` in namespace
20
+ function getFunctionBody(files, fnName, ns = 'test') {
21
+ const content = getFile(files, `${fnName}.mcfunction`);
22
+ if (!content) {
23
+ const paths = files.map(f => f.path).join('\n');
24
+ throw new Error(`Function '${fnName}' not found in output. Files:\n${paths}`);
25
+ }
26
+ return content;
27
+ }
28
+ // ---------------------------------------------------------------------------
29
+ // Constant index write: arr[1] = 99
30
+ // ---------------------------------------------------------------------------
31
+ describe('Constant index write: arr[1] = 99', () => {
32
+ const src = `
33
+ fn test() {
34
+ let nums: int[] = [10, 20, 30];
35
+ nums[1] = 99;
36
+ scoreboard_set("#out", "test", nums[1]);
37
+ }
38
+ `;
39
+ let files;
40
+ beforeAll(() => {
41
+ const result = (0, compile_1.compile)(src, { namespace: 'test' });
42
+ files = result.files;
43
+ });
44
+ test('compiles without error', () => {
45
+ expect(files.length).toBeGreaterThan(0);
46
+ });
47
+ test('test function contains nbt store to array path [1] (constant write)', () => {
48
+ const body = getFunctionBody(files, 'test');
49
+ // Should write to path like "nums[1]" via execute store result storage
50
+ expect(body).toMatch(/nums\[1\]/);
51
+ });
52
+ test('test function reads back from nums[1] after writing', () => {
53
+ const body = getFunctionBody(files, 'test');
54
+ // Should also read nums[1] for scoreboard_set
55
+ expect(body).toMatch(/nums\[1\]/);
56
+ });
57
+ });
58
+ // ---------------------------------------------------------------------------
59
+ // Dynamic index write: arr[i] = 99
60
+ // ---------------------------------------------------------------------------
61
+ describe('Dynamic index write: arr[i] = 99', () => {
62
+ const src = `
63
+ fn test() {
64
+ let nums: int[] = [10, 20, 30];
65
+ let i: int = 1;
66
+ nums[i] = 99;
67
+ scoreboard_set("#out", "test", nums[i]);
68
+ }
69
+ `;
70
+ let files;
71
+ beforeAll(() => {
72
+ const result = (0, compile_1.compile)(src, { namespace: 'test' });
73
+ files = result.files;
74
+ });
75
+ test('compiles without error', () => {
76
+ expect(files.length).toBeGreaterThan(0);
77
+ });
78
+ test('test function contains "with storage" (macro call for write)', () => {
79
+ const body = getFunctionBody(files, 'test');
80
+ expect(body).toContain('with storage');
81
+ });
82
+ test('a __dyn_wrt_ helper function is generated', () => {
83
+ const helperFile = files.find(f => f.path.includes('__dyn_wrt_'));
84
+ expect(helperFile).toBeDefined();
85
+ });
86
+ test('the write helper contains a macro line with arr_idx and arr_val', () => {
87
+ const helperFile = files.find(f => f.path.includes('__dyn_wrt_'));
88
+ expect(helperFile).toBeDefined();
89
+ expect(helperFile.content).toContain('$(arr_idx)');
90
+ expect(helperFile.content).toContain('$(arr_val)');
91
+ });
92
+ test('the write helper uses data modify set value', () => {
93
+ const helperFile = files.find(f => f.path.includes('__dyn_wrt_'));
94
+ expect(helperFile.content).toContain('data modify storage');
95
+ expect(helperFile.content).toContain('set value');
96
+ });
97
+ });
98
+ // ---------------------------------------------------------------------------
99
+ // Compound assignment: arr[i] += 5
100
+ // ---------------------------------------------------------------------------
101
+ describe('Compound index assignment: arr[i] += 5', () => {
102
+ const src = `
103
+ fn test() {
104
+ let nums: int[] = [10, 20, 30];
105
+ let i: int = 0;
106
+ nums[i] += 5;
107
+ scoreboard_set("#out", "test", nums[i]);
108
+ }
109
+ `;
110
+ let files;
111
+ beforeAll(() => {
112
+ const result = (0, compile_1.compile)(src, { namespace: 'test' });
113
+ files = result.files;
114
+ });
115
+ test('compiles without error', () => {
116
+ expect(files.length).toBeGreaterThan(0);
117
+ });
118
+ test('compound assignment generates both read and write macro calls', () => {
119
+ const body = getFunctionBody(files, 'test');
120
+ // Should call with storage at least twice (read for += and write + scoreboard_set read)
121
+ const matches = (body.match(/with storage/g) || []).length;
122
+ expect(matches).toBeGreaterThanOrEqual(1);
123
+ });
124
+ });
125
+ // ---------------------------------------------------------------------------
126
+ // Constant compound assignment: arr[0] += 5
127
+ // ---------------------------------------------------------------------------
128
+ describe('Constant compound index assignment: arr[0] += 5', () => {
129
+ const src = `
130
+ fn test() {
131
+ let nums: int[] = [10, 20, 30];
132
+ nums[0] += 5;
133
+ scoreboard_set("#out", "test", nums[0]);
134
+ }
135
+ `;
136
+ let files;
137
+ beforeAll(() => {
138
+ const result = (0, compile_1.compile)(src, { namespace: 'test' });
139
+ files = result.files;
140
+ });
141
+ test('compiles without error', () => {
142
+ expect(files.length).toBeGreaterThan(0);
143
+ });
144
+ test('test function contains array path [0] for read and write', () => {
145
+ const body = getFunctionBody(files, 'test');
146
+ expect(body).toMatch(/nums\[0\]/);
147
+ });
148
+ });
149
+ //# sourceMappingURL=array-write.test.js.map
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Tests for the redscript tuner engine, simulator, and ln-polynomial adapter.
3
+ */
4
+ export {};
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for the redscript tuner engine, simulator, and ln-polynomial adapter.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const engine_1 = require("../../tuner/engine");
7
+ const simulator_1 = require("../../tuner/simulator");
8
+ const metrics_1 = require("../../tuner/metrics");
9
+ const ln_polynomial_1 = require("../../tuner/adapters/ln-polynomial");
10
+ const sqrt_newton_1 = require("../../tuner/adapters/sqrt-newton");
11
+ // ─── simulator tests ──────────────────────────────────────────────────────────
12
+ describe('simulator', () => {
13
+ test('i32 truncates to int32', () => {
14
+ expect((0, simulator_1.i32)(3.7)).toBe(3);
15
+ expect((0, simulator_1.i32)(-3.7)).toBe(-3);
16
+ expect((0, simulator_1.i32)(2147483648)).toBe(-2147483648); // overflow wraps
17
+ expect((0, simulator_1.i32)(0)).toBe(0);
18
+ });
19
+ test('fixedMul basic', () => {
20
+ // 10000 * 10000 / 10000 = 10000
21
+ expect((0, simulator_1.fixedMul)(10000, 10000, 10000)).toBe(10000);
22
+ // 5000 * 2 / 10000 = 1
23
+ expect((0, simulator_1.fixedMul)(5000, 2, 10000)).toBe(1);
24
+ });
25
+ test('fixedMul returns Infinity on overflow', () => {
26
+ expect((0, simulator_1.fixedMul)(2147483647, 2147483647, 1)).toBe(Infinity);
27
+ });
28
+ test('isOverflow detects out-of-range', () => {
29
+ expect((0, simulator_1.isOverflow)(2147483648)).toBe(true);
30
+ expect((0, simulator_1.isOverflow)(-2147483649)).toBe(true);
31
+ expect((0, simulator_1.isOverflow)(Infinity)).toBe(true);
32
+ expect((0, simulator_1.isOverflow)(NaN)).toBe(true);
33
+ expect((0, simulator_1.isOverflow)(0)).toBe(false);
34
+ expect((0, simulator_1.isOverflow)(2147483647)).toBe(false);
35
+ });
36
+ });
37
+ // ─── Nelder-Mead convergence test ────────────────────────────────────────────
38
+ describe('Nelder-Mead engine', () => {
39
+ test('converges to minimum of (x-3)^2', () => {
40
+ // Simple 1D minimization: minimize (x-3)^2
41
+ const mockAdapter = {
42
+ name: 'test-quadratic',
43
+ description: 'Minimize (x-3)^2',
44
+ params: [
45
+ { name: 'x', range: [-10, 10], integer: false },
46
+ ],
47
+ simulate(input, params) {
48
+ // Return the residual as a scaled integer
49
+ const x = params['x'];
50
+ return Math.round(x * 10000);
51
+ },
52
+ reference(_input) {
53
+ // Target: x = 3 → value 30000
54
+ return 30000;
55
+ },
56
+ sampleInputs() {
57
+ return [1]; // single input, target value is 3.0 (×10000 = 30000)
58
+ },
59
+ generateCode(params) {
60
+ return `// x = ${params['x']}`;
61
+ },
62
+ };
63
+ const result = (0, engine_1.search)(mockAdapter, 5000);
64
+ // Should converge close to x=3
65
+ expect(result.params['x']).toBeCloseTo(3.0, 1);
66
+ expect(result.maxError).toBeLessThan(0.1);
67
+ });
68
+ test('handles integer constraints', () => {
69
+ const mockAdapter = {
70
+ name: 'test-integer',
71
+ description: 'Integer parameter test',
72
+ params: [
73
+ { name: 'n', range: [0, 10], integer: true },
74
+ ],
75
+ simulate(input, params) {
76
+ // Should snap to integer 7
77
+ return Math.round(params['n'] * 10000);
78
+ },
79
+ reference(_input) {
80
+ return 70000; // 7.0 × 10000
81
+ },
82
+ sampleInputs() {
83
+ return [1];
84
+ },
85
+ generateCode() {
86
+ return '';
87
+ },
88
+ };
89
+ const result = (0, engine_1.search)(mockAdapter, 2000);
90
+ // Should find n close to 7
91
+ expect(Math.round(result.params['n'])).toBe(7);
92
+ });
93
+ test('i32 overflow penalization', () => {
94
+ const mockAdapter = {
95
+ name: 'test-overflow',
96
+ description: 'Test overflow penalization',
97
+ params: [
98
+ { name: 'scale', range: [1, 1000], integer: true },
99
+ ],
100
+ simulate(_input, params) {
101
+ // Always overflow for any scale >= 500
102
+ if (params['scale'] >= 500)
103
+ return Infinity;
104
+ return params['scale'] * 10000;
105
+ },
106
+ reference(_input) {
107
+ return 2000000; // target: scale=200 → 2000000
108
+ },
109
+ sampleInputs() {
110
+ return [1];
111
+ },
112
+ generateCode() {
113
+ return '';
114
+ },
115
+ };
116
+ const { maxError, mae, rmse } = (0, metrics_1.evaluate)(mockAdapter, { scale: 2147483647 });
117
+ expect(maxError).toBe(Infinity);
118
+ expect(mae).toBe(Infinity);
119
+ expect(rmse).toBe(Infinity);
120
+ });
121
+ });
122
+ // ─── ln-polynomial adapter tests ─────────────────────────────────────────────
123
+ describe('ln-polynomial adapter', () => {
124
+ const defaultParams = ln_polynomial_1.defaultParams; // { A1: 20000, A3: 6667, A5: 4000 }
125
+ test('sample inputs cover the valid range', () => {
126
+ const inputs = ln_polynomial_1.lnPolynomialAdapter.sampleInputs();
127
+ expect(inputs.length).toBeGreaterThan(50);
128
+ // All inputs should be positive
129
+ expect(inputs.every(x => x > 0)).toBe(true);
130
+ });
131
+ test('reference matches Math.log', () => {
132
+ const SCALE = 10000;
133
+ // ln(1.0) = 0
134
+ expect(ln_polynomial_1.lnPolynomialAdapter.reference(SCALE)).toBeCloseTo(0, 5);
135
+ // ln(2.0) ≈ 0.6931 → 6931
136
+ expect(ln_polynomial_1.lnPolynomialAdapter.reference(2 * SCALE)).toBeCloseTo(6931.47, 0);
137
+ // ln(0.5) ≈ -0.6931 → -6931
138
+ expect(ln_polynomial_1.lnPolynomialAdapter.reference(5000)).toBeCloseTo(-6931.47, 0);
139
+ });
140
+ test('simulate produces reasonable output for x=1 (no error)', () => {
141
+ const result = ln_polynomial_1.lnPolynomialAdapter.simulate(10000, defaultParams);
142
+ // ln(1.0) = 0; allow some approximation error
143
+ expect(Math.abs(result)).toBeLessThan(500); // within 0.05
144
+ });
145
+ test('simulate returns Infinity for invalid input', () => {
146
+ const result = ln_polynomial_1.lnPolynomialAdapter.simulate(0, defaultParams);
147
+ expect(result).toBeLessThan(0); // negative sentinel or -MAX_INT
148
+ });
149
+ test('max_error < 0.001 with default atanh coefficients', () => {
150
+ const metrics = (0, metrics_1.evaluate)(ln_polynomial_1.lnPolynomialAdapter, defaultParams);
151
+ expect(metrics.maxError).toBeLessThan(0.001);
152
+ }, 10000);
153
+ test('search improves over default params', () => {
154
+ // Run a short search and confirm it doesn't get worse
155
+ const baseMetrics = (0, metrics_1.evaluate)(ln_polynomial_1.lnPolynomialAdapter, defaultParams);
156
+ const result = (0, engine_1.search)(ln_polynomial_1.lnPolynomialAdapter, 500); // short budget for test speed
157
+ // Either same or better
158
+ expect(result.maxError).toBeLessThanOrEqual(baseMetrics.maxError * 2);
159
+ expect(result.maxError).toBeLessThan(0.01);
160
+ }, 30000);
161
+ test('generateCode produces valid output', () => {
162
+ const meta = {
163
+ maxError: 0.00003,
164
+ mae: 0.000012,
165
+ rmse: 0.000015,
166
+ estimatedCmds: 38,
167
+ tuneDate: '2026-03-17',
168
+ budgetUsed: 5000,
169
+ };
170
+ const code = ln_polynomial_1.lnPolynomialAdapter.generateCode(defaultParams, meta);
171
+ expect(code).toContain('AUTO-GENERATED');
172
+ expect(code).toContain('ln-polynomial');
173
+ expect(code).toContain('fn ln');
174
+ expect(code).toContain('A1');
175
+ expect(code).toContain('A3');
176
+ expect(code).toContain('A5');
177
+ expect(code).toContain('2026-03-17');
178
+ });
179
+ test('searchSA achieves max_error < 0.001 on ln-polynomial', () => {
180
+ const result = (0, engine_1.searchSA)(ln_polynomial_1.lnPolynomialAdapter, 3000);
181
+ expect(result.maxError).toBeLessThan(0.001);
182
+ }, 30000);
183
+ });
184
+ // ─── sqrt-newton adapter tests ────────────────────────────────────────────────
185
+ describe('sqrt-newton adapter', () => {
186
+ test('simulate(10000, defaultParams) ≈ 10000 (sqrt(1.0)=1.0)', () => {
187
+ const result = sqrt_newton_1.sqrtNewtonAdapter.simulate(10000, sqrt_newton_1.defaultParams);
188
+ // sqrt(1.0) * 10000 = 10000
189
+ expect(Math.abs(result - 10000)).toBeLessThan(10);
190
+ });
191
+ test('simulate(40000, defaultParams) ≈ 20000 (sqrt(4.0)=2.0)', () => {
192
+ const result = sqrt_newton_1.sqrtNewtonAdapter.simulate(40000, sqrt_newton_1.defaultParams);
193
+ // sqrt(4.0) * 10000 = 20000
194
+ expect(Math.abs(result - 20000)).toBeLessThan(10);
195
+ });
196
+ test('simulate(0) returns 0', () => {
197
+ expect(sqrt_newton_1.sqrtNewtonAdapter.simulate(0, sqrt_newton_1.defaultParams)).toBe(0);
198
+ expect(sqrt_newton_1.sqrtNewtonAdapter.simulate(-1, sqrt_newton_1.defaultParams)).toBe(0);
199
+ });
200
+ test('simulate(250000, defaultParams) ≈ 50000 (sqrt(25.0)=5.0)', () => {
201
+ const result = sqrt_newton_1.sqrtNewtonAdapter.simulate(250000, sqrt_newton_1.defaultParams);
202
+ expect(Math.abs(result - 50000)).toBeLessThan(10);
203
+ });
204
+ test('sample inputs are all positive', () => {
205
+ const inputs = sqrt_newton_1.sqrtNewtonAdapter.sampleInputs();
206
+ expect(inputs.length).toBeGreaterThan(50);
207
+ expect(inputs.every(x => x > 0)).toBe(true);
208
+ });
209
+ test('reference matches Math.sqrt', () => {
210
+ const SCALE = 10000;
211
+ expect(sqrt_newton_1.sqrtNewtonAdapter.reference(SCALE)).toBe(SCALE); // sqrt(1.0)
212
+ expect(sqrt_newton_1.sqrtNewtonAdapter.reference(4 * SCALE)).toBe(2 * SCALE); // sqrt(4.0)
213
+ expect(sqrt_newton_1.sqrtNewtonAdapter.reference(9 * SCALE)).toBe(3 * SCALE); // sqrt(9.0)
214
+ expect(sqrt_newton_1.sqrtNewtonAdapter.reference(0)).toBe(0);
215
+ });
216
+ test('generateCode contains fn sqrt_fx', () => {
217
+ const meta = {
218
+ maxError: 1.5,
219
+ mae: 0.5,
220
+ rmse: 0.8,
221
+ estimatedCmds: 30,
222
+ tuneDate: '2026-03-17',
223
+ budgetUsed: 3000,
224
+ };
225
+ const code = sqrt_newton_1.sqrtNewtonAdapter.generateCode(sqrt_newton_1.defaultParams, meta);
226
+ expect(code).toContain('AUTO-GENERATED');
227
+ expect(code).toContain('sqrt-newton');
228
+ expect(code).toContain('fn sqrt_fx');
229
+ expect(code).toContain('2026-03-17');
230
+ });
231
+ });
232
+ //# sourceMappingURL=engine.test.js.map
@@ -228,6 +228,13 @@ export type Expr = {
228
228
  obj: Expr;
229
229
  index: Expr;
230
230
  span?: Span;
231
+ } | {
232
+ kind: 'index_assign';
233
+ obj: Expr;
234
+ index: Expr;
235
+ op: AssignOp;
236
+ value: Expr;
237
+ span?: Span;
231
238
  } | {
232
239
  kind: 'array_lit';
233
240
  elements: Expr[];
@@ -465,6 +465,11 @@ function rewriteExpr(expr, symbolMap) {
465
465
  rewriteExpr(expr.obj, symbolMap);
466
466
  rewriteExpr(expr.index, symbolMap);
467
467
  break;
468
+ case 'index_assign':
469
+ rewriteExpr(expr.obj, symbolMap);
470
+ rewriteExpr(expr.index, symbolMap);
471
+ rewriteExpr(expr.value, symbolMap);
472
+ break;
468
473
  case 'array_lit':
469
474
  for (const el of expr.elements)
470
475
  rewriteExpr(el, symbolMap);