Logic Update Scripts
Logical update scripts are recurring event hooks tied directly to OpenBOR's logical clock. They provide paired entry points immediately before and after each logical tick processed by the engine.
OpenBOR supplies both project-wide global files and level-specific commands:
| Hook | Scope | Execution point |
|---|---|---|
data/scripts/updatelogic.c
|
Project-wide | Before native processing for each logical tick |
updatelogicscript
|
Current level | After global updatelogic.c, before native tick processing
|
data/scripts/updatedlogic.c
|
Project-wide | After native processing for each logical tick |
updatedlogicscript
|
Current level | After global updatedlogic.c, before the logical clock advances
|
These hooks differ from the outer-cycle Update and Updated events. Outer update frequency may follow display synchronization, platform timing, and runtime workload. Logical update scripts instead execute once for every simulation tick OpenBOR actually processes, including each tick of a multi-tick catch-up cycle.
This direct relationship to the logical clock makes the series useful for deterministic state machines, tick-accurate controllers, custom simulation systems, pre-simulation preparation, post-simulation reconciliation, replay instrumentation, synchronized hazards, and other features that should advance with engine logic rather than display frequency.
Global Usage
Create either or both global files in data/scripts:
data/scripts/updatelogic.cdata/scripts/updatedlogic.c
OpenBOR loads them automatically. Both use a main() entry point and receive no automatic event variables.
void main()
{
long logical_tick = openborvariant("elapsed_time");
setglobalvar("logical_tick_begin", logical_tick);
}
updatelogic.c is the first script in the logical-tick sequence. updatedlogic.c is the first script after native tick processing.
Projects may use either hook independently or use both as a matched pair.
Level Usage
Add either command to a level definition and provide the path to its script file:
updatelogicscript data/scripts/levels/foundry_updatelogic.c
updatedlogicscript data/scripts/levels/foundry_updatedlogic.c
Each referenced script uses a main() entry point and receives no automatic event variables.
void main()
{
// Perform level-specific logical-tick work here.
}
OpenBOR loads these scripts with the level and executes them only while that level remains available. One updatelogicscript and one updatedlogicscript may be assigned to a level.
Inline Level Scripts
Both level commands support inline script blocks:
updatelogicscript @script
void main()
{
setglobalvar("foundry_tick_active", 1);
}
@end_script
updatedlogicscript @script
void main()
{
setglobalvar("foundry_tick_complete", 1);
}
@end_script
Inline form is convenient for concise level-only behavior. External files are easier to reuse and organize when logical controllers contain several systems or helper functions.
Execution Order
The relevant recurring sequence is:
- OpenBOR begins an outer update cycle and refreshes timing and input state.
- Global
data/scripts/update.cexecutes when eligible. - The current level's
updatescriptexecutes when declared. - OpenBOR processes applicable key events and determines how many logical ticks are due.
- For each logical tick:
- Global
data/scripts/updatelogic.cexecutes. - The current level's
updatelogicscriptexecutes. - OpenBOR processes native logical work, including applicable scrolling, timer, status, and entity updates.
- Global
data/scripts/updatedlogic.cexecutes. - The current level's
updatedlogicscriptexecutes. - OpenBOR advances the logical clock to the next tick.
- Global
- After all due ticks finish, OpenBOR prepares the display queue.
- Global
data/scripts/updated.cexecutes. - The current level's
updatedscriptexecutes when declared.
Global logic always executes before the matching level logic. Level scripts can therefore specialize or consume project-wide state established by their global counterparts during the same phase.
When an outer update cycle processes several logical ticks, the complete pre-tick and post-tick chain repeats for each one. When no logical tick is due, none of the four logical hooks executes during that outer cycle.
Logical Clock Value
Both sides of the pair observe the same logical clock value. OpenBOR executes updatedlogic.c and updatedlogicscript after native tick processing but before incrementing the clock.
void main()
{
long logical_tick = openborvariant("elapsed_time");
setglobalvar("logical_tick_finished", logical_tick);
}
This timing allows the early hook to capture or prepare state for tick N, while the late hook examines the results of that same tick N. The following logical iteration then begins with tick N + 1.
OpenBOR uses a default logical rate of 200 ticks per second. Projects may configure a different logical clock rate, so scripts should use the clock value or project timing configuration when converting ticks into real-time units.
Pause and Eligibility
Logical update scripts execute only while OpenBOR is actively processing logical ticks. Pausing prevents the logical loop from advancing, so neither the early nor late logical hook executes during paused outer cycles.
The alwaysupdate setting expands eligibility beyond normal in-game update calls. It does not bypass pause or create a logical tick when the current outer cycle has none to process.
This differs from outer-cycle updated hooks, which may still execute during paused in-game cycles. Systems that must remain active while paused belong in an appropriate outer update or presentation hook rather than the logical series.
Before and After Native Simulation
updatelogic is suited to work that must be ready before the engine updates gameplay state. Quick applications include applying queued decisions, advancing custom input state, preparing movement rules, selecting active hazards, updating custom clocks, or publishing values that entity scripts will consume during the current tick.
updatedlogic is suited to work that depends on the results of native processing. Quick applications include resolving objectives after entity updates, reconciling custom physics, recording resulting state, counting surviving participants, evaluating synchronized conditions, or preparing state for the next logical tick.
void main()
{
int pending_phase = getglobalvar("pending_phase");
if (pending_phase)
{
setglobalvar("active_phase", pending_phase);
setglobalvar("pending_phase", 0);
}
}
The example is appropriate for updatelogic.c when the new phase must be visible to every system during the upcoming native tick.
void main()
{
int targets_remaining = getglobalvar("targets_remaining");
if (targets_remaining <= 0)
{
setglobalvar("pending_phase", 3);
}
}
The matching updatedlogic.c can evaluate values produced during native processing and queue a phase change for the next tick.
Choosing an Update Hook
| Hook | Frequency | Phase | Typical responsibility |
|---|---|---|---|
Global update.c and level updatescript
|
Once per eligible outer update cycle | Before key events and logical processing | Outer-cycle coordination and early preparation |
Global updatelogic.c and level updatelogicscript
|
Once per processed logical tick | Immediately before native tick work | Tick-accurate preparation and custom simulation input |
| updateentityscript | During applicable entity updates | Within native logical processing | Per-entity recurring behavior |
Global updatedlogic.c and level updatedlogicscript
|
Once per processed logical tick | After native tick work, before clock advancement | Tick-accurate resolution and resulting-state inspection |
Global updated.c and level updatedscript
|
Once per eligible outer update cycle | After logical processing and display preparation | Late-cycle coordination and presentation |
Display synchronization does not define logical-script frequency. One outer cycle may process zero, one, or several logical ticks, and the logical hooks follow the tick count exactly.
Other Uses
Quick applications include:
- Deterministic controllers - Advance custom state machines exactly once per engine logical tick.
- Custom simulation - Run project-defined movement, resources, hazards, clocks, or world systems beside native logic.
- Pre-tick preparation - Publish values, apply queued commands, choose rules, or establish state consumed during the current tick.
- Post-tick reconciliation - Evaluate entity results, resolve simultaneous conditions, or queue changes for the next tick.
- Synchronized encounters - Coordinate hazards, waves, objectives, formations, or multi-part sequences on a common clock.
- Replay and diagnostics - Record pre-tick and post-tick state, identify the exact logical time of a change, or compare deterministic results.
- Level simulation - Keep stage-specific clock logic with the level while common systems remain in the global files.
- Timing tools - Build profilers, counters, test harnesses, or controlled simulation instrumentation around exact tick boundaries.
Related Scripts
| Script | Relationship |
|---|---|
| update.c | Global early outer-cycle hook that executes before logical processing begins. |
| updatescript | Level-specific early outer-cycle hook. |
| updateentityscript | Model hook executed for individual entities during native logical processing. |
| updated.c | Global late outer-cycle hook that executes after all due logical ticks. |
| updatedscript | Level-specific late outer-cycle hook. |