Cronus Zen Guide
GPC Scripting 3 min read

GPC basics: reading sticks, triggers and buttons with get_val

How GPC exposes controller input: the 0 to 100 and -100 to 100 value ranges, get_val versus get_ival, event_press and event_release for edges, get_ptime for hold duration, and how to write a deadzone or a stick swap correctly.

Updated
GPC script code on a dark editor

Quick answer

In GPC, get_val returns a normalised integer for any control: 0 or 100 for a digital button, 0 to 100 for a trigger, and -100 to 100 for a stick axis. It reads the current value every cycle, so use event_press for a one-shot reaction and get_ival for the untouched input after modification.

What values does the Zen give a script?

The Zen normalises everything. You never see the controller's raw hardware counts, only integers on a fixed scale, which is why one script works with a DualSense, an Xbox controller or a keyboard through the M&K translator.

ControlRangeNotes
Digital button0 or 100Released or pressed
Analog trigger0..100L2/R2, LT/RT
Stick axis-100..1000 is centre; left and up are negative
Touchpad presence0 or 100Finger present
Touchpad position-100..100Normalised

A few features use their own scales: polar stick functions take an angle of 0..359, rumble strength is 0..100, and PS5 adaptive trigger data is bytes of 0..255. The compiler checks constant arguments against these ranges; values you compute at runtime must be clamped yourself.

What does get_val actually read?

get_val(control) returns the control's current value in the output report. At the start of a cycle that equals the controller's input. If a line earlier in main has already called set_val on the same control, get_val returns the new value.

That matters for stick swaps and blocks. Consider:

gpc
main {
    set_val(PS5_RX, get_val(PS5_LX));
    set_val(PS5_LX, get_val(PS5_RX)); // reads the value you just wrote
}

The second line reads the already-modified right stick, so both sticks end up the same. The fix is get_ival, which returns the raw input regardless of what the script has done:

gpc
main {
    set_val(PS5_RX, get_ival(PS5_LX));
    set_val(PS5_LX, get_ival(PS5_RX));
}

Rule of thumb: get_ival when you care what the player did, get_val when you care what the console is about to receive.

How do I react once to a press?

get_val is true on every cycle a button is held. A cycle is a few milliseconds, so a half-second press is dozens of cycles. Anything you toggle with get_val flips dozens of times and lands in a random state. The official guide calls this out with a dedicated page.

Use event_press, true only on the exact cycle the control goes from released to pressed:

gpc
int enabled;

main {
    if(event_press(PS5_CROSS)) {
        enabled = !enabled;
    }
}

event_release is the mirror, true on the cycle the control lets go. Together they cover "on tap" and "on let go" without any manual edge tracking.

How do I detect a hold or a double tap?

Two timing built-ins do the work.

get_ptime(control) returns how many milliseconds the control has been in its current pressed or released state. It resets whenever the state changes.

gpc
main {
    if(get_ival(PS5_OPTIONS) && get_ptime(PS5_OPTIONS) >= 800) {
        // held for 0.8 s
    }
}

get_brtime(control) returns the time since the previous release, which makes a double tap a one-liner:

gpc
main {
    if(event_press(PS5_CROSS) && get_brtime(PS5_CROSS) < 300) {
        // second press within 300 ms
    }
}

The official page adds a testing note worth repeating: always device-test the first press after the script loads and the exact threshold boundary, because both are where off-by-one behaviour hides.

How do I write a deadzone?

A stick that rests slightly off centre produces small non-zero values that a game may treat as drift. A deadzone forces small values to zero:

gpc
define DZ = 8;

main {
    if(abs(get_ival(PS5_RX)) < DZ) set_val(PS5_RX, 0);
    if(abs(get_ival(PS5_RY)) < DZ) set_val(PS5_RY, 0);
}

A better version rescales the remaining range so movement starts smoothly at the deadzone edge, but the simple form shows the pattern: read raw, decide, write.

How do I read a trigger as a button?

Triggers report 0..100. Treat anything above a threshold as pressed:

gpc
main {
    if(get_ival(PS5_R2) > 20) set_val(PS5_R2, 100); // hair trigger
}

That two-line script is the classic beginner example in the official guide, and it demonstrates why the ranges matter: without knowing a trigger is 0..100 you cannot pick a sensible threshold.

Which identifiers should I use?

Controls are named by platform: PS5_CROSS, PS4_CROSS, XB1_A, SWITCH_B. The Zen maps them across platforms so a script written with PS5_ names works on Xbox; PS5_CROSS and XB1_A refer to the same physical position. Pick one family and stay consistent. The official cross-platform identifiers page lists the mapping, and TRACE_1 to TRACE_6 are debug outputs you can watch in Device Monitor without affecting the game.

What comes next?

With reading values solid, the next step is producing timed output, which is what combos are for. Continue with combos and wait timing, then persistent settings with SPVAR. The full beginner path starts at GPC scripting 101.

Frequently asked questions

What is the difference between get_val and get_ival?
get_val returns the control's current value in the output report, including any change an earlier set_val made this cycle. get_ival returns the raw input from the controller regardless of what the script has already done. Use get_ival when a line above has already remapped or blocked the control.
Why does my toggle flicker when I use get_val?
Because get_val is true on every cycle the button is held, so the toggle flips dozens of times per second. Use event_press, which is true only on the cycle the button goes from released to pressed. The official guide has a page on exactly this mistake.
How do I detect a long press?
Combine get_ival with get_ptime, which reports how long the control has been in its current state in milliseconds. The official example checks get_ival(PS5_OPTIONS) and get_ptime(PS5_OPTIONS) >= 800.
Do the identifiers change per console?
The names are prefixed by platform (PS5_CROSS, XB1_A and so on) but the Zen maps them across platforms, so a script written with PS5 names works on Xbox. The official cross-platform identifiers page explains the mapping.

Sources

Every link was live when this guide was last updated.

  1. 01Control value ranges (official GPC guide)guide.cronuszen.com
  2. 02Edges, holds and releases (official GPC guide)guide.cronuszen.com
  3. 03Toggle state without repeat firing (official GPC guide)guide.cronuszen.com
  4. 04Cross-platform identifiers (official GPC guide)guide.cronuszen.com

This guide is independent and unofficial; product names belong to their owners. Found an error? Use the contact page and we will correct it and note the change.