pub struct GameState {Show 51 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 goldens_since_green_coin: u32,
pub clench_ticks: u32,
pub particles: Vec<Particle>,
pub misclick_particles: Vec<MisclickParticle>,
pub goldens: [Option<GoldenCuque>; 3],
pub golden_cooldowns: [u32; 3],
pub green_coin: Option<GreenCoin>,
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>§goldens_since_green_coin: u32Green Coin pity counter. Increments on every regular Golden spawn,
drives a rng < counter * 0.01 roll for an alongside Green Coin
spawn, and resets the moment a Green Coin appears. Persisted so the
pity timer survives quit/restart.
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).
goldens: [Option<GoldenCuque>; 3]Per-variant Golden Cuque slots — goldens[GoldenVariant::Lucky as usize]
holds the on-screen Lucky, etc. Each variant has its own independent
slot AND its own independent cooldown so spawn timing is fully
desynchronized — Lucky’s clock doesn’t gate Frenzy’s, and a Buff
expiring doesn’t reset Lucky’s cooldown.
golden_cooldowns: [u32; 3]Per-variant spawn cooldowns, indexed the same as goldens. Each
freezes while its own slot is occupied (no point counting down
when there’s nowhere to spawn) and rolls a fresh value on
catch/expiry. Initial values come from next_cooldown() in
Default, which already randomizes them so no two variants
arrive at zero simultaneously on a fresh save.
green_coin: Option<GreenCoin>On-screen Green Coin, if one is currently visible. Lifetime ticked
down by tick_green_coin; cleared on catch or expiry. Not persisted
(parallel to golden) — closing and reopening the game shouldn’t
preserve a frozen coin frame.
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 catch_golden(&mut self, variant: GoldenVariant) -> f64
pub fn catch_golden(&mut self, variant: GoldenVariant) -> f64
Catch the Golden Cuque of the given variant if one is currently
on screen. Applies the variant-specific effect, increments
golden_caught + the per-variant counter, re-rolls the shared
spawn cooldown, and returns the flat reward (0.0 for non-Lucky).
Each variant lives in its own slot of state.goldens, so catching
e.g. a Lucky never disturbs an active Frenzy or Buff. Same applies
to expiry / cheat spawns.
The Buff variant attaches a MulFactor(7.0) modifier with a
60-second Ticks duration on a random owned fingerer, sourced as
PurpleCoin. Pre-#21 this was a global Buff::FingererBoost; the
modifier system replaces it.
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)
pub fn tick_golden(&mut self)
Sourcepub fn tick_green_coin(&mut self)
pub fn tick_green_coin(&mut self)
Lifetime tick for the Green Coin (mirror of tick_golden’s coin
half — Green Coin has no cooldown of its own; spawning is gated on
regular Golden spawns instead). Decrements life_ticks each call,
clears the slot when it hits 0.
Sourcepub fn catch_green_coin(&mut self) -> bool
pub fn catch_green_coin(&mut self) -> bool
Catch the on-screen Green Coin if any. Picks a random owned fingerer
(count > 0) and attaches a permanent +10% AddPercent modifier
sourced as GreenCoin. Returns true if a coin was consumed (catch
or no-op miss because nothing was owned), false if there was no
coin to catch in the first place.
Edge case: if the player owns nothing yet, the coin is consumed but no modifier attaches — same defensive behavior as the old Buff golden when the catalog was empty. The +10% had nothing to land on.
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