redscript-mc 2.1.1 → 2.3.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.
- package/CHANGELOG.md +31 -0
- package/README.md +66 -21
- package/README.zh.md +61 -61
- package/dist/src/__tests__/e2e/basic.test.js +25 -0
- package/dist/src/__tests__/e2e/coroutine.test.js +22 -0
- package/dist/src/__tests__/mc-integration.test.js +25 -13
- package/dist/src/__tests__/schedule.test.js +105 -0
- package/dist/src/__tests__/tuner/engine.test.d.ts +4 -0
- package/dist/src/__tests__/tuner/engine.test.js +232 -0
- package/dist/src/__tests__/typechecker.test.js +63 -0
- package/dist/src/emit/compile.js +1 -0
- package/dist/src/emit/index.js +3 -1
- package/dist/src/lir/lower.js +26 -0
- package/dist/src/mir/lower.js +341 -12
- package/dist/src/mir/types.d.ts +10 -0
- package/dist/src/optimizer/copy_prop.js +4 -0
- package/dist/src/optimizer/coroutine.d.ts +2 -0
- package/dist/src/optimizer/coroutine.js +33 -1
- package/dist/src/optimizer/dce.js +7 -1
- package/dist/src/optimizer/lir/const_imm.js +1 -1
- package/dist/src/optimizer/lir/dead_slot.js +1 -1
- package/dist/src/tuner/adapters/ln-polynomial.d.ts +23 -0
- package/dist/src/tuner/adapters/ln-polynomial.js +142 -0
- package/dist/src/tuner/adapters/sqrt-newton.d.ts +28 -0
- package/dist/src/tuner/adapters/sqrt-newton.js +125 -0
- package/dist/src/tuner/cli.d.ts +5 -0
- package/dist/src/tuner/cli.js +168 -0
- package/dist/src/tuner/engine.d.ts +17 -0
- package/dist/src/tuner/engine.js +215 -0
- package/dist/src/tuner/metrics.d.ts +15 -0
- package/dist/src/tuner/metrics.js +51 -0
- package/dist/src/tuner/simulator.d.ts +35 -0
- package/dist/src/tuner/simulator.js +78 -0
- package/dist/src/tuner/types.d.ts +32 -0
- package/dist/src/tuner/types.js +6 -0
- package/dist/src/typechecker/index.d.ts +2 -0
- package/dist/src/typechecker/index.js +29 -0
- package/docs/ROADMAP.md +35 -0
- package/docs/STDLIB_ROADMAP.md +142 -0
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/examples/coroutine-demo.mcrs +11 -10
- package/jest.config.js +19 -0
- package/package.json +1 -1
- package/src/__tests__/e2e/basic.test.ts +27 -0
- package/src/__tests__/e2e/coroutine.test.ts +23 -0
- package/src/__tests__/fixtures/array-test.mcrs +21 -22
- package/src/__tests__/fixtures/counter.mcrs +17 -0
- package/src/__tests__/fixtures/foreach-at-test.mcrs +9 -10
- package/src/__tests__/mc-integration.test.ts +25 -13
- package/src/__tests__/schedule.test.ts +112 -0
- package/src/__tests__/tuner/engine.test.ts +260 -0
- package/src/__tests__/typechecker.test.ts +68 -0
- package/src/emit/compile.ts +1 -0
- package/src/emit/index.ts +3 -1
- package/src/lir/lower.ts +27 -0
- package/src/mir/lower.ts +355 -9
- package/src/mir/types.ts +4 -0
- package/src/optimizer/copy_prop.ts +4 -0
- package/src/optimizer/coroutine.ts +37 -1
- package/src/optimizer/dce.ts +6 -1
- package/src/optimizer/lir/const_imm.ts +1 -1
- package/src/optimizer/lir/dead_slot.ts +1 -1
- package/src/stdlib/bigint.mcrs +155 -192
- package/src/stdlib/bits.mcrs +158 -0
- package/src/stdlib/color.mcrs +160 -0
- package/src/stdlib/geometry.mcrs +124 -0
- package/src/stdlib/list.mcrs +125 -0
- package/src/stdlib/math.mcrs +90 -0
- package/src/stdlib/math_hp.mcrs +65 -0
- package/src/stdlib/random.mcrs +67 -0
- package/src/stdlib/signal.mcrs +112 -0
- package/src/stdlib/timer.mcrs +10 -5
- package/src/stdlib/vec.mcrs +27 -0
- package/src/tuner/adapters/ln-polynomial.ts +147 -0
- package/src/tuner/adapters/sqrt-newton.ts +135 -0
- package/src/tuner/cli.ts +158 -0
- package/src/tuner/engine.ts +272 -0
- package/src/tuner/metrics.ts +66 -0
- package/src/tuner/simulator.ts +69 -0
- package/src/tuner/types.ts +44 -0
- package/src/typechecker/index.ts +39 -0
- package/docs/ARCHITECTURE.zh.md +0 -1088
- package/docs/COMPILATION_STATS.md +0 -142
- package/docs/IMPLEMENTATION_GUIDE.md +0 -512
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Nelder-Mead simplex optimization engine for hyperparameter tuning.
|
|
4
|
+
* Suitable for continuous parameter spaces without gradient information.
|
|
5
|
+
* Supports mixed-integer parameters.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.search = search;
|
|
9
|
+
exports.searchSA = searchSA;
|
|
10
|
+
const metrics_1 = require("./metrics");
|
|
11
|
+
/**
|
|
12
|
+
* Apply integer constraints to parameters.
|
|
13
|
+
*/
|
|
14
|
+
function applyIntegerConstraints(coords, specs) {
|
|
15
|
+
return coords.map((v, i) => {
|
|
16
|
+
const spec = specs[i];
|
|
17
|
+
// Clamp to range
|
|
18
|
+
const clamped = Math.max(spec.range[0], Math.min(spec.range[1], v));
|
|
19
|
+
return spec.integer ? Math.round(clamped) : clamped;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Convert coordinate array to params record.
|
|
24
|
+
*/
|
|
25
|
+
function coordsToParams(coords, specs) {
|
|
26
|
+
const params = {};
|
|
27
|
+
for (let i = 0; i < specs.length; i++) {
|
|
28
|
+
params[specs[i].name] = coords[i];
|
|
29
|
+
}
|
|
30
|
+
return params;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Evaluate a point using the adapter.
|
|
34
|
+
*/
|
|
35
|
+
function scorePoint(coords, specs, adapter) {
|
|
36
|
+
const constrained = applyIntegerConstraints(coords, specs);
|
|
37
|
+
const params = coordsToParams(constrained, specs);
|
|
38
|
+
const metrics = (0, metrics_1.evaluate)(adapter, params);
|
|
39
|
+
return metrics.maxError;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Run Nelder-Mead optimization.
|
|
43
|
+
*/
|
|
44
|
+
function search(adapter, budget = 10000, onProgress) {
|
|
45
|
+
const specs = adapter.params;
|
|
46
|
+
const n = specs.length;
|
|
47
|
+
if (n === 0) {
|
|
48
|
+
// No params to optimize
|
|
49
|
+
const params = coordsToParams([], specs);
|
|
50
|
+
const metrics = (0, metrics_1.evaluate)(adapter, params);
|
|
51
|
+
return {
|
|
52
|
+
params,
|
|
53
|
+
maxError: metrics.maxError,
|
|
54
|
+
mae: metrics.mae,
|
|
55
|
+
rmse: metrics.rmse,
|
|
56
|
+
budgetUsed: 0,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// Nelder-Mead parameters
|
|
60
|
+
const alpha = 1.0; // reflection
|
|
61
|
+
const gamma = 2.0; // expansion
|
|
62
|
+
const rho = 0.5; // contraction
|
|
63
|
+
const sigma = 0.5; // shrink
|
|
64
|
+
// Initialize simplex with n+1 points
|
|
65
|
+
const simplex = [];
|
|
66
|
+
// Start point: midpoint of each parameter range
|
|
67
|
+
const startCoords = specs.map(s => (s.range[0] + s.range[1]) / 2);
|
|
68
|
+
simplex.push({
|
|
69
|
+
coords: startCoords,
|
|
70
|
+
score: scorePoint(startCoords, specs, adapter),
|
|
71
|
+
});
|
|
72
|
+
// Generate remaining n points by perturbing each dimension
|
|
73
|
+
for (let i = 0; i < n; i++) {
|
|
74
|
+
const coords = [...startCoords];
|
|
75
|
+
const span = specs[i].range[1] - specs[i].range[0];
|
|
76
|
+
// Perturb by 20% of the range
|
|
77
|
+
coords[i] = startCoords[i] + span * 0.2;
|
|
78
|
+
simplex.push({
|
|
79
|
+
coords,
|
|
80
|
+
score: scorePoint(coords, specs, adapter),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
let iteration = 0;
|
|
84
|
+
while (iteration < budget) {
|
|
85
|
+
// Sort simplex by score (ascending = better)
|
|
86
|
+
simplex.sort((a, b) => a.score - b.score);
|
|
87
|
+
const best = simplex[0];
|
|
88
|
+
const worst = simplex[n];
|
|
89
|
+
const secondWorst = simplex[n - 1];
|
|
90
|
+
if (onProgress && iteration % 100 === 0) {
|
|
91
|
+
onProgress(iteration, best.score);
|
|
92
|
+
}
|
|
93
|
+
// Check convergence: if all scores are the same, we're stuck
|
|
94
|
+
if (simplex[n].score - simplex[0].score < 1e-15 &&
|
|
95
|
+
iteration > 100) {
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
// Compute centroid of all but worst
|
|
99
|
+
const centroid = new Array(n).fill(0);
|
|
100
|
+
for (let i = 0; i < n; i++) {
|
|
101
|
+
for (let j = 0; j < n; j++) {
|
|
102
|
+
centroid[j] += simplex[i].coords[j] / n;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Reflection
|
|
106
|
+
const reflected = centroid.map((c, j) => c + alpha * (c - worst.coords[j]));
|
|
107
|
+
const reflectedScore = scorePoint(reflected, specs, adapter);
|
|
108
|
+
iteration++;
|
|
109
|
+
if (reflectedScore < best.score) {
|
|
110
|
+
// Expansion
|
|
111
|
+
const expanded = centroid.map((c, j) => c + gamma * (reflected[j] - c));
|
|
112
|
+
const expandedScore = scorePoint(expanded, specs, adapter);
|
|
113
|
+
iteration++;
|
|
114
|
+
if (expandedScore < reflectedScore) {
|
|
115
|
+
simplex[n] = { coords: expanded, score: expandedScore };
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
simplex[n] = { coords: reflected, score: reflectedScore };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (reflectedScore < secondWorst.score) {
|
|
122
|
+
simplex[n] = { coords: reflected, score: reflectedScore };
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// Contraction
|
|
126
|
+
const useReflected = reflectedScore < worst.score;
|
|
127
|
+
const contractionBase = useReflected ? reflected : worst.coords;
|
|
128
|
+
const contracted = centroid.map((c, j) => c + rho * (contractionBase[j] - c));
|
|
129
|
+
const contractedScore = scorePoint(contracted, specs, adapter);
|
|
130
|
+
iteration++;
|
|
131
|
+
if (contractedScore < (useReflected ? reflectedScore : worst.score)) {
|
|
132
|
+
simplex[n] = { coords: contracted, score: contractedScore };
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// Shrink
|
|
136
|
+
for (let i = 1; i <= n; i++) {
|
|
137
|
+
simplex[i].coords = simplex[0].coords.map((c, j) => c + sigma * (simplex[i].coords[j] - c));
|
|
138
|
+
simplex[i].score = scorePoint(simplex[i].coords, specs, adapter);
|
|
139
|
+
iteration++;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Sort final simplex
|
|
145
|
+
simplex.sort((a, b) => a.score - b.score);
|
|
146
|
+
const bestCoords = applyIntegerConstraints(simplex[0].coords, specs);
|
|
147
|
+
const bestParams = coordsToParams(bestCoords, specs);
|
|
148
|
+
const finalMetrics = (0, metrics_1.evaluate)(adapter, bestParams);
|
|
149
|
+
return {
|
|
150
|
+
params: bestParams,
|
|
151
|
+
maxError: finalMetrics.maxError,
|
|
152
|
+
mae: finalMetrics.mae,
|
|
153
|
+
rmse: finalMetrics.rmse,
|
|
154
|
+
budgetUsed: iteration,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Simulated Annealing search strategy.
|
|
159
|
+
* More robust than Nelder-Mead for integer parameters and multimodal objectives.
|
|
160
|
+
* Uses 3 independent restarts, taking the global best.
|
|
161
|
+
*/
|
|
162
|
+
function searchSA(adapter, budget, onProgress) {
|
|
163
|
+
const RESTARTS = 3;
|
|
164
|
+
const budgetPerRun = Math.floor(budget / RESTARTS);
|
|
165
|
+
let globalBest = null;
|
|
166
|
+
for (let r = 0; r < RESTARTS; r++) {
|
|
167
|
+
// Random initialisation within param ranges
|
|
168
|
+
let current = {};
|
|
169
|
+
for (const p of adapter.params) {
|
|
170
|
+
current[p.name] = p.range[0] + Math.random() * (p.range[1] - p.range[0]);
|
|
171
|
+
if (p.integer)
|
|
172
|
+
current[p.name] = Math.round(current[p.name]);
|
|
173
|
+
}
|
|
174
|
+
// T: 1.0 → 1e-4 over budgetPerRun iterations
|
|
175
|
+
let T = 1.0;
|
|
176
|
+
const cooling = Math.pow(1e-4, 1 / budgetPerRun);
|
|
177
|
+
let currentError = (0, metrics_1.evaluate)(adapter, current).maxError;
|
|
178
|
+
let bestLocal = { params: { ...current }, error: currentError };
|
|
179
|
+
for (let i = 0; i < budgetPerRun; i++) {
|
|
180
|
+
T *= cooling;
|
|
181
|
+
// Perturb one random param by ±10% of its range
|
|
182
|
+
const neighbor = { ...current };
|
|
183
|
+
const spec = adapter.params[Math.floor(Math.random() * adapter.params.length)];
|
|
184
|
+
const step = (spec.range[1] - spec.range[0]) * 0.1 * (Math.random() * 2 - 1);
|
|
185
|
+
neighbor[spec.name] = Math.max(spec.range[0], Math.min(spec.range[1], neighbor[spec.name] + step));
|
|
186
|
+
if (spec.integer)
|
|
187
|
+
neighbor[spec.name] = Math.round(neighbor[spec.name]);
|
|
188
|
+
const neighborError = (0, metrics_1.evaluate)(adapter, neighbor).maxError;
|
|
189
|
+
const delta = neighborError - currentError;
|
|
190
|
+
if (delta < 0 || Math.random() < Math.exp(-delta / T)) {
|
|
191
|
+
current = neighbor;
|
|
192
|
+
currentError = neighborError;
|
|
193
|
+
}
|
|
194
|
+
if (currentError < bestLocal.error) {
|
|
195
|
+
bestLocal = { params: { ...current }, error: currentError };
|
|
196
|
+
}
|
|
197
|
+
const globalIter = r * budgetPerRun + i;
|
|
198
|
+
if (onProgress && globalIter % 100 === 0) {
|
|
199
|
+
onProgress(globalIter, globalBest?.error ?? bestLocal.error);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (!globalBest || bestLocal.error < globalBest.error) {
|
|
203
|
+
globalBest = bestLocal;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const finalMetrics = (0, metrics_1.evaluate)(adapter, globalBest.params);
|
|
207
|
+
return {
|
|
208
|
+
params: globalBest.params,
|
|
209
|
+
maxError: finalMetrics.maxError,
|
|
210
|
+
mae: finalMetrics.mae,
|
|
211
|
+
rmse: finalMetrics.rmse,
|
|
212
|
+
budgetUsed: budget,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error metrics for tuner adapters.
|
|
3
|
+
*/
|
|
4
|
+
import { TunerAdapter } from './types';
|
|
5
|
+
export interface EvaluationResult {
|
|
6
|
+
maxError: number;
|
|
7
|
+
mae: number;
|
|
8
|
+
rmse: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Evaluate an adapter with given params across all sample inputs.
|
|
12
|
+
* Returns max_error, mae, and rmse relative to the reference function.
|
|
13
|
+
* Penalizes overflow by returning Infinity for all metrics.
|
|
14
|
+
*/
|
|
15
|
+
export declare function evaluate(adapter: TunerAdapter, params: Record<string, number>): EvaluationResult;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Error metrics for tuner adapters.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.evaluate = evaluate;
|
|
7
|
+
/**
|
|
8
|
+
* Evaluate an adapter with given params across all sample inputs.
|
|
9
|
+
* Returns max_error, mae, and rmse relative to the reference function.
|
|
10
|
+
* Penalizes overflow by returning Infinity for all metrics.
|
|
11
|
+
*/
|
|
12
|
+
function evaluate(adapter, params) {
|
|
13
|
+
const inputs = adapter.sampleInputs();
|
|
14
|
+
let maxError = 0;
|
|
15
|
+
let sumAbsError = 0;
|
|
16
|
+
let sumSqError = 0;
|
|
17
|
+
let count = 0;
|
|
18
|
+
for (const input of inputs) {
|
|
19
|
+
const simResult = adapter.simulate(input, params);
|
|
20
|
+
const refResult = adapter.reference(input);
|
|
21
|
+
// Penalize overflow or NaN
|
|
22
|
+
if (!isFinite(simResult) || isNaN(simResult)) {
|
|
23
|
+
return { maxError: Infinity, mae: Infinity, rmse: Infinity };
|
|
24
|
+
}
|
|
25
|
+
if (!isFinite(refResult) || isNaN(refResult)) {
|
|
26
|
+
continue; // skip degenerate reference points
|
|
27
|
+
}
|
|
28
|
+
// Both are in fixed-point; normalize to compare in floating-point units
|
|
29
|
+
const SCALE = 10000;
|
|
30
|
+
const simFloat = simResult / SCALE;
|
|
31
|
+
const refFloat = refResult / SCALE;
|
|
32
|
+
const absError = Math.abs(simFloat - refFloat);
|
|
33
|
+
if (absError === Infinity) {
|
|
34
|
+
return { maxError: Infinity, mae: Infinity, rmse: Infinity };
|
|
35
|
+
}
|
|
36
|
+
if (absError > maxError)
|
|
37
|
+
maxError = absError;
|
|
38
|
+
sumAbsError += absError;
|
|
39
|
+
sumSqError += absError * absError;
|
|
40
|
+
count++;
|
|
41
|
+
}
|
|
42
|
+
if (count === 0) {
|
|
43
|
+
return { maxError: Infinity, mae: Infinity, rmse: Infinity };
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
maxError,
|
|
47
|
+
mae: sumAbsError / count,
|
|
48
|
+
rmse: Math.sqrt(sumSqError / count),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MC int32 arithmetic simulation.
|
|
3
|
+
* Minecraft scoreboards use 32-bit signed integers (Java int).
|
|
4
|
+
*/
|
|
5
|
+
export declare const INT32_MAX = 2147483647;
|
|
6
|
+
export declare const INT32_MIN = -2147483648;
|
|
7
|
+
/**
|
|
8
|
+
* Truncate to int32 using JavaScript bitwise (same as Java int cast).
|
|
9
|
+
*/
|
|
10
|
+
export declare function i32(x: number): number;
|
|
11
|
+
/**
|
|
12
|
+
* Check if a value is within int32 range (before truncation).
|
|
13
|
+
*/
|
|
14
|
+
export declare function isOverflow(x: number): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Fixed-point multiply: compute i32(i32(a * b) / scale).
|
|
17
|
+
* Returns Infinity if overflow is detected before truncation.
|
|
18
|
+
*/
|
|
19
|
+
export declare function fixedMul(a: number, b: number, scale: number): number;
|
|
20
|
+
/**
|
|
21
|
+
* Safe i32 addition - returns Infinity on overflow.
|
|
22
|
+
*/
|
|
23
|
+
export declare function safeAdd(a: number, b: number): number;
|
|
24
|
+
/**
|
|
25
|
+
* Safe i32 subtraction - returns Infinity on overflow.
|
|
26
|
+
*/
|
|
27
|
+
export declare function safeSub(a: number, b: number): number;
|
|
28
|
+
/**
|
|
29
|
+
* Safe i32 multiply - returns Infinity on overflow.
|
|
30
|
+
*/
|
|
31
|
+
export declare function safeMul(a: number, b: number): number;
|
|
32
|
+
/**
|
|
33
|
+
* Safe i32 division - returns Infinity on division by zero.
|
|
34
|
+
*/
|
|
35
|
+
export declare function safeDiv(a: number, b: number): number;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MC int32 arithmetic simulation.
|
|
4
|
+
* Minecraft scoreboards use 32-bit signed integers (Java int).
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.INT32_MIN = exports.INT32_MAX = void 0;
|
|
8
|
+
exports.i32 = i32;
|
|
9
|
+
exports.isOverflow = isOverflow;
|
|
10
|
+
exports.fixedMul = fixedMul;
|
|
11
|
+
exports.safeAdd = safeAdd;
|
|
12
|
+
exports.safeSub = safeSub;
|
|
13
|
+
exports.safeMul = safeMul;
|
|
14
|
+
exports.safeDiv = safeDiv;
|
|
15
|
+
exports.INT32_MAX = 2147483647;
|
|
16
|
+
exports.INT32_MIN = -2147483648;
|
|
17
|
+
/**
|
|
18
|
+
* Truncate to int32 using JavaScript bitwise (same as Java int cast).
|
|
19
|
+
*/
|
|
20
|
+
function i32(x) {
|
|
21
|
+
return x | 0;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if a value is within int32 range (before truncation).
|
|
25
|
+
*/
|
|
26
|
+
function isOverflow(x) {
|
|
27
|
+
return x > exports.INT32_MAX || x < exports.INT32_MIN || !isFinite(x) || isNaN(x);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Fixed-point multiply: compute i32(i32(a * b) / scale).
|
|
31
|
+
* Returns Infinity if overflow is detected before truncation.
|
|
32
|
+
*/
|
|
33
|
+
function fixedMul(a, b, scale) {
|
|
34
|
+
const product = a * b;
|
|
35
|
+
if (isOverflow(product))
|
|
36
|
+
return Infinity;
|
|
37
|
+
const truncated = i32(product);
|
|
38
|
+
const divided = truncated / scale;
|
|
39
|
+
if (isOverflow(divided))
|
|
40
|
+
return Infinity;
|
|
41
|
+
return i32(divided);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Safe i32 addition - returns Infinity on overflow.
|
|
45
|
+
*/
|
|
46
|
+
function safeAdd(a, b) {
|
|
47
|
+
const result = a + b;
|
|
48
|
+
if (isOverflow(result))
|
|
49
|
+
return Infinity;
|
|
50
|
+
return i32(result);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Safe i32 subtraction - returns Infinity on overflow.
|
|
54
|
+
*/
|
|
55
|
+
function safeSub(a, b) {
|
|
56
|
+
const result = a - b;
|
|
57
|
+
if (isOverflow(result))
|
|
58
|
+
return Infinity;
|
|
59
|
+
return i32(result);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Safe i32 multiply - returns Infinity on overflow.
|
|
63
|
+
*/
|
|
64
|
+
function safeMul(a, b) {
|
|
65
|
+
const result = a * b;
|
|
66
|
+
if (isOverflow(result))
|
|
67
|
+
return Infinity;
|
|
68
|
+
return i32(result);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Safe i32 division - returns Infinity on division by zero.
|
|
72
|
+
*/
|
|
73
|
+
function safeDiv(a, b) {
|
|
74
|
+
if (b === 0)
|
|
75
|
+
return Infinity;
|
|
76
|
+
return i32(a / b);
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=simulator.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the redscript tuner system.
|
|
3
|
+
*/
|
|
4
|
+
export interface ParamSpec {
|
|
5
|
+
name: string;
|
|
6
|
+
range: [number, number];
|
|
7
|
+
integer: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface ResultMeta {
|
|
10
|
+
maxError: number;
|
|
11
|
+
mae: number;
|
|
12
|
+
rmse: number;
|
|
13
|
+
estimatedCmds: number;
|
|
14
|
+
tuneDate: string;
|
|
15
|
+
budgetUsed: number;
|
|
16
|
+
}
|
|
17
|
+
export interface TunerAdapter {
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
params: ParamSpec[];
|
|
21
|
+
simulate(input: number, params: Record<string, number>): number;
|
|
22
|
+
reference(input: number): number;
|
|
23
|
+
sampleInputs(): number[];
|
|
24
|
+
generateCode(params: Record<string, number>, meta: ResultMeta): string;
|
|
25
|
+
}
|
|
26
|
+
export interface SearchResult {
|
|
27
|
+
params: Record<string, number>;
|
|
28
|
+
maxError: number;
|
|
29
|
+
mae: number;
|
|
30
|
+
rmse: number;
|
|
31
|
+
budgetUsed: number;
|
|
32
|
+
}
|
|
@@ -18,6 +18,8 @@ export declare class TypeChecker {
|
|
|
18
18
|
private currentReturnType;
|
|
19
19
|
private scope;
|
|
20
20
|
private selfTypeStack;
|
|
21
|
+
private loopDepth;
|
|
22
|
+
private condDepth;
|
|
21
23
|
private readonly richTextBuiltins;
|
|
22
24
|
constructor(source?: string, filePath?: string);
|
|
23
25
|
private getNodeLocation;
|
|
@@ -123,6 +123,9 @@ class TypeChecker {
|
|
|
123
123
|
this.scope = new Map();
|
|
124
124
|
// Stack for tracking @s type in different contexts
|
|
125
125
|
this.selfTypeStack = ['entity'];
|
|
126
|
+
// Depth of loop/conditional nesting (for static-allocation enforcement)
|
|
127
|
+
this.loopDepth = 0;
|
|
128
|
+
this.condDepth = 0;
|
|
126
129
|
this.richTextBuiltins = new Map([
|
|
127
130
|
['say', { messageIndex: 0 }],
|
|
128
131
|
['announce', { messageIndex: 0 }],
|
|
@@ -296,18 +299,24 @@ class TypeChecker {
|
|
|
296
299
|
break;
|
|
297
300
|
case 'if':
|
|
298
301
|
this.checkExpr(stmt.cond);
|
|
302
|
+
this.condDepth++;
|
|
299
303
|
this.checkIfBranches(stmt);
|
|
304
|
+
this.condDepth--;
|
|
300
305
|
break;
|
|
301
306
|
case 'while':
|
|
302
307
|
this.checkExpr(stmt.cond);
|
|
308
|
+
this.loopDepth++;
|
|
303
309
|
this.checkBlock(stmt.body);
|
|
310
|
+
this.loopDepth--;
|
|
304
311
|
break;
|
|
305
312
|
case 'for':
|
|
306
313
|
if (stmt.init)
|
|
307
314
|
this.checkStmt(stmt.init);
|
|
308
315
|
this.checkExpr(stmt.cond);
|
|
309
316
|
this.checkExpr(stmt.step);
|
|
317
|
+
this.loopDepth++;
|
|
310
318
|
this.checkBlock(stmt.body);
|
|
319
|
+
this.loopDepth--;
|
|
311
320
|
break;
|
|
312
321
|
case 'foreach':
|
|
313
322
|
this.checkExpr(stmt.iterable);
|
|
@@ -320,7 +329,9 @@ class TypeChecker {
|
|
|
320
329
|
});
|
|
321
330
|
// Push self type context for @s inside the loop
|
|
322
331
|
this.pushSelfType(entityType);
|
|
332
|
+
this.loopDepth++;
|
|
323
333
|
this.checkBlock(stmt.body);
|
|
334
|
+
this.loopDepth--;
|
|
324
335
|
this.popSelfType();
|
|
325
336
|
}
|
|
326
337
|
else {
|
|
@@ -331,7 +342,9 @@ class TypeChecker {
|
|
|
331
342
|
else {
|
|
332
343
|
this.scope.set(stmt.binding, { type: { kind: 'named', name: 'void' }, mutable: true });
|
|
333
344
|
}
|
|
345
|
+
this.loopDepth++;
|
|
334
346
|
this.checkBlock(stmt.body);
|
|
347
|
+
this.loopDepth--;
|
|
335
348
|
}
|
|
336
349
|
break;
|
|
337
350
|
case 'match':
|
|
@@ -612,6 +625,14 @@ class TypeChecker {
|
|
|
612
625
|
}
|
|
613
626
|
const builtin = BUILTIN_SIGNATURES[expr.fn];
|
|
614
627
|
if (builtin) {
|
|
628
|
+
if (expr.fn === 'setTimeout' || expr.fn === 'setInterval') {
|
|
629
|
+
if (this.loopDepth > 0) {
|
|
630
|
+
this.report(`${expr.fn}() cannot be called inside a loop. Declare timers at the top level.`, expr);
|
|
631
|
+
}
|
|
632
|
+
else if (this.condDepth > 0) {
|
|
633
|
+
this.report(`${expr.fn}() cannot be called inside an if/else body. Declare timers at the top level.`, expr);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
615
636
|
this.checkFunctionCallArgs(expr.args, builtin.params, expr.fn, expr);
|
|
616
637
|
return;
|
|
617
638
|
}
|
|
@@ -759,6 +780,14 @@ class TypeChecker {
|
|
|
759
780
|
}
|
|
760
781
|
}
|
|
761
782
|
checkStaticCallExpr(expr) {
|
|
783
|
+
if (expr.type === 'Timer' && expr.method === 'new') {
|
|
784
|
+
if (this.loopDepth > 0) {
|
|
785
|
+
this.report(`Timer::new() cannot be called inside a loop. Declare timers at the top level.`, expr);
|
|
786
|
+
}
|
|
787
|
+
else if (this.condDepth > 0) {
|
|
788
|
+
this.report(`Timer::new() cannot be called inside an if/else body. Declare timers at the top level.`, expr);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
762
791
|
const method = this.implMethods.get(expr.type)?.get(expr.method);
|
|
763
792
|
if (!method) {
|
|
764
793
|
this.report(`Type '${expr.type}' has no static method '${expr.method}'`, expr);
|
package/docs/ROADMAP.md
CHANGED
|
@@ -393,3 +393,38 @@ if let Some(player) = p {
|
|
|
393
393
|
---
|
|
394
394
|
|
|
395
395
|
*此文档由奇尔沙治生成 · 2026-03-16*
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## 遗留 Known Issues(未来修复)
|
|
400
|
+
|
|
401
|
+
### Timer stdlib 重构
|
|
402
|
+
**问题:** Timer 用硬编码 player 名存状态,只能有一个实例
|
|
403
|
+
**解法:** 全局自增 ID + macro 函数(`$scoreboard players set $(player) ...`),让每个实例有唯一的 player 名
|
|
404
|
+
**依赖:** lambda codegen(见下)
|
|
405
|
+
**工作量:** 2-3天
|
|
406
|
+
|
|
407
|
+
### lambda / closure codegen
|
|
408
|
+
**问题:** `setTimeout(20, () => { ... })` 的 lambda 在 MIR 层被丢弃(`const 0` 占位符),没有真正编译
|
|
409
|
+
**解法:** lambda body → 独立命名函数(`__timeout_callback_N`),在 HIR/MIR 层 lift
|
|
410
|
+
**工作量:** 3-5天
|
|
411
|
+
|
|
412
|
+
### Array literal 初始化
|
|
413
|
+
**已修复:** `nums[0]` 的读取修好了(NBT storage codegen)
|
|
414
|
+
**待完善:** `let nums = [10, 20, 30]` 的初始化写入目前走 fixture workaround
|
|
415
|
+
**工作量:** 1-2天
|
|
416
|
+
|
|
417
|
+
### mc-integration flaky tests
|
|
418
|
+
- `entity query: armor_stands survive peaceful mode` — 依赖服务器 peaceful mode 设置
|
|
419
|
+
- `E2E I: nested if/else boundary` — 偶发,服务器状态依赖
|
|
420
|
+
|
|
421
|
+
### 设计决策:Timer 实例化策略
|
|
422
|
+
|
|
423
|
+
**选定方案:编译期静态分配 + 禁止动态创建**
|
|
424
|
+
|
|
425
|
+
- `Timer::new()` 只能在函数顶层/模块级别调用,不能在循环/条件分支里调用
|
|
426
|
+
- 编译器给每个 `Timer::new()` 静态分配一个唯一 ID(`__timer_0`、`__timer_1`...)
|
|
427
|
+
- 在循环体内调用 `Timer::new()` → 编译错误,提示使用预分配的 Timer 变量
|
|
428
|
+
- 同理适用于 `setTimeout` / `setInterval`
|
|
429
|
+
|
|
430
|
+
好处:零运行时开销,行为完全可预测,符合 MC 的数据包执行模型。
|