flowchart TB
DB[("runtime_overrides table<br/>(SQL via callExtension)")] -->|"fnc_reload: SELECT active rules"| CACHE[In-memory rule cache<br/>keyed by event]
CACHE --> EVENTS
subgraph EVENTS [Event Handlers]
F[Fired<br/>(on_fire)]
H[HitPart<br/>(on_hit)]
K[EntityKilled<br/>(on_killed)]
end
F --> APPLY
H --> APPLY
K --> APPLY
APPLY[fnc_apply: dispatch] -->|"apply_function set"| FNC["call missionNamespace getVariable<br/>(applyDamage, applyVelocity, ...)"]
APPLY -->|"else"| GEN["generic operators<br/>set / add / mul / div / clamp"]
A3SQL Runtime Engine
The runtime engine applies DB-driven override rules to live game objects via event handlers. Rules sit in the runtime_overrides table and fire on specific events: weapon discharge, projectile impact, or unit death. The engine is domain-agnostic: the apply_function column names any SQF function to handle the override, so the same engine covers ballistics, damage, health, environment, or anything else a modder can reach from SQF.
- Overview
- Architecture
- SQL Schema
- Events
- Apply Function Dispatch
- Built-in Apply Functions
- Generic Variable Operators
- Writing Custom Apply Functions
- SQF API Reference
- CBA Settings
- Worked Examples
- Limitations
- Relationship to the Patch Framework
Overview
The runtime engine is a separate system from the patch framework. Where the patch framework polls for rule changes and applies setVariable patches, the runtime engine hooks into game events and transforms values in the event pipeline itself.
Key points:
- Event-driven. Rules fire on Fired, HitPart, or EntityKilled events. No polling, no dirty flags, no frame delay.
- Domain-agnostic. The
apply_functioncolumn names any SQF function. The engine does not care whether you modify damage, velocity, weather, or anything else. - DB-driven. Rules live in
runtime_overrides. Insert a row and the engine picks it up on the next matching event. No restart required. - In-process. The engine uses
callExtension(in-process Arma extension), not the TCP server. The standalone server is irrelevant for runtime rule loading.
Architecture
PostInit –
fnc_registercreates theruntime_overridestable (with ALTER TABLE migration for older schemas), seeds demo rules if the table is empty.fnc_reloadSELECTs all active rules into an in-memory hashmap cache keyed by event name.Event handlers –
XEH_postInitattaches a mission-wide EntityKilled handler. Fired and HitPart are object-level events, so a PerFrame poll cycle attaches them to each new object once.Rule matching – On each event, the handler iterates rules for that event type. Each rule is wrapped in
try {} catch {}so one failure does not block the rest. Match type filtering (exact, type_of, wildcard, regex) selects which objects the rule applies to.Apply dispatch –
fnc_applychecks theapply_functioncolumn. If set, it calls the named function viamissionNamespace getVariable. If empty, it falls back to generic variable operators (set/add/mul/ div/clamp) on the target object.
SQL Schema
runtime_overrides
| Column | Type | Default | Description |
|---|---|---|---|
| id | INTEGER | auto | Primary key |
| name | TEXT | required | Rule name (human-readable identifier) |
| active | INTEGER | 1 | 1 = enabled, 0 = disabled |
| event | TEXT | required | Event trigger: on_fire, on_hit, on_killed |
| match_type | TEXT | ‘exact’ | Matching: all, exact, type_of, wildcard, regex |
| match_value | TEXT | ’’ | Classname or pattern to match against |
| target_property | TEXT | ’’ | Object variable name (for generic operators) |
| operator | TEXT | ‘set’ | Operation: set, add, mul, div, clamp |
| value | TEXT | required | Operator argument (number, vector, or function arg) |
| priority | INTEGER | 0 | Higher values apply first |
| apply_function | TEXT | ’’ | SQF function name to call (empty = generic operators) |
CREATE TABLE IF NOT EXISTS runtime_overrides (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
active INTEGER DEFAULT 1,
event TEXT NOT NULL,
match_type TEXT DEFAULT 'exact',
match_value TEXT DEFAULT '',
target_property TEXT DEFAULT '',
operator TEXT DEFAULT 'set',
value TEXT NOT NULL,
priority INTEGER DEFAULT 0,
apply_function TEXT DEFAULT ''
);Events
| Event | SQF Event | Fired when | Server-side |
|---|---|---|---|
on_fire |
Fired | A unit fires a weapon | Yes (projectile exists on server) |
on_hit |
HitPart | A projectile hits an object | Camera-scoped (needs player) |
on_killed |
EntityKilled | A unit is killed | Yes |
on_fire and on_killed work on headless dedicated servers. on_hit requires a player connection because HitPart is camera-scoped.
CBA Event Interface
The engine registers CBA events so any addon or mission can control it without hardcoding a dependency. Fire them with CBA_fnc_globalEvent.
| Event (call) | Params | Action |
|---|---|---|
a3sql_runtime_reload |
none | Reload the rule cache from the database |
a3sql_runtime_toggle |
[enabled] |
Master switch, overrides the CBA setting per mission |
a3sql_runtime_query |
none | Request status; engine replies on a3sql_runtime_status |
a3sql_runtime_clearTracking |
none | Reset the per-object event-handler cache |
| Event (listen) | Params | Fired when |
|---|---|---|
a3sql_runtime_status |
[enabled, ruleCount] |
Engine answers a query event |
a3sql_runtime_ruleApplied |
[ruleName, target, result] |
A rule is applied (local event, server only) |
a3sql_runtime_rulesLoaded |
[ruleCount] |
The cache is reloaded (local event) |
a3sql_runtime_ready |
[true] |
The table exists and the engine is live |
Example: reload rules from another addon after writing to the database:
["a3sql_runtime_reload"] call CBA_fnc_globalEvent;
CBA Keybinds
Three operator keybinds are registered in the CBA keybind menu (A3SQL Runtime category). They are client-side: pressing a key forwards the command to the server via the CBA event interface.
| Keybind | Default | Action |
|---|---|---|
| Reload Rules | Ctrl + F5 | Fire a3sql_runtime_reload |
| Toggle Engine | Ctrl + F6 | Fire a3sql_runtime_toggle with the inverted state |
| Query Status | Ctrl + F7 | Fire a3sql_runtime_query and log the reply |
CBA Versioning
The runtime addon (and every A3SQL addon) declares the VERSIONING macro in its CfgPatches. This registers the mod with CBA’s versioning system: other addons can check the installed A3SQL version with CBA_fnc_checkCompat and warn on mismatches. The version comes from addons/main/script_version.hpp and is reported at startup, e.g.:
[CBA] (versioning) INFO: VERSIONING:cba=3.19.0.260808, a3sql=1.1.1.0, ace=3.21.2.113
Apply Function Dispatch
The apply_function column makes the engine domain-agnostic. When a rule matches an event, fnc_apply checks this column:
if apply_function is set:
call missionNamespace getVariable [apply_function, {}]
with params: [target, rule, context]
else:
use generic operators (set/add/mul/div/clamp) on target_property
The called function receives:
| Param | Type | Contents |
|---|---|---|
_target |
OBJECT | The object the event fired on (shooter, hit target, killed unit) |
_rule |
HASHMAP | The matched rule with all columns as keys |
_context |
HASHMAP | Event-specific data (ammo, projectile, incomingDamage, velocity, etc.) |
The function must return [returnCode, status, data] following the standard A3SQL response format.
Built-in Apply Functions
a3sql_runtime_fnc_applyDamage
Modifies incoming damage on a hit event.
| Rule field | Usage |
|---|---|
operator |
mul (multiply damage), add, div, set, clamp |
value |
Numeric argument for the operator |
context.incomingDamage |
The original damage value from the HitPart event |
Example: multiply all .50 BMG hits by 1.5x:
INSERT INTO runtime_overrides (name, event, match_type, match_value,
operator, value, apply_function)
VALUES ('heavy_50cal', 'on_hit', 'type_of', 'MSS_50_M33_Ball',
'mul', '1.5', 'a3sql_runtime_fnc_applyDamage');a3sql_runtime_fnc_applyVelocity
Scales projectile velocity on a fire event.
| Rule field | Usage |
|---|---|
operator |
mul (scale velocity), set (absolute), add (delta) |
value |
Scale factor (mul), absolute vector “x,y,z” (set), or delta vector (add) |
Example: give .50 BMG rounds 1.2x velocity:
INSERT INTO runtime_overrides (name, event, match_type, match_value,
operator, value, apply_function)
VALUES ('hot_load_50cal', 'on_fire', 'type_of', 'MSS_50_M33_Ball',
'mul', '1.2', 'a3sql_runtime_fnc_applyVelocity');a3sql_runtime_fnc_applyWeather
Modifies weather parameters (wind, rain, humidity). Works on any event.
| Rule field | Usage |
|---|---|
operator |
set (replace), mul (scale), add (delta) |
value |
Comma-separated: "wind_x,wind_y,rain,humidity" (any subset) |
Example: set heavy rain and adjust wind on mission start:
INSERT INTO runtime_overrides (name, event, match_type, match_value,
operator, value, apply_function)
VALUES ('storm_weather', 'on_fire', 'all', '',
'set', '5,3,0.8', 'a3sql_runtime_fnc_applyWeather');a3sql_runtime_fnc_applyAccuracy
Modifies AI skill values (aiming accuracy, aiming shake, spot distance). Clamps all values to 0.0–1.0. Works on any event.
| Rule field | Usage |
|---|---|
operator |
set (replace), mul (scale), add (delta), clamp (cap) |
value |
Comma-separated: "aimingAccuracy,aimingShake,spotDistance" (any subset) |
Example: boost killer’s accuracy by 1.5x on each kill:
INSERT INTO runtime_overrides (name, event, match_type, match_value,
operator, value, priority, apply_function)
VALUES ('kill_accuracy_boost', 'on_killed', 'all', '',
'mul', '1.5', 5, 'a3sql_runtime_fnc_applyAccuracy');a3sql_runtime_fnc_applySpeed
Modifies unit movement speed or vehicle max speed.
| Rule field | Usage |
|---|---|
operator |
set (replace), mul (scale) |
value |
Mode string ("walk", "run", "sprint") or numeric (km/h) |
Example: set all infantry to sprint speed on spawn:
INSERT INTO runtime_overrides (name, event, match_type, match_value,
operator, value, apply_function)
VALUES ('fast_movement', 'on_fire', 'all', '',
'set', 'sprint', 'a3sql_runtime_fnc_applySpeed');Generic Variable Operators
When apply_function is empty, the engine uses generic operators on the target object’s variables via setVariable / getVariable:
| Operator | Behaviour |
|---|---|
set |
_target setVariable [target_property, value] |
add |
current + value |
mul |
current * value |
div |
current / value (0 on div-by-zero) |
clamp |
current min value |
This path is useful for simple variable patching that does not need event-specific context (e.g. setting a flag on an object when it fires).
Writing Custom Apply Functions
Any compiled SQF function can serve as an apply function. Register it in missionNamespace and reference its name in the apply_function column.
Template
// my_custom_apply.sqf
params ["_target", "_rule", "_context"];
private _operator = toLower (_rule getOrDefault ["operator", "set"]);
private _value = _rule getOrDefault ["value", ""];
// Read event context
private _eventData = _context getOrDefault ["someField", nil];
// Apply your logic
// ...
// Return standard response
[0, "OK", _result]
Registration
Compile and register during mission init:
private _fnc = compileFinal "path/to/my_custom_apply.sqf";
missionNamespace setVariable ["my_custom_apply", _fnc];
Or use the call operator to compile inline:
missionNamespace setVariable ["my_custom_apply", {
params ["_target", "_rule", "_context"];
// Your logic here
[0, "OK", nil]
}];
Usage
INSERT INTO runtime_overrides (name, event, match_type, match_value,
operator, value, apply_function)
VALUES ('my_rule', 'on_fire', 'all', '',
'set', 'some_arg', 'my_custom_apply');Context Hashmap Fields
The context hashmap varies by event:
| Event | Key | Type | Description |
|---|---|---|---|
on_fire |
ammo |
STRING | Ammo classname |
on_fire |
projectile |
OBJECT | The projectile object |
on_fire |
weapon |
STRING | Weapon classname |
on_hit |
incomingDamage |
NUMBER | Damage before override |
on_hit |
projectile |
OBJECT | The projectile object |
on_hit |
selection |
ARRAY | Hit selections |
on_hit |
shooter |
OBJECT | Who fired |
on_killed |
killer |
OBJECT | Who killed the unit |
on_killed |
instigator |
OBJECT | Who caused the kill |
SQF API Reference
Functions
| Function | Description |
|---|---|
a3sql_runtime_fnc_register |
Create runtime_overrides table, seed demo rules if empty |
a3sql_runtime_fnc_reload |
SELECT active rules into in-memory cache |
a3sql_runtime_fnc_apply |
Dispatch a rule to its apply function or generic operators |
a3sql_runtime_fnc_applyDamage |
Built-in: modify incoming damage |
a3sql_runtime_fnc_applyVelocity |
Built-in: scale projectile velocity |
a3sql_runtime_fnc_applyWeather |
Built-in: modify wind, rain, humidity |
a3sql_runtime_fnc_applyAccuracy |
Built-in: modify AI aiming accuracy, shake, spot distance |
a3sql_runtime_fnc_applySpeed |
Built-in: modify unit movement speed or vehicle max speed |
a3sql_runtime_fnc_handleFired |
Process Fired events, match rules, call apply |
a3sql_runtime_fnc_handleHit |
Process HitPart events, match rules, call apply |
a3sql_runtime_fnc_handleKilled |
Process EntityKilled events, match rules, call apply |
a3sql_runtime_fnc_events |
Register the cross-mod CBA event listeners |
a3sql_runtime_fnc_keybinds |
Register the operator keybinds in the CBA keybind menu |
Reloading Rules
Rules are loaded into memory at mission start (deferred with CBA_fnc_waitUntilAndExecute until mission time > 0). To reload after inserting new rules via TCP or in-game:
[] call a3sql_runtime_fnc_reload;
Or from any other addon/mission, without a direct dependency:
["a3sql_runtime_reload"] call CBA_fnc_globalEvent;
CBA Settings
| Setting | Type | Default | Description |
|---|---|---|---|
a3sql_runtime_enabled |
CHECKBOX | true | Enable the runtime event engine |
a3sql_runtime_log_level |
LIST | 1 (WARN) | Verbosity: 0=ERROR, 1=WARN, 2=INFO, 3=DEBUG |
a3sql_runtime_poll_hz |
SLIDER | 5 | Per-object event handler attach rate in Hz |
Worked Examples
Example 1: Double all rifle damage
INSERT INTO runtime_overrides (name, event, match_type, match_value,
operator, value, apply_function)
VALUES ('double_rifle_damage', 'on_hit', 'wildcard', 'MSS_*',
'mul', '2.0', 'a3sql_runtime_fnc_applyDamage');Example 2: Custom weather effect on fire
Register a function that reads wind and adjusts projectile speed:
missionNamespace setVariable ["my_wind_adjust", {
params ["_target", "_rule", "_context"];
private _vel = velocity (_context getOrDefault ["projectile", objNull]);
private _wind = wind;
private _crosswind = [_wind select 0, _wind select 1, 0];
private _adjusted = _vel vectorAdd (_crosswind vectorMultiply 0.1);
_target setVelocity _adjusted;
[0, "OK", _adjusted]
}];
INSERT INTO runtime_overrides (name, event, match_type, match_value,
operator, value, apply_function)
VALUES ('wind_drift', 'on_fire', 'all', '',
'set', '', 'my_wind_adjust');Example 3: Disable via DB
UPDATE runtime_overrides SET active = 0 WHERE name = 'double_rifle_damage';
[] call a3sql_runtime_fnc_reload;Limitations
- HitPart is camera-scoped. On a dedicated server without players, HitPart events do not fire. Test on_hit rules with a player connected.
- Config values are static. The runtime engine overrides values via event handlers, not config. Config properties (initSpeed, airFriction) are set once at game start and cannot change at runtime. Use the compat PBO (Path A) for config overrides.
- In-memory cache. Rules are loaded once at mission start. Use
a3sql_runtime_fnc_reloadafter inserting new rules via TCP. - No persistence. The
runtime_overridestable is auto-created on mission start. If you need rules to survive server restarts, save them to a persistent table and INSERT intoruntime_overridesat postInit.
Relationship to the Patch Framework
| Feature | Patch Framework | Runtime Engine |
|---|---|---|
| Table | patch_rules |
runtime_overrides |
| Trigger | PerFrame poll (dirty flag) | Game events (Fired, HitPart, Killed) |
| Apply method | setVariable on objects | apply_function dispatch or generic operators |
| Domain | General (anything reachable via setVariable) | Event-driven (damage, velocity, kills) |
| Speed | Next frame after dirty flag | Same event tick |
| Persistence | Auto-save/load via CBA | Table created fresh each mission |
Use the patch framework for set-and-forget value patches (fuel, skills, textures). Use the runtime engine for event-driven transformations that need the event context (damage scaling, velocity modification, kill tracking).
Engine Verification
The SQL engine is verified against three independent reference sources. All tests run in CI on every push.
sqllogictest
The engine implements the sqllogictest::DB trait, so the same test harness used by DuckDB and DataFusion runs against A3SQL. Test files live in extension/tests/slt/ and cover core CRUD, type behaviour, and SQL syntax. Run with:
cargo test --manifest-path extension/Cargo.toml -- sqllogictestDifferential testing against SQLite
A 25-statement corpus (CREATE, INSERT, SELECT, UPDATE, DELETE, JOINs, aggregates, ORDER BY, LIMIT, NULLs) runs on both A3SQL and a real SQLite database (rusqlite, dev-dependency only). Results are compared order-insensitively to catch semantic drift. This is the same pattern AQAP 2210 Annex C prescribes for independent verification:
cargo test --manifest_path extension/Cargo.toml -- differentialMath function reference values
Seven tests verify numeric functions against known-good reference values and IEEE-754 edge cases (NaN, infinity, negative zero, precision limits). Covers ABS, CEIL, FLOOR, ROUND, POW, SQRT, SIGN, and aggregate functions (SUM, AVG, MIN, MAX, COUNT). Follows the PostgreSQL regression-test pattern (cross-product of special values against every operator):
cargo test --manifest-path extension/Cargo.toml -- math_verifyRunning all verification
cargo test --manifest-path extension/Cargo.tomlThe full suite (728+ tests) runs in under 60 seconds. CI also runs hemtt check -p -e for addon config validation and cargo clippy for Rust lint.