Add Inflytande resource, expand events, and balance the endgame
All checks were successful
Docker / build-and-push (push) Successful in 42s

Adds an influence currency that scales with follower count and a new
"exert influence" action to trade it for secrecy, uncaps recruiting
with scaling cost, adds a late-game Andakt sink, and makes hemlighet
drain scale with dread/ritual progress so the endgame carries real
risk. Includes a Vitest suite that simulates play to check balance,
which caught the influence-accrual rate being too slow to be useful
early game.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Fredrik Johansson
2026-07-08 17:28:32 +02:00
parent fd457ff312
commit 0efbe9d55b
8 changed files with 875 additions and 30 deletions

View File

@@ -6,6 +6,8 @@ export interface State {
troende: number;
andakt: number;
andaktRate: number; // per second
inflytande: number;
inflytandeRate: number; // multiplier on the base network-effect curve
dread: number;
dreadRate: number; // per second
hemlighet: number; // 0-100, lose at 0
@@ -31,12 +33,15 @@ export interface LogEntry {
}
// Bump this when State shape changes to invalidate old saves
const SAVE_VERSION = '001';
const SAVE_VERSION = '002';
const SAVE_KEY = `djupet_v${SAVE_VERSION}`;
const EVENT_INTERVAL_TICKS = 180; // ~6s at 30fps
const KAOS_THRESHOLD = 100;
const HEMLIGHET_DRAIN_PER_TROENDE = 0.002; // per second, per troende above 4
const DREAD_HEMLIGHET_DRAIN = 0.35; // per second, saturating with dread — the deeper it goes, the more it's noticed
const RITUAL_HEMLIGHET_DRAIN = 0.25; // per second at ritual=100 — an active ritual is hard to hide
const INFLYTANDE_BASE_RATE = 0.15; // per second, scales with troende^1.2 — a network effect
export function createState(): State {
return {
@@ -44,6 +49,8 @@ export function createState(): State {
troende: 1,
andakt: 10,
andaktRate: 0.3,
inflytande: 0,
inflytandeRate: 1,
dread: 0,
dreadRate: 0,
hemlighet: 100,
@@ -102,6 +109,7 @@ export function tick(state: State): State {
// Resource accumulation
s.andakt += s.andaktRate * s.troende * DT;
s.inflytande += INFLYTANDE_BASE_RATE * s.inflytandeRate * Math.pow(s.troende, 1.2) * DT;
s.dread += s.dreadRate * DT;
if (s.ritualUnlocked) s.ritual = Math.min(100, s.ritual + s.ritualRate * DT);
@@ -109,10 +117,11 @@ export function tick(state: State): State {
const kaosScale = 1 + (s.dread / 200);
s.kaos = Math.min(100, s.kaos + s.kaosRate * kaosScale * DT);
// Hemlighet drain — more believers = harder to stay hidden
if (s.troende > 4) {
s.hemlighet = Math.max(0, s.hemlighet - HEMLIGHET_DRAIN_PER_TROENDE * (s.troende - 4) * DT);
}
// Hemlighet drain — more believers, more dread, and a deepening ritual all draw attention
const troendeDrain = s.troende > 4 ? HEMLIGHET_DRAIN_PER_TROENDE * (s.troende - 4) : 0;
const dreadDrain = DREAD_HEMLIGHET_DRAIN * (s.dread / (s.dread + 150));
const ritualDrain = s.ritualUnlocked ? RITUAL_HEMLIGHET_DRAIN * (s.ritual / 100) : 0;
s.hemlighet = Math.max(0, s.hemlighet - (troendeDrain + dreadDrain + ritualDrain) * DT);
// Standard event
if (s.tick >= s.nextEventTick) {
@@ -266,12 +275,15 @@ export function buyUpgrade(state: State, id: string): State {
const upgrade = UPGRADES.find(u => u.id === id);
if (!upgrade) return state;
if (!upgrade.available(state)) return state;
if (upgrade.cost.andakt && state.andakt < upgrade.cost.andakt) return state;
if (upgrade.cost.dread && state.dread < upgrade.cost.dread) return state;
const cost = upgrade.cost(state);
if (cost.andakt && state.andakt < cost.andakt) return state;
if (cost.dread && state.dread < cost.dread) return state;
if (cost.inflytande && state.inflytande < cost.inflytande) return state;
const s = { ...state, areas: { ...state.areas }, upgrades: new Set(state.upgrades), log: [...state.log], usedEventIds: new Set(state.usedEventIds) };
if (upgrade.cost.andakt) s.andakt -= upgrade.cost.andakt;
if (upgrade.cost.dread) s.dread -= upgrade.cost.dread;
if (cost.andakt) s.andakt -= cost.andakt;
if (cost.dread) s.dread -= cost.dread;
if (cost.inflytande) s.inflytande -= cost.inflytande;
upgrade.effect(s);
if (upgrade.once) s.upgrades.add(id);
s.log.unshift({ tick: s.tick, text: `${upgrade.name}: ${upgrade.desc}`, kind: 'upgrade' });