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
@@ -0,0 +1,352 @@
1
+ "use strict";
2
+ /**
3
+ * stdlib/math.mcrs — Runtime behavioural tests
4
+ *
5
+ * Each test compiles the math stdlib together with a small driver function,
6
+ * runs it through MCRuntime, and checks scoreboard values.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const compile_1 = require("../compile");
45
+ const runtime_1 = require("../runtime");
46
+ const MATH_SRC = fs.readFileSync(path.join(__dirname, '../../src/stdlib/math.mcrs'), 'utf-8');
47
+ function run(driver) {
48
+ // Use librarySources so math functions are only compiled when actually called
49
+ const result = (0, compile_1.compile)(driver, { namespace: 'mathtest', librarySources: [MATH_SRC] });
50
+ if (!result.success)
51
+ throw new Error(result.error?.message ?? 'compile failed');
52
+ const runtime = new runtime_1.MCRuntime('mathtest');
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
+ runtime.loadFunction(`${match[1]}:${match[2]}`, file.content.split('\n'));
60
+ }
61
+ runtime.load();
62
+ return runtime;
63
+ }
64
+ function scoreOf(rt, key) {
65
+ return rt.getScore('out', `mathtest.${key}`);
66
+ }
67
+ // ─── abs ─────────────────────────────────────────────────────────────────────
68
+ describe('abs', () => {
69
+ it('abs of positive', () => {
70
+ const rt = run(`fn test() { scoreboard_set("out", "r", abs(42)); }`);
71
+ rt.execFunction('test');
72
+ expect(scoreOf(rt, 'r')).toBe(42);
73
+ });
74
+ it('abs of negative', () => {
75
+ const rt = run(`fn test() { scoreboard_set("out", "r", abs(-7)); }`);
76
+ rt.execFunction('test');
77
+ expect(scoreOf(rt, 'r')).toBe(7);
78
+ });
79
+ it('abs of zero', () => {
80
+ const rt = run(`fn test() { scoreboard_set("out", "r", abs(0)); }`);
81
+ rt.execFunction('test');
82
+ expect(scoreOf(rt, 'r')).toBe(0);
83
+ });
84
+ });
85
+ // ─── sign ────────────────────────────────────────────────────────────────────
86
+ describe('sign', () => {
87
+ it.each([
88
+ [5, 1],
89
+ [-3, -1],
90
+ [0, 0],
91
+ ])('sign(%d) == %d', (x, expected) => {
92
+ const rt = run(`fn test() { scoreboard_set("out", "r", sign(${x})); }`);
93
+ rt.execFunction('test');
94
+ expect(scoreOf(rt, 'r')).toBe(expected);
95
+ });
96
+ });
97
+ // ─── min / max ───────────────────────────────────────────────────────────────
98
+ describe('min', () => {
99
+ it.each([
100
+ [3, 7, 3],
101
+ [7, 3, 3],
102
+ [5, 5, 5],
103
+ [-2, 0, -2],
104
+ ])('min(%d, %d) == %d', (a, b, expected) => {
105
+ const rt = run(`fn test() { scoreboard_set("out", "r", min(${a}, ${b})); }`);
106
+ rt.execFunction('test');
107
+ expect(scoreOf(rt, 'r')).toBe(expected);
108
+ });
109
+ });
110
+ describe('max', () => {
111
+ it.each([
112
+ [3, 7, 7],
113
+ [7, 3, 7],
114
+ [5, 5, 5],
115
+ [-2, 0, 0],
116
+ ])('max(%d, %d) == %d', (a, b, expected) => {
117
+ const rt = run(`fn test() { scoreboard_set("out", "r", max(${a}, ${b})); }`);
118
+ rt.execFunction('test');
119
+ expect(scoreOf(rt, 'r')).toBe(expected);
120
+ });
121
+ });
122
+ // ─── clamp ───────────────────────────────────────────────────────────────────
123
+ describe('clamp', () => {
124
+ it.each([
125
+ [5, 0, 10, 5], // in range
126
+ [-5, 0, 10, 0], // below lo
127
+ [15, 0, 10, 10], // above hi
128
+ [0, 0, 10, 0], // at lo
129
+ [10, 0, 10, 10], // at hi
130
+ ])('clamp(%d, %d, %d) == %d', (x, lo, hi, expected) => {
131
+ const rt = run(`fn test() { scoreboard_set("out", "r", clamp(${x}, ${lo}, ${hi})); }`);
132
+ rt.execFunction('test');
133
+ expect(scoreOf(rt, 'r')).toBe(expected);
134
+ });
135
+ });
136
+ // ─── lerp ────────────────────────────────────────────────────────────────────
137
+ describe('lerp', () => {
138
+ it.each([
139
+ [0, 1000, 0, 0], // t=0 → a
140
+ [0, 1000, 1000, 1000], // t=1000 → b
141
+ [0, 1000, 500, 500], // t=0.5 → midpoint
142
+ [100, 200, 750, 175], // 100 + (200-100)*0.75 = 175
143
+ [0, 100, 333, 33], // integer division truncation
144
+ ])('lerp(%d, %d, %d) == %d', (a, b, t, expected) => {
145
+ const rt = run(`fn test() { scoreboard_set("out", "r", lerp(${a}, ${b}, ${t})); }`);
146
+ rt.execFunction('test');
147
+ expect(scoreOf(rt, 'r')).toBe(expected);
148
+ });
149
+ });
150
+ // ─── isqrt ───────────────────────────────────────────────────────────────────
151
+ describe('isqrt', () => {
152
+ it.each([
153
+ [0, 0],
154
+ [1, 1],
155
+ [4, 2],
156
+ [9, 3],
157
+ [10, 3], // floor
158
+ [16, 4],
159
+ [24, 4], // floor
160
+ [25, 5],
161
+ [100, 10],
162
+ [1000000, 1000],
163
+ ])('isqrt(%d) == %d', (n, expected) => {
164
+ const rt = run(`fn test() { scoreboard_set("out", "r", isqrt(${n})); }`);
165
+ rt.execFunction('test');
166
+ expect(scoreOf(rt, 'r')).toBe(expected);
167
+ });
168
+ });
169
+ // ─── sqrt_fixed ──────────────────────────────────────────────────────────────
170
+ describe('sqrt_fixed (scale=1000)', () => {
171
+ it.each([
172
+ [1000, 1000], // sqrt(1.0) = 1.0
173
+ [4000, 2000], // sqrt(4.0) = 2.0
174
+ [2000, 1414], // sqrt(2.0) ≈ 1.414 (truncated)
175
+ [9000, 3000], // sqrt(9.0) = 3.0
176
+ ])('sqrt_fixed(%d) ≈ %d', (x, expected) => {
177
+ const rt = run(`fn test() { scoreboard_set("out", "r", sqrt_fixed(${x})); }`);
178
+ rt.execFunction('test');
179
+ // Allow ±1 from integer truncation
180
+ expect(Math.abs(scoreOf(rt, 'r') - expected)).toBeLessThanOrEqual(1);
181
+ });
182
+ });
183
+ // ─── pow_int ─────────────────────────────────────────────────────────────────
184
+ describe('pow_int', () => {
185
+ it.each([
186
+ [2, 0, 1],
187
+ [2, 1, 2],
188
+ [2, 10, 1024],
189
+ [3, 3, 27],
190
+ [5, 4, 625],
191
+ [10, 5, 100000],
192
+ [7, 0, 1],
193
+ [1, 100, 1],
194
+ ])('pow_int(%d, %d) == %d', (base, exp, expected) => {
195
+ const rt = run(`fn test() { scoreboard_set("out", "r", pow_int(${base}, ${exp})); }`);
196
+ rt.execFunction('test');
197
+ expect(scoreOf(rt, 'r')).toBe(expected);
198
+ });
199
+ });
200
+ // ─── gcd ─────────────────────────────────────────────────────────────────────
201
+ describe('gcd', () => {
202
+ it.each([
203
+ [12, 8, 4],
204
+ [7, 5, 1],
205
+ [100, 25, 25],
206
+ [0, 5, 5],
207
+ [5, 0, 5],
208
+ [12, 12, 12],
209
+ [-12, 8, 4], // abs handled internally
210
+ ])('gcd(%d, %d) == %d', (a, b, expected) => {
211
+ const rt = run(`fn test() { scoreboard_set("out", "r", gcd(${a}, ${b})); }`);
212
+ rt.execFunction('test');
213
+ expect(scoreOf(rt, 'r')).toBe(expected);
214
+ });
215
+ });
216
+ // ─── Phase 4: Number theory & utilities ──────────────────────────────────────
217
+ describe('lcm', () => {
218
+ it.each([
219
+ [4, 6, 12],
220
+ [3, 5, 15],
221
+ [0, 5, 0],
222
+ [12, 12, 12],
223
+ [7, 1, 7],
224
+ ])('lcm(%d, %d) == %d', (a, b, expected) => {
225
+ const rt = run(`fn test() { scoreboard_set("out", "r", lcm(${a}, ${b})); }`);
226
+ rt.execFunction('test');
227
+ expect(scoreOf(rt, 'r')).toBe(expected);
228
+ });
229
+ });
230
+ describe('map', () => {
231
+ it.each([
232
+ [5, 0, 10, 0, 100, 50],
233
+ [0, 0, 10, 0, 100, 0],
234
+ [10, 0, 10, 0, 100, 100],
235
+ [1, 0, 10, 100, 200, 110],
236
+ [5, 0, 10, -100, 100, 0],
237
+ ])('map(%d, %d, %d, %d, %d) == %d', (x, il, ih, ol, oh, expected) => {
238
+ const rt = run(`fn test() { scoreboard_set("out", "r", map(${x}, ${il}, ${ih}, ${ol}, ${oh})); }`);
239
+ rt.execFunction('test');
240
+ expect(scoreOf(rt, 'r')).toBe(expected);
241
+ });
242
+ });
243
+ describe('ceil_div', () => {
244
+ it.each([
245
+ [7, 3, 3],
246
+ [6, 3, 2],
247
+ [9, 3, 3],
248
+ [1, 5, 1],
249
+ [10, 10, 1],
250
+ ])('ceil_div(%d, %d) == %d', (a, b, expected) => {
251
+ const rt = run(`fn test() { scoreboard_set("out", "r", ceil_div(${a}, ${b})); }`);
252
+ rt.execFunction('test');
253
+ expect(scoreOf(rt, 'r')).toBe(expected);
254
+ });
255
+ });
256
+ describe('log2_int', () => {
257
+ it.each([
258
+ [1, 0],
259
+ [2, 1],
260
+ [4, 2],
261
+ [8, 3],
262
+ [7, 2],
263
+ [1024, 10],
264
+ [0, -1],
265
+ ])('log2_int(%d) == %d', (n, expected) => {
266
+ const rt = run(`fn test() { scoreboard_set("out", "r", log2_int(${n})); }`);
267
+ rt.execFunction('test');
268
+ expect(scoreOf(rt, 'r')).toBe(expected);
269
+ });
270
+ });
271
+ // ─── Phase 3: Trigonometry ────────────────────────────────────────────────────
272
+ // MCRuntime doesn't support real NBT storage macro functions (data get storage
273
+ // path[$(i)]) — those require Minecraft 1.20.2+.
274
+ // We test what we can: compile-only + sin table initialisation check,
275
+ // and verify sin_fixed output for key angles where the MCRuntime
276
+ // can simulate the scoreboard value after we manually stub the lookup.
277
+ describe('sin table init', () => {
278
+ it('_math_init in __load when sin_fixed is called (via librarySources)', () => {
279
+ const mathSrc = require('fs').readFileSync(require('path').join(__dirname, '../../src/stdlib/math.mcrs'), 'utf-8');
280
+ const result = require('../compile').compile('fn test() { scoreboard_set("out", "r", sin_fixed(30)); }', { namespace: 'mathtest', librarySources: [mathSrc] });
281
+ expect(result.success).toBe(true);
282
+ const hasSinTable = result.files?.some((f) => f.content?.includes('data modify storage math:tables sin set value'));
283
+ expect(hasSinTable).toBe(true);
284
+ });
285
+ it('_math_init NOT in output when sin_fixed is not used (library DCE)', () => {
286
+ const mathSrc = require('fs').readFileSync(require('path').join(__dirname, '../../src/stdlib/math.mcrs'), 'utf-8');
287
+ const result = require('../compile').compile('fn test() { scoreboard_set("out", "r", abs(-5)); }', { namespace: 'mathtest', librarySources: [mathSrc] });
288
+ expect(result.success).toBe(true);
289
+ const hasSinTable = result.files?.some((f) => f.content?.includes('data modify storage math:tables sin set value'));
290
+ expect(hasSinTable).toBe(false);
291
+ });
292
+ });
293
+ describe('sin_fixed compile check', () => {
294
+ it('sin_fixed compiles without errors', () => {
295
+ const mathSrc = require('fs').readFileSync(require('path').join(__dirname, '../../src/stdlib/math.mcrs'), 'utf-8');
296
+ const result = require('../compile').compile('fn test() { scoreboard_set("out", "r", sin_fixed(30)); }', { namespace: 'mathtest', librarySources: [mathSrc] });
297
+ expect(result.success).toBe(true);
298
+ });
299
+ it('cos_fixed compiles without errors', () => {
300
+ const mathSrc = require('fs').readFileSync(require('path').join(__dirname, '../../src/stdlib/math.mcrs'), 'utf-8');
301
+ const result = require('../compile').compile('fn test() { scoreboard_set("out", "r", cos_fixed(0)); }', { namespace: 'mathtest', librarySources: [mathSrc] });
302
+ expect(result.success).toBe(true);
303
+ });
304
+ });
305
+ // ─── Phase 5: Vectors, directions & easing ───────────────────────────────────
306
+ describe('mulfix / divfix', () => {
307
+ it.each([
308
+ [500, 707, 353], // 0.5 × 0.707 ≈ 0.353
309
+ [1000, 1000, 1000],
310
+ [1000, 500, 500],
311
+ [0, 999, 0],
312
+ ])('mulfix(%d, %d) == %d', (a, b, expected) => {
313
+ const rt = run(`fn test() { scoreboard_set("out", "r", mulfix(${a}, ${b})); }`);
314
+ rt.execFunction('test');
315
+ expect(scoreOf(rt, 'r')).toBe(expected);
316
+ });
317
+ it.each([
318
+ [1, 3, 333],
319
+ [1, 2, 500],
320
+ [2, 1, 2000],
321
+ [0, 5, 0],
322
+ ])('divfix(%d, %d) == %d', (a, b, expected) => {
323
+ const rt = run(`fn test() { scoreboard_set("out", "r", divfix(${a}, ${b})); }`);
324
+ rt.execFunction('test');
325
+ expect(scoreOf(rt, 'r')).toBe(expected);
326
+ });
327
+ });
328
+ // dot2d, cross2d, length2d_fixed, manhattan, chebyshev, atan2_fixed
329
+ // have moved to vec.mcrs — tested in stdlib-vec.test.ts
330
+ describe('smoothstep', () => {
331
+ it.each([
332
+ [0, 0],
333
+ [100, 1000],
334
+ [50, 500], // midpoint: 3×0.5²−2×0.5³ = 0.5 → 500
335
+ ])('smoothstep(0,100,%d) == %d', (x, expected) => {
336
+ const rt = run(`fn test() { scoreboard_set("out", "r", smoothstep(0, 100, ${x})); }`);
337
+ rt.execFunction('test');
338
+ expect(scoreOf(rt, 'r')).toBe(expected);
339
+ });
340
+ it('smoothstep is monotonically increasing', () => {
341
+ let prev = -1;
342
+ for (const x of [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) {
343
+ const rt = run(`fn test() { scoreboard_set("out", "r", smoothstep(0, 100, ${x})); }`);
344
+ rt.execFunction('test');
345
+ const v = scoreOf(rt, 'r');
346
+ expect(v).toBeGreaterThanOrEqual(prev);
347
+ prev = v;
348
+ }
349
+ });
350
+ });
351
+ // atan2_fixed / _atan_init tests moved to stdlib-vec.test.ts
352
+ //# sourceMappingURL=stdlib-math.test.js.map
@@ -0,0 +1,4 @@
1
+ /**
2
+ * stdlib/vec.mcrs — runtime behavioural tests
3
+ */
4
+ export {};
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ /**
3
+ * stdlib/vec.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 VEC_SRC = fs.readFileSync(path.join(__dirname, '../../src/stdlib/vec.mcrs'), 'utf-8');
45
+ function run(driver) {
46
+ const result = (0, compile_1.compile)(driver, {
47
+ namespace: 'vectest',
48
+ librarySources: [MATH_SRC, VEC_SRC],
49
+ });
50
+ if (!result.success)
51
+ throw new Error(result.error?.message ?? 'compile failed');
52
+ const rt = new runtime_1.MCRuntime('vectest');
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', `vectest.${key}`);
66
+ }
67
+ // ─── 2D basic ────────────────────────────────────────────────────────────────
68
+ describe('dot2d', () => {
69
+ it('dot2d(3,4,3,4) == 25', () => {
70
+ const rt = run(`fn test() { scoreboard_set("out", "r", dot2d(3,4,3,4)); }`);
71
+ rt.execFunction('test');
72
+ expect(scoreOf(rt, 'r')).toBe(25);
73
+ });
74
+ it('perpendicular == 0', () => {
75
+ const rt = run(`fn test() { scoreboard_set("out", "r", dot2d(1,0,0,1)); }`);
76
+ rt.execFunction('test');
77
+ expect(scoreOf(rt, 'r')).toBe(0);
78
+ });
79
+ });
80
+ describe('cross2d', () => {
81
+ it('cross2d(1,0,0,1) == 1', () => {
82
+ const rt = run(`fn test() { scoreboard_set("out", "r", cross2d(1,0,0,1)); }`);
83
+ rt.execFunction('test');
84
+ expect(scoreOf(rt, 'r')).toBe(1);
85
+ });
86
+ it('cross2d parallel == 0', () => {
87
+ const rt = run(`fn test() { scoreboard_set("out", "r", cross2d(3,0,6,0)); }`);
88
+ rt.execFunction('test');
89
+ expect(scoreOf(rt, 'r')).toBe(0);
90
+ });
91
+ });
92
+ describe('length2d_fixed', () => {
93
+ it.each([
94
+ [3, 4, 5000],
95
+ [0, 5, 5000],
96
+ [5, 0, 5000],
97
+ [1, 1, 1414],
98
+ ])('length2d_fixed(%d,%d) == %d', (x, y, expected) => {
99
+ const rt = run(`fn test() { scoreboard_set("out", "r", length2d_fixed(${x},${y})); }`);
100
+ rt.execFunction('test');
101
+ expect(scoreOf(rt, 'r')).toBe(expected);
102
+ });
103
+ });
104
+ describe('distance2d_fixed', () => {
105
+ it('distance2d_fixed(0,0,3,4) == 5000', () => {
106
+ const rt = run(`fn test() { scoreboard_set("out", "r", distance2d_fixed(0,0,3,4)); }`);
107
+ rt.execFunction('test');
108
+ expect(scoreOf(rt, 'r')).toBe(5000);
109
+ });
110
+ it('distance2d_fixed(p,p) == 0', () => {
111
+ const rt = run(`fn test() { scoreboard_set("out", "r", distance2d_fixed(5,7,5,7)); }`);
112
+ rt.execFunction('test');
113
+ expect(scoreOf(rt, 'r')).toBe(0);
114
+ });
115
+ });
116
+ describe('manhattan', () => {
117
+ it.each([
118
+ [0, 0, 3, 4, 7],
119
+ [0, 0, 0, 5, 5],
120
+ [1, 1, 1, 1, 0],
121
+ ])('manhattan(%d,%d,%d,%d) == %d', (x1, y1, x2, y2, e) => {
122
+ const rt = run(`fn test() { scoreboard_set("out", "r", manhattan(${x1},${y1},${x2},${y2})); }`);
123
+ rt.execFunction('test');
124
+ expect(scoreOf(rt, 'r')).toBe(e);
125
+ });
126
+ });
127
+ describe('chebyshev', () => {
128
+ it.each([
129
+ [0, 0, 3, 4, 4],
130
+ [0, 0, 4, 3, 4],
131
+ [0, 0, 5, 5, 5],
132
+ ])('chebyshev(%d,%d,%d,%d) == %d', (x1, y1, x2, y2, e) => {
133
+ const rt = run(`fn test() { scoreboard_set("out", "r", chebyshev(${x1},${y1},${x2},${y2})); }`);
134
+ rt.execFunction('test');
135
+ expect(scoreOf(rt, 'r')).toBe(e);
136
+ });
137
+ });
138
+ describe('normalize2d', () => {
139
+ it('normalize2d_x(3,4) == 600', () => {
140
+ const rt = run(`fn test() { scoreboard_set("out", "r", normalize2d_x(3,4)); }`);
141
+ rt.execFunction('test');
142
+ expect(scoreOf(rt, 'r')).toBe(600);
143
+ });
144
+ it('normalize2d_y(3,4) == 800', () => {
145
+ const rt = run(`fn test() { scoreboard_set("out", "r", normalize2d_y(3,4)); }`);
146
+ rt.execFunction('test');
147
+ expect(scoreOf(rt, 'r')).toBe(800);
148
+ });
149
+ it('zero vector → 0', () => {
150
+ const rt = run(`fn test() { scoreboard_set("out", "r", normalize2d_x(0,0)); }`);
151
+ rt.execFunction('test');
152
+ expect(scoreOf(rt, 'r')).toBe(0);
153
+ });
154
+ });
155
+ describe('lerp2d', () => {
156
+ it('lerp2d_x midpoint', () => {
157
+ const rt = run(`fn test() { scoreboard_set("out", "r", lerp2d_x(0,0,100,200,500)); }`);
158
+ rt.execFunction('test');
159
+ expect(scoreOf(rt, 'r')).toBe(50);
160
+ });
161
+ it('lerp2d_y midpoint', () => {
162
+ const rt = run(`fn test() { scoreboard_set("out", "r", lerp2d_y(0,0,100,200,500)); }`);
163
+ rt.execFunction('test');
164
+ expect(scoreOf(rt, 'r')).toBe(100);
165
+ });
166
+ });
167
+ // ─── 2D direction ────────────────────────────────────────────────────────────
168
+ describe('atan2_fixed', () => {
169
+ it.each([
170
+ [0, 1, 0],
171
+ [1, 0, 90],
172
+ [0, -1, 180],
173
+ [-1, 0, 270],
174
+ [1, 1, 45],
175
+ ])('atan2_fixed(%d,%d) == %d', (y, x, expected) => {
176
+ const rt = run(`fn test() { scoreboard_set("out", "r", atan2_fixed(${y},${x})); }`);
177
+ rt.execFunction('test');
178
+ expect(scoreOf(rt, 'r')).toBe(expected);
179
+ });
180
+ });
181
+ describe('rotate2d', () => {
182
+ it('rotate 90°: (1000,0) → x≈0', () => {
183
+ const rt = run(`fn test() { scoreboard_set("out", "r", rotate2d_x(1000,0,90)); }`);
184
+ rt.execFunction('test');
185
+ expect(Math.abs(scoreOf(rt, 'r'))).toBeLessThan(5); // ≈0, allow rounding
186
+ });
187
+ it('rotate 90°: (1000,0) → y≈1000', () => {
188
+ const rt = run(`fn test() { scoreboard_set("out", "r", rotate2d_y(1000,0,90)); }`);
189
+ rt.execFunction('test');
190
+ expect(scoreOf(rt, 'r')).toBe(1000);
191
+ });
192
+ it('rotate 0°: no change', () => {
193
+ const rt = run(`fn test() { scoreboard_set("out", "r", rotate2d_x(700,0,0)); }`);
194
+ rt.execFunction('test');
195
+ expect(scoreOf(rt, 'r')).toBe(700);
196
+ });
197
+ });
198
+ // ─── 3D geometry ─────────────────────────────────────────────────────────────
199
+ describe('dot3d', () => {
200
+ it('dot3d(1,0,0,1,0,0) == 1', () => {
201
+ const rt = run(`fn test() { scoreboard_set("out", "r", dot3d(1,0,0,1,0,0)); }`);
202
+ rt.execFunction('test');
203
+ expect(scoreOf(rt, 'r')).toBe(1);
204
+ });
205
+ it('perpendicular == 0', () => {
206
+ const rt = run(`fn test() { scoreboard_set("out", "r", dot3d(1,0,0,0,1,0)); }`);
207
+ rt.execFunction('test');
208
+ expect(scoreOf(rt, 'r')).toBe(0);
209
+ });
210
+ });
211
+ describe('cross3d', () => {
212
+ // (1,0,0) × (0,1,0) = (0,0,1)
213
+ it('cross3d_z(1,0,0,0,1,0) == 1', () => {
214
+ const rt = run(`fn test() { scoreboard_set("out", "r", cross3d_z(1,0,0,0,1,0)); }`);
215
+ rt.execFunction('test');
216
+ expect(scoreOf(rt, 'r')).toBe(1);
217
+ });
218
+ it('cross3d_x(1,0,0,0,1,0) == 0', () => {
219
+ const rt = run(`fn test() { scoreboard_set("out", "r", cross3d_x(1,0,0,0,1,0)); }`);
220
+ rt.execFunction('test');
221
+ expect(scoreOf(rt, 'r')).toBe(0);
222
+ });
223
+ });
224
+ describe('length3d_fixed', () => {
225
+ it('length3d_fixed(1,1,1) == 1732', () => {
226
+ const rt = run(`fn test() { scoreboard_set("out", "r", length3d_fixed(1,1,1)); }`);
227
+ rt.execFunction('test');
228
+ expect(scoreOf(rt, 'r')).toBe(1732);
229
+ });
230
+ it('length3d_fixed(3,4,0) == 5000', () => {
231
+ const rt = run(`fn test() { scoreboard_set("out", "r", length3d_fixed(3,4,0)); }`);
232
+ rt.execFunction('test');
233
+ expect(scoreOf(rt, 'r')).toBe(5000);
234
+ });
235
+ });
236
+ describe('manhattan3d / chebyshev3d', () => {
237
+ it('manhattan3d(0,0,0,1,2,3) == 6', () => {
238
+ const rt = run(`fn test() { scoreboard_set("out", "r", manhattan3d(0,0,0,1,2,3)); }`);
239
+ rt.execFunction('test');
240
+ expect(scoreOf(rt, 'r')).toBe(6);
241
+ });
242
+ it('chebyshev3d(0,0,0,3,1,2) == 3', () => {
243
+ const rt = run(`fn test() { scoreboard_set("out", "r", chebyshev3d(0,0,0,3,1,2)); }`);
244
+ rt.execFunction('test');
245
+ expect(scoreOf(rt, 'r')).toBe(3);
246
+ });
247
+ });
248
+ // ─── library DCE check ────────────────────────────────────────────────────────
249
+ describe('library DCE: vec.mcrs', () => {
250
+ it('only dot2d compiled when only dot2d called', () => {
251
+ const result = require('../compile').compile('fn test() { scoreboard_set("out", "r", dot2d(1,0,0,1)); }', { namespace: 'vectest', librarySources: [MATH_SRC, VEC_SRC] });
252
+ expect(result.success).toBe(true);
253
+ // atan2_fixed not called → no tan table in __load
254
+ const hasTanTable = result.files?.some((f) => f.content?.includes('data modify storage math:tables tan set value'));
255
+ expect(hasTanTable).toBe(false);
256
+ });
257
+ it('_atan_init in __load when atan2_fixed is called', () => {
258
+ const result = require('../compile').compile('fn test() { scoreboard_set("out", "r", atan2_fixed(1,0)); }', { namespace: 'vectest', librarySources: [MATH_SRC, VEC_SRC] });
259
+ expect(result.success).toBe(true);
260
+ const hasTanTable = result.files?.some((f) => f.content?.includes('data modify storage math:tables tan set value'));
261
+ expect(hasTanTable).toBe(true);
262
+ });
263
+ });
264
+ //# sourceMappingURL=stdlib-vec.test.js.map
@@ -426,7 +426,7 @@ export type Stmt = {
426
426
  };
427
427
  export type Block = Stmt[];
428
428
  export interface Decorator {
429
- name: 'tick' | 'load' | 'on' | 'on_trigger' | 'on_advancement' | 'on_craft' | 'on_death' | 'on_login' | 'on_join_team';
429
+ name: 'tick' | 'load' | 'on' | 'on_trigger' | 'on_advancement' | 'on_craft' | 'on_death' | 'on_login' | 'on_join_team' | 'keep' | 'require_on_load';
430
430
  args?: {
431
431
  rate?: number;
432
432
  eventType?: string;
@@ -435,6 +435,14 @@ export interface Decorator {
435
435
  item?: string;
436
436
  team?: string;
437
437
  };
438
+ /** Raw positional arguments (used by @requires and future generic decorators). */
439
+ rawArgs?: Array<{
440
+ kind: 'string';
441
+ value: string;
442
+ } | {
443
+ kind: 'number';
444
+ value: number;
445
+ }>;
438
446
  }
439
447
  export interface Param {
440
448
  name: string;
@@ -442,6 +450,10 @@ export interface Param {
442
450
  default?: Expr;
443
451
  }
444
452
  export interface FnDecl {
453
+ /** Set when this function was parsed from a `module library;` source.
454
+ * Library functions are NOT MC entry points — DCE only keeps them if they
455
+ * are reachable from a non-library (user) entry point. */
456
+ isLibraryFn?: boolean;
445
457
  name: string;
446
458
  params: Param[];
447
459
  returnType: TypeNode;
@@ -495,4 +507,8 @@ export interface Program {
495
507
  implBlocks: ImplBlock[];
496
508
  enums: EnumDecl[];
497
509
  consts: ConstDecl[];
510
+ /** True when the source file declares `module library;`.
511
+ * Library-mode: all functions are DCE-eligible by default — none are treated
512
+ * as MC entry points unless they carry @tick / @load / @on / @keep etc. */
513
+ isLibrary?: boolean;
498
514
  }