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.
- package/.github/workflows/publish-extension-on-ci.yml +1 -0
- package/dist/__tests__/cli.test.js +1 -1
- package/dist/__tests__/codegen.test.js +12 -6
- package/dist/__tests__/e2e.test.js +6 -6
- package/dist/__tests__/lowering.test.js +8 -8
- package/dist/__tests__/optimizer.test.js +31 -0
- package/dist/__tests__/stdlib-advanced.test.d.ts +4 -0
- package/dist/__tests__/stdlib-advanced.test.js +264 -0
- package/dist/__tests__/stdlib-math.test.d.ts +7 -0
- package/dist/__tests__/stdlib-math.test.js +352 -0
- package/dist/__tests__/stdlib-vec.test.d.ts +4 -0
- package/dist/__tests__/stdlib-vec.test.js +264 -0
- package/dist/ast/types.d.ts +17 -1
- package/dist/codegen/mcfunction/index.js +159 -18
- package/dist/codegen/var-allocator.d.ts +17 -0
- package/dist/codegen/var-allocator.js +33 -3
- package/dist/compile.d.ts +14 -0
- package/dist/compile.js +62 -5
- package/dist/index.js +20 -1
- package/dist/ir/types.d.ts +4 -0
- package/dist/lexer/index.d.ts +1 -1
- package/dist/lexer/index.js +1 -0
- package/dist/lowering/index.d.ts +5 -0
- package/dist/lowering/index.js +83 -10
- package/dist/optimizer/dce.js +21 -5
- package/dist/optimizer/passes.js +18 -6
- package/dist/optimizer/structure.js +7 -0
- package/dist/parser/index.d.ts +5 -0
- package/dist/parser/index.js +43 -2
- package/dist/runtime/index.d.ts +6 -0
- package/dist/runtime/index.js +109 -9
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/package.json +1 -1
- package/src/__tests__/cli.test.ts +1 -1
- package/src/__tests__/codegen.test.ts +12 -6
- package/src/__tests__/e2e.test.ts +6 -6
- package/src/__tests__/lowering.test.ts +8 -8
- package/src/__tests__/optimizer.test.ts +33 -0
- package/src/__tests__/stdlib-advanced.test.ts +259 -0
- package/src/__tests__/stdlib-math.test.ts +374 -0
- package/src/__tests__/stdlib-vec.test.ts +259 -0
- package/src/ast/types.ts +11 -1
- package/src/codegen/mcfunction/index.ts +148 -19
- package/src/codegen/var-allocator.ts +36 -3
- package/src/compile.ts +72 -5
- package/src/index.ts +21 -1
- package/src/ir/types.ts +2 -0
- package/src/lexer/index.ts +2 -1
- package/src/lowering/index.ts +96 -10
- package/src/optimizer/dce.ts +22 -5
- package/src/optimizer/passes.ts +18 -5
- package/src/optimizer/structure.ts +6 -1
- package/src/parser/index.ts +47 -2
- package/src/runtime/index.ts +108 -10
- package/src/stdlib/advanced.mcrs +249 -0
- package/src/stdlib/math.mcrs +259 -19
- 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,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
|
package/dist/ast/types.d.ts
CHANGED
|
@@ -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
|
}
|