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

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_function column 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

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"]

  1. PostInitfnc_register creates the runtime_overrides table (with ALTER TABLE migration for older schemas), seeds demo rules if the table is empty. fnc_reload SELECTs all active rules into an in-memory hashmap cache keyed by event name.

  2. Event handlersXEH_postInit attaches a mission-wide EntityKilled handler. Fired and HitPart are object-level events, so a PerFrame poll cycle attaches them to each new object once.

  3. 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.

  4. Apply dispatchfnc_apply checks the apply_function column. If set, it calls the named function via missionNamespace 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_reload after inserting new rules via TCP.
  • No persistence. The runtime_overrides table is auto-created on mission start. If you need rules to survive server restarts, save them to a persistent table and INSERT into runtime_overrides at 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 -- sqllogictest

Differential 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 -- differential

Math 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_verify

Running all verification

cargo test --manifest-path extension/Cargo.toml

The 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.