title: IC10 device addressing (pin, alias, ReferenceId) type: GameSystems created_in: 0.2.6228.27061 verified_in: 0.2.6228.27061 verified_at: 2026-05-31 sources: - rocketstation_Data/Managed/Assembly-CSharp.dll :: Assets.Scripts.Objects.Device.BatchRead (four overloads; per-mode empty-set return: Maximum=-Infinity, Average=NaN, Sum/Minimum=0) - rocketstation_Data/Managed/Assembly-CSharp.dll :: Assets.Scripts.Objects.Electrical.ProgrammableChip._LB_Operation / _LBN_Operation / _SB_Operation / _SBN_Operation.Execute (batch read/write dispatch; sb/sbn write every matching device, no BATCH_MODE operand) - rocketstation_Data/Managed/Assembly-CSharp.dll :: Assets.Scripts.Objects.Electrical.ProgrammableChip (parser switch, _L_Operation, _LD_Operation, _S_Operation, _SD_Operation, _Operation._MakeDeviceVariable, _Operation_I, syntax-help formatter) - rocketstation_Data/Managed/Assembly-CSharp.dll :: Assets.Scripts.Objects.Electrical.ProgrammableChip (_BRDSE_Operation, _BRDNS_Operation, _BDSE_Operation, _BDNS_Operation, _SDSE_Operation, _SDNS_Operation) - rocketstation_Data/Managed/Assembly-CSharp.dll :: Assets.Scripts.Objects.Electrical.ProgrammableChip (_PUSH_Operation, _POP_Operation, _StackPointerIndex, RETURN_ADDRESS_STRING / STACK_POINTER_STRING auto-aliases) - rocketstation_Data/Managed/Assembly-CSharp.dll :: Assets.Scripts.Objects.Electrical.CircuitHousing.GetLogicableFromId related: - ./LogicType.md - ./IC10ExecutionTick.md tags: [ic10, logic]
IC10 device addressing¶
IC10 supports three forms for the "which device" operand of read/write instructions: a pin reference (d0..d5, dr0..dr5, db), an alias defined via alias, or a numeric ReferenceId ($hex literal, decimal literal, or a register holding the value). Some opcodes accept all three forms, others are restricted to ReferenceId only.
Opcode list and accepted device-operand forms¶
Source of truth: the syntax-help formatter in ProgrammableChip (the switch returning MakeString(...) for each ScriptCommand). It declares per-opcode token classes for each operand. DEVICE_INDEX covers d0..d5, dr*, and db; REGISTER covers r0..r17 and aliases resolving to a register; REF_ID covers $hex / decimal / a register holding a ReferenceId. The set of token classes a slot accepts is the OR of those listed.
| Opcode | Device operand accepts | Other operands |
|---|---|---|
l |
DEVICE_INDEX + REGISTER + REF_ID | register, LOGIC_TYPE |
ld |
REGISTER + REF_ID | register, LOGIC_TYPE |
s |
DEVICE_INDEX + REGISTER + REF_ID | LOGIC_TYPE, register |
sd |
REGISTER + REF_ID | LOGIC_TYPE, register |
ls |
DEVICE_INDEX + REGISTER + REF_ID | register, SLOT_INDEX, LOGIC_SLOT_TYPE |
ss |
DEVICE_INDEX + REGISTER + REF_ID | SLOT_INDEX, LOGIC_SLOT_TYPE, register |
lr |
DEVICE_INDEX + REGISTER + REF_ID | register, REAGENT_MODE, INTEGER |
get |
DEVICE_INDEX + REGISTER + REF_ID | register, address |
put |
DEVICE_INDEX + REGISTER + REF_ID | address, value |
getd |
REGISTER + REF_ID | register, address |
putd |
REGISTER + REF_ID | address, value |
clrd |
REGISTER + NUMBER | (clears stack of device by id) |
lb |
DEVICE_HASH (prefab hash) | register, LOGIC_TYPE, BATCH_MODE |
lbn |
DEVICE_HASH + NAME_HASH | register, LOGIC_TYPE, BATCH_MODE |
lbs |
DEVICE_HASH + SLOT_INDEX | register, LOGIC_SLOT_TYPE, BATCH_MODE |
lbns |
DEVICE_HASH + NAME_HASH + SLOT_INDEX | register, LOGIC_SLOT_TYPE, BATCH_MODE |
sb |
DEVICE_HASH | LOGIC_TYPE, register |
sbn |
DEVICE_HASH + NAME_HASH | LOGIC_TYPE, register |
sbs |
DEVICE_HASH + SLOT_INDEX | LOGIC_SLOT_TYPE, register |
Notable: sbns does NOT exist (the lbns slot-load-by-name has no slot-store-by-name twin). The *d variants (ld, sd, getd, putd, clrd) are the dedicated ReferenceId-only opcodes; the non-d variants (l, s, ls, ss, lr, get, put) accept all three operand forms including ReferenceId.
How the ReferenceId form is parsed¶
ProgrammableChip._Operation._MakeDeviceVariable(chip, lineNumber, deviceCode) is the dispatcher used by every opcode whose device operand is DEVICE_INDEX + REGISTER + REF_ID:
if (deviceCode.Length > 0 && (deviceCode[0] == '$' || deviceCode[0] == '%' || char.IsDigit(deviceCode[0])))
return new DirectDeviceVariable(chip, lineNumber, deviceCode, MaskDoubleValue | DeviceIndex | NetworkIndex, throwException: false);
if (deviceCode.Length > 1 && deviceCode[0] == 'r' && char.IsDigit(deviceCode[1]))
return new DirectDeviceVariable(chip, lineNumber, deviceCode, MaskDoubleValue | DeviceIndex | NetworkIndex, throwException: false);
string[] array = deviceCode.Split(':');
if (array.Length != 0 && array[0].StartsWith('d')) {
if (array[0] == "db")
return new DeviceIndexVariable(chip, lineNumber, deviceCode, MaskDeviceIndex, throwException: false);
if (Regex.IsMatch(array[0], "^(d[0-9]|dr*[r0-9][0-9])$"))
return new DeviceIndexVariable(chip, lineNumber, deviceCode, MaskDeviceIndex, throwException: false);
}
return new DeviceAliasVariable(chip, lineNumber, deviceCode, MaskDoubleValue | DeviceIndex | NetworkIndex, throwException: false);
Recognised token shapes:
$AD4F,%1010, or a leading digit → numeric literal (ReferenceId or numeric variant), wrapped asDirectDeviceVariable.r0..r17→ register form, wrapped asDirectDeviceVariable. The register holds a numeric value treated as a ReferenceId at execute time.db→ IC10 housing self-reference,DeviceIndexVariable.d0..d5anddr*(with optional network suffix:N) → pin form,DeviceIndexVariable.- Anything else →
DeviceAliasVariable, which resolves through the script'salias/definetable.
For the dedicated ld / sd / getd / putd opcodes, the parser bypasses _MakeDeviceVariable and constructs _Operation_I:
private abstract class _Operation_I : _Operation_1_0 {
protected readonly IntValuedVariable _DeviceId;
public _Operation_I(ProgrammableChip chip, int lineNumber, string registerStoreCode, string referenceId)
: base(chip, lineNumber, registerStoreCode) {
_DeviceId = new IntValuedVariable(chip, lineNumber, referenceId, MaskDoubleValue, throwException: false);
}
}
The device operand is parsed straight as an int (no pin handling, no db, no alias-as-pin path). The accepted token classes therefore narrow to REGISTER + REF_ID per the help-string spec.
Resolution at execute time: GetLogicableFromId¶
_LD_Operation.Execute and _SD_Operation.Execute both resolve the device by calling _Chip.CircuitHousing.GetLogicableFromId(int deviceId):
public ILogicable GetLogicableFromId(int deviceId, int networkIndex = int.MinValue)
{
if (deviceId == 0L) return null;
Device device = Referencable.Find<Device>(deviceId);
if (base.InputNetwork1 != null && !base.InputNetwork1.DataDeviceList.Contains(device))
return null;
if (networkIndex != int.MinValue)
return ((IConnected)device)?.GetNetwork(networkIndex);
return device;
}
Three constraints follow:
- The id is interpreted as a
Referencable.Find<Device>(int)lookup. This is the same global referencable registry that backsThing.ReferenceId(LogicType 217). So yes, the ReferenceId you read off any device is whatld/sdaccept. - The device must be on the IC10 housing's data network. If
InputNetwork1is non-null and the resolved device is not inInputNetwork1.DataDeviceList, the method returns null. ReferenceId addressing does not bypass network reachability; it is not a global "any device anywhere" handle. The same data-network-only constraint that limitslb/lbnapplies. - The id is narrowed to
inton the IC10 side.Referencable.ReferenceIdis along, but_DeviceIdisIntValuedVariableandGetLogicableFromIdtakesint. ReferenceIds in saves and runtime sequences fit in 32 bits in normal play, but a value with bit 31 set will sign-extend or truncate at the register/double boundary; the safe assumption is that ReferenceIds outsideint.MinValue..int.MaxValuecannot be addressed by IC10. (Not separately verified against extreme-id behavior; flagged in Open Questions.)
_S_Operation (the non-d version) instead uses _DeviceIndex.GetDevice(_Chip.CircuitHousing) (an IDeviceVariable), which reaches GetLogicableFromId indirectly when the parsed form is the numeric/register variant. The pin and alias forms reach it via GetLogicableFromIndex instead.
Read-side null guard: missing in _LD_Operation¶
_SD_Operation.Execute checks for null before dereferencing:
ILogicable logicableFromId = _Chip.CircuitHousing.GetLogicableFromId(variableValue);
if (logicableFromId == null)
throw new ProgrammableChipException(ICExceptionType.DeviceNotFound, _LineNumber);
_LD_Operation.Execute does NOT:
ILogicable logicableFromId = _Chip.CircuitHousing.GetLogicableFromId(variableValue);
LogicType variableValue2 = _LogicType.GetVariableValue(_AliasTarget.Register);
if (variableValue2 == LogicType.None)
throw new ProgrammableChipException(ICExceptionType.LogicTypeIsNone, _LineNumber);
if (!logicableFromId.CanLogicRead(variableValue2)) // NRE if logicableFromId == null
throw new ProgrammableChipException(ICExceptionType.IncorrectLogicType, _LineNumber);
If the supplied id resolves to null (id is 0, device does not exist, or device is not on the IC10's data network), ld will throw a NullReferenceException through CanLogicRead. The IC10 surfaces this as a generic chip error rather than the cleaner DeviceNotFound that sd raises. Mods that wrap or replace _LD_Operation should guard explicitly.
ReferenceId is also addressable via the non-d opcodes¶
Because _MakeDeviceVariable accepts $hex / decimal / register tokens, the regular l, s, ls, ss, lr, get, put instructions also accept a ReferenceId in the device slot. Example:
alias pump $AD4F # define alias for a known ReferenceId
l r0 pump Pressure # regular `l` resolves the alias to the numeric form
ld r1 $AD4F Setting # equivalent via the dedicated `ld` opcode
The pragmatic difference between l <reg> <refid> <type> and ld <reg> <refid> <type> is the parse path (and the ld null-guard gap above), not the addressing capability.
Existence-check opcodes for guarding ReferenceId addressing¶
ld / sd (and getd / putd / clrd) throw DeviceNotFound (or NRE in _LD_Operation's case) when the supplied id resolves to null. To guard against this, IC10 has six existence-check opcodes that all share the same null-check semantics as the underlying read/write path:
| Opcode | Form | Behavior |
|---|---|---|
bdse <device> <addr> |
absolute branch | Branch to addr if device exists. |
bdns <device> <addr> |
absolute branch | Branch to addr if device does NOT exist. |
brdse <device> <offset> |
relative branch | Same as bdse but offset is a signed PC delta. |
brdns <device> <offset> |
relative branch | Same as bdns but offset is a signed PC delta. |
bdseal <device> <addr> / bdnsal <device> <addr> |
branch + link | Variants that also write ra (link register) on the taken branch. |
sdse <register> <device> |
set register | Writes 1 to register if device exists, 0 if not. |
sdns <register> <device> |
set register | Writes 1 to register if device does NOT exist, 0 if so. |
All six accept the same device-operand forms that _MakeDeviceVariable produces (pin / register / $hex / alias), so a literal ReferenceId works directly:
// _BRDSE_Operation.Execute (covers bdse/brdse via wrapper)
if (_DeviceIndex.GetDevice(_Chip.CircuitHousing) == null) {
hasJumped = false;
return index + 1;
}
hasJumped = true;
return index + offset + _JumpIndex.GetVariableValue(_AliasTarget.Register);
_DeviceIndex.GetDevice for the numeric/register form ultimately routes through CircuitHousing.GetLogicableFromId, which means the existence check enforces the same data-network membership constraint as the addressing itself: a device that exists in the world but is not on InputNetwork1.DataDeviceList reads as "not set" through bdse / bdns / sdse / sdns. This is the correct guard, not a partial check.
sdse / sdns are the right tool for non-branching one-shot guards (set up a flag in a register, use it later); bdse / bdns jump straight to a labeled handler when the existence question gates a code path.
define names work for sd but NOT for brdns / bdns / bdse / brdse / sdse / sdns¶
define <name> <value> (_DEFINE_Operation) writes the name into ProgrammableChip._Defines (a Dictionary<string, double>). alias <name> <register-or-pin> (_ALIAS_Operation) writes the name into ProgrammableChip._Aliases (a Dictionary<string, _AliasValue>). The two dictionaries are entirely separate, and per-opcode resolvers consult one or the other, not both.
The asymmetry that bites scripts:
sd <devCode> <type> <value>constructs_DeviceId = new IntValuedVariable(...).IntValuedVariable.GetVariableValueconsults_Defines(visible at line 1958 / 2038 inProgrammableChipdecompile via theInstructionInclude.Defineflag insideMaskDoubleValue = 0x6F). Adefine'd hex literal therefore resolves correctly:define BasePowerTransmitter $39FA7followed bysd BasePowerTransmitter MicrowaveAutoAimTarget 0works.brdns <devCode> <offset>(andbdns,bdse,brdse,sdse,sdns,bdnsal,bdseal) constructs_DeviceIndex = _Operation._MakeDeviceVariable(...), whose fallback path isDeviceAliasVariable.DeviceAliasVariable.GetDevicecallsGetAliasType(_Alias):
protected _AliasTarget GetAliasType(string alias, bool throwException = true)
{
if (string.IsNullOrEmpty(_Alias) || !_Chip._Aliases.TryGetValue(alias, out var value))
{
if (throwException)
throw new ProgrammableChipException(ICExceptionType.IncorrectVariable, _LineNumber);
return _AliasTarget.None;
}
return value.Target;
}
This checks _Aliases only; _Defines is never consulted. A token resolved by _MakeDeviceVariable to a DeviceAliasVariable therefore throws IncorrectVariable on a define'd name even though the same name resolves correctly when the same opcode receives it via IntValuedVariable.
In practice: define BasePowerTransmitter $39FA7 then brdns BasePowerTransmitter 2 raises "incorrect variable" at the brdns line. Workarounds, in order of preference:
- Use the
$hexliteral directly:brdns $39FA7 2. Loses thedefinereadability for the guard line but is otherwise free. - Pre-load the id into a register:
move r0 BasePowerTransmitter(whereIntValuedVariableresolves the define) thenbrdns r0 2. Ther0form routes throughDirectDeviceVariable, which callsGetLogicableFromId(int)directly instead ofGetAliasType. - Replace the
definewithalias.aliasonly accepts a register or a pin (r0..r17,d0..d5,dr*,db) per the syntax-help formatter (aliasrow:STRING, REGISTER + DEVICE_INDEX); it does not accept a numeric ReferenceId. So this is only an option after the value is already in a register.
The asymmetry exists because _MakeDeviceVariable was written for the original device-operand triad (pin / register / alias-of-pin-or-register), and the ReferenceId form was bolted on later without extending the alias-resolution branch to consult _Defines. The dedicated *d opcodes (ld, sd, getd, putd, clrd) sidestep this by skipping _MakeDeviceVariable entirely.
Stack-driven iteration over a list of ReferenceIds¶
For scripts that link N pairs of devices identically (the "auto-aim every transmitter to its receiver" pattern, repeated per-network with at most one pair physically present), the cleanest extensibility is to push every ReferenceId onto the IC10 stack at the top of the script, then pop two at a time and link them. Each pair becomes two push lines at the top with no further structural changes.
Relevant primitives, all in ProgrammableChip:
_StackPointerIndex = 16. The chip reservesr16as the stack pointer;r17is the return-address register.STACK_POINTER_STRING = "sp"andRETURN_ADDRESS_STRING = "ra". At chip startupOnPrefabsLoadedrunsnew _ALIAS_Operation(this, 0, "sp", "r16").Execute(0)and the equivalent forra, sospandraare pre-registered aliases visible to every script._PUSH_Operationwrites the value to_Stack[sp]then incrementsspby 1._POP_Operationdecrementsspby 1 then reads_Stack[sp]. Stack starts atsp = 0._Chip._Stack.Lengthdefaults to 512, so up to 512 push-without-pop is safe; overflow throwsStackOverFlow.
Idiom (the extensibility win is that push $hex # name is the only line a future user adds per ReferenceId):
push $39FA7 # BasePowerTransmitter
push $3A124 # BasePowerReceiver
push $1DB99 # SiliconPowerTransmitter
push $236B7 # SiliconPowerReceiver
link_loop:
beqz sp link_end
pop r1
pop r0
brdns r0 2
sd r0 MicrowaveAutoAimTarget r1
brdns r1 2
sd r1 MicrowaveAutoAimTarget r0
j link_loop
link_end:
Pair semantics: pushing (TX1, RX1, TX2, RX2) leaves the stack as [TX1, RX1, TX2, RX2]. LIFO popping gives RX2, TX2, RX1, TX1, so each two-pop window is a coherent (TX, RX) pair, processed in reverse insertion order. The brdns rN 2 guard inside the loop preserves the data-network-membership semantics already documented for sd: pairs whose dishes are not on this IC10's network silently skip without throwing.
This pattern also resolves the define-vs-alias asymmetry from the previous section. push <token> constructs a DoubleValueVariable, which consults _Defines, so push BasePowerTransmitter works after a define BasePowerTransmitter $39FA7 if the user prefers to keep a separate defines block. The trade-off is two parallel lists to maintain (defines and pushes) versus one annotated push list with names in comments.
Batch-op safety for missing devices¶
Note the asymmetry between writes and reads. On the WRITE side, batch instructions (sb, sbn, sbs) are silent no-ops on empty result sets: sb PowerTransmitters On 1 with zero matching transmitters on the network does not throw; it just iterates an empty list. On the READ side (lb, lbn, lbs, lbns), an empty match set also does not throw, but the returned value is mode-dependent and is NOT uniformly 0: only Sum and Minimum return 0; Maximum returns -Infinity and Average returns NaN (verbatim source in "Batch-read empty-set return value per mode" below). So no batch op throws on a missing device, but a batch READ with Maximum/Average returns a poison value that silently corrupts the comparison it feeds. The addressing paths that actually THROW on a missing device (and therefore need an existence guard like bdse/bdns) are the *d family and l/s/ls/ss/lr/get/put when fed a numeric/register form (which dispatches to GetLogicableFromId and throws DeviceNotFound on null).
Batch-read empty-set return value per mode¶
The four Device.BatchRead overloads (Assembly-CSharp.dll :: Assets.Scripts.Objects.Device.BatchRead, the (method, LogicType, deviceHash, devices) and (method, LogicType, deviceHash, nameHash, devices) forms that back lb / lbn, plus the two LogicSlotType forms for lbs / lbns) all share the same per-mode initialisation and empty-set handling. When ZERO devices in the batch list match the prefab hash (and name hash, for the lbn form), the accumulator loop body never executes and the seed value is returned as-is, except for the two modes that post-process the seed:
BATCH_MODE |
int | Seed before loop | Empty-set return | Reset guard present? |
|---|---|---|---|---|
Average |
0 | num2 = 0, num = 0 |
NaN (0.0 / (double)0) |
n/a (division by zero count) |
Sum |
1 | num2 = 0 |
0 |
n/a |
Minimum |
2 | num2 = double.PositiveInfinity |
0 |
YES: if (num2 >= double.PositiveInfinity) num2 = 0.0; |
Maximum |
3 | num2 = double.NegativeInfinity |
-Infinity |
NO (no symmetric reset) |
The asymmetry is the trap. Minimum explicitly resets its +Infinity sentinel back to 0 after the loop, but Maximum has no corresponding if (num2 <= double.NegativeInfinity) num2 = 0.0;, so the -Infinity sentinel leaks straight into the destination register. Verbatim Maximum branch (named overload, the unnamed overload is identical):
case LogicBatchMethod.Maximum:
{
num2 = double.NegativeInfinity;
int count = devices.Count;
while (count-- > 0)
{
ILogicable logicable = devices[count];
if (logicable != null && logicable.GetPrefabHash() == deviceHash && logicable.GetNameHash() == nameHash)
{
double logicValue = logicable.GetLogicValue(logicType);
if (!(logicValue <= num2))
{
num2 = logicValue;
}
}
}
break; // <-- no reset; returns num2 == double.NegativeInfinity when nothing matched
}
Contrast the Minimum branch, which does reset:
case LogicBatchMethod.Minimum:
{
num2 = double.PositiveInfinity;
// ... loop ...
if (num2 >= double.PositiveInfinity)
{
num2 = 0.0;
}
break;
}
Consequences for IC10 register values (registers are double, so -Infinity and NaN are representable and propagate):
lb/lbn ... Maximumagainst an empty match set yields-Infinity. Abgtz/bgez/bltztest against-Infinitybehaves as the ordinary float comparison (-Infinity > 0is false;-Infinity < anything-finiteis true). Aseq rX <reg> 0equality test against-Infinityis false, so an "is this flag 0 / is the door closed" check built onlbn ... Maximumnever sees the closed/zero state when the device is missing or misnamed.Averageagainst an empty match set yieldsNaN. Every comparison againstNaNis false (NaN > 0,NaN <= 0,NaN == 0all false), so both branches of a conditional can fall through unexpectedly.- Only
SumandMinimumgive the intuitive0on an empty set.
This is distinct from the write side: sb / sbn have no BATCH_MODE operand, iterate the whole batch list, and write to every device whose GetPrefabHash() (and GetNameHash() for sbn) matches. Zero matches is a genuine silent no-op (loop body never runs, no exception). A typo'd name hash or an unplaced device therefore makes the write vanish with no diagnostic.
Verification History¶
- 2026-04-26: Page created from decompiled
Assembly-CSharp.dll(game version 0.2.6228.27061). Replaces an earlier draft of this page that cited a Steam Community forum post and contained a fabricatedsbnsopcode and an inaccurate description ofld/sdarity. Source for every claim is now the DLL paths in the frontmattersourcesblock. - 2026-04-26: Added "Existence-check opcodes for guarding ReferenceId addressing" and "Batch-op safety for missing devices" sections from
_BRDSE_Operation/_BRDNS_Operation/_BDSE_Operation/_BDNS_Operation/_SDSE_Operation/_SDNS_Operationdecompiles. Documents that all six existence checks route throughGetLogicableFromIdand therefore inherit the data-network membership constraint. - 2026-04-26: Added "
definenames work forsdbut NOT forbrdns/bdns/bdse/brdse/sdse/sdns" section. Discovered while debugging a realbrdns BasePowerTransmitter 2failure ("incorrect variable") in a user script that useddefineto name device IDs. Root cause:_MakeDeviceVariablefalls through toDeviceAliasVariable, whoseGetDevicecallsGetAliasTypewhich only consults_Aliases, never_Defines. The dedicated*dopcodes (ld/sd/getd/putd) bypass_MakeDeviceVariableand useIntValuedVariabledirectly, which does consult_Defines, sodefineworks there. - 2026-04-26: Added "Stack-driven iteration over a list of ReferenceIds" section from
_PUSH_Operation,_POP_Operation,_StackPointerIndex = 16, and theSTACK_POINTER_STRING = "sp"/RETURN_ADDRESS_STRING = "ra"constants plus the_ALIAS_Operation(this, 0, "sp", "r16").Execute(0)startup wiring. Documents the extensibility pattern (onepush $hex # nameline per ReferenceId at the top, fixedpop-loop below) for IC10 scripts that link N pairs of devices identically across networks. - 2026-05-31: Added "Batch-read empty-set return value per mode" section from a direct read of all four
Device.BatchReadoverloads. Establishes that the empty-set return is NOT uniformly 0:Maximumreturnsdouble.NegativeInfinity(no reset guard) andAveragereturnsNaN(0.0/0), while onlySumandMinimumreturn0. This contradicts the blanket "Reads (lb,lbn) with no match return0" sentence in the older "Batch-op safety for missing devices" section (2026-04-26). Per Research/WORKFLOW.md Rule 3, changing the older verified sentence requires a fresh validator; this agent could not spawn one (running as a sub-agent with no Task tool). The new section was added as ADDITIVE content (allowed without a validator), the older sentence was annotated with a forward-pointing correction but left factually intact, and the conflict is logged in Open Questions for the fresh-validator pass. Also expanded the frontmattersourcesto citeDevice.BatchReadand the_LB/_LBN/_SB/_SBN_Operation.Executedispatch confirminglb/lbncallBatchReadandsb/sbnwrite every matching device with noBATCH_MODEoperand. - 2026-05-31: Fresh-validator resolution (main agent re-read all four
Device.BatchReadoverloads directly atAssembly-CSharp.decompiled.cslines 349820-349960). CONFIRMED the empty-set return:Maximum=double.NegativeInfinity(thecasebreaks with no post-loop reset),Average=NaN(num2 /= (double)numwithnum == 0),Sum=0,Minimum=0(explicitif (num2 >= double.PositiveInfinity) num2 = 0.0;reset). The older "reads with no match return0" sentence in "Batch-op safety for missing devices" has been rewritten to the mode-dependent statement and restamped; the conflicting Open Question is resolved and removed.
Open Questions¶
- Behaviour of
ld/sdwhen the supplied numeric id has bit 31 set (long ReferenceId outsideint.MinValue..int.MaxValue). Decompile showsintnarrowing at_DeviceId.GetVariableValueand atGetLogicableFromId(int), but the runtime sign-extension / overflow path was not exercised. ReferenceIds in normal play fit in 32 bits, so the case may never arise.