CronusZenWiki
GPC Scripting

GPC Syntax Reference: Variables, Main Loop, Operators

Complete GPC syntax reference for Cronus Zen scripting: script sections, variables, operators, the main loop, combo timing rules, and core functions.

Updated September 2, 20267 min readCECZW Editorial

This page is a working GPC syntax reference for the Cronus Zen: the script sections in their required shape, statement and variable rules, operators, the main-loop execution model, combo timing semantics, and a table of the core built-in functions. It is meant to be kept open in a second tab while you write. For a gentler on-ramp, the first script tutorial builds a real script line by line; for the conceptual model behind input reading and writing, see the inputs and events explainer.

How a GPC script is laid out

A .gpc file is a plain-text file whose sections conventionally appear in this order:

Section Keyword Runs Purpose
Constants define compile time Named values for timings and thresholds
Globals int Variables that persist across loop passes
Data data(...) Read-only data tables (advanced; see Cronus's docs)
Init init { } once, at script start One-time setup
Main main { } continuously The loop; reacts to every controller report
Combos combo Name { } when triggered Timed input sequences
Functions function name(...) { } when called Reusable helpers

A compact skeleton showing every common section, as a complete script:

// Skeleton: every common GPC section in conventional order.

define TAP_MS = 60;              // named constant

int presses;                     // global variable (integers only)

init {
    presses = 0;                 // runs once when the script loads
}

main {
    // Runs once per controller report while the script is active.
    if (event_press(PS4_CROSS)) {
        presses = bump(presses); // call a user function
        combo_run(TapCircle);    // start a timed sequence
    }
}

combo TapCircle {
    set_val(PS4_CIRCLE, 100);    // held during the wait below
    wait(TAP_MS);
    set_val(PS4_CIRCLE, 0);      // released during this wait
    wait(TAP_MS);
}

function bump(n) {
    return n + 1;                // user-defined function with return
}

The execution model in one sentence: init runs once, then main re-runs for every input report from your controller, and any set_val you perform applies to that report on its way to the console.

GPC syntax basics: statements, comments, and variables

Statements end with a semicolon. The closing brace of a block implies one, so the last line before a } does not strictly need its own — but writing the semicolon anyway is harmless and easier to keep consistent. Braces { } group blocks; a single-statement if body can technically omit them, but braces-always avoids a classic class of bugs.

Comments come in two forms:

// Single line: everything to the end of the line is ignored.

/* Multi-line: everything until the closing
   marker is ignored, across lines. */

main {
    // A complete (if idle) script, to keep the example runnable.
}

Constants use define, evaluated at compile time:

define FIRE_HOLD = 40;    // milliseconds

main {
    // Constants substitute anywhere a number is expected.
    if (get_val(XB1_RT) > 90) {
        combo_run(Pulse);
    }
}

combo Pulse {
    set_val(XB1_RT, 100);
    wait(FIRE_HOLD);
    set_val(XB1_RT, 0);
    wait(FIRE_HOLD);
}

Variables are declared with int — integers are the only variable type. Globals persist between passes of main, which is how scripts remember toggle states and counters. Declare globals outside all blocks; initialize them in init or rely on their starting value of zero. There are no strings and no floating point; timing math is done in whole milliseconds and stick math in whole percentage points.

Names (variables, combos, functions) follow C rules: letters, digits, and underscores, not starting with a digit. Published Cronus examples use built-in identifiers in both cases (XB1_RT and xb1_rt); pick one style and stay consistent within a script.

Operators

Category Operators Notes
Arithmetic + - * / % Integer math; division truncates
Comparison == != < <= > >= Result is true/false
Logical && || ! Combine conditions; !x also flips a 0/1 flag
Assignment = x = x + 1; — write increments explicitly

The ! operator doubles as the standard toggle idiom: flag = !flag; flips a variable between 0 and 1. GPC also has bitwise operators for packing flags into a single integer; the exact set is in Cronus's language reference — verify there before relying on them, since day-to-day scripts rarely need any.

The main loop, init, and conditionals

main is not a function you call; it is the body the Zen executes on every controller report, indefinitely. Three consequences shape all GPC code:

  1. Never busy-wait in main. There is no sleeping inside main; wait() is only valid inside combos. Anything with duration belongs in a combo.
  2. Locals do not carry over. Only global int variables persist from one pass to the next.
  3. set_val is per-pass. A value you set in main applies to the current report only; to hold it, either set it every pass while a condition holds or use a combo.

Flow control inside main is if / else if / else:

main {
    if (get_val(PS4_L2) && get_val(PS4_R2)) {
        // both triggers held
        set_val(PS4_R1, 100);
    } else if (get_val(PS4_L2)) {
        // only the left trigger
        set_val(PS4_R1, 0);
    } else {
        // neither — pass through untouched
    }
}

The loop itself is your iteration construct; GPC scripting does not revolve around while/for loops the way desktop C does, and a loop that never yields would stall input. If you think you need a loop, you almost always need a combo or a counter that increments once per pass.

Combos and the wait() timing model

A combo is a named block of set_val and wait statements. The timing rule that governs everything: each wait(ms) holds whatever the statements since the previous wait (or the combo's start, or the last call) have set, for that many milliseconds. When the combo ends, the script stops overriding those controls.

// Timing model demo: what the console sees, window by window.
main {
    if (event_press(XB1_Y)) {
        combo_run(Demo);
    }
}

combo Demo {
    set_val(XB1_A, 100);    // window 1: A held...
    wait(500);              // ...for 500 ms
    set_val(XB1_A, 0);      // window 2: A forced released,
    set_val(XB1_B, 100);    //           B held...
    wait(200);              // ...for 200 ms
    call(Tail);             // pause here, run Tail, then resume
}

combo Tail {
    set_val(XB1_X, 100);    // X held for 150 ms
    wait(150);
}

Key facts, per Cronus's combo documentation:

  • wait() accepts 1 to 32767 milliseconds.
  • call(OtherCombo) is valid only inside combos; it suspends the current combo, runs the called one to completion, then resumes.
  • combo_run(X) on an already-running combo has no effect; use combo_restart(X) to start it over.
  • A combo continues to completion on its own timeline even if the triggering condition in main has ended, unless something calls combo_stop(X).

Worked combo construction, including interrupts and retrigger guards, is the subject of the combos and macros tutorial.

Core function reference

The functions below cover the overwhelming majority of real scripts. Value semantics: buttons and triggers read 0 to 100; stick axes read -100 to +100.

Function Signature What it does
get_val get_val(id) Current value of a control in this pass
get_ival get_ival(id) The raw input value, before script modification
get_lval get_lval(id) The control's value in the previous pass
get_ptime get_ptime(id) Milliseconds since the control last changed state (0–32767)
event_press event_press(id) True only on the pass where the control becomes pressed
event_release event_release(id) True only on the pass where the control becomes released
set_val set_val(id, v) Sets the value sent to the console this pass
swap swap(id1, id2) Exchanges two controls' values (on-the-fly remap)
block block(id, ms) Suppresses forwarding of a control for 20–4000 ms
sensitivity sensitivity(id, mid, ratio) Rescales an analog control around a midpoint
deadzone deadzone(idx, idy, dzx, dzy) Adjusts stick deadzone on two axes
combo_run combo_run(Name) Starts a combo (no effect if already running)
combo_running combo_running(Name) True while the named combo runs
combo_stop combo_stop(Name) Halts a running combo immediately
combo_restart combo_restart(Name) Restarts a combo from its first statement
wait wait(ms) Combo-only: hold current window for 1–32767 ms
call call(Name) Combo-only: run another combo, then resume

Beyond these, GPC includes device functions (LED control, rumble, slot management, battery queries) whose exact signatures are best taken from Cronus's current GPC reference rather than memorized from forum posts.

Identifiers and value ranges

Every physical control has named identifiers. The XB1_ and PS4_ families are aliases for the same internal slots, so scripts written with either work across consoles.

Group Examples Range
Face buttons XB1_A, PS4_CROSS, PS4_SQUARE 0 to 100
Shoulder bumpers XB1_RB, PS4_R1 0 to 100
Triggers (analog) XB1_RT, PS4_R2 0 to 100
D-pad XB1_UP, PS4_LEFT 0 to 100
Stick axes XB1_LX, XB1_LY, PS4_RX, PS4_RY -100 to +100
Stick clicks XB1_LS, PS4_R3 0 to 100
Trace channels TRACE_1TRACE_6 script-defined; shown in the device monitor

Axis polarity: negative is up/left, positive is down/right. Each named identifier maps to a number under the hood (PS4_CROSS is 19, for example) and raw numbers are accepted by the compiler, but names are self-documenting — always prefer them. The full identifier list, including console-specific extras like the PS4 touchpad, is in Cronus's identifier documentation.

FAQ

Is GPC syntax the same as C?

The surface is C: braces, semicolons, // comments, the same operator symbols. The differences are structural — a perpetually looping main instead of a run-once program, int as the only variable type, no standard library, and combo blocks with wait() as the timing mechanism.

Where can I use wait() in a GPC script?

Only inside combo blocks. main must finish each pass quickly so the next controller report can be processed; anything that needs duration gets a combo, triggered from main with combo_run().

What is the difference between define and int in GPC?

define creates a compile-time constant — a name substituted for a fixed number, unchangeable at runtime. int declares a variable that occupies memory and can be reassigned every pass. Use define for tunable timings, int for state such as toggles and counters.

How fast does the GPC main loop run?

Once per controller input report, which on the Zen is on the order of every few milliseconds. Cronus's introductory documentation does not pin an exact figure, so never design logic that assumes a precise loop period — measure durations with get_ptime() or structure timing inside combos instead.

Why does my script compile but a combo never fires?

The three usual causes: the triggering condition uses event_press on a control you are holding rather than tapping; the combo is already running so combo_run does nothing (add a combo_running() check while debugging); or the wrong memory slot is selected on the device.