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
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Nelder-Mead simplex optimization engine for hyperparameter tuning.
3
+ * Suitable for continuous parameter spaces without gradient information.
4
+ * Supports mixed-integer parameters.
5
+ */
6
+
7
+ import { TunerAdapter, SearchResult, ParamSpec } from './types';
8
+ import { evaluate } from './metrics';
9
+
10
+ export type ProgressCallback = (iteration: number, bestError: number) => void;
11
+
12
+ interface SimplexPoint {
13
+ coords: number[];
14
+ score: number;
15
+ }
16
+
17
+ /**
18
+ * Apply integer constraints to parameters.
19
+ */
20
+ function applyIntegerConstraints(
21
+ coords: number[],
22
+ specs: ParamSpec[]
23
+ ): number[] {
24
+ return coords.map((v, i) => {
25
+ const spec = specs[i];
26
+ // Clamp to range
27
+ const clamped = Math.max(spec.range[0], Math.min(spec.range[1], v));
28
+ return spec.integer ? Math.round(clamped) : clamped;
29
+ });
30
+ }
31
+
32
+ /**
33
+ * Convert coordinate array to params record.
34
+ */
35
+ function coordsToParams(
36
+ coords: number[],
37
+ specs: ParamSpec[]
38
+ ): Record<string, number> {
39
+ const params: Record<string, number> = {};
40
+ for (let i = 0; i < specs.length; i++) {
41
+ params[specs[i].name] = coords[i];
42
+ }
43
+ return params;
44
+ }
45
+
46
+ /**
47
+ * Evaluate a point using the adapter.
48
+ */
49
+ function scorePoint(
50
+ coords: number[],
51
+ specs: ParamSpec[],
52
+ adapter: TunerAdapter
53
+ ): number {
54
+ const constrained = applyIntegerConstraints(coords, specs);
55
+ const params = coordsToParams(constrained, specs);
56
+ const metrics = evaluate(adapter, params);
57
+ return metrics.maxError;
58
+ }
59
+
60
+ /**
61
+ * Run Nelder-Mead optimization.
62
+ */
63
+ export function search(
64
+ adapter: TunerAdapter,
65
+ budget: number = 10000,
66
+ onProgress?: ProgressCallback
67
+ ): SearchResult {
68
+ const specs = adapter.params;
69
+ const n = specs.length;
70
+
71
+ if (n === 0) {
72
+ // No params to optimize
73
+ const params = coordsToParams([], specs);
74
+ const metrics = evaluate(adapter, params);
75
+ return {
76
+ params,
77
+ maxError: metrics.maxError,
78
+ mae: metrics.mae,
79
+ rmse: metrics.rmse,
80
+ budgetUsed: 0,
81
+ };
82
+ }
83
+
84
+ // Nelder-Mead parameters
85
+ const alpha = 1.0; // reflection
86
+ const gamma = 2.0; // expansion
87
+ const rho = 0.5; // contraction
88
+ const sigma = 0.5; // shrink
89
+
90
+ // Initialize simplex with n+1 points
91
+ const simplex: SimplexPoint[] = [];
92
+
93
+ // Start point: midpoint of each parameter range
94
+ const startCoords = specs.map(s => (s.range[0] + s.range[1]) / 2);
95
+ simplex.push({
96
+ coords: startCoords,
97
+ score: scorePoint(startCoords, specs, adapter),
98
+ });
99
+
100
+ // Generate remaining n points by perturbing each dimension
101
+ for (let i = 0; i < n; i++) {
102
+ const coords = [...startCoords];
103
+ const span = specs[i].range[1] - specs[i].range[0];
104
+ // Perturb by 20% of the range
105
+ coords[i] = startCoords[i] + span * 0.2;
106
+ simplex.push({
107
+ coords,
108
+ score: scorePoint(coords, specs, adapter),
109
+ });
110
+ }
111
+
112
+ let iteration = 0;
113
+
114
+ while (iteration < budget) {
115
+ // Sort simplex by score (ascending = better)
116
+ simplex.sort((a, b) => a.score - b.score);
117
+
118
+ const best = simplex[0];
119
+ const worst = simplex[n];
120
+ const secondWorst = simplex[n - 1];
121
+
122
+ if (onProgress && iteration % 100 === 0) {
123
+ onProgress(iteration, best.score);
124
+ }
125
+
126
+ // Check convergence: if all scores are the same, we're stuck
127
+ if (
128
+ simplex[n].score - simplex[0].score < 1e-15 &&
129
+ iteration > 100
130
+ ) {
131
+ break;
132
+ }
133
+
134
+ // Compute centroid of all but worst
135
+ const centroid = new Array(n).fill(0);
136
+ for (let i = 0; i < n; i++) {
137
+ for (let j = 0; j < n; j++) {
138
+ centroid[j] += simplex[i].coords[j] / n;
139
+ }
140
+ }
141
+
142
+ // Reflection
143
+ const reflected = centroid.map(
144
+ (c, j) => c + alpha * (c - worst.coords[j])
145
+ );
146
+ const reflectedScore = scorePoint(reflected, specs, adapter);
147
+ iteration++;
148
+
149
+ if (reflectedScore < best.score) {
150
+ // Expansion
151
+ const expanded = centroid.map(
152
+ (c, j) => c + gamma * (reflected[j] - c)
153
+ );
154
+ const expandedScore = scorePoint(expanded, specs, adapter);
155
+ iteration++;
156
+
157
+ if (expandedScore < reflectedScore) {
158
+ simplex[n] = { coords: expanded, score: expandedScore };
159
+ } else {
160
+ simplex[n] = { coords: reflected, score: reflectedScore };
161
+ }
162
+ } else if (reflectedScore < secondWorst.score) {
163
+ simplex[n] = { coords: reflected, score: reflectedScore };
164
+ } else {
165
+ // Contraction
166
+ const useReflected = reflectedScore < worst.score;
167
+ const contractionBase = useReflected ? reflected : worst.coords;
168
+ const contracted = centroid.map(
169
+ (c, j) => c + rho * (contractionBase[j] - c)
170
+ );
171
+ const contractedScore = scorePoint(contracted, specs, adapter);
172
+ iteration++;
173
+
174
+ if (contractedScore < (useReflected ? reflectedScore : worst.score)) {
175
+ simplex[n] = { coords: contracted, score: contractedScore };
176
+ } else {
177
+ // Shrink
178
+ for (let i = 1; i <= n; i++) {
179
+ simplex[i].coords = simplex[0].coords.map(
180
+ (c, j) => c + sigma * (simplex[i].coords[j] - c)
181
+ );
182
+ simplex[i].score = scorePoint(simplex[i].coords, specs, adapter);
183
+ iteration++;
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ // Sort final simplex
190
+ simplex.sort((a, b) => a.score - b.score);
191
+ const bestCoords = applyIntegerConstraints(simplex[0].coords, specs);
192
+ const bestParams = coordsToParams(bestCoords, specs);
193
+ const finalMetrics = evaluate(adapter, bestParams);
194
+
195
+ return {
196
+ params: bestParams,
197
+ maxError: finalMetrics.maxError,
198
+ mae: finalMetrics.mae,
199
+ rmse: finalMetrics.rmse,
200
+ budgetUsed: iteration,
201
+ };
202
+ }
203
+
204
+ /**
205
+ * Simulated Annealing search strategy.
206
+ * More robust than Nelder-Mead for integer parameters and multimodal objectives.
207
+ * Uses 3 independent restarts, taking the global best.
208
+ */
209
+ export function searchSA(
210
+ adapter: TunerAdapter,
211
+ budget: number,
212
+ onProgress?: ProgressCallback,
213
+ ): SearchResult {
214
+ const RESTARTS = 3;
215
+ const budgetPerRun = Math.floor(budget / RESTARTS);
216
+ let globalBest: { params: Record<string, number>; error: number } | null = null;
217
+
218
+ for (let r = 0; r < RESTARTS; r++) {
219
+ // Random initialisation within param ranges
220
+ let current: Record<string, number> = {};
221
+ for (const p of adapter.params) {
222
+ current[p.name] = p.range[0] + Math.random() * (p.range[1] - p.range[0]);
223
+ if (p.integer) current[p.name] = Math.round(current[p.name]);
224
+ }
225
+
226
+ // T: 1.0 → 1e-4 over budgetPerRun iterations
227
+ let T = 1.0;
228
+ const cooling = Math.pow(1e-4, 1 / budgetPerRun);
229
+ let currentError = evaluate(adapter, current).maxError;
230
+ let bestLocal = { params: { ...current }, error: currentError };
231
+
232
+ for (let i = 0; i < budgetPerRun; i++) {
233
+ T *= cooling;
234
+ // Perturb one random param by ±10% of its range
235
+ const neighbor = { ...current };
236
+ const spec = adapter.params[Math.floor(Math.random() * adapter.params.length)];
237
+ const step = (spec.range[1] - spec.range[0]) * 0.1 * (Math.random() * 2 - 1);
238
+ neighbor[spec.name] = Math.max(
239
+ spec.range[0],
240
+ Math.min(spec.range[1], neighbor[spec.name] + step),
241
+ );
242
+ if (spec.integer) neighbor[spec.name] = Math.round(neighbor[spec.name]);
243
+
244
+ const neighborError = evaluate(adapter, neighbor).maxError;
245
+ const delta = neighborError - currentError;
246
+ if (delta < 0 || Math.random() < Math.exp(-delta / T)) {
247
+ current = neighbor;
248
+ currentError = neighborError;
249
+ }
250
+ if (currentError < bestLocal.error) {
251
+ bestLocal = { params: { ...current }, error: currentError };
252
+ }
253
+ const globalIter = r * budgetPerRun + i;
254
+ if (onProgress && globalIter % 100 === 0) {
255
+ onProgress(globalIter, globalBest?.error ?? bestLocal.error);
256
+ }
257
+ }
258
+
259
+ if (!globalBest || bestLocal.error < globalBest.error) {
260
+ globalBest = bestLocal;
261
+ }
262
+ }
263
+
264
+ const finalMetrics = evaluate(adapter, globalBest!.params);
265
+ return {
266
+ params: globalBest!.params,
267
+ maxError: finalMetrics.maxError,
268
+ mae: finalMetrics.mae,
269
+ rmse: finalMetrics.rmse,
270
+ budgetUsed: budget,
271
+ };
272
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Error metrics for tuner adapters.
3
+ */
4
+
5
+ import { TunerAdapter } from './types';
6
+
7
+ export interface EvaluationResult {
8
+ maxError: number;
9
+ mae: number;
10
+ rmse: number;
11
+ }
12
+
13
+ /**
14
+ * Evaluate an adapter with given params across all sample inputs.
15
+ * Returns max_error, mae, and rmse relative to the reference function.
16
+ * Penalizes overflow by returning Infinity for all metrics.
17
+ */
18
+ export function evaluate(
19
+ adapter: TunerAdapter,
20
+ params: Record<string, number>
21
+ ): EvaluationResult {
22
+ const inputs = adapter.sampleInputs();
23
+ let maxError = 0;
24
+ let sumAbsError = 0;
25
+ let sumSqError = 0;
26
+ let count = 0;
27
+
28
+ for (const input of inputs) {
29
+ const simResult = adapter.simulate(input, params);
30
+ const refResult = adapter.reference(input);
31
+
32
+ // Penalize overflow or NaN
33
+ if (!isFinite(simResult) || isNaN(simResult)) {
34
+ return { maxError: Infinity, mae: Infinity, rmse: Infinity };
35
+ }
36
+
37
+ if (!isFinite(refResult) || isNaN(refResult)) {
38
+ continue; // skip degenerate reference points
39
+ }
40
+
41
+ // Both are in fixed-point; normalize to compare in floating-point units
42
+ const SCALE = 10000;
43
+ const simFloat = simResult / SCALE;
44
+ const refFloat = refResult / SCALE;
45
+
46
+ const absError = Math.abs(simFloat - refFloat);
47
+ if (absError === Infinity) {
48
+ return { maxError: Infinity, mae: Infinity, rmse: Infinity };
49
+ }
50
+
51
+ if (absError > maxError) maxError = absError;
52
+ sumAbsError += absError;
53
+ sumSqError += absError * absError;
54
+ count++;
55
+ }
56
+
57
+ if (count === 0) {
58
+ return { maxError: Infinity, mae: Infinity, rmse: Infinity };
59
+ }
60
+
61
+ return {
62
+ maxError,
63
+ mae: sumAbsError / count,
64
+ rmse: Math.sqrt(sumSqError / count),
65
+ };
66
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * MC int32 arithmetic simulation.
3
+ * Minecraft scoreboards use 32-bit signed integers (Java int).
4
+ */
5
+
6
+ export const INT32_MAX = 2147483647;
7
+ export const INT32_MIN = -2147483648;
8
+
9
+ /**
10
+ * Truncate to int32 using JavaScript bitwise (same as Java int cast).
11
+ */
12
+ export function i32(x: number): number {
13
+ return x | 0;
14
+ }
15
+
16
+ /**
17
+ * Check if a value is within int32 range (before truncation).
18
+ */
19
+ export function isOverflow(x: number): boolean {
20
+ return x > INT32_MAX || x < INT32_MIN || !isFinite(x) || isNaN(x);
21
+ }
22
+
23
+ /**
24
+ * Fixed-point multiply: compute i32(i32(a * b) / scale).
25
+ * Returns Infinity if overflow is detected before truncation.
26
+ */
27
+ export function fixedMul(a: number, b: number, scale: number): number {
28
+ const product = a * b;
29
+ if (isOverflow(product)) return Infinity;
30
+ const truncated = i32(product);
31
+ const divided = truncated / scale;
32
+ if (isOverflow(divided)) return Infinity;
33
+ return i32(divided);
34
+ }
35
+
36
+ /**
37
+ * Safe i32 addition - returns Infinity on overflow.
38
+ */
39
+ export function safeAdd(a: number, b: number): number {
40
+ const result = a + b;
41
+ if (isOverflow(result)) return Infinity;
42
+ return i32(result);
43
+ }
44
+
45
+ /**
46
+ * Safe i32 subtraction - returns Infinity on overflow.
47
+ */
48
+ export function safeSub(a: number, b: number): number {
49
+ const result = a - b;
50
+ if (isOverflow(result)) return Infinity;
51
+ return i32(result);
52
+ }
53
+
54
+ /**
55
+ * Safe i32 multiply - returns Infinity on overflow.
56
+ */
57
+ export function safeMul(a: number, b: number): number {
58
+ const result = a * b;
59
+ if (isOverflow(result)) return Infinity;
60
+ return i32(result);
61
+ }
62
+
63
+ /**
64
+ * Safe i32 division - returns Infinity on division by zero.
65
+ */
66
+ export function safeDiv(a: number, b: number): number {
67
+ if (b === 0) return Infinity;
68
+ return i32(a / b);
69
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Shared types for the redscript tuner system.
3
+ */
4
+
5
+ export interface ParamSpec {
6
+ name: string;
7
+ range: [number, number]; // search range
8
+ integer: boolean; // whether to force integer rounding
9
+ }
10
+
11
+ export interface ResultMeta {
12
+ maxError: number;
13
+ mae: number;
14
+ rmse: number;
15
+ estimatedCmds: number; // estimated mcfunction command count
16
+ tuneDate: string;
17
+ budgetUsed: number;
18
+ }
19
+
20
+ export interface TunerAdapter {
21
+ name: string;
22
+ description: string;
23
+ params: ParamSpec[];
24
+
25
+ // Simulate function with int32 semantics, returns fixed-point integer result
26
+ simulate(input: number, params: Record<string, number>): number;
27
+
28
+ // Reference value (floating-point) for error computation
29
+ reference(input: number): number;
30
+
31
+ // Generate input sample points
32
+ sampleInputs(): number[];
33
+
34
+ // Generate full .mcrs code with the optimal params
35
+ generateCode(params: Record<string, number>, meta: ResultMeta): string;
36
+ }
37
+
38
+ export interface SearchResult {
39
+ params: Record<string, number>;
40
+ maxError: number;
41
+ mae: number;
42
+ rmse: number;
43
+ budgetUsed: number;
44
+ }
@@ -619,6 +619,12 @@ export class TypeChecker {
619
619
  this.checkExpr(expr.value)
620
620
  break
621
621
 
622
+ case 'index_assign':
623
+ this.checkExpr(expr.obj)
624
+ this.checkExpr(expr.index)
625
+ this.checkExpr(expr.value)
626
+ break
627
+
622
628
  case 'index':
623
629
  this.checkExpr(expr.obj)
624
630
  this.checkExpr(expr.index)