pub struct GameState {Show 50 fields
pub version: u32,
pub cuques: f64,
pub total_clicks: u64,
pub lifetime_cuques: f64,
pub best_fps: f64,
pub golden_caught: u64,
pub lucky_caught: u64,
pub frenzy_caught: u64,
pub buff_caught: u64,
pub green_coin_caught: u64,
pub fingerers_state: HashMap<String, FingererState>,
pub achievements_earned: HashSet<String>,
pub upgrades_earned: HashSet<String>,
pub prestige: u64,
pub total_play_ticks: u64,
pub buffs: Vec<Buff>,
pub clench_ticks: u32,
pub particles: Vec<Particle>,
pub misclick_particles: Vec<MisclickParticle>,
pub powerups: Vec<Powerup>,
pub next_spawn_id: u64,
pub powerup_cooldowns: [u32; 4],
pub session_ticks: u64,
pub newly_unlocked: Vec<String>,
pub active_unlock_id: Option<String>,
pub active_unlock_ticks: u32,
pub visual_debt: f64,
pub lucky_flash_ticks: u32,
pub achievement_flash_ticks: u32,
pub green_coin_flash_ticks: u32,
pub border_phase: u32,
pub steady_phase: u32,
pub purchase_flash_ticks: u32,
pub purchase_flash_strength: f32,
pub fingerer_flash_ticks: Vec<u32>,
pub upgrade_flash_ticks: Vec<u32>,
pub fingerer_unaffordable_flash: Vec<u32>,
pub upgrade_unaffordable_flash: Vec<u32>,
pub fingerer_unlock_flash: Vec<u32>,
pub upgrade_unlock_flash: Vec<u32>,
pub fingerer_green_coin_flash: Vec<u32>,
pub prev_fingerer_affordable: Vec<bool>,
pub prev_upgrade_affordable: Vec<bool>,
pub space_pressed_this_tick: bool,
pub ticks_since_last_press: u32,
pub space_hold_ticks: u32,
pub displayed_cuques: f64,
pub displayed_fps: f64,
pub cuques_flash_ticks: u32,
pub cuques_spend_flash_ticks: u32,
}Expand description
Persistent game state. Catalog-addressed state (fingerers_state,
upgrades_earned, achievements_earned) is keyed by STABLE STRING IDS,
not positional indices, so reordering / inserting / removing entries in
FINGERERS, UPGRADES, or ACHIEVEMENTS never corrupts an old save.
Unknown ids in a save are ignored (forward-compat); missing ids default
to zero / absent (backward-compat).
Fields§
§version: u32Save schema version. The on-disk migration chain (crate::save)
reads this via peek_version before deserializing into the right
GameStateVN struct. A live in-memory state always equals
crate::save::CURRENT_VERSION — the chain stamps it on conversion
and Default initializes it that way. Pre-versioned saves on disk
have no version key, which peek_version treats as V1.
cuques: f64§total_clicks: u64§lifetime_cuques: f64§best_fps: f64§golden_caught: u64Lifetime grand total of every powerup caught (Lucky, Frenzy, Buff, Green Coin). Stays a strict rollup so existing achievements that gate on it continue to work, and pre-V3 saves whose breakdown was never recorded keep an honest total. The four per-variant counters below were added in V3; they only count post-V3 catches.
lucky_caught: u64§frenzy_caught: u64§buff_caught: u64§green_coin_caught: u64§fingerers_state: HashMap<String, FingererState>Fingerer id → owned count + attached modifiers + aggregate cache.
achievements_earned: HashSet<String>Set of earned achievement ids.
upgrades_earned: HashSet<String>Set of earned upgrade ids.
prestige: u64§total_play_ticks: u64§buffs: Vec<Buff>§clench_ticks: u32§particles: Vec<Particle>§misclick_particles: Vec<MisclickParticle>Screen-anchored “misclick” tap particles — independent buffer because they don’t follow the biscuit (they’re feedback for clicks that MISSED the biscuit, including the dead zone at low zoom).
powerups: Vec<Powerup>All currently on-screen powerups, regardless of kind. Multiple of
any kind can coexist — the spawn side has no per-kind slot cap.
Each entry carries a stable spawn_id; click hit-test and the g
hotkey reference instances by id, never by Vec index. Not
persisted: closing and reopening the game shouldn’t preserve frozen
powerup frames.
next_spawn_id: u64Monotonic counter that mints Powerup::spawn_id. Session-scoped
(re-seeded to 0 on every load) — ids only need to be stable within
a single live state, not across restarts.
powerup_cooldowns: [u32; 4]Per-kind inter-arrival cooldown clocks, indexed by kind as usize.
Each ticks down independently and rolls fresh from
powerup::next_cooldown(kind) on every spawn (including
force-spawns from the dev cheats). Doesn’t freeze when the kind
already has on-screen instances — pile-ups self-resolve via the
short lifetime, so the only cost is “another marker on screen for
11s”.
session_ticks: u64§newly_unlocked: Vec<String>Queue of achievement ids that unlocked but haven’t yet been shown as a
toast. Drained one-at-a-time by tick() into active_unlock_id.
active_unlock_id: Option<String>Currently-on-screen achievement toast (id) and its remaining life in
ticks. None means no toast right now; tick() pops the next pending
id off newly_unlocked when this clears.
active_unlock_ticks: u32§visual_debt: f64§lucky_flash_ticks: u32§achievement_flash_ticks: u32§green_coin_flash_ticks: u32Brief green border channel pulse fired on a Green Coin catch.
Behaves like lucky_flash_ticks (plateau-fade); coexists with
other channels so a Green Coin caught during a Frenzy or Lucky
adds a green moiré rather than overwriting them.
border_phase: u32HUD title border phase clock. Advances by border_speed() each
tick, so the title border visibly speeds up under Frenzy / Lucky /
purchase events. INTENTIONALLY NOT shared with secondary shimmers
(panel borders, sidebar / upgrade rows) — they need a constant-rate
clock so a global speed-up on the HUD doesn’t drag them along.
steady_phase: u32Constant-rate phase clock for secondary shimmers — sidebar row,
upgrade row, and panel-border flashes. Advances by exactly 1 per
tick regardless of game state, so e.g. an Achievement / Frenzy
event accelerating border_phase doesn’t accelerate the
“can’t-buy” shimmer that happens to be running on a fingerer
row at the same time.
purchase_flash_ticks: u32§purchase_flash_strength: f32Strength multiplier (1.0..=3.0) for the most recent purchase flash, scaled by bulk-buy quantity. The border + panel borders read this so a max-buy lands harder than a single click.
fingerer_flash_ticks: Vec<u32>One slot per visible sidebar row; indexed by catalog position because it’s purely a render-time flash and doesn’t need to survive reorders.
upgrade_flash_ticks: Vec<u32>Mirror of fingerer_flash_ticks for the Upgrades panel. Sized to
UPGRADES.len() lazily by migrate().
fingerer_unaffordable_flash: Vec<u32>Negative-feedback flash: red row pulse when a click hit a row but
cuques < cost. One slot per fingerer / upgrade index.
upgrade_unaffordable_flash: Vec<u32>§fingerer_unlock_flash: Vec<u32>“Just became affordable” flash: a brief one-shot green shimmer
fired the tick a row’s affordability flips false → true. Distinct
from *_flash_ticks (purchase) — shorter duration, no panel
border bleed — so the player can tell “now buyable” apart from
“you just bought.”
upgrade_unlock_flash: Vec<u32>§fingerer_green_coin_flash: Vec<u32>Brief gold shimmer on a fingerer row when a Green Coin catch
targeted it. Closes the visual loop with the floating
+10% {fingerer} particle and the green-tinted title-border
pulse — the gold here matches the catch particle, so the player
can see at a glance which row in the sidebar just took the boost.
prev_fingerer_affordable: Vec<bool>Previous-tick affordability per row, used to detect the
false→true edge that triggers *_unlock_flash. Sized to catalog
length by migrate() and seeded at init from the live state, so a
freshly-loaded save with rows already affordable doesn’t fire a
fake unlock flash on tick 1.
prev_upgrade_affordable: Vec<bool>§space_pressed_this_tick: boolHeld-spacebar tracking.
space_pressed_this_tick is set whenever Action::ClickCenter
arrives (terminal key-repeat fires Press events at ~30Hz, easily
hitting every 50ms tick when a key is genuinely held).
ticks_since_last_press is a small countdown that allows up to 3
missed ticks (~150ms) before declaring the key released — handles
real keyboard-repeat jitter so a 1-tick gap doesn’t kill the
streak. space_hold_ticks is the consecutive “active” tick streak;
space_held() is true once it crosses 1 second.
Net result: spamming spacebar at human speed (≥150ms between presses) never triggers held; actually holding the key climbs the streak past 20 ticks within ~1s.
ticks_since_last_press: u32§space_hold_ticks: u32§displayed_cuques: f64HUD count-up tween: rendered numbers smoothly chase the real ones. Initialized to the live values on load so the first frame doesn’t look like a count-up from zero.
displayed_fps: f64§cuques_flash_ticks: u32Brief green flash on the HUD digits when cuques jump UP — golden catch, frenzy click, F4 dev cheat, etc. (“money coming in”)
cuques_spend_flash_ticks: u32Brief red flash on the HUD digits when cuques drop — successful
purchase, prestige reset (the big -all event). Mirrors
cuques_flash_ticks and competes with it: whichever channel is
stronger this frame drives the HUD color sweep, so a buy that
happens during a still-decaying gain pulse correctly flips the
digits red instead of staying green.
Implementations§
Source§impl GameState
impl GameState
Sourcepub fn migrate_runtime(self) -> Self
pub fn migrate_runtime(self) -> Self
Initialize ephemeral runtime state that #[serde(skip)] left empty
after deserialization, and normalize any fields that need live values
rather than the serde default.
Runtime-only. Persisted-shape migrations live in
crate::save::versions::vN.rs (see CLAUDE.md “Save versioning”).
This method runs after the migration chain has produced a live
GameState; it must not assume any particular pre-state and must
be safe to call multiple times.
pub fn fingerer_count(&self, id: &str) -> u32
pub fn fingerer_count_idx(&self, idx: usize) -> u32
pub fn fingerers_owned_total(&self) -> u32
Sourcepub fn fingerer_aggregate(&self, id: &str) -> FingererAggregate
pub fn fingerer_aggregate(&self, id: &str) -> FingererAggregate
Return the cached modifier aggregate for id, or the identity
(Default) if the fingerer has no entry. Hot-path read for fps()
and the sidebar — never iterates the underlying Vec<Modifier>.
Sourcepub fn attach_modifier(&mut self, fingerer_id: &str, m: Modifier)
pub fn attach_modifier(&mut self, fingerer_id: &str, m: Modifier)
Attach a modifier to the given fingerer id. Creates the
FingererState entry on the fly if absent (count stays 0). Rebuilds
the aggregate cache. Use this from goldens, debug cheats, future
events.
Sourcepub fn attach_modifier_random_owned(&mut self, m: Modifier) -> Option<String>
pub fn attach_modifier_random_owned(&mut self, m: Modifier) -> Option<String>
Pick a random fingerer with count > 0 and attach m to it. Returns
the chosen id, or None if no fingerer is owned. Used by the Buff
Golden (Purple Coin), where targeting an un-owned tier is pointless
— a temporary x7 multiplier on a count of zero produces zero output.
Sourcepub fn attach_modifier_random_visible(&mut self, m: Modifier) -> Option<String>
pub fn attach_modifier_random_visible(&mut self, m: Modifier) -> Option<String>
Pick a random fingerer that is currently visible in the sidebar
— by the same fingerer::visible rule the UI uses (idx == 0 ||
owned > 0 || lifetime_cuques >= base_cost * 0.5) — and attach
m to it.
Used by the Green Coin: a permanent +10% boost is still useful on
a tier the player can see but hasn’t bought yet; when they finally
buy it the boost is already in place. Index Finger is always visible
(idx == 0), so as long as FINGERERS is non-empty this picks
something. Returns None only on an empty catalog (never in
practice).
pub fn has_upgrade(&self, id: &str) -> bool
pub fn has_achievement(&self, id: &str) -> bool
pub fn has_achievement_idx(&self, idx: usize) -> bool
pub fn click(&mut self, origin: (u16, u16), biscuit: Rect)
Sourcepub fn spawn_misclick(&mut self, col: u16, row: u16)
pub fn spawn_misclick(&mut self, col: u16, row: u16)
Spawn a screen-anchored “·” particle at a click point that hit nothing (biscuit dead zone, blank panel area, etc). Acknowledges that the click registered without altering any game state.
Sourcepub fn spawn_confetti(&mut self, n: u32)
pub fn spawn_confetti(&mut self, n: u32)
Spawn n confetti particles scattered over the biscuit. Used for
bulk-buy juice — a max-buy of a fingerer pops a small burst.
pub fn click_power(&self) -> f64
pub fn fingerer_mult(&self, idx: usize) -> f64
Sourcepub fn dev_add_cuques(&mut self, amount: f64)
pub fn dev_add_cuques(&mut self, amount: f64)
Dev-build cheat. Bypasses normal flow; not reachable in release builds
because the F-key that triggers it is gated behind App::debug.
Sourcepub fn mint_spawn_id(&mut self) -> u64
pub fn mint_spawn_id(&mut self) -> u64
Mint a fresh, monotonic spawn id. Session-scoped — wraps around at
u64::MAX (would take ~5 trillion years at one spawn per nanosecond,
so wrap collision isn’t a real concern).
Sourcepub fn catch_powerup(&mut self, spawn_id: u64) -> f64
pub fn catch_powerup(&mut self, spawn_id: u64) -> f64
Catch the powerup with the given spawn_id, if it’s still on
screen. Applies the kind-specific effect, increments
golden_caught (lifetime grand total — keeps the existing
achievements working) plus the per-kind counter, and returns the
flat reward (only Lucky is non-zero).
The Vec is unbounded; multiple powerups of the same kind can
coexist. Catching one never disturbs the others — swap_remove
is fine because input routes by spawn_id, not by Vec index.
Per-kind cooldown is NOT touched here — it ticks independently from spawns; a catch just removes the on-screen instance.
pub fn fps(&self) -> f64
pub fn border_speed(&self) -> u32
Sourcepub fn trigger_purchase_flash(&mut self, strength: f32)
pub fn trigger_purchase_flash(&mut self, strength: f32)
Trigger the green purchase flash on the global border + the panel
border. strength scales how loud the flash is (1.0 = single buy,
up to 3.0 = bulk max-buy) so a max-buy lands harder than a +1.
pub fn prestige_mult(&self) -> f64
pub fn prestige_earned_total(&self) -> u64
pub fn prestige_available(&self) -> u64
pub fn prestige_reset(&mut self) -> bool
pub fn tick(&mut self)
pub fn tick_achievements(&mut self)
Sourcepub fn tick_powerups(&mut self)
pub fn tick_powerups(&mut self)
Tick every on-screen powerup down by one frame and decrement every
per-kind cooldown. Expired entries (those that just hit 0) are
dropped in place via retain_mut. Cooldowns tick independently
of on-screen instances — multiple of the same kind can coexist, so
freezing the clock while occupied would block the parallelism this
refactor exists to enable.
pub fn trigger_clench(&mut self)
Sourcepub fn space_held(&self) -> bool
pub fn space_held(&self) -> bool
True when the spacebar has been held continuously for ≥ 1 second.
Driven by space_hold_ticks (a streak counter that increments on
every tick where at least one ClickCenter arrived, resets the
instant a tick passes without one). Switches the biscuit’s clench
animation from a burning * to the spin frames \ | / -.
Sourcepub fn spawn_auto_particle(&mut self, frac_x: f32, frac_y: f32)
pub fn spawn_auto_particle(&mut self, frac_x: f32, frac_y: f32)
Spawn a “+N” particle representing cuques earned since the last
auto-particle. Silently skips if there isn’t a whole cuque of accrued
income to show — at low FPS the caller is a rate-based timer that
fires faster than cuques arrive, and spawning a “+1” in that window
used to lie (particle flying up while the HUD counter didn’t move).
The shown amount is always real cuques that just accrued into
visual_debt.
pub fn cost(&self, idx: usize) -> f64
Sourcepub fn affordable_cuques(&self) -> f64
pub fn affordable_cuques(&self) -> f64
Cuques the player can ACTUALLY spend right now: the lesser of real
cuques and the displayed counter. Both bounds matter:
- Gating ONLY on
cuques(real) lets the row turn green and a click succeed before the counter visibly catches up — the “I have 8 but the row says I can buy a 17” lie. - Gating ONLY on
displayed_cuques.floor()lets a click DRAIN real cuques NEGATIVE during a spend’s tween-down: real already dropped, displayed hasn’t caught down yet, gate sees the high displayed value and lets the buy through against the depleted real. Oncecuquesgoes negative, the HUD floor() shows “0” for a long time while the slow income climbs back.
Taking min(real, displayed.floor()) makes both conditions
equally binding: row turns green only when the visible counter
AND the underlying balance both reach the cost; click succeeds
only when both still hold. No overspend, no visual lie.
pub fn can_buy(&self, idx: usize) -> bool
pub fn buy(&mut self, idx: usize) -> bool
pub fn buy_n(&mut self, idx: usize, n: u32) -> u32
pub fn buy_max(&mut self, idx: usize) -> u32
pub fn buy_upgrade(&mut self, idx: usize) -> bool
Trait Implementations§
Source§impl<'de> Deserialize<'de> for GameState
impl<'de> Deserialize<'de> for GameState
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Auto Trait Implementations§
impl Freeze for GameState
impl RefUnwindSafe for GameState
impl Send for GameState
impl Sync for GameState
impl Unpin for GameState
impl UnsafeUnpin for GameState
impl UnwindSafe for GameState
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more