From zero to writing your own Cronus Zen scripts. Every concept explained, every function shown in real code, no programming experience required.
The Cronus Zen sits physically between your controller and the console. Your script runs on the Zen itself — not on the console, not on a PC. Roughly every 10 milliseconds, this happens:
Your script sees every button and stick value, can change any of them, and the console receives whatever your script decided. The console has no idea a script exists — it just sees a controller.
This also tells you what GPC can't do: it can't see the screen, read the game, or know what's happening in the match. Everything a script "knows" comes from the controller in your hands.
The Zen holds up to 8 script slots. You compile a script, flash it to a slot, then select the slot from the device. While testing you'll flash the same slot over and over — that's normal workflow.
| BUTTON IN ZEN STUDIO | WHAT IT DOES |
|---|---|
| Compile | checks your code and reports errors with line numbers |
| Build & Run | compiles and runs on the Zen immediately (best while testing) |
| Program Device | flashes the script permanently into a slot |
| Device Monitor | shows live input/output values — your best debugging friend |
Before any theory, get one script running end to end. This removes every "but does it actually work?" doubt and teaches you the workflow you'll use forever.
Total time: well under 30 minutes. Everything after this chapter is just learning more things to put between those braces.
Every GPC script has the same skeleton. Here it is with every section labeled:
main is a loop that never stops: read the controller, decide, repeat — about 100 times per second. That one fact explains most GPC rules: no long pauses in main (the game would freeze from your point of view), and anything you want "remembered" between passes must live in a variable.
GPC has no for/while loops. You don't need them — main is your loop. If you catch yourself wanting "do this until X", the answer is a variable plus an if, or a combo.
One type: int — whole numbers, positive or negative. No decimals, no text. Declare at the top of the script, outside any section:
A define gives a fixed value a name. Change it in one place, and the whole script updates. Every serious script starts with a block of defines:
| MATH | MEANING |
|---|---|
| + - | add, subtract |
| * / | multiply, divide |
| % | remainder (great for cycling modes) |
| = | assign a value |
| COMPARE / LOGIC | MEANING |
|---|---|
| == != | equal, not equal |
| < > <= >= | less / greater than |
| && || | AND, OR |
| ! | NOT (flips true/false) |
The classic trap: = assigns, == compares. Writing if (mode = 1) when you meant if (mode == 1) is the oldest bug in programming.
Every input has a name. Buttons and triggers read 0–100; stick axes read -100 to 100 (negative = up/left). The Device Monitor shows you every identifier live — press a button and watch.
| PLAYSTATION | XBOX | WHAT | RANGE |
|---|---|---|---|
| PS4_CROSS | XB1_A | face button | 0–100 |
| PS4_R2 / PS4_L2 | XB1_RT / XB1_LT | triggers (analog) | 0–100 |
| PS4_R1 / PS4_L1 | XB1_RB / XB1_LB | bumpers | 0–100 |
| PS4_LX / PS4_LY | XB1_LX / XB1_LY | left stick axes | -100–100 |
| PS4_RX / PS4_RY | XB1_RX / XB1_RY | right stick axes | -100–100 |
| PS4_L3 / PS4_R3 | XB1_LS / XB1_RS | stick clicks | 0–100 |
| PS4_UP … PS4_RIGHT | XB1_UP … XB1_RIGHT | d-pad | 0–100 |
Scripts written with PS4_ names still work on Xbox and vice versa — the identifiers map to positions, not consoles. Pick one family and stay consistent.
| FUNCTION | ANSWERS THE QUESTION | TRUE WHEN |
|---|---|---|
| get_val(btn) | "is it held right now?" | every loop while held |
| event_press(btn) | "was it just pressed?" | one single loop, on press |
| event_release(btn) | "was it just let go?" | one single loop, on release |
| get_ptime(btn) | "how long has it been held?" | returns milliseconds |
The #1 beginner bug is using get_val where you meant event_press. get_val fires ~100 times a second while held — a toggle flipped with get_val will flicker on and off madly. Toggles and counters want event_press.
set_val lasts one loop. It is not a switch you flip once — main must keep setting it every pass. That's exactly why state lives in variables (next chapter).
These do in one line what would take several with get_val/set_val. A whole "remap script" can be a single swap() in main.
Tap once = on, tap again = off. The variable is the memory; main re-applies the effect every loop while it's on:
One button, two actions: tap = melee, hold = grenade. Decide on release, using how long it was down:
Cycle through 3 profiles with one button. The % operator wraps the counter back to 0:
Notice the guard: get_val(XB1_LT) && makes it a deliberate two-hand action, so nobody switches modes mid-fight by accident. Guarding your mode button is a mark of a well-made script.
Run something only when a button is pressed twice quickly. This needs a stopwatch: get_rtime() returns the milliseconds since the previous pass through main, so adding it up every loop gives you elapsed time:
This stopwatch trick — timer = timer + get_rtime(); — is how GPC measures any gap between events. You'll reuse it constantly.
A combo is the only place time is allowed to pass. Each wait(ms) holds the current outputs for that many milliseconds, then moves on:
How to read it: a set_val inside a combo stays active until the next wait() finishes. A wait() with nothing set before it is a pause with everything released. That "empty wait" trick is how you put gaps between presses.
| FUNCTION | WHAT IT DOES |
|---|---|
| combo_run(Name) | starts a combo (if already running, does nothing) |
| combo_running(Name) | TRUE while it runs — great for guards |
| combo_stop(Name) | kills it immediately, outputs release |
| combo_restart(Name) | jumps it back to the first line |
Rules that save you pain:
When the same logic appears twice, move it into a function. Functions take inputs, can return a value, and make main read like a sentence:
Keep functions small and named after what they answer or do: isAiming(), resetModes(). If you can't name it simply, it's doing too much.
A script that changes modes silently feels broken. The Zen can flash the controller LED and fire the rumble motors — use a short buzz to confirm a toggle:
A good convention: one buzz = mode 1, two buzzes = mode 2. Players learn it instantly.
Normal variables reset every time the Zen restarts. Persistent variables (SPVAR_1 through SPVAR_16) are stored on the device itself — perfect for user-adjustable settings. This script lets the player tune stick sensitivity with the d-pad and remembers it forever:
This is what separates a script from a product: users set it once and it stays set. Read the value in init, save on every change, clamp with sensible min/max.
This is a full quality-of-life script using every chapter so far: defines, a guarded toggle, tap-vs-hold, a combo, rumble feedback, and clean structure. It compiles as-is. Read it top to bottom — you now know every line:
Why this is "well built" — the things to copy into your own scripts:
| ERROR SAYS | IT USUALLY MEANS |
|---|---|
| syntax error | missing semicolon on the line above, or an unmatched { } |
| undeclared identifier | typo in a name, or the variable isn't declared at the top |
| already defined | two variables/combos share one name |
Fix the first error and recompile — one missing brace can produce twenty phantom errors below it.
Open the Device Monitor: inputs on one side, your script's outputs on the other. Most bugs are visible instantly. For anything you can't see — like a variable's value — send it to a TRACE:
TRACE values appear in the Device Monitor without affecting the game. It's GPC's version of print-debugging — when a toggle "doesn't work", tracing the variable usually shows it flipping twice per press (get_val vs event_press again).
| CHECK | HOW |
|---|---|
| Is the right slot active? | the Zen's display shows the slot number — cycle to the one you flashed |
| Did Build & Run actually succeed? | re-compile; a failed build leaves the OLD script running, which is very confusing |
| Does your if ever fire? | trace the condition: set_val(TRACE_1, get_val(XB1_LT)); |
| Works in Monitor, not in game? | the game's own settings remap buttons — check its control scheme matches what you assumed |
Reading makes it familiar; building makes it yours. Do these in order, without looking at the solutions until you're stuck for real. Every one uses only what's in this handbook.
| # | DRILL | YOU'LL PRACTICE |
|---|---|---|
| 1 | Swap Triangle and R1. | swap, the workflow |
| 2 | Auto-walk: while the left stick is pushed nearly fully forward, hold L3 for the player. | axes, get_val, set_val |
| 3 | Double-tap Cross runs a 2-press combo. | the stopwatch pattern, combos |
| 4 | Two modes on LT+UP — mode 1 buzzes once, mode 2 buzzes twice. | mode cycling, feedback |
| 5 | D-pad-adjustable sensitivity that survives a restart. | persistent variables |
Done all five? Congratulations — you've used every core mechanism in GPC. Your next project should be your own idea.
| MISTAKE | WHAT YOU SEE | FIX |
|---|---|---|
| wait() inside main | compile error / frozen feel | move timed things into a combo |
| get_val for a toggle | toggle flickers on/off while held | use event_press |
| = instead of == | if is always true, chaos | == compares, = assigns |
| expecting set_val to stick | press lasts one instant | hold state in a variable, set every loop |
| combo fired with get_val | combo restarts endlessly | fire with event_press |
| no wait after last set_val | final press never registers | end every combo with a wait |
| rumble never stops | angry hands | reset_rumble() after the wait |
| sticks treated as 0–100 | up/left never detected | axes run -100 to 100 |
| get_val(b) | 0–100 while held |
| event_press(b) | the press moment |
| event_release(b) | the release moment |
| get_ptime(b) | ms held so far |
| get_rtime() | ms since last loop |
| set_val(b, v) | output v this loop |
| swap(a, b) | exchange buttons |
| block(b, ms) | suppress briefly |
| sensitivity(ax,NOT_USE,s) | scale axis |
| deadzone(x,y,dx,dy) | kill drift |
| combo_run(C) | start |
| combo_stop(C) | kill now |
| combo_running(C) | is it live? |
| combo_restart(C) | back to line 1 |
| wait(ms) | hold outputs (combo only) |
| set_rumble(RUMBLE_A, p) | buzz 0–100 |
| reset_rumble() | stop buzzing |
| set_led(LED_n, s) | controller LED |
| set_val(TRACE_1, x) | watch x live |
| get_pvar(SPVAR_n,min,max,def) | read saved |
| set_pvar(SPVAR_n, v) | save to device |
| TERM | MEANING |
|---|---|
| GPC | the scripting language Cronus devices run |
| compile | turn your text into something the Zen can execute; catches mistakes first |
| flash / program | copy the compiled script into one of the Zen's 8 memory slots |
| slot | one of 8 storage positions on the Zen; you pick which one is active |
| firmware | the Zen's own internal software — keep it updated via Zen Studio |
| loop / pass | one run through main, roughly every 10 ms, ~100 times a second |
| ms (millisecond) | 1/1000 of a second; all GPC timing is in ms — a fast human tap is ~120–200 ms |
| identifier | the fixed name of an input, like PS4_CROSS or XB1_RT |
| analog | an input with a range (trigger 0–100, stick -100 to 100), not just on/off |
| axis | one direction of a stick: LX is left stick left/right, LY is up/down |
| deadzone | the small area near stick center that's ignored so drift doesn't register |
| combo | a timed sequence of outputs that runs step by step, started from main |
| toggle | tap once = on, tap again = off; held in a variable |
| guard | an extra held button required for an action, preventing accidents (LT + UP) |
| macro / QoL | an automated convenience — quality-of-life, e.g. crouch toggle, auto-walk |
| Device Monitor | Zen Studio's live view of inputs and outputs; your main debugging tool |
| TRACE | six debug channels (TRACE_1–6) that show variable values in the Monitor |
| persistent variable | a value saved on the device itself (SPVAR); survives power-off |
Your path: chapter 3's first script → the drills in chapter 14 → your own QOL pack like chapter 12. At that point you're not learning GPC anymore. You're writing it.