V5 save + Mag log-magnitude: unbounded play, orphan fix, rebalance#28
Merged
Conversation
Replaces the f64-typed gameplay counters with a `Mag` log-magnitude type
so end-game stacks no longer overflow to `Infinity`. The runaway-mult bug
class that broke a long-haul save in triage (cuques=0 from a saved Inf,
311-trillion-x PurpleCoin modifiers, 58x amplified GreenCoin AddPercents)
is structurally eliminated.
Mag bignum (src/bignum.rs):
- `Mag { log10: f64 }`. Mul/div fold to log addition (sub-ns ops, no
allocation); add via `log10(1 + 10^Δ)`; `try_sub` returns `Option`.
- Untagged serde shim accepts both raw JSON numbers (legacy saves) and
the `{"log10": x}` struct form (values past f64::MAX) so existing
saves keep loading without a wire-format break for the small-value case.
V5 save schema (src/save/versions/v5.rs):
- Mag-typed counters frozen into a vN-style schema. V1..V4 frozen
modules untouched; `From<GameStateV4>` does the f64 -> Mag conversion
at the boundary, collapsing any leftover Inf/NaN to `Mag::ZERO`.
- CURRENT_VERSION = 5. Chain walks v1 -> v2 -> v3 -> v4 -> v5 ->
GameState.
Tree orphan fix (src/game/tree/node.rs):
- New `reaches_origin` predicate: a lot is a node iff a
strictly-decreasing-manhattan chain of populated neighbors reaches
origin. `anchor_of`'s pass-3 fallback was letting two outskirt lots
become each other's parent — a closed 2-node island unreachable from
the cuque. Reproduced at (-6,-17) and (-5,-17) in the wild.
- Thread-local memo keeps the recursion O(lots visited).
- 40-radius BFS test asserts zero orphans (was 50+).
Procgen magnitude rebalance (src/game/tree/node.rs::roll_magnitude):
- Per-node boon/bane magnitudes ~1000x smaller across the board.
- Multiplicative ops (MulFactor / EffectMul / SpawnRateMul / CostMul)
another 100x tighter on top — 100k-deep MulFactor stack = ×148
(was overflowing f64 around stack 1700). AddPercent and FlatAdd stay
at 1000x (linear, doesn't compound).
- NODE_COST_GROWTH 1.75 -> 2.5 stretches the cost ramp.
Format / display (src/format.rs):
- Infinite alphabetic suffix tail past Decillion: `aa..zz`, `Aa..Zz`,
`aaa..zzz`, ... (`big_mag` and `big`).
- Precision-aware `mul_magnitude` / `percent_magnitude` / `flat_magnitude`
so per-node `×1.0000004` renders with its meaningful digits in the
info pane instead of collapsing to `×1.00`.
- `?` is now strictly an Inf/NaN sentinel.
CI parity:
- `cargo clippy --all-targets -- -D warnings` so test-side lint
regressions don't slip past CI.
182 tests pass, fmt and clippy --all-targets clean. Stress-tested with
the original broken save (loads cleanly, gameplay accrues into Mag-space
values past f64::MAX) and a long playtest with 100+ stacked PurpleCoin
Buffs (FPS bounded, decays cleanly, no `?` rendered).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Maglog-magnitude type (src/bignum.rs) storeslog10(value)in an f64. Replaces every gameplay-side counter that compounds multiplicatively (cuques, FPS, costs, tree aggregate multipliers, persistedMulFactormodifiers). Kills the f64-Infinitybug class that broke a long-haul save in triage — cuques saturated to 0, 311-trillion-x PurpleCoin modifiers, 58x-amplified GreenCoin AddPercents.src/save/versions/v5.rs) with Mag-typed counters. V4 frozen schema untouched;From<GameStateV4>convertsf64 → Magat the migration boundary and collapses any leftoverInf/NaNtoMag::ZERO. Untagged serde shim onMagaccepts both legacy raw JSON numbers and{"log10": x}(huge values pastf64::MAX), so V4-shaped saves keep loading and small V5 values still write as plain numbers.reaches_originpredicate requires a strictly-decreasing-manhattan chain of populated neighbors back to origin.anchor_of's pass-3 fallback was producing closed 2-node islands (the reported "Halo of the Heel / Bless Matins at -6,-17"). Thread-local memo keeps it O(lots visited). New 40-radius BFS test asserts zero orphans (was 50+ across that region before).MulFactor/EffectMul/SpawnRateMul/CostMul) another 100× tighter on top so a 100k-deep stack stays in single-digit-× multiplier territory (was overflowing f64 around stack 1.7k).AddPercent/FlatAddkept at 1000× because they're linear, not compounding.NODE_COST_GROWTH1.75 → 2.5 stretches the cost ramp.format::big_mag(aa..zz,Aa..Zz,aaa..zzz, …). Precision-awaremul_magnitude/percent_magnitude/flat_magnitudehelpers so per-node×1.0000004renders with its meaningful digits in the info pane instead of collapsing to×1.00.?is now strictly anInf/NaNsentinel.cargo clippy --all-targets -- -D warnings(was lib-only), so test-side lint regressions don't slip past CI in future PRs.Test plan
cargo fmt --check,cargo clippy --all-targets -- -D warnings,cargo test, the 4-target build matrix, wasm32 check.×1.00000145 effect on Frenzy reward.oo/dm/bq), no?rendered anywhere.?rendered.v4_json_with_plain_number_fields_still_loads_under_v5covers the wire-format compatibility.cuques.log10 > 300serializes (struct form) and reloads (struct form accepted by V5 schema) without falling back todefault(). Regression test for the V5-blocker found in adversarial review.earnedmatches legacyfloor(sqrt(lifetime/1e6))at integer-tier boundaries —lifetime=25M → 5,81M → 9, etc. Regression test for an off-by-one round-trip-precision bug found in review.