Add Inflytande resource, expand events, and balance the endgame
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
co-authored by Claude Sonnet 5
parent fd457ff312
commit 0efbe9d55b
8 changed files with 875 additions and 30 deletions
+15 -6
View File
@@ -63,6 +63,11 @@ export function initUI(app: HTMLElement, onUpgrade: (id: string) => void, onRest
<div class="resource-value" id="r-andakt">0</div>
</div>
<div class="resource">
<div class="resource-label">Inflytande</div>
<div class="resource-value" id="r-inflytande">0</div>
</div>
<div class="resource">
<div class="resource-label">Dread <span id="r-dread-rate" style="color:var(--dread);font-size:10px;"></span></div>
<div class="resource-value" style="color:var(--dread);" id="r-dread">0</div>
@@ -145,6 +150,7 @@ export function renderState(state: State): void {
setText('r-troende', String(state.troende));
setText('r-andakt', fmt(state.andakt));
setText('r-andakt-rate', `+${(state.andaktRate * state.troende).toFixed(1)}/s`);
setText('r-inflytande', fmt(state.inflytande));
setText('r-dread', fmt(state.dread));
setText('r-dread-rate', state.dreadRate > 0 ? `+${state.dreadRate.toFixed(1)}/s` : '');
setText('r-hemlighet', Math.floor(state.hemlighet).toString());
@@ -179,7 +185,7 @@ export function renderState(state: State): void {
if (state.hemlighet < 20) {
inv.textContent = 'Nära — väldigt nära'; inv.style.color = 'var(--danger)';
} else if (state.hemlighet < 50) {
inv.textContent = 'Missstänksamma'; inv.style.color = 'var(--kaos)';
inv.textContent = 'Misstänksamma'; inv.style.color = 'var(--kaos)';
} else {
inv.textContent = 'Inaktiva'; inv.style.color = 'var(--safe)';
}
@@ -202,8 +208,9 @@ function renderUpgrades(state: State): void {
// Only rebuild DOM when availability or affordability changes
const key = available.map(u => {
const canAfford = (!u.cost.andakt || state.andakt >= u.cost.andakt) && (!u.cost.dread || state.dread >= u.cost.dread);
return `${u.id}:${canAfford}`;
const cost = u.cost(state);
const canAfford = (!cost.andakt || state.andakt >= cost.andakt) && (!cost.dread || state.dread >= cost.dread) && (!cost.inflytande || state.inflytande >= cost.inflytande);
return `${u.id}:${canAfford}:${cost.andakt ?? ''}`;
}).join('|');
if (key === lastUpgradeKey) return;
@@ -215,10 +222,12 @@ function renderUpgrades(state: State): void {
}
container.innerHTML = available.map(u => {
const canAfford = (!u.cost.andakt || state.andakt >= u.cost.andakt) && (!u.cost.dread || state.dread >= u.cost.dread);
const cost = u.cost(state);
const canAfford = (!cost.andakt || state.andakt >= cost.andakt) && (!cost.dread || state.dread >= cost.dread) && (!cost.inflytande || state.inflytande >= cost.inflytande);
const costs = [
u.cost.andakt ? `${u.cost.andakt} andakt` : '',
u.cost.dread ? `${u.cost.dread} dread` : '',
cost.andakt ? `${cost.andakt} andakt` : '',
cost.dread ? `${cost.dread} dread` : '',
cost.inflytande ? `${cost.inflytande} inflytande` : '',
].filter(Boolean).join(', ');
return `<button class="upgrade-btn" data-upgrade="${u.id}" ${canAfford ? '' : 'disabled'}>
<span class="up-name">${u.name}</span>