Skip to main content

GameState

Struct GameState 

Source
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: u32

Save 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: u64

Lifetime 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: u64

Monotonic 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: u32

Brief 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: u32

HUD 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: u32

Constant-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: f32

Strength 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: bool

Held-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: f64

HUD 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: u32

Brief 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: u32

Brief 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

Source

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.

Source

pub fn fingerer_count(&self, id: &str) -> u32

Source

pub fn fingerer_count_idx(&self, idx: usize) -> u32

Source

pub fn fingerers_owned_total(&self) -> u32

Source

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>.

Source

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.

Source

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.

Source

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).

Source

pub fn has_upgrade(&self, id: &str) -> bool

Source

pub fn has_achievement(&self, id: &str) -> bool

Source

pub fn has_achievement_idx(&self, idx: usize) -> bool

Source

pub fn click(&mut self, origin: (u16, u16), biscuit: Rect)

Source

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.

Source

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.

Source

pub fn click_power(&self) -> f64

Source

pub fn fingerer_mult(&self, idx: usize) -> f64

Source

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.

Source

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).

Source

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.

Source

pub fn fps(&self) -> f64

Source

pub fn border_speed(&self) -> u32

Source

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.

Source

pub fn prestige_mult(&self) -> f64

Source

pub fn prestige_earned_total(&self) -> u64

Source

pub fn prestige_available(&self) -> u64

Source

pub fn prestige_reset(&mut self) -> bool

Source

pub fn tick(&mut self)

Source

pub fn tick_achievements(&mut self)

Source

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.

Source

pub fn trigger_clench(&mut self)

Source

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 \ | / -.

Source

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.

Source

pub fn cost(&self, idx: usize) -> f64

Source

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. Once cuques goes 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.

Source

pub fn can_buy(&self, idx: usize) -> bool

Source

pub fn buy(&mut self, idx: usize) -> bool

Source

pub fn buy_n(&mut self, idx: usize, n: u32) -> u32

Source

pub fn buy_max(&mut self, idx: usize) -> u32

Source

pub fn buy_upgrade(&mut self, idx: usize) -> bool

Trait Implementations§

Source§

impl Clone for GameState

Source§

fn clone(&self) -> GameState

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Default for GameState

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for GameState

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Serialize for GameState

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,