IC10 Execution Tick Rate and Yield Semantics
Execution Invocation Chain¶
IC10 code execution is driven by the following call stack, not by a MonoBehaviour Update/FixedUpdate:
- GameManager.cs:748 calls
ElectricityManager.ElectricityTick() - ElectricityManager.cs:111 calls
CircuitHolders.Execute() - CircuitHolders.cs:33-35 iterates all
ICircuitHolderinstances and calls theirExecute()method - CircuitHousing.cs:308-314 defines
Execute()which callsProgrammableChip.Execute(128)if conditions are met
Verbatim from CircuitHolders.cs:
public static void Execute()
{
AllCircuitHolders.ForEach(CircuitHolderAction);
}
private static readonly Action<ICircuitHolder> CircuitHolderAction = delegate(ICircuitHolder iCircuitHolder)
{
iCircuitHolder?.Execute();
};
Verbatim from CircuitHousing.cs:
public void Execute()
{
if (GameManager.RunSimulation && !IsCursor && OnOff && Powered && GameManager.GameState == GameState.Running && !WorldManager.IsGamePaused && GameManager.GameState != GameState.None && !(ProgrammableChip == null) && !ProgrammableChip.CompilationError)
{
ProgrammableChip.Execute(128);
}
}
Tick Period and Frequency¶
The game tick loop is defined in GameManager.cs:685:
await UniTask.Delay(GameTickSpeedMs, DelayType.UnscaledDeltaTime, PlayerLoopTiming.Update, cancellationToken);
The tick speed is a constant defined at GameManager.cs:148:
Therefore: - Wall-clock period: 500 milliseconds per game tick - Frequency: 2 Hz (0.5 seconds per tick) - Not synchronized to atmospheric ticks (which run at 20 Hz / 50 ms)
The ElectricityTick (and thus IC10 execution) is called once every game tick at line 748 within the main GameTick loop. This occurs at a different frequency than the 20 Hz atmospheric tick (which is documented as 50 ms).
Instructions Executed Per Tick¶
CircuitHousing.RUN_COUNT constant (CircuitHousing.cs:21):
This constant is passed directly to the Execute() method call (CircuitHousing.cs:312):
The Execute(int runCount) method in ProgrammableChip.cs:6072-6119 processes a loop that executes up to runCount instructions per invocation:
public void Execute(int runCount)
{
if (_NextAddr < 0 || _NextAddr >= _LinesOfCode.Count || _LinesOfCode.Count == 0)
{
return;
}
int nextAddr = _NextAddr;
int num = runCount;
while (num-- > 0 && _NextAddr >= 0 && _NextAddr < _LinesOfCode.Count)
{
nextAddr = _NextAddr;
try
{
_Operation operation = _LinesOfCode[_NextAddr].Operation;
_NextAddr = operation.Execute(_NextAddr);
}
catch (ProgrammableChipException ex)
{
CircuitHousing?.RaiseError(1);
_ErrorLineNumber = ex.LineNumber;
_ErrorType = ex.ExceptionType;
_NextAddr = nextAddr;
break;
}
catch (Exception)
{
if (CircuitHousing != null)
{
CircuitHousing.RaiseError(1);
}
_ErrorLineNumber = (ushort)nextAddr;
_ErrorType = ProgrammableChipException.ICExceptionType.Unknown;
_NextAddr = nextAddr;
break;
}
if (CircuitHousing != null)
{
_ErrorLineNumber = 0;
_ErrorType = ProgrammableChipException.ICExceptionType.None;
CircuitHousing.RaiseError(0);
}
if (_NextAddr < 0)
{
_NextAddr = -_NextAddr;
break;
}
}
}
Therefore:
- Instructions per tick: up to 128 per invocation
- This limit is enforced by the while (num-- > 0) loop counter
- Execution continues until either 128 instructions are consumed or a yield is encountered
Yield Instruction Implementation¶
The yield instruction is parsed as ScriptCommand.yield (ProgrammableChip.cs:1072-1078):
case ScriptCommand.yield:
if (array.Length != 1)
{
throw new ProgrammableChipException(ProgrammableChipException.ICExceptionType.IncorrectArgumentCount, lineNumber);
}
Operation = new _YIELD_Operation(chip, lineNumber);
break;
The _YIELD_Operation class (ProgrammableChip.cs:4681-4692) implements the suspend semantics:
private class _YIELD_Operation : _Operation
{
public _YIELD_Operation(ProgrammableChip chip, int lineNumber)
: base(chip, lineNumber)
{
}
public override int Execute(int index)
{
return -index - 1;
}
}
The return value -index - 1 is a signal to the main Execute loop. In the ProgrammableChip.Execute() method (lines 6113-6117):
When Execute() returns a negative value (from yield), the return value is negated to recover the original address, and the loop breaks immediately, suspending execution until the next tick.
Therefore:
- yield returns control to the game tick immediately
- The next instruction after yield will execute in the next IC10 tick (500 ms later)
- No explicit countdown or sleep mechanism; suspension is implemented via return value and loop break
Relationship to Atmospheric Ticks¶
IC10 execution and atmospheric ticks are decoupled and asynchronous:
- Atmospheric tick rate: 20 Hz (50 ms) (documented via
OnAtmosphericTickcallback) - IC10 tick rate: 2 Hz (500 ms) (derived from GameManager.DefaultTickSpeedMs)
- Ratio: 1 IC10 tick per 10 atmospheric ticks
Both are called from the same GameManager main game tick loop (GameManager.cs:700-780), but they are independent operations:
- Line 740:
AtmosphericsManager.ThingAtmosphereTick()(triggers OnAtmosphericTick on all Things) - Line 748:
ElectricityManager.ElectricityTick()(triggers IC10 execution via CircuitHolders.Execute)
The execution order within a single game tick is: atmospherics operations complete, then electricity (IC10) execution occurs. There is no synchronization lock or dependency between them.
Verification History¶
- 2026-04-25: Page created. Decompiled Assembly-CSharp.dll v0.2.6228.27061 and traced full execution path from GameManager through CircuitHolders to ProgrammableChip.Execute(). Confirmed 128 instructions per tick, 500 ms period, and yield semantics via negative return value.
Open Questions¶
None at present.