feat: Making the game harder to 'beat'

This commit is contained in:
gauvainboiche
2026-10-08 17:17:21 +02:00
parent 2f7c307f0e
commit 8b15c76b14
20 changed files with 637 additions and 91 deletions
+8
View File
@@ -784,3 +784,11 @@ select,
transition: none;
}
}
/* An important message: stays until it is clicked (the container ignores the pointer otherwise). */
.toast--sticky {
max-width: 36rem;
border: 2px solid var(--accent);
cursor: pointer;
pointer-events: auto;
}
+14 -14
View File
@@ -1,16 +1,16 @@
[
{"id":"e_click","requires":"click_3","baseCost":6000000,"costGrowth":2.5,"maxLevel":10000,"effect":{"type":"click_mult","value":2}},
{"id":"e_gen1","requires":"g1_b","baseCost":600000,"costGrowth":2.6,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen1","value":1.5}},
{"id":"e_gen2","requires":"g2_b","baseCost":8000000,"costGrowth":2.6,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen2","value":1.5}},
{"id":"e_gen3","requires":"g3_b","baseCost":100000000,"costGrowth":2.6,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen3","value":1.5}},
{"id":"e_gen4","requires":"g4_b","baseCost":1200000000,"costGrowth":2.6,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen4","value":1.4}},
{"id":"e_gen5","requires":"g5_b","baseCost":16000000000,"costGrowth":2.7,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen5","value":1.3}},
{"id":"e_gen6","requires":"g6_b","baseCost":200000000000,"costGrowth":2.8,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen6","value":1.25}},
{"id":"e_global","requires":"global_4","baseCost":20000000000,"costGrowth":2.4,"maxLevel":10000,"effect":{"type":"global_mult","value":1.2}},
{"id":"e_demand","requires":"demand_4","baseCost":2000000000,"costGrowth":2.4,"maxLevel":10000,"effect":{"type":"demand_mult","value":1.2}},
{"id":"e_value","requires":"value_3","baseCost":1200000000,"costGrowth":2.4,"maxLevel":10000,"effect":{"type":"value_mult","value":1.15}},
{"id":"e_efficiency","requires":"mat_eff_2","baseCost":40000000,"costGrowth":2.5,"maxLevel":10000,"effect":{"type":"material_efficiency","value":1.2}},
{"id":"e_discount","requires":"mat_disc_2","baseCost":40000000,"costGrowth":2.5,"maxLevel":10000,"effect":{"type":"batch_discount","value":0.9}},
{"id":"e_stack","requires":"stack_5","baseCost":4000000000,"costGrowth":3.5,"maxLevel":10000,"effect":{"type":"batch_size_mult","value":2}},
{"id":"e_cost","requires":"g3_b","baseCost":2000000000,"costGrowth":2.4,"maxLevel":10000,"effect":{"type":"generator_cost_mult","value":0.95}}
{"id":"e_click","requires":"click_3","baseCost":60000000,"costGrowth":2.65,"maxLevel":10000,"effect":{"type":"click_mult","value":2}},
{"id":"e_gen1","requires":"g1_b","baseCost":6000000,"costGrowth":2.75,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen1","value":1.5}},
{"id":"e_gen2","requires":"g2_b","baseCost":80000000,"costGrowth":2.75,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen2","value":1.5}},
{"id":"e_gen3","requires":"g3_b","baseCost":1000000000,"costGrowth":2.75,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen3","value":1.5}},
{"id":"e_gen4","requires":"g4_b","baseCost":12000000000,"costGrowth":2.75,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen4","value":1.4}},
{"id":"e_gen5","requires":"g5_b","baseCost":160000000000,"costGrowth":2.85,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen5","value":1.3}},
{"id":"e_gen6","requires":"g6_b","baseCost":2000000000000,"costGrowth":2.95,"maxLevel":10000,"effect":{"type":"generator_mult","target":"gen6","value":1.25}},
{"id":"e_global","requires":"global_4","baseCost":200000000000,"costGrowth":2.55,"maxLevel":10000,"effect":{"type":"global_mult","value":1.2}},
{"id":"e_demand","requires":"demand_4","baseCost":20000000000,"costGrowth":2.55,"maxLevel":10000,"effect":{"type":"demand_mult","value":1.2}},
{"id":"e_value","requires":"value_3","baseCost":12000000000,"costGrowth":2.55,"maxLevel":10000,"effect":{"type":"value_mult","value":1.15}},
{"id":"e_efficiency","requires":"mat_eff_2","baseCost":400000000,"costGrowth":2.65,"maxLevel":10000,"effect":{"type":"material_efficiency","value":1.2}},
{"id":"e_discount","requires":"mat_disc_2","baseCost":400000000,"costGrowth":2.65,"maxLevel":10000,"effect":{"type":"batch_discount","value":0.9}},
{"id":"e_stack","requires":"stack_5","baseCost":40000000000,"costGrowth":3.65,"maxLevel":10000,"effect":{"type":"batch_size_mult","value":2}},
{"id":"e_cost","requires":"g3_b","baseCost":20000000000,"costGrowth":2.55,"maxLevel":10000,"effect":{"type":"generator_cost_mult","value":0.95}}
]
+5 -5
View File
@@ -1,8 +1,8 @@
[
{"id":"lg_prod","baseCost":1,"costGrowth":1.6,"maxLevel":15,"effect":{"type":"global_mult","value":1.3}},
{"id":"lg_demand","baseCost":1,"costGrowth":1.6,"maxLevel":15,"effect":{"type":"demand_mult","value":1.3}},
{"id":"lg_cost","baseCost":2,"costGrowth":1.7,"maxLevel":10,"effect":{"type":"generator_cost_mult","value":0.92}},
{"id":"lg_start","baseCost":1,"costGrowth":1.5,"maxLevel":10,"effect":{"type":"start_funds","value":50000}},
{"id":"lg_prod","baseCost":1,"costGrowth":2,"maxLevel":15,"effect":{"type":"global_mult","value":1.15}},
{"id":"lg_demand","baseCost":1,"costGrowth":2,"maxLevel":15,"effect":{"type":"demand_mult","value":1.15}},
{"id":"lg_cost","baseCost":2,"costGrowth":2,"maxLevel":10,"effect":{"type":"generator_cost_mult","value":0.95}},
{"id":"lg_start","baseCost":1,"costGrowth":2,"maxLevel":10,"effect":{"type":"start_funds","value":50000}},
{"id":"lg_offline","baseCost":2,"costGrowth":2,"maxLevel":5,"effect":{"type":"offline_add","value":0.1}},
{"id":"lg_rp","baseCost":2,"costGrowth":1.6,"maxLevel":10,"effect":{"type":"rp_mult","value":1.25}}
{"id":"lg_rp","baseCost":2,"costGrowth":2,"maxLevel":10,"effect":{"type":"rp_mult","value":1.15}}
]
+15 -15
View File
@@ -1,17 +1,17 @@
[
{"id":"r_prod_1","branch":"prod","cost":20,"requires":[],"effect":{"type":"global_mult","value":1.5}},
{"id":"r_prod_2","branch":"prod","cost":120,"requires":["r_prod_1"],"effect":{"type":"global_mult","value":2}},
{"id":"r_prod_3","branch":"prod","cost":600,"requires":["r_prod_2"],"effect":{"type":"global_mult","value":3}},
{"id":"r_mkt_1","branch":"mkt","cost":25,"requires":[],"effect":{"type":"demand_mult","value":2}},
{"id":"r_mkt_2","branch":"mkt","cost":150,"requires":["r_mkt_1"],"effect":{"type":"demand_mult","value":3}},
{"id":"r_mkt_3","branch":"mkt","cost":400,"requires":["r_mkt_2"],"effect":{"type":"value_mult","value":2}},
{"id":"r_log_1","branch":"log","cost":30,"requires":[],"effect":{"type":"material_efficiency","value":1.5}},
{"id":"r_log_2","branch":"log","cost":200,"requires":["r_log_1"],"effect":{"type":"batch_discount","value":0.7}},
{"id":"r_log_3","branch":"log","cost":500,"requires":["r_log_2"],"effect":{"type":"generator_cost_mult","value":0.8}},
{"id":"r_fin_1","branch":"fin","cost":40,"requires":[],"effect":{"type":"dividend_mult","value":1.5}},
{"id":"r_fin_2","branch":"fin","cost":160,"requires":["r_fin_1"],"effect":{"type":"reliability_add","value":0.05}},
{"id":"r_fin_3","branch":"fin","cost":350,"requires":["r_fin_2"],"effect":{"type":"contract_mult","value":2}},
{"id":"r_ins_1","branch":"ins","cost":50,"requires":[],"effect":{"type":"rp_mult","value":1.5}},
{"id":"r_ins_2","branch":"ins","cost":250,"requires":["r_ins_1"],"effect":{"type":"offline_add","value":0.25}},
{"id":"r_ins_3","branch":"ins","cost":800,"requires":["r_ins_2","r_mkt_2"],"effect":{"type":"price_advisor"}}
{"id":"r_prod_1","branch":"prod","cost":40,"requires":[],"effect":{"type":"global_mult","value":1.5}},
{"id":"r_prod_2","branch":"prod","cost":240,"requires":["r_prod_1"],"effect":{"type":"global_mult","value":2}},
{"id":"r_prod_3","branch":"prod","cost":1200,"requires":["r_prod_2"],"effect":{"type":"global_mult","value":3}},
{"id":"r_mkt_1","branch":"mkt","cost":50,"requires":[],"effect":{"type":"demand_mult","value":2}},
{"id":"r_mkt_2","branch":"mkt","cost":300,"requires":["r_mkt_1"],"effect":{"type":"demand_mult","value":3}},
{"id":"r_mkt_3","branch":"mkt","cost":800,"requires":["r_mkt_2"],"effect":{"type":"value_mult","value":2}},
{"id":"r_log_1","branch":"log","cost":60,"requires":[],"effect":{"type":"material_efficiency","value":1.5}},
{"id":"r_log_2","branch":"log","cost":400,"requires":["r_log_1"],"effect":{"type":"batch_discount","value":0.7}},
{"id":"r_log_3","branch":"log","cost":1000,"requires":["r_log_2"],"effect":{"type":"generator_cost_mult","value":0.8}},
{"id":"r_fin_1","branch":"fin","cost":80,"requires":[],"effect":{"type":"dividend_mult","value":1.5}},
{"id":"r_fin_2","branch":"fin","cost":320,"requires":["r_fin_1"],"effect":{"type":"reliability_add","value":0.05}},
{"id":"r_fin_3","branch":"fin","cost":700,"requires":["r_fin_2"],"effect":{"type":"contract_mult","value":2}},
{"id":"r_ins_1","branch":"ins","cost":100,"requires":[],"effect":{"type":"rp_mult","value":1.5}},
{"id":"r_ins_2","branch":"ins","cost":500,"requires":["r_ins_1"],"effect":{"type":"offline_add","value":0.25}},
{"id":"r_ins_3","branch":"ins","cost":1600,"requires":["r_ins_2","r_mkt_2"],"effect":{"type":"price_advisor"}}
]
+5 -3
View File
@@ -68,11 +68,13 @@
"labBaseCost": 2000000,
"labCostGrowth": 3,
"labMax": 30,
"rpPerLabPerSecond": 0.01
"rpPerLabPerSecond": 0.002
},
"legacy": {
"threshold": 1000000000,
"passivePerPoint": 0.05
"threshold": 10000000000000,
"exponent": 0.4,
"passiveScale": 0.1,
"passivePower": 0.5
},
"contracts": {
"offerEverySeconds": 60,
+20 -4
View File
@@ -5,11 +5,27 @@
import { modifiersFor } from './modifiers.js';
import { createState, META_KEYS } from './state.js';
/** Points a restart would grant right now (0 until the run is big enough). */
/**
* How many points a lifetime production deserves in total. Diminishing returns
* (a power below 1) on EVERYTHING ever produced, not on a single run: each
* further point needs far more production than the last, so restarting more and
* more often cannot snowball.
*/
export function pointsFor(lifetimeProduced, ctx, mods) {
const { threshold, exponent } = ctx.rules.legacy;
if (!(lifetimeProduced >= threshold)) return 0;
return Math.floor((lifetimeProduced / threshold) ** exponent * mods.legacyGain);
}
/** Lifetime production at which the next point is earned (ignoring bonuses to point gains). */
export function nextPointAt(state, ctx) {
const { threshold, exponent } = ctx.rules.legacy;
return threshold * (state.legacy.earned + 1) ** (1 / exponent);
}
/** Points a restart would grant right now (0 until the next point is within reach). */
export function legacyGain(state, ctx, mods) {
const { threshold } = ctx.rules.legacy;
if (state.totalProduced < threshold) return 0;
return Math.floor(Math.sqrt(state.totalProduced / threshold) * mods.legacyGain);
return Math.max(0, pointsFor(state.lifetimeProduced, ctx, mods) - state.legacy.earned);
}
export function perkLevel(state, perk) {
+10 -4
View File
@@ -3,6 +3,12 @@
import { foldEffects } from './effects.js';
import { chipDemandBonus } from './formulas.js';
/** The multiplier given by all legacy points earned so far: diminishing returns (a power below 1). */
export function passiveBonus(state, ctx) {
const { passiveScale, passivePower } = ctx.rules.legacy;
return 1 + passiveScale * state.legacy.earned ** passivePower;
}
/** All `{ effect, times }` entries that apply to `state`. */
export function collectEffects(state, ctx) {
const entries = [];
@@ -18,10 +24,10 @@ export function collectEffects(state, ctx) {
entries.push({ effect: ctx.perksById.get(id)?.effect, times: level });
}
// Every legacy point ever earned gives a small passive bonus.
const passive = 1 + ctx.rules.legacy.passivePerPoint;
entries.push({ effect: { type: 'global_mult', value: passive }, times: state.legacy.earned });
entries.push({ effect: { type: 'demand_mult', value: passive }, times: state.legacy.earned });
// Every legacy point ever earned adds a flat bonus. It has diminishing returns and does
// not compound: stacking x1.05 per point once reached 1e100 within a few runs.
entries.push({ effect: { type: 'global_mult', value: passiveBonus(state, ctx) } });
entries.push({ effect: { type: 'demand_mult', value: passiveBonus(state, ctx) } });
// Chips make the brand more attractive, with diminishing returns.
entries.push({
+48 -2
View File
@@ -1,6 +1,7 @@
import { clamp } from './formulas.js';
export const STATE_VERSION = 2;
// 3: legacy points come from lifetime production and the passive bonus no longer compounds.
export const STATE_VERSION = 3;
const MAX_NUMBER = 1e300;
const MAX_OWNED = 4000; // keeps cost formulas (growth ** owned) finite
@@ -17,6 +18,7 @@ const TRANSIENT = [
'contractTimer',
'eventTimer',
'gambleCooldown',
'repaired',
];
// What a legacy reset keeps (the "meta" progression); everything else is the run.
@@ -81,6 +83,7 @@ export function createState(ctx, now = Date.now()) {
contractTimer: contracts.offerEverySeconds,
eventTimer: events.minSeconds,
gambleCooldown: 0,
repaired: false, // set when an old save had to be rebalanced, so the game can say so
};
}
@@ -168,7 +171,7 @@ function sanitizeEvents(raw, ctx) {
* Unknown ids are dropped and numbers are clamped; returns null if `raw` is not an object.
* Saves from older versions are upgraded: missing fields take their defaults.
*/
export function sanitizeState(raw, ctx, now = Date.now()) {
function parseState(raw, ctx, now) {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
const { market, materialMarket, gamble } = ctx.rules;
const base = createState(ctx, now);
@@ -228,3 +231,46 @@ export function serializeState(state) {
typeof value === 'number' && Number.isFinite(value) ? Number(value.toPrecision(10)) : value,
);
}
// ------------------------------------------------------- repairing old saves
const MAX_LEGIT_POINTS = 25; // more than this in a save older than v3 can only come from the old runaway bonus
const RUNAWAY_PRODUCTION = 1e40;
const REPAIRED_POINTS = 10; // what a runaway save is brought back to
const REPAIRED_PERK_LEVEL = 3;
/**
* Before version 3 every legacy point multiplied output by 1.05 on top of the
* others, so a few thousand points gave 1e100 and runs were over in seconds.
* Such a save keeps its research and achievements but gets its legacy points
* and perks capped and its (meaningless) run restarted. Any older save also has
* its lifetime production capped, so an inflated total cannot hand out a pile
* of points at the next restart.
*/
function repairOlderSave(state, ctx, now) {
const runaway = state.legacy.earned > MAX_LEGIT_POINTS || state.lifetimeProduced > RUNAWAY_PRODUCTION;
if (runaway) {
state.legacy.earned = Math.min(state.legacy.earned, REPAIRED_POINTS);
state.legacy.points = 0;
for (const id of Object.keys(state.legacy.perks)) {
state.legacy.perks[id] = Math.min(state.legacy.perks[id], REPAIRED_PERK_LEVEL);
}
const fresh = createState(ctx, now);
for (const key of Object.keys(fresh)) {
if (!META_KEYS.includes(key) && key !== 'v' && key !== 'savedAt') state[key] = fresh[key];
}
state.repaired = true;
}
const { threshold, exponent } = ctx.rules.legacy;
state.lifetimeProduced = Math.min(state.lifetimeProduced, threshold * (state.legacy.earned + 3) ** (1 / exponent));
return state;
}
/** Untrusted data (cookie, imported file) in, valid and up-to-date state out; null if unusable. */
export function sanitizeState(raw, ctx, now = Date.now()) {
const state = parseState(raw, ctx, now);
if (!state) return null;
return (Number(raw.v) || 1) < STATE_VERSION ? repairOlderSave(state, ctx, now) : state;
}
+9
View File
@@ -189,6 +189,13 @@ function catchUp(elapsedSeconds) {
);
}
/** Tell the player when an older save had to be rebalanced (see repairOlderSave in state.js). */
function reportRepair() {
if (!state.repaired) return;
state.repaired = false;
showToast(t('toast.repaired'), { sticky: true });
}
let lastFrame = Date.now();
function frame() {
@@ -279,6 +286,7 @@ createSettings({
}),
importText(text) {
state = parseImport(text, ctx); // throws before anything changes
reportRepair();
income = 0;
persist();
render();
@@ -322,6 +330,7 @@ async function boot() {
await setTheme(initialTheme.id);
state = loadSavedState() ?? createState(ctx);
reportRepair();
catchUp((Date.now() - state.savedAt) / 1000);
persist();
render();
+9 -7
View File
@@ -1,6 +1,7 @@
// Legacy tab: restart the run for permanent points, spend them on perks.
import { legacyGain, perkCost, perkLevel } from '../engine/legacy.js';
import { legacyGain, nextPointAt, perkCost, perkLevel } from '../engine/legacy.js';
import { passiveBonus } from '../engine/modifiers.js';
import { t } from '../services/i18n.js';
import { h, icon, makeBuyable, setBuyable } from './dom.js';
import { bump, purchased } from './fx.js';
@@ -58,7 +59,7 @@ export function createLegacyView(root, actions) {
'section',
{ class: 'panel' },
h('h2', { class: 'panel__title', text: t('tab.legacy') }),
h('p', { class: 'hint', text: t('legacy.intro', { passive: fmt.number(ctx.rules.legacy.passivePerPoint * 100) }) }),
h('p', { class: 'hint', text: t('legacy.intro') }),
h('div', { class: 'tile tile--primary' }, icon('res.legacy'), h('div', {}, h('span', { class: 'tile__label', text: t('theme.res.legacy') }), els.points)),
h(
'dl',
@@ -84,21 +85,22 @@ export function createLegacyView(root, actions) {
}
function update(state, d) {
const { threshold, passivePerPoint } = ctx.rules.legacy;
setText(els.points, fmt.number(state.legacy.points));
setText(els.earned, fmt.number(state.legacy.earned));
setText(els.prestiges, fmt.number(state.legacy.prestiges));
setText(els.passive, t('legacy.passiveValue', { bonus: fmt.number((1 + passivePerPoint) ** state.legacy.earned) }));
setText(els.passive, t('legacy.passiveValue', { bonus: fmt.number(passiveBonus(state, ctx)) }));
const gain = legacyGain(state, ctx, d.mods);
setText(els.gain, t('legacy.gain', { gain: fmt.number(gain) }));
els.progress.max = threshold;
els.progress.value = Math.min(state.totalProduced, threshold);
// Points are earned from everything ever produced, so the bar shows the lifetime total.
const target = nextPointAt(state, ctx);
els.progress.max = target;
els.progress.value = Math.min(state.lifetimeProduced, target);
setText(
els.need,
gain >= 1
? t('legacy.ready')
: t('legacy.need', { have: fmt.number(Math.floor(state.totalProduced)), need: fmt.number(threshold) }),
: t('legacy.need', { have: fmt.number(Math.floor(state.lifetimeProduced)), need: fmt.number(target) }),
);
setText(els.restart, t('legacy.restart'));
els.restart.disabled = gain < 1;
+17 -4
View File
@@ -3,10 +3,23 @@ import { $, h } from './dom.js';
const DURATION_MS = 6000;
const MAX_VISIBLE = 3; // a big jump (import, long absence) must not bury the page in messages
export function showToast(message, { error = false } = {}) {
/**
* Show a message for a few seconds.
* `sticky` messages are important: they stay until clicked, and routine messages never push them away.
*/
export function showToast(message, { error = false, sticky = false } = {}) {
const container = $('#toasts');
const toast = h('p', { class: error ? 'toast toast--error' : 'toast', text: message });
const classes = ['toast', error && 'toast--error', sticky && 'toast--sticky'].filter(Boolean);
const toast = h('p', { class: classes.join(' '), text: message });
if (sticky) {
toast.setAttribute('role', 'alert');
toast.addEventListener('click', () => toast.remove());
} else {
setTimeout(() => toast.remove(), DURATION_MS);
}
container.append(toast);
while (container.children.length > MAX_VISIBLE) container.firstElementChild.remove();
setTimeout(() => toast.remove(), DURATION_MS);
const routine = [...container.children].filter((el) => !el.classList.contains('toast--sticky'));
for (const old of routine.slice(0, Math.max(0, routine.length - MAX_VISIBLE))) old.remove();
}
+5 -4
View File
@@ -93,7 +93,8 @@
"contractFailed": "Contract failed: time ran out.",
"eventIncident": "Incident: {title}",
"achievement": "Achievement: {title}",
"restarted": "A new run begins! +{gain} {@theme.res.legacy}"
"restarted": "A new run begins! +{gain} {@theme.res.legacy}",
"repaired": "Your save came from an older version whose legacy bonus could grow out of control. It was rebalanced: legacy points and perks were capped and the run restarted. Research and achievements were kept."
},
"error": {
"import": {
@@ -237,15 +238,15 @@
}
},
"legacy": {
"intro": "Start over for permanent {@theme.res.legacy}. Every point also gives +{passive}% production and demand, forever. You keep research, perks, achievements and statistics.",
"intro": "Start over for permanent {@theme.res.legacy}. Each point adds a lasting bonus to production and demand, but with diminishing returns: the more points you have, the less each new one adds. Points come from everything you have ever produced, so each further point takes much more than the last. You keep research, perks, achievements and statistics.",
"earned": "Earned in total",
"restarts": "Restarts",
"passive": "Passive bonus",
"passiveValue": "×{bonus}",
"restartTitle": "Start a new run",
"gain": "Restarting now earns {gain} {@theme.res.legacy}.",
"need": "Reach {need} {@theme.res.units} in this run ({have} so far).",
"ready": "You can restart now. The bigger the run, the bigger the reward.",
"need": "The next point comes at {need} {@theme.res.units} produced in total ({have} so far).",
"ready": "You can restart now. Waiting for a much bigger total before restarting earns more points at once.",
"restart": "Restart",
"confirm": "Start a new run? You keep research, legacy, achievements and statistics. You lose this run's production, funds, upgrades, investments and chips.",
"perks": "Perks",
+5 -4
View File
@@ -93,7 +93,8 @@
"contractFailed": "Contrat échoué : le temps est écoulé.",
"eventIncident": "Incident : {title}",
"achievement": "Succès : {title}",
"restarted": "Une nouvelle partie commence ! +{gain} ({@theme.res.legacy})"
"restarted": "Une nouvelle partie commence ! +{gain} ({@theme.res.legacy})",
"repaired": "Votre sauvegarde venait d'une ancienne version dont le bonus d'héritage pouvait s'emballer. Elle a été rééquilibrée : points et avantages d'héritage plafonnés, partie redémarrée. La recherche et les succès sont conservés."
},
"error": {
"import": {
@@ -237,15 +238,15 @@
}
},
"legacy": {
"intro": "Recommencez de zéro pour obtenir des {@theme.res.legacy} permanents. Chaque point donne aussi +{passive} % de production et de demande, pour toujours. Vous gardez la recherche, les avantages, les succès et les statistiques.",
"intro": "Recommencez de zéro pour obtenir des {@theme.res.legacy} permanents. Chaque point ajoute un bonus durable à la production et à la demande, mais avec des rendements décroissants : plus vous en avez, moins chaque nouveau point rapporte. Les points viennent de tout ce que vous avez déjà produit, donc chaque point suivant demande bien plus que le précédent. Vous gardez la recherche, les avantages, les succès et les statistiques.",
"earned": "Gagnés au total",
"restarts": "Redémarrages",
"passive": "Bonus passif",
"passiveValue": "×{bonus}",
"restartTitle": "Lancer une nouvelle partie",
"gain": "Redémarrer maintenant rapporte {gain} ({@theme.res.legacy}).",
"need": "Atteignez {need} ({@theme.res.units}) dans cette partie ({have} pour l'instant).",
"ready": "Vous pouvez redémarrer. Plus la partie est grosse, plus la récompense l'est.",
"need": "Le prochain point arrive à {need} ({@theme.res.units}) produits au total ({have} pour l'instant).",
"ready": "Vous pouvez redémarrer. Attendre un total bien plus grand avant de redémarrer rapporte plus de points d'un coup.",
"restart": "Redémarrer",
"confirm": "Lancer une nouvelle partie ? Vous gardez la recherche, l'héritage, les succès et les statistiques. Vous perdez la production, les fonds, les améliorations, les investissements et les jetons de cette partie.",
"perks": "Avantages",