GPC Combos & wait() Timing: Deep Dive

GPC Programming10 min read

How combos execute, what wait() blocks, combo_run vs combo_stop, overlapping combos, and debugging timing bugs in production scripts.

# GPC Combo, wait(), and Timing Deep Dive

Combos are the timed execution engine of GPC — they turn single-poll main logic into multi-step button and stick sequences spanning hundreds of milliseconds. The wait() function controls combo pacing; combo_run and combo_stop control lifecycle. Misunderstanding any of these three causes the majority of custom-script timing bugs: stuck sticks, double triggers, early releases, and online-only inconsistency. This deep dive explains combo internals, wait blocking semantics, interaction with main, debugging techniques, and patterns used in commercial shooter and sports scripts. Prerequisite reading: GPC Script Basics. Context on polling: How Cronus Zen Scripts Work.

Combo anatomy

A combo is a named block of sequential instructions:

combo ExampleSequence {
    set_val(PS4_RY, -80);
    wait(120);
    set_val(PS4_RY, 0);
    wait(40);
    set_val(PS4_CROSS, 100);
    wait(50);
    set_val(PS4_CROSS, 0);
}

Execution properties:

PropertyBehavior
EntryStarts at line 1 on `combo_run` unless resume logic exists
StepsSequential top to bottom within combo
`wait`Blocks advance to next combo line until duration elapses
ExitEnds after last line; combo no longer running
Override`set_val` during combo affects report until next `set_val` or `combo_stop`

Combos are not threads in the operating system sense — they are bytecode coroutines advanced by the Zen interpreter between input polls.

wait() semantics

wait(argument) accepts milliseconds as integer or variable:

int green_delay = 240;
wait(green_delay);
wait(80);

What wait blocks

BlockedNot blocked
Current combo's next lines`main` loop execution
Same combo re-entry from duplicate `combo_run` if guardedOther combos if already running
Physical input reading via `get_val`

Because main continues each poll, other mods may execute simultaneous set_val on the same axis while a combo waits between its own steps. Commercial scripts serialize phases to prevent fights; custom editors must preserve order.

wait precision

FactorEffect
Integer ms resolutionSub-millisecond values round
Poll alignmentActual wait ≈ requested ms ± one poll jitter
Multiple waits in seriesSum of durations = total combo length
Zero wait`wait(0)` advances one combo step quickly — rare, used for spacing

Sports green windows and shot releases often hinge on 10–30 ms wait deltas. FPS recoil patterns use 40–200 ms segments per weapon.

Common wait mistakes

MistakeSymptomFix
`wait` in `main` (invalid)Compile errorMove wait inside combo only
wait too short onlineInconsistent releaseIncrease 15–25 ms for high ping
wait too longSluggish modDecrease 10 ms steps
No wait between set_valInstant toggleAdd wait for human-visible duration

combo_run lifecycle

combo_run(ComboName) instructs the interpreter to execute the named combo.

main {
    if (event_press(PS4_R2)) {
        combo_run(FirePattern);
    }
}

First run

Combo starts at first line. If already running, behavior depends on guards:

if (!combo_running(FirePattern)) {
    combo_run(FirePattern);
}

Without guard, repeated combo_run on every press frame may restart combo from line 1, stuttering output.

Re-trigger on hold

event_press fires once per physical press. Holding R2 does not retrigger. For periodic retrigger while held:

if (get_val(PS4_R2)) {
    if (!combo_running(BurstFire)) {
        combo_run(BurstFire);
    }
}

Design burst spacing with wait inside BurstFire combo.

combo_stop lifecycle

combo_stop(ComboName) halts combo immediately:

main {
    if (event_release(PS4_R2)) {
        combo_stop(FirePattern);
    }
}

Why stop is mandatory

Without combo_stop on release:

  • Last set_val in combo may persist indefinitely
  • Stick reports non-zero values at rest → user reports "drift"
  • Subsequent combos conflict on same axis
PatternRelease handler
Hold to aim mod`combo_stop` on ADS release
Tap fire burstStop optional if combo self-terminates
Toggle fire modeStop on toggle off event

Drift troubleshooting: Troubleshooting Cronus Zen Scripts Tier 4.

combo_running() guards

combo_running(Name) returns TRUE while combo active.

Use cases:

// Prevent stack overflow style restarts
if (event_press(PS4_R2) && !combo_running(Recoil)) {
    combo_run(Recoil);
}

// Mutual exclusion between two combos on same axis
if (combo_running(RecoilA)) {
    combo_stop(RecoilB);
}

Mutual exclusion table:

Combo ACombo BSame axis?Strategy
AntiRecoilSnapAimRY / RXStop one when starting other
AutoGreenDribbleMoveRYNever run concurrently
RapidFireSingleShotR2Toggle mode switch

Concurrent combo execution

Multiple combos may run if they touch different buttons/axes or provider designed intentional overlap.

combo VerticalPull {
    set_val(PS4_RY, pull_strength);
    wait(fire_ms);
    set_val(PS4_RY, 0);
}

combo HorizontalPull {
    set_val(PS4_RX, horiz_strength);
    wait(fire_ms);
    set_val(PS4_RX, 0);
}

Both can combo_run on same fire press if strengths tuned not to overshoot. Same-axis concurrent set_val without coordination — last writer wins per poll rules in main combo interleave.

Interaction between main and active combos

Typical poll ordering conceptually:

  1. Read physical input
  2. Execute main top to bottom
  3. Advance active combos by elapsed time; execute combo steps due this poll
  4. Merge outputs into final HID report

If main sets PS4_RY to 0 while combo sets PS4_RY to -80 same poll, implementation-defined winner applies — assume combo override wins during active combo step for safety in design, but do not rely on conflict.

Best practice: when combo active for axis, skip conflicting main set_val on that axis:

if (!combo_running(AntiRecoil)) {
    set_val(PS4_RY, manual_ry);
}

Timing patterns by genre

FPS anti-recoil combo

combo ARRecoil {
    set_val(PS4_RY, ar_vertical);
    wait(ar_duration);
    set_val(PS4_RY, ar_vertical / 2);
    wait(ar_duration / 2);
    set_val(PS4_RY, 0);
}

Duration variables map to fire rate and magazine length tuning in Cronus Zen Aim Assist and Anti-Recoil Settings.

Sports shot timing combo

combo ReleaseWindow {
    set_val(PS4_RY, -100);
    wait(release_delay);
    set_val(PS4_RY, 0);
    wait(40);
    set_val(PS4_R2, 0);
}

release_delay is the primary online tuning variable — adjust ±15 ms per ping band.

Rapid fire combo

combo RapidBurst {
    set_val(PS4_R2, 100);
    wait(press_ms);
    set_val(PS4_R2, 0);
    wait(gap_ms);
}

Loop via re-combo_run on timer or hold guard. gap_ms below platform debounce causes ignored inputs.

Debugging combo timing

Compile-time

ErrorCause
Combo not definedTypo in `combo_run` name
wait outside comboSyntax placement error
Combo memoryToo many long combos — trim unused

Runtime offline

  1. Enable one combo mod only.
  2. Log mentally: press → wait duration → release → stop fired?
  3. Watch stick return to center within 50 ms of release.
  4. Adjust one wait variable ±10 ms per test.

Runtime online

Increase primary wait 15–25 ms over offline optimal when ping exceeds 60 ms. Packet loss requires acceptance of variance — not every combo bug.

Advanced: chained combo_run from combo end

Some patterns trigger secondary combo at end of first:

combo PhaseA {
    set_val(PS4_RY, -50);
    wait(100);
    combo_run(PhaseB);
}

combo PhaseB {
    set_val(PS4_RY, -25);
    wait(80);
    set_val(PS4_RY, 0);
}

Chaining risks forgotten combo_stop — stop parent chain name if provider documents chain stop API, or stop each known child on release.

Memory and combo count limits

Each combo consumes bytecode space. Unused combos from deprecated game modes should be removed before approaching slot limits. Compile output reports size — see install guide How to Install a Cronus Zen Script.

Slot and versioning note

Combo timing changes are why providers version scripts per game patch. Backup before tuning extremes — Cronus Zen Slots, Profiles, and Backup.

Frequently asked questions

Can wait pause the entire script?

No. wait pauses only the current combo's step advancement. main continues every poll.

Why does my combo restart mid-sequence?

Duplicate combo_run without combo_running guard on hold or repeated event_press from bounce. Add guard or debounce with short wait combo.

combo_stop vs setting values to zero?

combo_stop halts combo and clears combo state. Manual set_val(0) without stop may leave combo logically running, blocking guards.

How many waits can one combo have?

Dozens technically; practical limits are readability and total combo duration. Long combos exceed human interaction windows.

Does wait account for ping?

No automatically. You tune ms values for your network environment manually.

Can two combo_run calls same poll start two combos?

Yes if names differ and axes independent. Same combo twice — restart behavior without guard.

Is wait affected by frame rate?

Console frame rate does not lock combo timing. Combos use ms timers on Zen, not game frames.

Why compile error "wait not allowed here"?

wait only valid inside combo blocks, not in main or init.

Worked example: building a hold-to-fire recoil combo

The following pattern demonstrates correct pairing of event_press, event_release, combo_run, combo_stop, and variable-driven wait values. Adapt names to your script.

int recoil_ms = 140;
int recoil_pull = -65;

combo SustainRecoil {
    set_val(PS4_RY, recoil_pull);
    wait(recoil_ms);
    set_val(PS4_RY, recoil_pull / 2);
    wait(recoil_ms / 2);
    set_val(PS4_RY, 0);
}

main {
    if (event_press(PS4_R2)) {
        if (!combo_running(SustainRecoil)) {
            combo_run(SustainRecoil);
        }
    }
    if (get_val(PS4_R2) && combo_running(SustainRecoil)) {
        // Re-trigger pattern while holding for long magazines
        if (!combo_running(SustainRecoil)) {
            combo_run(SustainRecoil);
        }
    }
    if (event_release(PS4_R2)) {
        combo_stop(SustainRecoil);
        set_val(PS4_RY, 0);
    }
}

Walk through execution mentally: press fires combo, waits sum to 210 ms per cycle, release stops and zeroes stick. Tweak recoil_ms in 15 ms steps online per Cronus Zen Aim Assist and Anti-Recoil Settings.

Timing measurement without external tools

You do not need oscilloscopes to validate combo length. Use in-game counting:

  1. Record clip of mod firing at 60 fps if platform allows.
  2. Count frames from press to stick center on release.
  3. Multiply frames by 16.67 ms for approximate duration.
  4. Compare to sum of wait calls plus estimated poll jitter.

Discrepancy greater than 40 ms suggests missing combo_stop or competing main set_val on same axis.

Relationship to installation and slots

Combo logic lives in bytecode flashed to slots. After editing wait values, recompile and write per How to Install a Cronus Zen Script. Backup slot before experimental timing extremes per Cronus Zen Slots, Profiles, and Backup.

Worked example: building a hold-to-fire recoil combo from scratch

The following end-to-end example ties together event_press, combo_run, wait, and combo_stop for a hold-activated anti-recoil pattern. Read it alongside GPC Script Basics variable declarations.

int recoil_strength = 30;
int recoil_segment_ms = 90;
int master_toggle = TRUE;

init {
    recoil_strength = 30;
    recoil_segment_ms = 90;
}

main {
    if (!master_toggle) return;

    if (get_val(PS4_L2) && get_val(PS4_R2)) {
        if (!combo_running(HoldRecoil)) {
            combo_run(HoldRecoil);
        }
    } else {
        combo_stop(HoldRecoil);
        set_val(PS4_RY, 0);
    }
}

combo HoldRecoil {
    set_val(PS4_RY, recoil_strength);
    wait(recoil_segment_ms);
    set_val(PS4_RY, recoil_strength / 2);
    wait(recoil_segment_ms);
    set_val(PS4_RY, 0);
    wait(10);
}

Walkthrough by interaction phase:

  1. Player aims (L2) and fires (R2). main detects both held and starts HoldRecoil once via combo_running guard.
  2. Combo applies full recoil_strength for recoil_segment_ms milliseconds using first wait.
  3. Combo reduces pull to half strength for second segment — mimicking diminishing kick on some weapons.
  4. Combo zeros stick and waits 10 ms spacer before ending.
  5. While fire held, main re-invokes combo_run only when combo not running — creating repeating pattern per completion cycle depending on interpreter resume behavior; provider scripts often use looping combos instead.

On release of either L2 or R2, combo_stop fires and explicit set_val(PS4_RY, 0) ensures no residual pull.

Tuning sequence for this example:

StepVariableAction
1`recoil_strength`Adjust 25→30→35% watching wall grouping
2`recoil_segment_ms`Adjust 80→90→100 ms for online ping
3Release handlerConfirm stop fires — no drift 2 sec after release

Timing measurement without external tools

You do not need oscilloscopes to validate combo timing. Practical measurement techniques:

TechniqueWhat it validates
Wall bullet count per secondRapid fire `wait(gap_ms)` calibration
Stopwatch + single tap actionLong single-sequence combos
Slow-motion phone capture of menu OLEDMenu debounce `wait` values
Compare offline vs online same inputNetwork-sensitive sports releases

Document baseline before changes: "90 ms segment — tight offline, early online at 65 ms ping → try 105 ms."

Provider vs custom combo patterns

Commercial scripts hide complexity in included .gph libraries. When reverse-engineering behavior:

  1. Search source for combo_run calls in main — maps features to combo names.
  2. Open each combo; sum wait durations for total sequence length.
  3. Note which combos share axes — mutual exclusion required?
  4. Check get_slot() branches — timing may differ per slot profile.

Do not paste provider combos into public repos if license prohibits. Use learnings to inform your own test slot tuning per Cronus Zen Slots, Profiles, and Backup.

Related reading