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
@@ -360,6 +360,35 @@ function lowerExpr(expr) {
360
360
  return { kind: 'member', obj: lowerExpr(expr.obj), field: expr.field, span: expr.span };
361
361
  case 'index':
362
362
  return { kind: 'index', obj: lowerExpr(expr.obj), index: lowerExpr(expr.index), span: expr.span };
363
+ // --- Desugaring: compound index_assign → plain index_assign ---
364
+ case 'index_assign':
365
+ if (expr.op !== '=') {
366
+ const binOp = COMPOUND_TO_BINOP[expr.op];
367
+ const obj = lowerExpr(expr.obj);
368
+ const index = lowerExpr(expr.index);
369
+ return {
370
+ kind: 'index_assign',
371
+ obj,
372
+ index,
373
+ op: '=',
374
+ value: {
375
+ kind: 'binary',
376
+ op: binOp,
377
+ left: { kind: 'index', obj, index },
378
+ right: lowerExpr(expr.value),
379
+ span: expr.span,
380
+ },
381
+ span: expr.span,
382
+ };
383
+ }
384
+ return {
385
+ kind: 'index_assign',
386
+ obj: lowerExpr(expr.obj),
387
+ index: lowerExpr(expr.index),
388
+ op: expr.op,
389
+ value: lowerExpr(expr.value),
390
+ span: expr.span,
391
+ };
363
392
  case 'call':
364
393
  return { kind: 'call', fn: expr.fn, args: expr.args.map(lowerExpr), typeArgs: expr.typeArgs, span: expr.span };
365
394
  case 'invoke':
@@ -278,6 +278,8 @@ class Monomorphizer {
278
278
  return { ...expr, value: this.rewriteExpr(expr.value, ctx) };
279
279
  case 'member_assign':
280
280
  return { ...expr, obj: this.rewriteExpr(expr.obj, ctx), value: this.rewriteExpr(expr.value, ctx) };
281
+ case 'index_assign':
282
+ return { ...expr, obj: this.rewriteExpr(expr.obj, ctx), index: this.rewriteExpr(expr.index, ctx), value: this.rewriteExpr(expr.value, ctx) };
281
283
  case 'member':
282
284
  return { ...expr, obj: this.rewriteExpr(expr.obj, ctx) };
283
285
  case 'index':
@@ -12,8 +12,8 @@
12
12
  * All types and names are preserved from the AST.
13
13
  */
14
14
  import type { Span, TypeNode, EntitySelector, CoordComponent, Decorator, RangeExpr, FStringPart, SelectorFilter, EntityTypeName, LambdaParam } from '../ast/types';
15
- import type { BinOp, CmpOp } from '../ast/types';
16
- export type { Span, TypeNode, EntitySelector, CoordComponent, Decorator, RangeExpr, FStringPart, SelectorFilter, EntityTypeName, LambdaParam, BinOp, CmpOp, };
15
+ import type { BinOp, CmpOp, AssignOp } from '../ast/types';
16
+ export type { Span, TypeNode, EntitySelector, CoordComponent, Decorator, RangeExpr, FStringPart, SelectorFilter, EntityTypeName, LambdaParam, BinOp, CmpOp, AssignOp, };
17
17
  export type HIRExpr = {
18
18
  kind: 'int_lit';
19
19
  value: number;
@@ -124,6 +124,13 @@ export type HIRExpr = {
124
124
  field: string;
125
125
  value: HIRExpr;
126
126
  span?: Span;
127
+ } | {
128
+ kind: 'index_assign';
129
+ obj: HIRExpr;
130
+ index: HIRExpr;
131
+ op: AssignOp;
132
+ value: HIRExpr;
133
+ span?: Span;
127
134
  } | {
128
135
  kind: 'member';
129
136
  obj: HIRExpr;
@@ -41,6 +41,10 @@ class LoweringContext {
41
41
  this.currentMIRFn = null;
42
42
  /** Block map for quick lookup */
43
43
  this.blockMap = new Map();
44
+ /** Track generated dynamic array macro helper functions to avoid duplicates: key → fn name */
45
+ this.dynIdxHelpers = new Map();
46
+ /** Track generated dynamic array write helper functions: key → fn name */
47
+ this.dynWrtHelpers = new Map();
44
48
  this.namespace = namespace;
45
49
  this.objective = objective;
46
50
  }
@@ -62,6 +66,68 @@ class LoweringContext {
62
66
  addFunction(fn) {
63
67
  this.functions.push(fn);
64
68
  }
69
+ /**
70
+ * Get or create a macro helper function for dynamic array index reads.
71
+ * The helper function is: $return run data get storage <ns> <pathPrefix>[$(arr_idx)] 1
72
+ * Returns the qualified MC function name (namespace:fnName).
73
+ */
74
+ getDynIdxHelper(ns, pathPrefix) {
75
+ const key = `${ns}\0${pathPrefix}`;
76
+ const existing = this.dynIdxHelpers.get(key);
77
+ if (existing)
78
+ return existing;
79
+ // Generate deterministic name from ns and pathPrefix
80
+ const sanitize = (s) => s.replace(/[^a-z0-9_]/gi, '_').toLowerCase();
81
+ // Extract just the storage name part from ns (e.g. "myns:arrays" → "myns_arrays")
82
+ const nsStr = sanitize(ns);
83
+ const prefixStr = sanitize(pathPrefix);
84
+ const helperName = `__dyn_idx_${nsStr}_${prefixStr}`;
85
+ // The helper is placed in the current namespace
86
+ const qualifiedName = `${this.namespace}:${helperName}`;
87
+ // Generate the macro function content:
88
+ // $return run data get storage <ns> <pathPrefix>[$(arr_idx)] 1
89
+ const macroLine = {
90
+ kind: 'macro_line',
91
+ template: `return run data get storage ${ns} ${pathPrefix}[$(arr_idx)] 1`,
92
+ };
93
+ this.addFunction({
94
+ name: helperName,
95
+ instructions: [macroLine],
96
+ isMacro: true,
97
+ macroParams: ['arr_idx'],
98
+ });
99
+ this.dynIdxHelpers.set(key, qualifiedName);
100
+ return qualifiedName;
101
+ }
102
+ /**
103
+ * Get or create a macro helper function for dynamic array index writes.
104
+ * The helper function: $data modify storage <ns> <pathPrefix>[$(arr_idx)] set value $(arr_val)
105
+ * Returns the qualified MC function name.
106
+ */
107
+ getDynWrtHelper(ns, pathPrefix) {
108
+ const key = `${ns}\0${pathPrefix}`;
109
+ const existing = this.dynWrtHelpers.get(key);
110
+ if (existing)
111
+ return existing;
112
+ const sanitize = (s) => s.replace(/[^a-z0-9_]/gi, '_').toLowerCase();
113
+ const nsStr = sanitize(ns);
114
+ const prefixStr = sanitize(pathPrefix);
115
+ const helperName = `__dyn_wrt_${nsStr}_${prefixStr}`;
116
+ const qualifiedName = `${this.namespace}:${helperName}`;
117
+ // Macro line: $data modify storage <ns> <pathPrefix>[$(arr_idx)] set value $(arr_val)
118
+ const macroLine = {
119
+ kind: 'macro_line',
120
+ template: `data modify storage ${ns} ${pathPrefix}[$(arr_idx)] set value $(arr_val)`,
121
+ };
122
+ this.addFunction({
123
+ name: helperName,
124
+ instructions: [macroLine],
125
+ isMacro: true,
126
+ macroParams: ['arr_idx', 'arr_val'],
127
+ });
128
+ this.dynWrtHelpers.set(key, qualifiedName);
129
+ return qualifiedName;
130
+ }
65
131
  /** Attach sourceLoc to newly added instructions (from the given start index onward) */
66
132
  tagSourceLoc(instrs, fromIndex, sourceLoc) {
67
133
  if (!sourceLoc)
@@ -263,6 +329,41 @@ function lowerInstrInner(instr, fn, ctx, instrs) {
263
329
  });
264
330
  break;
265
331
  }
332
+ case 'nbt_read_dynamic': {
333
+ // Strategy:
334
+ // 1. Store the index value into rs:macro_args __arr_idx (int)
335
+ // 2. Call the per-array macro helper function with 'with storage rs:macro_args'
336
+ // 3. Result comes back via $ret scoreboard slot (the macro uses $return)
337
+ const dst = ctx.slot(instr.dst);
338
+ const idxSlot = operandToSlot(instr.indexSrc, ctx, instrs);
339
+ // Step 1: store index score → rs:macro_args arr_idx (int, scale 1)
340
+ instrs.push({
341
+ kind: 'store_score_to_nbt',
342
+ ns: 'rs:macro_args',
343
+ path: 'arr_idx',
344
+ type: 'int',
345
+ scale: 1,
346
+ src: idxSlot,
347
+ });
348
+ // Step 2: get or create the macro helper function, then call it
349
+ const helperFn = ctx.getDynIdxHelper(instr.ns, instr.pathPrefix);
350
+ instrs.push({ kind: 'call_macro', fn: helperFn, storage: 'rs:macro_args' });
351
+ // Step 3: the macro uses $return which sets the MC return value.
352
+ // We need to capture that. In MC, $return run ... returns the result
353
+ // to the caller via the execute store mechanism.
354
+ // Use store_cmd_to_score to capture the return value of the macro call.
355
+ // Actually, the call_macro instruction above already ran the function.
356
+ // The $return run data get ... sets the scoreboard return value for the
357
+ // *calling* function context. We need to use execute store result score.
358
+ // Rewrite: use raw command instead:
359
+ instrs.pop(); // remove the call_macro we just added
360
+ instrs.push({
361
+ kind: 'store_cmd_to_score',
362
+ dst,
363
+ cmd: { kind: 'call_macro', fn: helperFn, storage: 'rs:macro_args' },
364
+ });
365
+ break;
366
+ }
266
367
  case 'nbt_write': {
267
368
  const srcSlot = operandToSlot(instr.src, ctx, instrs);
268
369
  instrs.push({
@@ -275,6 +376,36 @@ function lowerInstrInner(instr, fn, ctx, instrs) {
275
376
  });
276
377
  break;
277
378
  }
379
+ case 'nbt_write_dynamic': {
380
+ // Strategy:
381
+ // 1. Store index score → rs:macro_args arr_idx (int)
382
+ // 2. Store value score → rs:macro_args arr_val (int)
383
+ // 3. Call macro helper: $data modify storage <ns> <pathPrefix>[$(arr_idx)] set value $(arr_val)
384
+ const idxSlot = operandToSlot(instr.indexSrc, ctx, instrs);
385
+ const valSlot = operandToSlot(instr.valueSrc, ctx, instrs);
386
+ // Store index
387
+ instrs.push({
388
+ kind: 'store_score_to_nbt',
389
+ ns: 'rs:macro_args',
390
+ path: 'arr_idx',
391
+ type: 'int',
392
+ scale: 1,
393
+ src: idxSlot,
394
+ });
395
+ // Store value
396
+ instrs.push({
397
+ kind: 'store_score_to_nbt',
398
+ ns: 'rs:macro_args',
399
+ path: 'arr_val',
400
+ type: 'int',
401
+ scale: 1,
402
+ src: valSlot,
403
+ });
404
+ // Call macro helper function
405
+ const helperFn = ctx.getDynWrtHelper(instr.ns, instr.pathPrefix);
406
+ instrs.push({ kind: 'call_macro', fn: helperFn, storage: 'rs:macro_args' });
407
+ break;
408
+ }
278
409
  case 'score_read': {
279
410
  // execute store result score $dst __obj run scoreboard players get <player> <obj>
280
411
  const dst = ctx.slot(instr.dst);
@@ -929,12 +929,20 @@ function lowerExpr(expr, ctx, scope) {
929
929
  return { kind: 'temp', name: t };
930
930
  }
931
931
  case 'index': {
932
- // Check if obj is a tracked array variable with a constant index
932
+ // Check if obj is a tracked array variable
933
933
  if (expr.obj.kind === 'ident') {
934
934
  const arrInfo = ctx.arrayVars.get(expr.obj.name);
935
- if (arrInfo && expr.index.kind === 'int_lit') {
935
+ if (arrInfo) {
936
936
  const t = ctx.freshTemp();
937
- ctx.emit({ kind: 'nbt_read', dst: t, ns: arrInfo.ns, path: `${arrInfo.pathPrefix}[${expr.index.value}]`, scale: 1 });
937
+ if (expr.index.kind === 'int_lit') {
938
+ // Constant index: direct NBT read
939
+ ctx.emit({ kind: 'nbt_read', dst: t, ns: arrInfo.ns, path: `${arrInfo.pathPrefix}[${expr.index.value}]`, scale: 1 });
940
+ }
941
+ else {
942
+ // Dynamic index: emit nbt_read_dynamic
943
+ const idxOp = lowerExpr(expr.index, ctx, scope);
944
+ ctx.emit({ kind: 'nbt_read_dynamic', dst: t, ns: arrInfo.ns, pathPrefix: arrInfo.pathPrefix, indexSrc: idxOp });
945
+ }
938
946
  return { kind: 'temp', name: t };
939
947
  }
940
948
  }
@@ -944,6 +952,25 @@ function lowerExpr(expr, ctx, scope) {
944
952
  ctx.emit({ kind: 'copy', dst: t, src: obj });
945
953
  return { kind: 'temp', name: t };
946
954
  }
955
+ case 'index_assign': {
956
+ const valOp = lowerExpr(expr.value, ctx, scope);
957
+ if (expr.obj.kind === 'ident') {
958
+ const arrInfo = ctx.arrayVars.get(expr.obj.name);
959
+ if (arrInfo) {
960
+ if (expr.index.kind === 'int_lit') {
961
+ // constant index → direct nbt_write
962
+ ctx.emit({ kind: 'nbt_write', ns: arrInfo.ns, path: `${arrInfo.pathPrefix}[${expr.index.value}]`, type: 'int', scale: 1, src: valOp });
963
+ }
964
+ else {
965
+ // dynamic index → nbt_write_dynamic
966
+ const idxOp = lowerExpr(expr.index, ctx, scope);
967
+ ctx.emit({ kind: 'nbt_write_dynamic', ns: arrInfo.ns, pathPrefix: arrInfo.pathPrefix, indexSrc: idxOp, valueSrc: valOp });
968
+ }
969
+ return valOp;
970
+ }
971
+ }
972
+ return valOp;
973
+ }
947
974
  case 'call': {
948
975
  // Handle scoreboard_get / score — read from vanilla MC scoreboard
949
976
  if (expr.fn === 'scoreboard_get' || expr.fn === 'score') {
@@ -963,6 +990,49 @@ function lowerExpr(expr, ctx, scope) {
963
990
  ctx.emit({ kind: 'const', dst: t, value: 0 });
964
991
  return { kind: 'temp', name: t };
965
992
  }
993
+ // Handle list_push(arr_name, val) — append an int to an NBT int array
994
+ // list_push("rs:lists", "mylist", val) or simpler: uses the array's storage path
995
+ if (expr.fn === 'list_push') {
996
+ // list_push(array_var, value)
997
+ // 1. Append a placeholder 0
998
+ // 2. Overwrite [-1] with the actual value
999
+ if (expr.args[0].kind === 'ident') {
1000
+ const arrInfo = ctx.arrayVars.get(expr.args[0].name);
1001
+ if (arrInfo) {
1002
+ const valOp = lowerExpr(expr.args[1], ctx, scope);
1003
+ // Step 1: append placeholder
1004
+ ctx.emit({ kind: 'call', dst: null, fn: `__raw:data modify storage ${arrInfo.ns} ${arrInfo.pathPrefix} append value 0`, args: [] });
1005
+ // Step 2: overwrite last element with actual value
1006
+ ctx.emit({ kind: 'nbt_write', ns: arrInfo.ns, path: `${arrInfo.pathPrefix}[-1]`, type: 'int', scale: 1, src: valOp });
1007
+ const t = ctx.freshTemp();
1008
+ ctx.emit({ kind: 'const', dst: t, value: 0 });
1009
+ return { kind: 'temp', name: t };
1010
+ }
1011
+ }
1012
+ }
1013
+ // Handle list_pop(arr_var) — remove last element from NBT int array
1014
+ if (expr.fn === 'list_pop') {
1015
+ if (expr.args[0].kind === 'ident') {
1016
+ const arrInfo = ctx.arrayVars.get(expr.args[0].name);
1017
+ if (arrInfo) {
1018
+ ctx.emit({ kind: 'call', dst: null, fn: `__raw:data remove storage ${arrInfo.ns} ${arrInfo.pathPrefix}[-1]`, args: [] });
1019
+ const t = ctx.freshTemp();
1020
+ ctx.emit({ kind: 'const', dst: t, value: 0 });
1021
+ return { kind: 'temp', name: t };
1022
+ }
1023
+ }
1024
+ }
1025
+ // Handle list_len(arr_var) — get length of NBT int array
1026
+ if (expr.fn === 'list_len') {
1027
+ if (expr.args[0].kind === 'ident') {
1028
+ const arrInfo = ctx.arrayVars.get(expr.args[0].name);
1029
+ if (arrInfo) {
1030
+ const t = ctx.freshTemp();
1031
+ ctx.emit({ kind: 'nbt_read', dst: t, ns: arrInfo.ns, path: `${arrInfo.pathPrefix}`, scale: 1 });
1032
+ return { kind: 'temp', name: t };
1033
+ }
1034
+ }
1035
+ }
966
1036
  // Handle setTimeout/setInterval: lift lambda arg to a named helper function
967
1037
  if ((expr.fn === 'setTimeout' || expr.fn === 'setInterval') && expr.args.length === 2) {
968
1038
  const ticksArg = expr.args[0];
@@ -139,6 +139,11 @@ function scanExpr(expr, paramNames, macroParams) {
139
139
  scanExpr(expr.obj, paramNames, macroParams);
140
140
  scanExpr(expr.value, paramNames, macroParams);
141
141
  break;
142
+ case 'index_assign':
143
+ scanExpr(expr.obj, paramNames, macroParams);
144
+ scanExpr(expr.index, paramNames, macroParams);
145
+ scanExpr(expr.value, paramNames, macroParams);
146
+ break;
142
147
  case 'member':
143
148
  scanExpr(expr.obj, paramNames, macroParams);
144
149
  break;
@@ -130,6 +130,12 @@ export type MIRInstr = MIRInstrBase & ({
130
130
  ns: string;
131
131
  path: string;
132
132
  scale: number;
133
+ } | {
134
+ kind: 'nbt_read_dynamic';
135
+ dst: Temp;
136
+ ns: string;
137
+ pathPrefix: string;
138
+ indexSrc: Operand;
133
139
  } | {
134
140
  kind: 'nbt_write';
135
141
  ns: string;
@@ -137,6 +143,12 @@ export type MIRInstr = MIRInstrBase & ({
137
143
  type: NBTType;
138
144
  scale: number;
139
145
  src: Operand;
146
+ } | {
147
+ kind: 'nbt_write_dynamic';
148
+ ns: string;
149
+ pathPrefix: string;
150
+ indexSrc: Operand;
151
+ valueSrc: Operand;
140
152
  } | {
141
153
  kind: 'score_read';
142
154
  dst: Temp;
@@ -155,6 +155,7 @@ function getDst(instr) {
155
155
  case 'or':
156
156
  case 'not':
157
157
  case 'nbt_read':
158
+ case 'nbt_read_dynamic':
158
159
  return instr.dst;
159
160
  case 'call':
160
161
  case 'call_macro':
@@ -188,9 +189,15 @@ function getUsedTemps(instr) {
188
189
  break;
189
190
  case 'nbt_read':
190
191
  break;
192
+ case 'nbt_read_dynamic':
193
+ temps.push(...getOperandTemps(instr.indexSrc));
194
+ break;
191
195
  case 'nbt_write':
192
196
  temps.push(...getOperandTemps(instr.src));
193
197
  break;
198
+ case 'nbt_write_dynamic':
199
+ temps.push(...getOperandTemps(instr.indexSrc), ...getOperandTemps(instr.valueSrc));
200
+ break;
194
201
  case 'call':
195
202
  for (const arg of instr.args)
196
203
  temps.push(...getOperandTemps(arg));
@@ -75,6 +75,10 @@ function rewriteUses(instr, copies) {
75
75
  return { ...instr, a: resolve(instr.a, copies), b: resolve(instr.b, copies) };
76
76
  case 'nbt_write':
77
77
  return { ...instr, src: resolve(instr.src, copies) };
78
+ case 'nbt_write_dynamic':
79
+ return { ...instr, indexSrc: resolve(instr.indexSrc, copies), valueSrc: resolve(instr.valueSrc, copies) };
80
+ case 'nbt_read_dynamic':
81
+ return { ...instr, indexSrc: resolve(instr.indexSrc, copies) };
78
82
  case 'call':
79
83
  return { ...instr, args: instr.args.map(a => resolve(a, copies)) };
80
84
  case 'call_macro':
@@ -104,6 +108,7 @@ function getDst(instr) {
104
108
  case 'or':
105
109
  case 'not':
106
110
  case 'nbt_read':
111
+ case 'nbt_read_dynamic':
107
112
  return instr.dst;
108
113
  case 'call':
109
114
  case 'call_macro':
@@ -713,8 +713,12 @@ function rewriteInstr(instr, promoted) {
713
713
  return { ...instr, dst: rTemp(instr.dst), src: rOp(instr.src) };
714
714
  case 'nbt_read':
715
715
  return { ...instr, dst: rTemp(instr.dst) };
716
+ case 'nbt_read_dynamic':
717
+ return { ...instr, dst: rTemp(instr.dst), indexSrc: rOp(instr.indexSrc) };
716
718
  case 'nbt_write':
717
719
  return { ...instr, src: rOp(instr.src) };
720
+ case 'nbt_write_dynamic':
721
+ return { ...instr, indexSrc: rOp(instr.indexSrc), valueSrc: rOp(instr.valueSrc) };
718
722
  case 'call':
719
723
  return { ...instr, dst: instr.dst ? rTemp(instr.dst) : null, args: instr.args.map(rOp) };
720
724
  case 'call_macro':
@@ -770,6 +774,7 @@ function getDst(instr) {
770
774
  case 'or':
771
775
  case 'not':
772
776
  case 'nbt_read':
777
+ case 'nbt_read_dynamic':
773
778
  return instr.dst;
774
779
  case 'call':
775
780
  case 'call_macro':
@@ -802,6 +807,13 @@ function getUsedTemps(instr) {
802
807
  case 'nbt_write':
803
808
  addOp(instr.src);
804
809
  break;
810
+ case 'nbt_write_dynamic':
811
+ addOp(instr.indexSrc);
812
+ addOp(instr.valueSrc);
813
+ break;
814
+ case 'nbt_read_dynamic':
815
+ addOp(instr.indexSrc);
816
+ break;
805
817
  case 'call':
806
818
  instr.args.forEach(addOp);
807
819
  break;
@@ -75,6 +75,7 @@ function recomputePreds(blocks) {
75
75
  function hasSideEffects(instr) {
76
76
  if (instr.kind === 'call' || instr.kind === 'call_macro' ||
77
77
  instr.kind === 'call_context' || instr.kind === 'nbt_write' ||
78
+ instr.kind === 'nbt_write_dynamic' ||
78
79
  instr.kind === 'score_write')
79
80
  return true;
80
81
  // Return field temps (__rf_) write to global return slots — not dead even if unused locally
@@ -106,6 +107,7 @@ function getDst(instr) {
106
107
  case 'or':
107
108
  case 'not':
108
109
  case 'nbt_read':
110
+ case 'nbt_read_dynamic':
109
111
  return instr.dst;
110
112
  case 'call':
111
113
  case 'call_macro':
@@ -140,6 +142,13 @@ function getUsedTemps(instr) {
140
142
  case 'nbt_write':
141
143
  addOp(instr.src);
142
144
  break;
145
+ case 'nbt_write_dynamic':
146
+ addOp(instr.indexSrc);
147
+ addOp(instr.valueSrc);
148
+ break;
149
+ case 'nbt_read_dynamic':
150
+ addOp(instr.indexSrc);
151
+ break;
143
152
  case 'call':
144
153
  instr.args.forEach(addOp);
145
154
  break;
@@ -287,6 +287,8 @@ function substituteInstr(instr, sub) {
287
287
  return { ...instr, a: substituteOp(instr.a, sub), b: substituteOp(instr.b, sub) };
288
288
  case 'nbt_write':
289
289
  return { ...instr, src: substituteOp(instr.src, sub) };
290
+ case 'nbt_write_dynamic':
291
+ return { ...instr, indexSrc: substituteOp(instr.indexSrc, sub), valueSrc: substituteOp(instr.valueSrc, sub) };
290
292
  case 'call':
291
293
  return { ...instr, args: instr.args.map(a => substituteOp(a, sub)) };
292
294
  case 'call_macro':
@@ -317,6 +319,7 @@ function getInstrDst(instr) {
317
319
  case 'or':
318
320
  case 'not':
319
321
  case 'nbt_read':
322
+ case 'nbt_read_dynamic':
320
323
  return instr.dst;
321
324
  case 'call':
322
325
  case 'call_macro':
@@ -1048,6 +1048,11 @@ class Parser {
1048
1048
  const value = this.parseAssignment();
1049
1049
  return this.withLoc({ kind: 'member_assign', obj: left.obj, field: left.field, op, value }, this.getLocToken(left) ?? token);
1050
1050
  }
1051
+ // Index assignment: arr[0] = val, arr[i] = val
1052
+ if (left.kind === 'index') {
1053
+ const value = this.parseAssignment();
1054
+ return this.withLoc({ kind: 'index_assign', obj: left.obj, index: left.index, op, value }, this.getLocToken(left) ?? token);
1055
+ }
1051
1056
  }
1052
1057
  return left;
1053
1058
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * ln(x) polynomial approximation adapter — atanh series form.
3
+ *
4
+ * Algorithm:
5
+ * Input x: fixed-point integer (×10000), e.g. 10000 = 1.0
6
+ * 1. Range reduction: find k s.t. xr ∈ [10000, 20000)
7
+ * 2. s = (xr - 10000) * 10000 / (xr + 10000) → s ∈ [0, 3333]
8
+ * 3. ln(xr/10000) ≈ A1*s/SCALE + A3*s³/SCALE² + A5*s⁵/SCALE³
9
+ * (coefficients absorb the factor of 2; theoretical: A1=20000, A3=6667, A5=4000)
10
+ * 4. ln(x/10000) = k * LN2 + ln(xr/10000)
11
+ *
12
+ * Intermediate overflow analysis (s ≤ 3333, SCALE = 10000):
13
+ * s² = s*s ≤ 11M — fits int32 (max ~2.1B)
14
+ * s2 = s²/SCALE ≤ 1111
15
+ * s3 = s*s2 ≤ 3.7M — fits int32
16
+ * s5 = s3*s2 ≤ 4.1M — fits int32
17
+ * A1*s ≤ 22000*3333 ≤ 73M — fits int32
18
+ * A3*s3 ≤ 7000*3703 ≤ 26M — fits int32
19
+ * A5*s5 ≤ 5000*4115 ≤ 21M — fits int32
20
+ */
21
+ import { TunerAdapter } from '../types';
22
+ export declare const defaultParams: Record<string, number>;
23
+ export declare const lnPolynomialAdapter: TunerAdapter;