GPC persistent variables: saving settings with SPVAR, get_pvar and set_pvar
How to make a Cronus Zen script remember settings between sessions: the 64 per-slot SPVAR stores, validated loading with get_pvar's range and default, event-driven saving with set_pvar, and the mistakes that wear out flash or corrupt values.
- Updated

Quick answer
GPC gives each memory slot 64 private persistent stores, SPVAR_1 to SPVAR_64, that survive power cycles. Load them in init with get_pvar(slot, min, max, default), which returns the default if the stored value is out of range, and write them with set_pvar only when the user deliberately saves a setting. Never call set_pvar every cycle.
Why do scripts forget things?
Every variable you declare with int lives in RAM. Unplug the Zen and it is gone. For a toggle that resets each session that is fine, but a sensitivity value the player spent ten minutes tuning should still be there tomorrow. Persistent variables solve this.
What is an SPVAR?
SPVAR_1 through SPVAR_64 are 64 persistent stores that belong to the current memory slot. Each holds a signed 32-bit integer and survives power loss. Because they are per slot, two scripts in different slots can both use SPVAR_1 without interfering. You access them through two built-ins:
get_pvar(SPVAR_n, min, max, default)reads a store.set_pvar(SPVAR_n, value)writes one.
There is no direct variable syntax; you always go through the functions, then keep a working copy in an ordinary int.
How do I load settings safely?
Load in init, which runs once when the slot starts, and always with a real range:
int strength;
int mode;
init {
strength = get_pvar(SPVAR_1, 0, 100, 25);
mode = get_pvar(SPVAR_2, 0, 3, 0);
}get_pvar returns the stored value if it lies between min and max, otherwise the default. This gives you three things for free:
- First-run defaults. A fresh slot holds garbage; the range check rejects it and you get
25and0. - Protection against a changed layout. If a previous version of your script stored something else in
SPVAR_1, an out-of-range value falls back to the default instead of crashing behaviour. - Input validation. A setting can never load as a value your code does not expect.
The official guide's phrasing is worth quoting: "Pick a range that actually validates the field." 0, 32767 validates nothing.
How do I save without wearing out the flash?
The official example separates "changed" from "saved":
int strength = 25;
int settings_dirty;
main {
if(event_press(PS5_UP)) {
strength = strength + 1;
if(strength > 100) strength = 100;
settings_dirty = TRUE;
}
if(settings_dirty && event_press(PS5_OPTIONS)) {
set_pvar(SPVAR_1, strength);
settings_dirty = FALSE;
}
}Pressing Up adjusts the working copy and marks it dirty. Only a deliberate press of Options writes to flash, and only if something changed. The guide states the rule directly: do not put set_pvar at base level in main. A write every cycle is hundreds of writes a second into memory with a finite life.
The pattern also gives you an undo for free: if the player changes a value and unplugs without saving, the old value loads next time.
How should I structure a settings menu?
A common layout for a script with several adjustable values:
- Enter edit mode with a chord, for example holding L2 and pressing Options. Set
editing = TRUEand show a menu line on the OLED. - Select a field with Left and Right, changing an
int fieldindex. - Adjust with Up and Down, clamping to each field's range and marking
dirty. - Save and exit with Options: write only the fields that are dirty, then
editing = FALSE. - Cancel with Circle: reload from
get_pvarto discard changes.
The official architecture guide has a page whose title is a design principle in itself: "edit, view and save are different operations." Keep them separate and the script stays predictable.
Where do things go wrong?
- Saving every cycle. Covered above. If a script's OLED flickers or the Zen feels sluggish while a value is held, look for a
set_pvarin the hot path. - No range validation. A first run loads a huge number and the script behaves wildly until the user manually fixes every field.
- Changing what a store means between script versions without changing the range or adding a version field. Recipe 8 in the official examples shows a versioned, packed SPVAR that detects an old layout and resets.
- Assuming sharing across slots. It does not happen. For shared data the guide documents BVAR storage with indexes
0..1023, a different tool with different ownership rules. - Testing only the happy path. The official persistence test matrix is a checklist: fresh slot, saved value, out-of-range value, power cycle, reprogram. Run through it once.
How do I debug a value that won't stick?
- Confirm the save code is actually reached. Put
set_val(TRACE_1, 1)next to theset_pvarand watch Device Monitor. - Confirm the load range includes the value you saved. A saved
100with a load range of0, 99silently becomes the default. - Confirm you are looking at the same slot. Reprogramming a slot keeps its SPVARs; moving the script to another slot does not.
- Read the official persistence diagnosis page, which walks through the same steps in more depth.
What comes next?
With input, timing and persistence covered, you have the three parts of any real script. The remaining piece is feedback: telling the player what state the script is in. Continue with OLED and LED feedback in GPC, or revisit the beginner path at GPC scripting 101.
Frequently asked questions
Are SPVARs shared between slots?
What does get_pvar return the first time, before anything is saved?
How often can I call set_pvar?
Can I store more than one setting in a single SPVAR?
Sources
Every link was live when this guide was last updated.
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.