redscript-mc 1.2.27 → 1.2.29

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/src/index.ts CHANGED
@@ -10,7 +10,7 @@ export const version = '1.2.11'
10
10
  import { Lexer } from './lexer'
11
11
  import { Parser } from './parser'
12
12
  import { TypeChecker } from './typechecker'
13
- import { Lowering } from './lowering'
13
+ import { Lowering, setScoreboardObjective } from './lowering'
14
14
  import type { Warning } from './lowering'
15
15
  import {
16
16
  constantFoldingWithStats,
@@ -36,6 +36,11 @@ export interface CompileOptions {
36
36
  filePath?: string
37
37
  dce?: boolean
38
38
  mangle?: boolean
39
+ /** Scoreboard objective used for all variable slots.
40
+ * Defaults to '__<namespace>' (e.g. '__mathshow') to avoid collisions when
41
+ * multiple RedScript datapacks are loaded simultaneously, without occupying
42
+ * the user's own namespace. Override only if you need a specific name. */
43
+ scoreboardObjective?: string
39
44
  }
40
45
 
41
46
  export interface CompileResult {
@@ -101,12 +106,18 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
101
106
  typeErrors = checker.check(ast)
102
107
  }
103
108
 
109
+ // Configure scoreboard objective for this compilation.
110
+ // Default: use the datapack namespace so each datapack gets its own objective
111
+ // automatically, preventing variable collisions when multiple datapacks coexist.
112
+ const scoreboardObj = options.scoreboardObjective ?? `__${namespace}`
113
+ setScoreboardObjective(scoreboardObj)
114
+
104
115
  // Lowering to IR
105
116
  const lowering = new Lowering(namespace, preprocessed.ranges)
106
117
  const ir = lowering.lower(ast)
107
118
 
108
119
  let optimizedIR: IRModule = ir
109
- let generated = generateDatapackWithStats(ir, { optimizeCommands: shouldOptimize, mangle })
120
+ let generated = generateDatapackWithStats(ir, { optimizeCommands: shouldOptimize, mangle, scoreboardObjective: scoreboardObj })
110
121
  let optimizationStats: OptimizationStats | undefined
111
122
 
112
123
  if (shouldOptimize) {
@@ -128,10 +139,10 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
128
139
  const copyPropagatedIR: IRModule = { ...ir, functions: copyPropagatedFunctions }
129
140
  optimizedIR = { ...ir, functions: deadCodeEliminatedFunctions }
130
141
 
131
- const baselineGenerated = generateDatapackWithStats(ir, { optimizeCommands: false, mangle })
132
- const beforeDceGenerated = generateDatapackWithStats(copyPropagatedIR, { optimizeCommands: false, mangle })
133
- const afterDceGenerated = generateDatapackWithStats(optimizedIR, { optimizeCommands: false, mangle })
134
- generated = generateDatapackWithStats(optimizedIR, { optimizeCommands: true, mangle })
142
+ const baselineGenerated = generateDatapackWithStats(ir, { optimizeCommands: false, mangle, scoreboardObjective: scoreboardObj })
143
+ const beforeDceGenerated = generateDatapackWithStats(copyPropagatedIR, { optimizeCommands: false, mangle, scoreboardObjective: scoreboardObj })
144
+ const afterDceGenerated = generateDatapackWithStats(optimizedIR, { optimizeCommands: false, mangle, scoreboardObjective: scoreboardObj })
145
+ generated = generateDatapackWithStats(optimizedIR, { optimizeCommands: true, mangle, scoreboardObjective: scoreboardObj })
135
146
 
136
147
  stats.deadCodeRemoved =
137
148
  countMcfunctionCommands(beforeDceGenerated.files) - countMcfunctionCommands(afterDceGenerated.files)
@@ -147,7 +158,7 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
147
158
  optimizationStats = stats
148
159
  } else {
149
160
  optimizedIR = ir
150
- generated = generateDatapackWithStats(ir, { optimizeCommands: false, mangle })
161
+ generated = generateDatapackWithStats(ir, { optimizeCommands: false, mangle, scoreboardObjective: scoreboardObj })
151
162
  }
152
163
 
153
164
  return {
@@ -29,6 +29,15 @@ import { getBaseSelectorType, areCompatibleTypes, getConcreteSubtypes } from '..
29
29
  // All builtins support macro parameters - any arg that's a function param
30
30
  // will automatically use MC 1.20.2+ macro syntax when needed
31
31
 
32
+ // ---------------------------------------------------------------------------
33
+ // Scoreboard Objective
34
+ // Set per-compilation via setScoreboardObjective() — defaults to 'rs'.
35
+ // ---------------------------------------------------------------------------
36
+
37
+ /** Current scoreboard objective. Set once per compile() call. */
38
+ export let LOWERING_OBJ = 'rs'
39
+ export function setScoreboardObjective(obj: string): void { LOWERING_OBJ = obj }
40
+
32
41
  // ---------------------------------------------------------------------------
33
42
  // Builtin Functions
34
43
  // ---------------------------------------------------------------------------
@@ -50,9 +59,13 @@ const BUILTINS: Record<string, (args: string[]) => string | null> = {
50
59
  const pos = [x ?? '~', y ?? '~', z ?? '~'].join(' ')
51
60
  return nbt ? `summon ${type} ${pos} ${nbt}` : `summon ${type} ${pos}`
52
61
  },
53
- particle: ([name, x, y, z]) => {
62
+ particle: ([name, x, y, z, dx, dy, dz, speed, count]) => {
54
63
  const pos = [x ?? '~', y ?? '~', z ?? '~'].join(' ')
55
- return `particle ${name} ${pos}`
64
+ // dx/dy/dz/speed/count are optional; omit trailing undefineds
65
+ const extra = [dx, dy, dz, speed, count].filter(v => v !== undefined && v !== null)
66
+ return extra.length > 0
67
+ ? `particle ${name} ${pos} ${extra.join(' ')}`
68
+ : `particle ${name} ${pos}`
56
69
  },
57
70
  playsound: ([sound, source, sel, x, y, z, volume, pitch, minVolume]) =>
58
71
  ['playsound', sound, source, sel, x, y, z, volume, pitch, minVolume].filter(Boolean).join(' '),
@@ -465,6 +478,16 @@ export class Lowering {
465
478
  if (expr.kind === 'struct_lit' || expr.kind === 'array_lit') {
466
479
  return { str: this.exprToSnbt(expr) }
467
480
  }
481
+ // Float literals: preserve the float value for MC commands that accept floats
482
+ // (particle spread, playsound volume/pitch, etc.)
483
+ // We do NOT truncate here — that only applies to scoreboard/IR contexts.
484
+ if (expr.kind === 'float_lit') {
485
+ return { str: expr.value.toString() }
486
+ }
487
+ // Unary minus applied to a float literal (e.g. -0.5)
488
+ if (expr.kind === 'unary' && expr.op === '-' && expr.operand.kind === 'float_lit') {
489
+ return { str: (-expr.operand.value).toString() }
490
+ }
468
491
  return { str: this.exprToString(expr) }
469
492
  }
470
493
 
@@ -485,9 +508,9 @@ export class Lowering {
485
508
  for (let i = 0; i < loweredArgs.length; i++) {
486
509
  const operand = loweredArgs[i]
487
510
  if (operand.kind === 'const') {
488
- this.builder.emitRaw(`scoreboard players set $p${i} rs ${operand.value}`)
511
+ this.builder.emitRaw(`scoreboard players set $p${i} ${LOWERING_OBJ} ${operand.value}`)
489
512
  } else if (operand.kind === 'var') {
490
- this.builder.emitRaw(`scoreboard players operation $p${i} rs = ${operand.name} rs`)
513
+ this.builder.emitRaw(`scoreboard players operation $p${i} ${LOWERING_OBJ} = ${operand.name} ${LOWERING_OBJ}`)
491
514
  }
492
515
  }
493
516
 
@@ -501,7 +524,7 @@ export class Lowering {
501
524
  this.builder.emitRaw(`data modify storage rs:macro_args ${macroParam} set value ${operand.value}`)
502
525
  } else if (operand.kind === 'var') {
503
526
  this.builder.emitRaw(
504
- `execute store result storage rs:macro_args ${macroParam} int 1 run scoreboard players get ${operand.name} rs`
527
+ `execute store result storage rs:macro_args ${macroParam} int 1 run scoreboard players get ${operand.name} ${LOWERING_OBJ}`
505
528
  )
506
529
  }
507
530
  }
@@ -511,7 +534,7 @@ export class Lowering {
511
534
 
512
535
  // Copy return value (callers may use it)
513
536
  const dst = this.builder.freshTemp()
514
- this.builder.emitRaw(`scoreboard players operation ${dst} rs = $ret rs`)
537
+ this.builder.emitRaw(`scoreboard players operation ${dst} ${LOWERING_OBJ} = $ret ${LOWERING_OBJ}`)
515
538
  return { kind: 'var', name: dst }
516
539
  }
517
540
 
@@ -792,7 +815,7 @@ export class Lowering {
792
815
  const originalTerm = entry.term
793
816
 
794
817
  entry.instrs = [
795
- { op: 'raw', cmd: `scoreboard players add ${counterVar} rs 1` },
818
+ { op: 'raw', cmd: `scoreboard players add ${counterVar} ${LOWERING_OBJ} 1` },
796
819
  ]
797
820
 
798
821
  // Create conditional jump
@@ -809,14 +832,14 @@ export class Lowering {
809
832
  // Add check instruction
810
833
  entry.instrs.push({
811
834
  op: 'raw',
812
- cmd: `execute store success score ${counterVar}_check rs if score ${counterVar} rs matches ${rate}..`,
835
+ cmd: `execute store success score ${counterVar}_check ${LOWERING_OBJ} if score ${counterVar} ${LOWERING_OBJ} matches ${rate}..`,
813
836
  })
814
837
 
815
838
  // Body block (original logic + counter reset)
816
839
  fn.blocks.push({
817
840
  label: bodyLabel,
818
841
  instrs: [
819
- { op: 'raw', cmd: `scoreboard players set ${counterVar} rs 0` },
842
+ { op: 'raw', cmd: `scoreboard players set ${counterVar} ${LOWERING_OBJ} 0` },
820
843
  ...originalInstrs,
821
844
  ],
822
845
  term: originalTerm,
@@ -943,7 +966,7 @@ export class Lowering {
943
966
  this.builder.emitRaw(`data modify storage ${path} set value ${fieldValue.value}`)
944
967
  } else if (fieldValue.kind === 'var') {
945
968
  // Copy from scoreboard to NBT
946
- this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${fieldValue.name} rs`)
969
+ this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${fieldValue.name} ${LOWERING_OBJ}`)
947
970
  }
948
971
  }
949
972
  return
@@ -977,7 +1000,7 @@ export class Lowering {
977
1000
  this.builder.emitRaw(`data modify storage rs:heap ${stmt.name} append value ${elemValue.value}`)
978
1001
  } else if (elemValue.kind === 'var') {
979
1002
  this.builder.emitRaw(`data modify storage rs:heap ${stmt.name} append value 0`)
980
- this.builder.emitRaw(`execute store result storage rs:heap ${stmt.name}[-1] int 1 run scoreboard players get ${elemValue.name} rs`)
1003
+ this.builder.emitRaw(`execute store result storage rs:heap ${stmt.name}[-1] int 1 run scoreboard players get ${elemValue.name} ${LOWERING_OBJ}`)
981
1004
  }
982
1005
  }
983
1006
  return
@@ -1028,7 +1051,7 @@ export class Lowering {
1028
1051
  if (fieldValue.kind === 'const') {
1029
1052
  this.builder.emitRaw(`data modify storage ${path} set value ${fieldValue.value}`)
1030
1053
  } else if (fieldValue.kind === 'var') {
1031
- this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${fieldValue.name} rs`)
1054
+ this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${fieldValue.name} ${LOWERING_OBJ}`)
1032
1055
  }
1033
1056
  }
1034
1057
  this.builder.emitReturn({ kind: 'const', value: 0 })
@@ -1247,9 +1270,9 @@ export class Lowering {
1247
1270
  this.varMap.set(stmt.varName, loopVar)
1248
1271
  const startVal = this.lowerExpr(stmt.start)
1249
1272
  if (startVal.kind === 'const') {
1250
- this.builder.emitRaw(`scoreboard players set ${loopVar} rs ${startVal.value}`)
1273
+ this.builder.emitRaw(`scoreboard players set ${loopVar} ${LOWERING_OBJ} ${startVal.value}`)
1251
1274
  } else if (startVal.kind === 'var') {
1252
- this.builder.emitRaw(`scoreboard players operation ${loopVar} rs = ${startVal.name} rs`)
1275
+ this.builder.emitRaw(`scoreboard players operation ${loopVar} ${LOWERING_OBJ} = ${startVal.name} ${LOWERING_OBJ}`)
1253
1276
  }
1254
1277
 
1255
1278
  // Call loop function
@@ -1272,12 +1295,12 @@ export class Lowering {
1272
1295
  this.lowerBlock(stmt.body)
1273
1296
 
1274
1297
  // Increment
1275
- this.builder.emitRaw(`scoreboard players add ${loopVar} rs 1`)
1298
+ this.builder.emitRaw(`scoreboard players add ${loopVar} ${LOWERING_OBJ} 1`)
1276
1299
 
1277
1300
  // Loop condition: execute if score matches ..<end-1> run function
1278
1301
  const endVal = this.lowerExpr(stmt.end)
1279
1302
  const endNum = endVal.kind === 'const' ? endVal.value - 1 : '?'
1280
- this.builder.emitRaw(`execute if score ${loopVar} rs matches ..${endNum} run function ${this.namespace}:${subFnName}`)
1303
+ this.builder.emitRaw(`execute if score ${loopVar} ${LOWERING_OBJ} matches ..${endNum} run function ${this.namespace}:${subFnName}`)
1281
1304
 
1282
1305
  if (!this.builder.isBlockSealed()) {
1283
1306
  this.builder.emitReturn()
@@ -1382,13 +1405,13 @@ export class Lowering {
1382
1405
  }
1383
1406
 
1384
1407
  const subFnName = `${this.currentFn}/match_${this.foreachCounter++}`
1385
- this.builder.emitRaw(`execute if score ${matchedVar} rs matches ..0 if score ${subject} rs matches ${matchCondition} run function ${this.namespace}:${subFnName}`)
1408
+ this.builder.emitRaw(`execute if score ${matchedVar} ${LOWERING_OBJ} matches ..0 if score ${subject} ${LOWERING_OBJ} matches ${matchCondition} run function ${this.namespace}:${subFnName}`)
1386
1409
  this.emitMatchArmSubFunction(subFnName, matchedVar, arm.body, true)
1387
1410
  }
1388
1411
 
1389
1412
  if (defaultArm) {
1390
1413
  const subFnName = `${this.currentFn}/match_${this.foreachCounter++}`
1391
- this.builder.emitRaw(`execute if score ${matchedVar} rs matches ..0 run function ${this.namespace}:${subFnName}`)
1414
+ this.builder.emitRaw(`execute if score ${matchedVar} ${LOWERING_OBJ} matches ..0 run function ${this.namespace}:${subFnName}`)
1392
1415
  this.emitMatchArmSubFunction(subFnName, matchedVar, defaultArm.body, false)
1393
1416
  }
1394
1417
  }
@@ -1406,7 +1429,7 @@ export class Lowering {
1406
1429
 
1407
1430
  this.builder.startBlock('entry')
1408
1431
  if (setMatched) {
1409
- this.builder.emitRaw(`scoreboard players set ${matchedVar} rs 1`)
1432
+ this.builder.emitRaw(`scoreboard players set ${matchedVar} ${LOWERING_OBJ} 1`)
1410
1433
  }
1411
1434
  this.lowerBlock(body)
1412
1435
  if (!this.builder.isBlockSealed()) {
@@ -1445,7 +1468,7 @@ export class Lowering {
1445
1468
 
1446
1469
  this.builder.emitAssign(indexVar, { kind: 'const', value: 0 })
1447
1470
  this.builder.emitAssign(oneVar, { kind: 'const', value: 1 })
1448
- this.builder.emitRaw(`execute store result score ${lengthVar} rs run data get storage rs:heap ${arrayName}`)
1471
+ this.builder.emitRaw(`execute store result score ${lengthVar} ${LOWERING_OBJ} run data get storage rs:heap ${arrayName}`)
1449
1472
 
1450
1473
  const checkLabel = this.builder.freshLabel('foreach_array_check')
1451
1474
  const bodyLabel = this.builder.freshLabel('foreach_array_body')
@@ -1462,7 +1485,7 @@ export class Lowering {
1462
1485
  this.builder.emitAssign(bindingVar, element)
1463
1486
  this.lowerBlock(stmt.body)
1464
1487
  if (!this.builder.isBlockSealed()) {
1465
- this.builder.emitRaw(`scoreboard players operation ${indexVar} rs += ${oneVar} rs`)
1488
+ this.builder.emitRaw(`scoreboard players operation ${indexVar} ${LOWERING_OBJ} += ${oneVar} ${LOWERING_OBJ}`)
1466
1489
  this.builder.emitJump(checkLabel)
1467
1490
  }
1468
1491
 
@@ -1841,7 +1864,7 @@ export class Lowering {
1841
1864
  if (mapped && mapped.startsWith('@e[tag=__rs_obj_')) {
1842
1865
  // World object field access → scoreboard get
1843
1866
  const dst = this.builder.freshTemp()
1844
- this.builder.emitRaw(`scoreboard players operation ${dst} rs = ${mapped} rs`)
1867
+ this.builder.emitRaw(`scoreboard players operation ${dst} ${LOWERING_OBJ} = ${mapped} ${LOWERING_OBJ}`)
1845
1868
  return { kind: 'var', name: dst }
1846
1869
  }
1847
1870
 
@@ -1850,14 +1873,14 @@ export class Lowering {
1850
1873
  const path = `rs:heap ${structName}_${expr.obj.name}.${expr.field}`
1851
1874
  const dst = this.builder.freshTemp()
1852
1875
  // Read from NBT storage into scoreboard
1853
- this.builder.emitRaw(`execute store result score ${dst} rs run data get storage ${path}`)
1876
+ this.builder.emitRaw(`execute store result score ${dst} ${LOWERING_OBJ} run data get storage ${path}`)
1854
1877
  return { kind: 'var', name: dst }
1855
1878
  }
1856
1879
 
1857
1880
  // Array length property
1858
1881
  if (varType?.kind === 'array' && expr.field === 'len') {
1859
1882
  const dst = this.builder.freshTemp()
1860
- this.builder.emitRaw(`execute store result score ${dst} rs run data get storage rs:heap ${expr.obj.name}`)
1883
+ this.builder.emitRaw(`execute store result score ${dst} ${LOWERING_OBJ} run data get storage rs:heap ${expr.obj.name}`)
1861
1884
  return { kind: 'var', name: dst }
1862
1885
  }
1863
1886
  }
@@ -1876,9 +1899,9 @@ export class Lowering {
1876
1899
  const value = this.lowerExpr(expr.value)
1877
1900
  if (expr.op === '=') {
1878
1901
  if (value.kind === 'const') {
1879
- this.builder.emitRaw(`scoreboard players set ${mapped} rs ${value.value}`)
1902
+ this.builder.emitRaw(`scoreboard players set ${mapped} ${LOWERING_OBJ} ${value.value}`)
1880
1903
  } else if (value.kind === 'var') {
1881
- this.builder.emitRaw(`scoreboard players operation ${mapped} rs = ${value.name} rs`)
1904
+ this.builder.emitRaw(`scoreboard players operation ${mapped} ${LOWERING_OBJ} = ${value.name} ${LOWERING_OBJ}`)
1882
1905
  }
1883
1906
  } else {
1884
1907
  // Compound assignment
@@ -1887,9 +1910,9 @@ export class Lowering {
1887
1910
  if (value.kind === 'const') {
1888
1911
  const constTemp = this.builder.freshTemp()
1889
1912
  this.builder.emitAssign(constTemp, value)
1890
- this.builder.emitRaw(`scoreboard players operation ${mapped} rs ${opMap[binOp]} ${constTemp} rs`)
1913
+ this.builder.emitRaw(`scoreboard players operation ${mapped} ${LOWERING_OBJ} ${opMap[binOp]} ${constTemp} ${LOWERING_OBJ}`)
1891
1914
  } else if (value.kind === 'var') {
1892
- this.builder.emitRaw(`scoreboard players operation ${mapped} rs ${opMap[binOp]} ${value.name} rs`)
1915
+ this.builder.emitRaw(`scoreboard players operation ${mapped} ${LOWERING_OBJ} ${opMap[binOp]} ${value.name} ${LOWERING_OBJ}`)
1893
1916
  }
1894
1917
  }
1895
1918
  return { kind: 'const', value: 0 }
@@ -1904,15 +1927,15 @@ export class Lowering {
1904
1927
  if (value.kind === 'const') {
1905
1928
  this.builder.emitRaw(`data modify storage ${path} set value ${value.value}`)
1906
1929
  } else if (value.kind === 'var') {
1907
- this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${value.name} rs`)
1930
+ this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${value.name} ${LOWERING_OBJ}`)
1908
1931
  }
1909
1932
  } else {
1910
1933
  // Compound assignment: read, modify, write back
1911
1934
  const dst = this.builder.freshTemp()
1912
- this.builder.emitRaw(`execute store result score ${dst} rs run data get storage ${path}`)
1935
+ this.builder.emitRaw(`execute store result score ${dst} ${LOWERING_OBJ} run data get storage ${path}`)
1913
1936
  const binOp = expr.op.slice(0, -1)
1914
1937
  this.builder.emitBinop(dst, { kind: 'var', name: dst }, binOp as any, value)
1915
- this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${dst} rs`)
1938
+ this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${dst} ${LOWERING_OBJ}`)
1916
1939
  }
1917
1940
  return { kind: 'const', value: 0 }
1918
1941
  }
@@ -1945,13 +1968,13 @@ export class Lowering {
1945
1968
  this.builder.emitAssign(dst, left)
1946
1969
  const rightVar = this.operandToVar(right)
1947
1970
  // dst = dst && right → if dst != 0 then dst = right
1948
- this.builder.emitRaw(`execute if score ${dst} rs matches 1.. run scoreboard players operation ${dst} rs = ${rightVar} rs`)
1971
+ this.builder.emitRaw(`execute if score ${dst} ${LOWERING_OBJ} matches 1.. run scoreboard players operation ${dst} ${LOWERING_OBJ} = ${rightVar} ${LOWERING_OBJ}`)
1949
1972
  } else {
1950
1973
  // Short-circuit OR
1951
1974
  this.builder.emitAssign(dst, left)
1952
1975
  const rightVar = this.operandToVar(right)
1953
1976
  // dst = dst || right → if dst == 0 then dst = right
1954
- this.builder.emitRaw(`execute if score ${dst} rs matches ..0 run scoreboard players operation ${dst} rs = ${rightVar} rs`)
1977
+ this.builder.emitRaw(`execute if score ${dst} ${LOWERING_OBJ} matches ..0 run scoreboard players operation ${dst} ${LOWERING_OBJ} = ${rightVar} ${LOWERING_OBJ}`)
1955
1978
  }
1956
1979
  return { kind: 'var', name: dst }
1957
1980
  }
@@ -1970,15 +1993,15 @@ export class Lowering {
1970
1993
  // Divide by 1000 to correct for double scaling
1971
1994
  const constDiv = this.builder.freshTemp()
1972
1995
  this.builder.emitAssign(constDiv, { kind: 'const', value: 1000 })
1973
- this.builder.emitRaw(`scoreboard players operation ${dst} rs /= ${constDiv} rs`)
1996
+ this.builder.emitRaw(`scoreboard players operation ${dst} ${LOWERING_OBJ} /= ${constDiv} ${LOWERING_OBJ}`)
1974
1997
  } else {
1975
1998
  // Division: a * 1000 / b
1976
1999
  const constMul = this.builder.freshTemp()
1977
2000
  this.builder.emitAssign(constMul, { kind: 'const', value: 1000 })
1978
2001
  this.builder.emitAssign(dst, left)
1979
- this.builder.emitRaw(`scoreboard players operation ${dst} rs *= ${constMul} rs`)
2002
+ this.builder.emitRaw(`scoreboard players operation ${dst} ${LOWERING_OBJ} *= ${constMul} ${LOWERING_OBJ}`)
1980
2003
  const rightVar = this.operandToVar(right)
1981
- this.builder.emitRaw(`scoreboard players operation ${dst} rs /= ${rightVar} rs`)
2004
+ this.builder.emitRaw(`scoreboard players operation ${dst} ${LOWERING_OBJ} /= ${rightVar} ${LOWERING_OBJ}`)
1982
2005
  }
1983
2006
  return { kind: 'var', name: dst }
1984
2007
  }
@@ -2057,7 +2080,7 @@ export class Lowering {
2057
2080
  const storagePath = this.getStringStoragePath(expr.args[0])
2058
2081
  if (storagePath) {
2059
2082
  const dst = this.builder.freshTemp()
2060
- this.builder.emitRaw(`execute store result score ${dst} rs run data get storage ${storagePath}`)
2083
+ this.builder.emitRaw(`execute store result score ${dst} ${LOWERING_OBJ} run data get storage ${storagePath}`)
2061
2084
  return { kind: 'var', name: dst }
2062
2085
  }
2063
2086
 
@@ -2095,7 +2118,7 @@ export class Lowering {
2095
2118
  const entity = this.exprToString(expr.args[0])
2096
2119
  const tagName = this.exprToString(expr.args[1])
2097
2120
  const dst = this.builder.freshTemp()
2098
- this.builder.emitRaw(`execute store result score ${dst} rs if entity ${entity}[tag=${tagName}]`)
2121
+ this.builder.emitRaw(`execute store result score ${dst} ${LOWERING_OBJ} if entity ${entity}[tag=${tagName}]`)
2099
2122
  return { kind: 'var', name: dst }
2100
2123
  }
2101
2124
 
@@ -2110,7 +2133,7 @@ export class Lowering {
2110
2133
  this.builder.emitRaw(`data modify storage rs:heap ${arrName} append value ${value.value}`)
2111
2134
  } else if (value.kind === 'var') {
2112
2135
  this.builder.emitRaw(`data modify storage rs:heap ${arrName} append value 0`)
2113
- this.builder.emitRaw(`execute store result storage rs:heap ${arrName}[-1] int 1 run scoreboard players get ${value.name} rs`)
2136
+ this.builder.emitRaw(`execute store result storage rs:heap ${arrName}[-1] int 1 run scoreboard players get ${value.name} ${LOWERING_OBJ}`)
2114
2137
  }
2115
2138
  }
2116
2139
  return { kind: 'const', value: 0 }
@@ -2120,7 +2143,7 @@ export class Lowering {
2120
2143
  const arrName = this.getArrayStorageName(expr.args[0])
2121
2144
  const dst = this.builder.freshTemp()
2122
2145
  if (arrName) {
2123
- this.builder.emitRaw(`execute store result score ${dst} rs run data get storage rs:heap ${arrName}[-1]`)
2146
+ this.builder.emitRaw(`execute store result score ${dst} ${LOWERING_OBJ} run data get storage rs:heap ${arrName}[-1]`)
2124
2147
  this.builder.emitRaw(`data remove storage rs:heap ${arrName}[-1]`)
2125
2148
  } else {
2126
2149
  this.builder.emitAssign(dst, { kind: 'const', value: 0 })
@@ -2420,7 +2443,7 @@ export class Lowering {
2420
2443
  const dst = this.builder.freshTemp()
2421
2444
  const min = args[0] ? this.exprToLiteral(args[0]) : '0'
2422
2445
  const max = args[1] ? this.exprToLiteral(args[1]) : '100'
2423
- this.builder.emitRaw(`scoreboard players random ${dst} rs ${min} ${max}`)
2446
+ this.builder.emitRaw(`scoreboard players random ${dst} ${LOWERING_OBJ} ${min} ${max}`)
2424
2447
  return { kind: 'var', name: dst }
2425
2448
  }
2426
2449
 
@@ -2429,7 +2452,7 @@ export class Lowering {
2429
2452
  const dst = this.builder.freshTemp()
2430
2453
  const min = args[0] ? this.exprToLiteral(args[0]) : '0'
2431
2454
  const max = args[1] ? this.exprToLiteral(args[1]) : '100'
2432
- this.builder.emitRaw(`execute store result score ${dst} rs run random value ${min} ${max}`)
2455
+ this.builder.emitRaw(`execute store result score ${dst} ${LOWERING_OBJ} run random value ${min} ${max}`)
2433
2456
  return { kind: 'var', name: dst }
2434
2457
  }
2435
2458
 
@@ -2446,7 +2469,7 @@ export class Lowering {
2446
2469
  const dst = this.builder.freshTemp()
2447
2470
  const player = this.exprToTargetString(args[0])
2448
2471
  const objective = this.resolveScoreboardObjective(args[0], args[1], callSpan)
2449
- this.builder.emitRaw(`execute store result score ${dst} rs run scoreboard players get ${player} ${objective}`)
2472
+ this.builder.emitRaw(`execute store result score ${dst} ${LOWERING_OBJ} run scoreboard players get ${player} ${objective}`)
2450
2473
  return { kind: 'var', name: dst }
2451
2474
  }
2452
2475
 
@@ -2460,7 +2483,7 @@ export class Lowering {
2460
2483
  } else if (value.kind === 'var') {
2461
2484
  // Read directly from the computed scoreboard temp. Routing through a fresh
2462
2485
  // temp here breaks once optimization removes the apparently-dead assign.
2463
- this.builder.emitRaw(`execute store result score ${player} ${objective} run scoreboard players get ${value.name} rs`)
2486
+ this.builder.emitRaw(`execute store result score ${player} ${objective} run scoreboard players get ${value.name} ${LOWERING_OBJ}`)
2464
2487
  }
2465
2488
  return { kind: 'const', value: 0 }
2466
2489
  }
@@ -2536,7 +2559,7 @@ export class Lowering {
2536
2559
 
2537
2560
  if (name === 'bossbar_get_value') {
2538
2561
  const dst = this.builder.freshTemp()
2539
- this.builder.emitRaw(`execute store result score ${dst} rs run bossbar get ${this.exprToString(args[0])} value`)
2562
+ this.builder.emitRaw(`execute store result score ${dst} ${LOWERING_OBJ} run bossbar get ${this.exprToString(args[0])} value`)
2540
2563
  return { kind: 'var', name: dst }
2541
2564
  }
2542
2565
 
@@ -2583,7 +2606,7 @@ export class Lowering {
2583
2606
  : this.exprToString(args[1])
2584
2607
  const path = this.exprToString(args[2])
2585
2608
  const scale = args[3] ? this.exprToString(args[3]) : '1'
2586
- this.builder.emitRaw(`execute store result score ${dst} rs run data get ${targetType} ${target} ${path} ${scale}`)
2609
+ this.builder.emitRaw(`execute store result score ${dst} ${LOWERING_OBJ} run data get ${targetType} ${target} ${path} ${scale}`)
2587
2610
  return { kind: 'var', name: dst }
2588
2611
  }
2589
2612
 
@@ -2593,7 +2616,7 @@ export class Lowering {
2593
2616
  // array_key : e.g. "sin"
2594
2617
  // index : integer index (const or runtime)
2595
2618
  //
2596
- // Const index: execute store result score $dst rs run data get storage math:tables sin[N] 1
2619
+ // Const index: execute store result score $dst ${LOWERING_OBJ} run data get storage math:tables sin[N] 1
2597
2620
  // Runtime index: macro sub-function via rs:heap, mirrors readArrayElement.
2598
2621
  if (name === 'storage_get_int') {
2599
2622
  const storageNs = this.exprToString(args[0]) // "math:tables"
@@ -2603,7 +2626,7 @@ export class Lowering {
2603
2626
 
2604
2627
  if (indexOperand.kind === 'const') {
2605
2628
  this.builder.emitRaw(
2606
- `execute store result score ${dst} rs run data get storage ${storageNs} ${arrayKey}[${indexOperand.value}] 1`
2629
+ `execute store result score ${dst} ${LOWERING_OBJ} run data get storage ${storageNs} ${arrayKey}[${indexOperand.value}] 1`
2607
2630
  )
2608
2631
  } else {
2609
2632
  // Runtime index: store the index into rs:heap under a unique key,
@@ -2614,7 +2637,7 @@ export class Lowering {
2614
2637
  ? indexOperand.name
2615
2638
  : this.operandToVar(indexOperand)
2616
2639
  this.builder.emitRaw(
2617
- `execute store result storage rs:heap ${macroKey} int 1 run scoreboard players get ${indexVar} rs`
2640
+ `execute store result storage rs:heap ${macroKey} int 1 run scoreboard players get ${indexVar} ${LOWERING_OBJ}`
2618
2641
  )
2619
2642
  this.builder.emitRaw(`function ${this.namespace}:${subFnName} with storage rs:heap`)
2620
2643
  // Prefix \x01 is a sentinel for the MC macro '$' line-start marker.
@@ -2623,7 +2646,7 @@ export class Lowering {
2623
2646
  // Codegen replaces \x01 → '$' when emitting the mc function file.
2624
2647
  this.emitRawSubFunction(
2625
2648
  subFnName,
2626
- `\x01execute store result score ${dst} rs run data get storage ${storageNs} ${arrayKey}[$(${macroKey})] 1`
2649
+ `\x01execute store result score ${dst} ${LOWERING_OBJ} run data get storage ${storageNs} ${arrayKey}[$(${macroKey})] 1`
2627
2650
  )
2628
2651
  }
2629
2652
  return { kind: 'var', name: dst }
@@ -2650,7 +2673,7 @@ export class Lowering {
2650
2673
  // value : integer value to write (const or runtime)
2651
2674
  //
2652
2675
  // Const index + const value:
2653
- // execute store result storage <ns> <key>[N] int 1 run scoreboard players set $const_V rs V
2676
+ // execute store result storage <ns> <key>[N] int 1 run scoreboard players set $const_V ${LOWERING_OBJ} V
2654
2677
  // Runtime index or value: macro sub-function via rs:heap
2655
2678
  if (name === 'storage_set_int') {
2656
2679
  const storageNs = this.exprToString(args[0])
@@ -2664,7 +2687,7 @@ export class Lowering {
2664
2687
  ? valueOperand.name
2665
2688
  : this.operandToVar(valueOperand)
2666
2689
  this.builder.emitRaw(
2667
- `execute store result storage ${storageNs} ${arrayKey}[${indexOperand.value}] int 1 run scoreboard players get ${valVar} rs`
2690
+ `execute store result storage ${storageNs} ${arrayKey}[${indexOperand.value}] int 1 run scoreboard players get ${valVar} ${LOWERING_OBJ}`
2668
2691
  )
2669
2692
  } else {
2670
2693
  // Runtime index: we need a macro sub-function.
@@ -2680,13 +2703,13 @@ export class Lowering {
2680
2703
  ? valueOperand.name
2681
2704
  : this.operandToVar(valueOperand)
2682
2705
  this.builder.emitRaw(
2683
- `execute store result storage rs:heap ${macroIdxKey} int 1 run scoreboard players get ${indexVar} rs`
2706
+ `execute store result storage rs:heap ${macroIdxKey} int 1 run scoreboard players get ${indexVar} ${LOWERING_OBJ}`
2684
2707
  )
2685
2708
  // Pin valVar in the optimizer's read-set so the assignment is not dead-code-eliminated.
2686
2709
  // The value is stored to rs:heap but NOT used by the macro (the macro reads the scoreboard
2687
2710
  // slot directly to avoid the MC 'data modify set value $(n)' macro substitution bug).
2688
2711
  this.builder.emitRaw(
2689
- `execute store result storage rs:heap ${macroValKey} int 1 run scoreboard players get ${valVar} rs`
2712
+ `execute store result storage rs:heap ${macroValKey} int 1 run scoreboard players get ${valVar} ${LOWERING_OBJ}`
2690
2713
  )
2691
2714
  this.builder.emitRaw(`function ${this.namespace}:${subFnName} with storage rs:heap`)
2692
2715
  // Use execute store result (not 'data modify set value $(val)') to avoid MC macro
@@ -2694,7 +2717,7 @@ export class Lowering {
2694
2717
  // into the macro sub-function at compile time — only the array index is macro-substituted.
2695
2718
  this.emitRawSubFunction(
2696
2719
  subFnName,
2697
- `\x01execute store result storage ${storageNs} ${arrayKey}[$(${macroIdxKey})] int 1 run scoreboard players get ${valVar} rs`
2720
+ `\x01execute store result storage ${storageNs} ${arrayKey}[$(${macroIdxKey})] int 1 run scoreboard players get ${valVar} ${LOWERING_OBJ}`
2698
2721
  )
2699
2722
  }
2700
2723
  return { kind: 'const', value: 0 }
@@ -2744,7 +2767,7 @@ export class Lowering {
2744
2767
  const dst = this.builder.freshTemp()
2745
2768
  const setId = this.exprToString(args[0])
2746
2769
  const value = this.exprToString(args[1])
2747
- this.builder.emitRaw(`execute store result score ${dst} rs if data storage rs:sets ${setId}[{value:${value}}]`)
2770
+ this.builder.emitRaw(`execute store result score ${dst} ${LOWERING_OBJ} if data storage rs:sets ${setId}[{value:${value}}]`)
2748
2771
  return { kind: 'var', name: dst }
2749
2772
  }
2750
2773
 
@@ -3061,7 +3084,7 @@ export class Lowering {
3061
3084
  return
3062
3085
  }
3063
3086
 
3064
- components.push({ score: { name: this.operandToVar(operand), objective: 'rs' } })
3087
+ components.push({ score: { name: this.operandToVar(operand), objective: LOWERING_OBJ } })
3065
3088
  }
3066
3089
 
3067
3090
  private exprToString(expr: Expr): string {
@@ -3227,12 +3250,20 @@ export class Lowering {
3227
3250
 
3228
3251
  private exprToScoreboardObjective(expr: Expr, span?: Span): string {
3229
3252
  if (expr.kind === 'mc_name') {
3230
- return expr.value
3253
+ // 'rs' is the canonical token for the current RS scoreboard objective.
3254
+ // Resolve to LOWERING_OBJ so it respects --scoreboard / namespace default.
3255
+ return expr.value === 'rs' ? LOWERING_OBJ : expr.value
3231
3256
  }
3232
3257
 
3233
3258
  const objective = this.exprToString(expr)
3234
3259
  if (objective.startsWith('#') || objective.includes('.')) {
3235
- return objective.startsWith('#') ? objective.slice(1) : objective
3260
+ if (objective.startsWith('#')) {
3261
+ const name = objective.slice(1)
3262
+ // '#rs' is the canonical way to reference the current RS scoreboard objective.
3263
+ // Resolve to LOWERING_OBJ so it tracks the --scoreboard option.
3264
+ return name === 'rs' ? LOWERING_OBJ : name
3265
+ }
3266
+ return objective
3236
3267
  }
3237
3268
 
3238
3269
  return `${this.getObjectiveNamespace(span)}.${objective}`
@@ -3252,7 +3283,7 @@ export class Lowering {
3252
3283
  return this.namespace
3253
3284
  }
3254
3285
 
3255
- return this.isStdlibFile(filePath) ? 'rs' : this.namespace
3286
+ return this.isStdlibFile(filePath) ? LOWERING_OBJ : this.namespace
3256
3287
  }
3257
3288
 
3258
3289
  private tryGetStdlibInternalObjective(playerExpr: Expr | undefined, objectiveExpr: Expr, span?: Span): string | null {
@@ -3271,7 +3302,7 @@ export class Lowering {
3271
3302
  }
3272
3303
 
3273
3304
  const hash = this.shortHash(this.serializeCallSite(this.currentStdlibCallSite))
3274
- return `rs._${resourceBase}_${hash}`
3305
+ return `${LOWERING_OBJ}._${resourceBase}_${hash}`
3275
3306
  }
3276
3307
 
3277
3308
  private getStdlibInternalResourceBase(playerExpr: Expr | undefined): string | null {
@@ -3593,18 +3624,18 @@ export class Lowering {
3593
3624
  const dst = this.builder.freshTemp()
3594
3625
 
3595
3626
  if (index.kind === 'const') {
3596
- this.builder.emitRaw(`execute store result score ${dst} rs run data get storage rs:heap ${arrayName}[${index.value}]`)
3627
+ this.builder.emitRaw(`execute store result score ${dst} ${LOWERING_OBJ} run data get storage rs:heap ${arrayName}[${index.value}]`)
3597
3628
  return { kind: 'var', name: dst }
3598
3629
  }
3599
3630
 
3600
3631
  const macroKey = `__rs_index_${this.foreachCounter++}`
3601
3632
  const subFnName = `${this.currentFn}/array_get_${this.foreachCounter++}`
3602
3633
  const indexVar = index.kind === 'var' ? index.name : this.operandToVar(index)
3603
- this.builder.emitRaw(`execute store result storage rs:heap ${macroKey} int 1 run scoreboard players get ${indexVar} rs`)
3634
+ this.builder.emitRaw(`execute store result storage rs:heap ${macroKey} int 1 run scoreboard players get ${indexVar} ${LOWERING_OBJ}`)
3604
3635
  this.builder.emitRaw(`function ${this.namespace}:${subFnName} with storage rs:heap`)
3605
3636
  this.emitRawSubFunction(
3606
3637
  subFnName,
3607
- `\x01execute store result score ${dst} rs run data get storage rs:heap ${arrayName}[$(${macroKey})]`
3638
+ `\x01execute store result score ${dst} ${LOWERING_OBJ} run data get storage rs:heap ${arrayName}[$(${macroKey})]`
3608
3639
  )
3609
3640
  return { kind: 'var', name: dst }
3610
3641
  }