diff --git a/devplacepy/docs_api/groups/game.py b/devplacepy/docs_api/groups/game.py index 960b4ff4..bfb974e1 100644 --- a/devplacepy/docs_api/groups/game.py +++ b/devplacepy/docs_api/groups/game.py @@ -275,7 +275,7 @@ client are on the [Code Farm guide](/docs/code-farm.html). method="POST", path="/game/prestige", title="Refactor (prestige)", - summary="Reset the farm at level 10+ for a permanent +25% coin bonus and earn Stars to spend on Legacy upgrades. Refactoring costs a coin fee that scales with prestige and current wealth (the farm state's refactor_cost); the fee funds the community treasury and a fraction of the remaining coins (10% base, up to 35% with the Golden Parachute Legacy upgrade) carries over. From prestige 50 onward, every 10 more prestige also earns a permanent Mastery point.", + summary="Reset the farm at level 10+ for a permanent +25% coin bonus and earn Stars to spend on Legacy upgrades. Refactoring costs a coin fee that scales with prestige and current wealth (the farm state's refactor_cost); the fee funds the community treasury and a fraction of the remaining coins (10% base, up to 60% with the Golden Parachute Legacy upgrade at level 10) carries over. From prestige 50 onward, every 10 more prestige also earns a permanent Mastery point.", auth="user", destructive=True, sample_response={"ok": True, "farm": {"prestige": 1, "coins": 6550}}, diff --git a/devplacepy/services/game/CLAUDE.md b/devplacepy/services/game/CLAUDE.md index 0c0cf0e0..ab23ce21 100644 --- a/devplacepy/services/game/CLAUDE.md +++ b/devplacepy/services/game/CLAUDE.md @@ -32,7 +32,7 @@ Devii plays via the `game_*` `http` actions in `actions/catalog/game.py` (state/ ## Stealing (competitive loop, backwards compatible) -`store.steal(thief, owner, slot)` mirrors `water`: it requires the owner plot to be `ready` AND past the protection window `economy.effective_steal_grace(owner_defense_level)` (base `STEAL_GRACE_SECONDS` 60s, +30s per owner Branch Protection level) measured from `ready_at`, AND that the thief is **off cooldown for this victim** (`steal_cooldown_remaining(thief, owner, now) == 0`, i.e. no row in `game_steals` for the pair within `STEAL_COOLDOWN_SECONDS` = 3600 - you can raid a given neighbour only once per hour); it takes a **share** of the build rather than destroying it: `conditional_update_row` adds the share to the plot's `raided_fraction` (new `game_plots` REAL column, default 0, `COALESCE`d everywhere) and the crop stays in place, so the owner still harvests `1 - raided_fraction` of its value (`_harvest_crop` takes a `raided_fraction` argument). A build is raidable until the shares reach 1.0, after which `steal_reason` is `"stripped"`. The thief's payout is `realizable_harvest_coins(...) * share`, **clamped to the un-raided remainder** so repeated raids can never mint coins (a property sweep proved the un-clamped `max(1, round(...))` overshot by 1 coin on a fully stripped build). Raids are additionally capped at `STEAL_MAX_PER_VICTIM_PER_DAY` (3) per victim per day via `raids_against_today` over the new `idx_game_steals_owner_time` index, so an offline player cannot be stripped by an unlimited queue of raiders. Payout is **coins only** - no XP, no `total_harvests`, so the leaderboard stays earned by real farming - then inserts a `game_steals` row stamping the cooldown. The route `POST /game/farm/{username}/steal` (reusing `GameSlotForm`) then `track_action`s both sides, fires `create_notification(owner, "harvest_stolen", "Someone raided your Code Farm...", thief_uid, "/game")` (the message never names the thief; `related_uid` is internal), and `await notify_farm`s **both** the owner and the thief usernames so both farms refresh live. The new `harvest_stolen` notification type rides the existing in-app relay (live toast, no new wiring). No DB migration: grace + payout are computed from existing `ready_at`/perk/legacy columns, the `game_steals` table is created by the ensure-block (absent rows -> cooldown 0 -> first steal always allowed), and the new `GamePlotOut.can_steal`/`steal_coins`/`steal_cooldown_seconds`/`steal_reason` + `GameFarmOut.steal_cooldown_seconds` + `GameFarmViewOut.stole_coins` schema fields default safe. The per-pair cooldown is computed **once per farm** in `serialize_farm` (when the viewer is not the owner) and threaded into every `serialize_plot` as `steal_locked_until`, so `can_steal` is false and `steal_reason` is `"cooldown"`/`"protected"` accordingly. **Display must equal enforcement:** `serialize_farm` also threads the owner's Defense building into every `serialize_plot` (`steal_grace_bonus` = the tier's `grace_bonus`, `steal_floor` = the tier's `steal_fraction_floor` raised to `OBSERVABILITY_STEAL_FLOOR` when the owner owns Observability), so the serialized `can_steal`/`steal_coins` match exactly what `store/actions.py::steal()` will enforce and pay - a client acting on `can_steal=true` can never get a "still protected" 400, and `steal_coins` is the exact payout. Never let `serialize_plot` and `steal()` compute grace or payout from different inputs. Frontend: the ready/not-owner branch of `_game_grid.html` and `GameFarm.js._plotHtml` render a `btn-danger` Steal button (`data-game-action="steal"`, `data-confirm` gated like prestige) carrying `steal_coins`, or a disabled "Raid again in " label when on cooldown; on success `GameFarm.js._submit` toasts the payout from `data.stole_coins`. +`store.steal(thief, owner, slot)` mirrors `water`: it requires the owner plot to be `ready` AND past the protection window `economy.effective_steal_grace(owner_defense_level)` (base `STEAL_GRACE_SECONDS` 60s, +30s per owner Branch Protection level) measured from `ready_at`, AND that the thief is **off cooldown for this victim** (`steal_cooldown_remaining(thief, owner, now) == 0`, i.e. no row in `game_steals` for the pair within `STEAL_COOLDOWN_SECONDS` = 3600 - you can raid a given neighbour only once per hour); it takes a **share** of the build rather than destroying it: `conditional_update_row` adds the share to the plot's `raided_fraction` (new `game_plots` REAL column, default 0, `COALESCE`d everywhere) and the crop stays in place, so the owner still harvests `1 - raided_fraction` of its value (`_harvest_crop` takes a `raided_fraction` argument). A build is raidable until the shares reach 1.0, after which `steal_reason` is `"stripped"`. The thief's payout is `realizable_harvest_coins(...) * share`, **clamped to the un-raided remainder** so repeated raids can never mint coins (a property sweep proved the un-clamped `max(1, round(...))` overshot by 1 coin on a fully stripped build). Raids are additionally capped at `STEAL_MAX_PER_VICTIM_PER_DAY` (3) per victim per day via `raids_against_today` over the new `idx_game_steals_owner_time` index, so an offline player cannot be stripped by an unlimited queue of raiders. Payout is **coins only** - no XP, no `total_harvests`, so the leaderboard stays earned by real farming - then inserts a `game_steals` row stamping the cooldown. The route `POST /game/farm/{username}/steal` (reusing `GameSlotForm`) then `track_action`s both sides, fires `create_notification(owner, "harvest_stolen", " raided your Code Farm and took N coins (P%) from your build...", thief_uid, "/game")` (the message names the raider, the crop, the coins and the percentage), and `await notify_farm`s **both** the owner and the thief usernames so both farms refresh live. The new `harvest_stolen` notification type rides the existing in-app relay (live toast, no new wiring). No DB migration: grace + payout are computed from existing `ready_at`/perk/legacy columns, the `game_steals` table is created by the ensure-block (absent rows -> cooldown 0 -> first steal always allowed), and the new `GamePlotOut.can_steal`/`steal_coins`/`steal_cooldown_seconds`/`steal_reason` + `GameFarmOut.steal_cooldown_seconds` + `GameFarmViewOut.stole_coins` schema fields default safe. The per-pair cooldown is computed **once per farm** in `serialize_farm` (when the viewer is not the owner) and threaded into every `serialize_plot` as `steal_locked_until`, so `can_steal` is false and `steal_reason` is `"cooldown"`/`"protected"` accordingly. **Display must equal enforcement:** `serialize_farm` also threads the owner's Defense building into every `serialize_plot` (`steal_grace_bonus` = the tier's `grace_bonus`, `steal_floor` = the tier's `steal_fraction_floor` raised to `OBSERVABILITY_STEAL_FLOOR` when the owner owns Observability), so the serialized `can_steal`/`steal_coins` match exactly what `store/actions.py::steal()` will enforce and pay - a client acting on `can_steal=true` can never get a "still protected" 400, and `steal_coins` is the exact payout. Never let `serialize_plot` and `steal()` compute grace or payout from different inputs. Frontend: the ready/not-owner branch of `_game_grid.html` and `GameFarm.js._plotHtml` render a `btn-danger` Steal button (`data-game-action="steal"`, `data-confirm` gated like prestige) carrying `steal_coins`, or a disabled "Raid again in " label when on cooldown; on success `GameFarm.js._submit` toasts the payout from `data.stole_coins`. ## Extended mechanics (all backwards compatible) @@ -96,7 +96,7 @@ A new, genuinely new table `game_market_ticks` (`crop_key`, `hour_bucket` = UTC ### 3. Mastery track and new crop families (`economy.py` prestige/mastery/crops sections + `store/mastery.py`) -Orthogonal to prestige, modeled directly on the existing Legacy shape (leveled, its own currency, survives prestige). **Two-counter design, load-bearing:** `mastery_points` (spendable, decreases on spend) vs `mastery_points_earned_total` (never decreases) - crop unlocks gate on the earned total so spending points can never re-lock content. `economy.mastery_points_awarded(old_prestige, new_prestige)` awards the FIRST Mastery point the moment prestige crosses `MASTERY_UNLOCK_PRESTIGE` (50) and one more every `MASTERY_PRESTIGE_STEP` (10) thereafter (`_mastery_total_for(p) = 1 + (p-50)//10` for `p>=50`, difference of the before/after totals - correct even if prestige ever jumps by more than 1). `store/actions.py::prestige()` adds the award into the same reset dict, alongside `stars`, as a field that is **not** zeroed. `POST /game/mastery` (`store.upgrade_mastery`) spends `mastery_points` on `economy.MASTERY_UPGRADES`: `autoreplant` (Continuous Delivery - wired via a shared `_try_autoreplant` helper called from both `harvest()` and `_auto_harvest()` after crediting, replants the same crop immediately if still affordable/unlocked), `analytics` (Farm Analytics - `GameFarmOut.mastery_analytics_unlocked` gates the `lifetime_coins_earned`/`lifetime_harvests` stats panel, rendered by `_game_analytics.html`/`data-analytics-host` inside the Mastery section - both counters are accumulated by `credit_farm`, so they are exact from the moment the upgrade is bought, not backfilled for prior activity), `contracts` (Legacy Contracts - unlocks the weekly contract slot, see section 6; this deliberately consolidates the critique's "Legacy Contracts" and "weekly contracts" into ONE mechanism rather than two redundant long-goal systems). +Orthogonal to prestige, modeled directly on the existing Legacy shape (leveled, its own currency, survives prestige). **Two-counter design, load-bearing:** `mastery_points` (spendable, decreases on spend) vs `mastery_points_earned_total` (never decreases) - crop unlocks gate on the earned total so spending points can never re-lock content. `economy.mastery_points_awarded(old_prestige, new_prestige)` awards the FIRST Mastery point the moment prestige crosses `MASTERY_UNLOCK_PRESTIGE` (50) and one more every `MASTERY_PRESTIGE_STEP` (5) thereafter (`_mastery_total_for(p) = 1 + (p-50)//5` for `p>=50`, difference of the before/after totals - correct even if prestige ever jumps by more than 1). `store/actions.py::prestige()` adds the award into the same reset dict, alongside `stars`, as a field that is **not** zeroed. `POST /game/mastery` (`store.upgrade_mastery`) spends `mastery_points` on `economy.MASTERY_UPGRADES`: `autoreplant` (Continuous Delivery - wired via a shared `_try_autoreplant` helper called from both `harvest()` and `_auto_harvest()` after crediting, replants the same crop immediately if still affordable/unlocked), `analytics` (Farm Analytics - `GameFarmOut.mastery_analytics_unlocked` gates the `lifetime_coins_earned`/`lifetime_harvests` stats panel, rendered by `_game_analytics.html`/`data-analytics-host` inside the Mastery section - both counters are accumulated by `credit_farm`, so they are exact from the moment the upgrade is bought, not backfilled for prior activity), `contracts` (Legacy Contracts - unlocks the weekly contract slot, see section 6; this deliberately consolidates the critique's "Legacy Contracts" and "weekly contracts" into ONE mechanism rather than two redundant long-goal systems). Three new high-tier crops (`distsys`/`mlpipe`/`secfort` in `economy.CROPS`) extend the `Crop` dataclass with three new **trailing, defaulted** fields (`min_mastery: int = 0`, `steal_immune: bool = False`, `era_key: str | None = None`) - safe because every existing `Crop(...)` construction is positional over the original 8 non-defaulted fields. They set `min_level=MAX_LEVEL` (so, like every other crop, they re-lock after each refactor until the player re-levels to 20 - consistent with how Kernel etc. already behave post-prestige) and `min_mastery=1` (gated on `mastery_points_earned_total`, checked both in `store/actions.py::plant()` - real enforcement - and in `economy.crop_payload`'s `locked` flag - display). `secfort` (Security Fortress) is `steal_immune=True`: `serialize_plot` forces `can_steal=False`/`steal_reason="immune"` and `store/actions.py::steal()` raises immediately, before any grace/cooldown check, when the target crop is immune. @@ -131,7 +131,7 @@ Continuous Delivery used to be a one-way lock: the harvested crop was replanted Refactoring is no longer free at level 10 - it is the economy's progressive wealth sink, and the sink funds a redistribution loop. All backwards compatible: new `game_farms` columns (`legacy_carryover`, `last_grant_week`) default NULL and are `COALESCE`d, and the `game_treasury` table starts empty (lazily seeded by `ensure_treasury`). - **Dynamic fee** (`economy.refactor_cost(prestige, coins)` = `REFACTOR_BASE_COST` (20k) `* (1 + prestige) * prestige_multiplier(prestige) + REFACTOR_WEALTH_PCT` (15%) `* coins`): the polynomial prestige term makes time-to-afford grow linearly with prestige (a soft cap on prestige cycling - income grows only linearly, so an exponential fee would wall off Mastery), and the wealth term is a progressive tax a hoarder cannot dodge. At prestige 0 the fee means a level-10 farm must keep farming (~23.5k coins minimum) before its first refactor - instant level-10 cycling is dead. -- **Carry-over** (`economy.refactor_carryover(coins, fee, carryover_level)`): after the fee, `REFACTOR_CARRYOVER_BASE` (10%) of the remainder survives the reset, raised +5%/level by the new **Golden Parachute** Legacy upgrade (`legacy_carryover`, max 5 -> 35%). Always strictly below 100% so a refactor can never be net-profitable (same invariant class as the fertilize fix). `prestige()` pins the balance in the atomic WHERE (`AND coins = :coins_now`) so a concurrent harvest yields a "balance just changed - refresh" `GameError` instead of a wrong charge, then credits the fee to the treasury. +- **Carry-over** (`economy.refactor_carryover(coins, fee, carryover_level)`): after the fee, `REFACTOR_CARRYOVER_BASE` (10%) of the remainder survives the reset, raised +5%/level by the new **Golden Parachute** Legacy upgrade (`legacy_carryover`, max 10 -> 60%). Always strictly below 100% so a refactor can never be net-profitable (same invariant class as the fertilize fix). `prestige()` pins the balance in the atomic WHERE (`AND coins = :coins_now`) so a concurrent harvest yields a "balance just changed - refresh" `GameError` instead of a wrong charge, then credits the fee to the treasury. - **Treasury** (`game_treasury`, single row `uid="treasury-main"`, `balance`/`collected_total`/`granted_total`): every refactor fee is credited atomically (`credit_treasury`, raw conditional SQL like `conditional_update_farm`). The ledger invariant `balance == collected_total - granted_total` is fuzz-verified. - **Community grant** (`POST /game/grant`, `store.claim_grant`): once per ISO week (`last_grant_week`), an **active low-wealth, low-prestige** farm (coins < `GRANT_WEALTH_CEILING` 10k, `harvests_week >= GRANT_MIN_WEEK_HARVESTS` 5 - the activity gate blocks idle alts, prestige <= `GRANT_MAX_PRESTIGE` 5 - so post-refactor whales with a briefly low balance never draw welfare) claims `economy.grant_amount(balance)` (capped `GRANT_CAP` 2,500). Order of operations: atomic treasury debit first (`WHERE balance >= :amount`), then the atomic farm credit+week-stamp; a losing farm update refunds the treasury. Race-verified with real OS processes: concurrent claims by one farm pay exactly once, and two farms racing an underfunded treasury never overdraw it. - **Fan-out:** `serialize_farm` exposes `refactor_cost`/`refactor_affordable`/`refactor_carryover_pct`/`refactor_carryover_preview` plus the owner-only `grant_*`/`treasury_balance`; the shop row and `GameFarm.js._shopHtml` show the fee and hide the button until affordable; the sidebar **Community grant** section is `_game_grant.html`/`data-grant-host`/`_grantHtml`. Devii: `game_prestige` is now in `CONFIRM_REQUIRED` (large irreversible spend) with a declared `confirm` param; `game_claim_grant` is a plain auth tool. Docs: `game-prestige` updated + `game-grant` added. diff --git a/devplacepy/templates/docs/code-farm.html b/devplacepy/templates/docs/code-farm.html index eb63e23d..6746e8b3 100644 --- a/devplacepy/templates/docs/code-farm.html +++ b/devplacepy/templates/docs/code-farm.html @@ -53,9 +53,11 @@ never be stolen. Build time is the base time divided by the farm build speed (CI tier, the Build Cache perk, the Bare-Metal Legacy upgrade, and the Private Registry for its three crops), so upgrades finish -everything proportionally faster. Coin and experience rewards shown in the API already include +everything proportionally faster. Coin rewards shown in the API already include your perks, refactor bonus, Legacy multiplier, and the current Market Saturation factor, so read -them from the live state rather than from this table. Each crop in the state also carries a +them from the live state rather than from this table. The `reward_xp` in `crops` is the crop's base +experience: your actual experience is that value times `1 + 0.05 * Mentorship level` (see Exact +formulas). Each crop in the state also carries a `market_state` of `normal`, `saturated`, or `boosted`. ## Plots @@ -327,8 +329,8 @@ fee = round(20000 * (1 + prestige) * (1 + 0.25 * prestige) + 0.15 * coins) so it grows with both your refactor count and your current balance (a wealth tax), and each refactor takes longer to afford than the last. The live state reports the exact `refactor_cost` and whether you can pay it right now (`refactor_affordable`). After the fee, **10%** of your -remaining coins carry over into the new run - up to **35%** with the Golden Parachute Legacy -upgrade - previewed exactly as `refactor_carryover_preview` (and `refactor_carryover_pct`). The +remaining coins carry over into the new run - `10% + 5%` per Golden Parachute Legacy level, so +up to **60%** at its maximum level 10 - previewed exactly as `refactor_carryover_preview` (and `refactor_carryover_pct`). The new run starts with the 50 starting coins plus that carry-over. Each refactor also awards **Stars**: `1 + level / 5 + prestige` (rounded down, using the level @@ -409,6 +411,65 @@ an Era-exclusive cosmetic. Your own state reports `era_active`, `era_name`, `era `era_harvests` while one is running; an Era can also make Era-exclusive crops plantable for its duration. +## Exact formulas (for calculators and bots) + +Every number the game shows comes from one formula, and the same formula is used when a client +reads it and when the server pays it, so a bot can reproduce any figure below exactly. `P` is your +prestige count and `L` the level of the named upgrade. All results are rounded with ordinary +half-to-even rounding to whole units, the way the live state reports them. + +| Quantity | Formula | +|----------|---------| +| Prestige multiplier | `1 + 0.25 * P` | +| Legacy multiplier | `1 + 0.10 * L(multiplier)` | +| Income scale (social rewards, quest goals) | `prestige multiplier * legacy multiplier` | +| Build speed | `CI speed * (1 + 0.04 * L(growth)) * (1 + 0.05 * L(speed))`, times `1.15` for Rust, Compiler and Kernel with the Private Registry | +| Build seconds | `max(1, round(base seconds / build speed))` | +| Planting cost | `max(1, round(cost * max(0, 1 - 0.03 * L(discount))))` | +| Harvest coins | `round(base coins * (1 + 0.05 * L(yield)) * prestige multiplier * legacy multiplier * market factor)`, times `1.25` more with an active Underdog boost | +| Realized harvest coins | the harvest coins above, then times `5` if golden, then times `1.2` with an active weekly-contract boost, then times `1.12` for the Canary upside when owned (the Canary roll itself is random at harvest time) | +| Harvest experience | `round(base xp * (1 + 0.05 * L(xp)))` | +| Site experience from a harvest | `floor(farm experience / 25)`, so one Kernel harvest (400) is 16 site XP | +| Raid payout | `min(int(realized * (1 - raided so far)), max(1, round(realized * taken)))`, where `realized` is the **owner's** realized harvest coins for that build (the owner's perks, prestige, Legacy, boosts and golden flag, plus the current market factor, not yours), `taken = min(1 - raided so far, min(cap, max(floor, (0.50 - 0.05 * L(defense)) * (1 - tier reduction))))` and `L(defense)` is the owner's Branch Protection level. A raid is refused when nothing worth at least 1 coin is left | +| Watering reward | `max(6, round(6 * income scale))` coins and 3 experience | +| Daily bonus | `max(base, round(base * max(1, income scale)))` with `base = 20 + 12 * (min(streak, 7) - 1)` | +| Refactor fee | `round(20000 * (1 + P) * (1 + 0.25 * P) + 0.15 * coins)` | +| Refactor Stars | `1 + floor(level / 5) + P` | +| Grant | `min(2500, floor(treasury / eligible farms))`, zero when below 250 | +| Build seconds saved by one watering | `max(1, round(build seconds * 0.08))` | +| Fertilize cost | `ceil(realized coins * skipped seconds / full build seconds * 1.05)` | + +**What a raid and a harvest do to your numbers.** A harvest pays coins and experience, advances the +`harvest` and `earn` quests, counts toward `total_harvests` and the weekly harvest count, awards +site XP, and adds one tick to that crop's Market Saturation supply. A **raid pays the raider coins +only**: no experience, no harvest count, no site XP, and it adds **no** Market Saturation supply, +because a raid moves value that was already produced instead of printing new supply. The owner is +paid the remaining share when they harvest. + +**What counts as "active" for Market Saturation.** Supply is +`harvests in the last 48 hours * base build seconds / 86400 / active farms`, where an active farm is +any farm with at least one harvest this ISO week (at least 1). Only harvests by the owner, manual or +by the CI Bot, are counted. + +**Daily quests, exactly.** Each day three of the four kinds below are given, taken in the order +`plant`, `harvest`, `water`, `earn` starting at a position chosen from your user and the date. The +goal is picked inside the range shown, and `scale` is your income scale (never below 1): + +| Kind | Goal range | Coins | Experience | +|------|-----------|-------|------------| +| `plant` | 3 to 6 | `round(goal * 8 * scale)` | `goal * 2` | +| `harvest` | 3 to 5 | `round(goal * 14 * scale)` | `goal * 4` | +| `water` | 2 to 4 | `round(goal * 16 * scale)` | `goal * 5` | +| `earn` | 150 to 400, then `floor(goal * scale / 10) * 10` (never below 150) | `round(goal * 0.15)` | `max(1, round(goal / 30 / scale))` | + +**Weekly contract, exactly.** One kind per ISO week. Goal is `4 * (min + max)` of the kind's range +above (36 plant, 32 harvest, 24 water, 2200 earn), and the `earn` goal is then scaled by your +income scale and rounded down to tens. It pays Stars and experience that do **not** scale: 3 Stars +and 40 XP for `plant` and `harvest`, 3 Stars and 30 XP for `water`, 5 Stars and 60 XP for `earn`. + +**Not counted.** Auto-replant from Continuous Delivery does not advance the `plant` quest (see the +Continuous Delivery section). Quest progress is capped at the goal. + ## Live updates A farm page subscribes to the pub/sub topic `public.game.farm.{username}` and refreshes whenever @@ -534,7 +595,8 @@ lists you act on. A plot looks like this: ``` A plot's `state` is `empty`, `growing`, or `ready`. `reward_coins`/`reward_xp` on a plot are the -crop's **base** rewards; the multiplied live values are on the matching entry in `crops`. An +crop's **base** rewards; the multiplied live coin value is on the matching entry in `crops` (its +`reward_xp` is still base experience, see Exact formulas). An entry in `crops` carries the live `cost`, `reward_coins`, `reward_xp`, `grow_seconds`, `min_level`, `market_state`, and a `locked` flag, so a client can decide what is both unlocked and affordable without hard-coding the tables above. @@ -555,7 +617,10 @@ read your **own** farm - on someone else's farm the lists are empty and the flag **Lists:** `plots`, `crops`, plus *(owner)* `perks`, `quests` (daily entries, and the weekly contract when unlocked), `legacy`, `mastery`, `infrastructure`, `cosmetics`. Each purchasable entry carries `key`, `name`, `icon`, `description`, its current `level`/`owned` state, the exact -next `cost`, and a `maxed` flag where applicable. +next `cost`, and a `maxed` flag where applicable. Perks and Legacy upgrades also carry +`max_level`. Each entry in `crops` carries `locked` plus the reason: `locked_reason` is `level`, +`mastery` or `era` (empty when plantable), `locked_text` is the human sentence, `min_level` is the +unlock level and `min_mastery` is the Mastery points that must have been earned (0 or 1). **Refactor:** `prestige`, `prestige_multiplier`, `prestige_min_level`, `prestige_available` *(owner)*, `refactor_cost`, `refactor_affordable` *(owner)*, `refactor_carryover_pct`, diff --git a/pyproject.toml b/pyproject.toml index 692b9b7a..2eb4f62a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "devplacepy" -version = "1.1.6" +version = "1.1.7" description = "DevPlace - The Developer Social Network" requires-python = ">=3.12" dependencies = [