diff --git a/src/engine.test.ts b/src/engine.test.ts index 147eeec..702e37d 100644 --- a/src/engine.test.ts +++ b/src/engine.test.ts @@ -136,7 +136,7 @@ describe('deepen_ritual cost scaling', () => { it('grows monotonically with each purchase, giving andakt a late-game sink', () => { let s = createState(); s.upgrades.add('grand_ritual_prep'); - s.ritualRate = 2.5; // baseline after torso_beacon + grand_ritual_prep + s.ritualRate = 0.5; // baseline after torso_beacon (0.15) + grand_ritual_prep (0.35) s.andakt = 1_000_000; s.dread = 1_000_000; diff --git a/src/engine.ts b/src/engine.ts index 6810f48..66a0822 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -180,7 +180,8 @@ export function tick(state: State): State { if (!s.pendingEvent && (s.kaos >= KAOS_THRESHOLD || s.tick >= s.nextKaosTick)) { const ev = pickEvent(KAOS_EVENTS, s); if (ev) { - s.usedEventIds.add(ev.id); + // Kaos events are chaos-relief flavor, not narrative progression — let them recur + // over a long game instead of permanently depleting a pool of only ~24 entries. if (ev.choices?.length) { pushLog(s, { tick: s.tick, text: `⚡ ${ev.text}`, kind: 'kaos' }); s.pendingEvent = { event: ev, kind: 'kaos' }; diff --git a/src/events.ts b/src/events.ts index 5136c05..b0e7be1 100644 --- a/src/events.ts +++ b/src/events.ts @@ -621,6 +621,76 @@ export const STANDARD_EVENTS: GameEvent[] = [ }, ], }, + + // ── High dread / late game ────────────────────────────────────────────────── + { + id: 'late_torso_lean', + text: 'Turning Torso lutar nu tre grader mer än vad byggnadskonstruktören godkänt. Ingenjörerna har mätt om det fem gånger. Alla fem mätningarna skiljer sig åt. Alla fem pekar mot samma punkt i Öresund.', + effect: { dread: 25, ritual: 4 }, + weight: 5, + requires: { minDread: 90 }, + }, + { + id: 'late_streetlights', + text: 'Gatlyktorna längs hela Amiralsgatan slocknar samtidigt varje natt klockan 03.13, i exakt tolv sekunder. Kommunen hittar inget fel. Ingen i Malmö sover mellan 03.00 och 03.15 längre, men ingen pratar om det.', + effect: { dread: 30, hemlighet: -6 }, + weight: 5, + requires: { minDread: 120 }, + }, + { + id: 'late_choir_beneath', + text: 'En kör hörs nu under staden, tydligast vid tunnelbanans arbetsschakt vid Triangeln. Arbetarna har slutat gräva djupare. De säger att marken "svarar för nära" om de fortsätter.', + effect: { dread: 35, ritual: 6 }, + weight: 4, + requires: { minDread: 160 }, + }, + { + id: 'late_sun_wrong', + text: 'Solen gick upp i väster i morse. Den korrigerade sig själv inom en minut. SMHI kallar det en "atmosfärisk synvilla." Ingen som såg det tror på den förklaringen, inklusive de som skrev den.', + effect: { dread: 40, kaos: 15 }, + weight: 4, + requires: { minDread: 200 }, + }, + { + id: 'late_children_hum', + text: 'Barnen på förskolan vid Bellevuegården har börjat nynna samma melodi, oberoende av varandra, utan att någon lärt dem den. Personalen har slutat fråga varifrån den kommer. De nynnar den nu också.', + effect: { dread: 45, hemlighet: -10, troende: 1 }, + weight: 4, + requires: { minDread: 250 }, + }, + { + id: 'late_sky_seam', + text: 'Himlen över Malmö har fått en söm. Den syns bara i skymningen, ett tunt rakt streck rakt över Turning Torso, som om något stort suttit ihop fel. Ingen har fotograferat den framgångsrikt än. Kamerorna vägrar.', + effect: { dread: 55, ritual: 8 }, + weight: 3, + requires: { minDread: 320 }, + }, + { + id: 'late_final_stillness', + text: 'Hela Malmö är alldeles tyst i tre sekunder klockan fyra på morgonen. Trafiken stannar. Fåglarna tystnar. Ingen märker det utom ni. Ni vet alla exakt vad det betydde. Ingen säger det högt.', + effect: { dread: 60, ritual: 10, hemlighet: -8 }, + weight: 3, + requires: { minDread: 400 }, + }, + { + id: 'choice_final_congregation', + text: 'En troende ni inte känner igen står plötsligt mitt i cirkeln under ett möte. Hon säger att hon "alltid varit här" och att ni bara "inte sett henne förrän nu." Ingen minns att hon klev in.', + effect: {}, + weight: 4, + requires: { minDread: 150 }, + choices: [ + { + text: 'Ge henne en plats i cirkeln', + effect: { troende: 1, dread: 20, ritual: 5 }, + resultText: 'Ni flyttar ihop för att göra plats. Det behövdes ingen extra stol. Det fanns redan en.', + }, + { + text: 'Fråga vem hon är', + effect: { hemlighet: -5, kaos: 10 }, + resultText: 'Hon svarar. Ingen av er minns svaret efteråt, men alla nickar som om det var uppenbart.', + }, + ], + }, ]; // ── Kaos events (Henderson-tier) ───────────────────────────────────────────── @@ -785,4 +855,44 @@ export const KAOS_EVENTS: GameEvent[] = [ weight: 5, requires: { minDread: 40 }, }, + { + id: 'k_ica_hoarding', + text: 'Alla ICA-butiker i Malmö har fått slut på konservburkar med fisk samtidigt. Kunderna köper allt de kan bära utan att kunna förklara varför. En kassörska säger att hyllorna "kändes hungrigare än vanligt."', + effect: { kaos: -30, andakt: 15, troende: 1 }, + weight: 8, + }, + { + id: 'k_pigeon_choir', + text: 'Duvorna över hela innerstaden har synkroniserat sitt kuttrande till en enda rytm. Ornitologer vid universitetet kallar det "statistiskt osannolikt." Ingen av dem vågar kalla det något annat.', + effect: { kaos: -35, dread: 20 }, + weight: 7, + }, + { + id: 'k_bridge_toll_glitch', + text: 'Betalstationen vid Öresundsbron har börjat ta betalt i en valuta ingen känner igen. Systemet accepterar den ändå. Bokföringen går ihop perfekt. Ingen vill fråga hur.', + effect: { kaos: -45, andakt: 30, dread: 25 }, + weight: 6, + requires: { minDread: 60 }, + }, + { + id: 'k_missing_hour', + text: 'Klockan blev plötsligt två timmar senare än den borde för alla i Malmö samtidigt, utan att någon märkte tidshoppet medan det hände. Ingen saknar den förlorade timmen. Det är det som oroar mest.', + effect: { kaos: -70, dread: 65, ritual: 7 }, + weight: 3, + requires: { minDread: 100 }, + }, + { + id: 'k_second_torso', + text: 'En andra Turning Torso syns i horisonten i tio minuter varje kväll, en spegelbild som inte har någon vattenyta att spegla sig i. Turistbyrån har börjat sälja vykort på den. De säljer bra.', + effect: { kaos: -75, dread: 70, ritual: 10 }, + weight: 2, + requires: { minDread: 180 }, + }, + { + id: 'k_everyone_dreams_same', + text: 'Hela Malmö drömde samma dröm i natt. Ingen minns innehållet, bara att den var er. Sömnforskare vid Lund har ingen förklaring. De har slutat publicera sina fynd om natten.', + effect: { kaos: -65, troende: 2, ritual: 12, hemlighet: -12 }, + weight: 3, + requires: { minDread: 220 }, + }, ]; diff --git a/src/upgrades.ts b/src/upgrades.ts index a5fcd56..614199a 100644 --- a/src/upgrades.ts +++ b/src/upgrades.ts @@ -78,8 +78,8 @@ export const UPGRADES: Upgrade[] = [ id: 'torso_beacon', name: 'Altaret vid Turning Torso', desc: 'Calatrava-Santiago ritade byggnaden som en vridande ryggrad. Det är korrekt. Det är inte en metafor. Byggnaden har alltid lyssnat. Nu ber vi den svara. Den gör det. Svaret är ett ljud under hörselgränsen som vi känner i tänderna.', - cost: () => ({ andakt: 400, dread: 100 }), - effect: s => { s.areas.torso = true; s.dreadRate += 3; s.ritualRate += 0.5; }, + cost: () => ({ andakt: 500, dread: 100 }), + effect: s => { s.areas.torso = true; s.dreadRate += 3; s.ritualRate += 0.15; }, available: s => s.upgrades.has('varnhem_circle') && s.dread >= 100 && !s.upgrades.has('torso_beacon'), once: true, }, @@ -87,22 +87,40 @@ export const UPGRADES: Upgrade[] = [ id: 'grand_ritual_prep', name: 'Förberedelser för Den Stora Ritualen', desc: 'Allt har lett hit. Turning Torso pekar. Värnhemstorget svarar. Öresund drar sig tillbaka som om det vet vad som kommer. Skriften är nu fullständig. Den sista sidan skrevs av sig själv i natt. Det är ett recept.', - cost: () => ({ andakt: 600, dread: 200 }), - effect: s => { s.ritualRate += 2; s.kaosRate += 0.5; }, + cost: () => ({ andakt: 900, dread: 250 }), + effect: s => { s.ritualRate += 0.35; s.kaosRate += 0.5; }, available: s => s.upgrades.has('torso_beacon') && s.ritual >= 50 && !s.upgrades.has('grand_ritual_prep'), once: true, }, + { + id: 'sunken_choir', + name: 'Den sjunkna kören', + desc: 'Rösterna under Öresund har börjat svara i takt med era. Ni behöver bara lyssna tillbaka. Det underlättar. Det är också svårt att sluta göra.', + cost: () => ({ andakt: 1400, dread: 300 }), + effect: s => { s.ritualRate += 0.25; s.andaktRate += 1; }, + available: s => s.upgrades.has('grand_ritual_prep') && s.dread >= 300 && !s.upgrades.has('sunken_choir'), + once: true, + }, + { + id: 'nine_chairs_full', + name: 'Alla nio stolarna fyllda', + desc: 'Källarens nio stolar har äntligen alla sina rätta innehavare. Den varma stolen är också upptagen nu. Ingen pratar om vem som sitter i den.', + cost: () => ({ andakt: 2200, dread: 350 }), + effect: s => { s.ritualRate += 0.25; s.kaosRate += 0.3; }, + available: s => s.upgrades.has('sunken_choir') && s.troende >= 9 && !s.upgrades.has('nine_chairs_full'), + once: true, + }, { id: 'deepen_ritual', name: 'Fördjupa ritualen', desc: 'Ännu ett lager av mening under det förra. Andakt är inget värt om det bara samlas i en burk. Varje fördjupning kostar mer än den förra — Den Sovande blir kräsnare ju närmre ni kommer.', - cost: s => ({ andakt: Math.round(300 * Math.pow(1.6, ritualDeepenings(s))), dread: 50 }), - effect: s => { s.ritualRate += 0.3; }, + cost: s => ({ andakt: Math.round(350 * Math.pow(1.55, ritualDeepenings(s))), dread: 60 }), + effect: s => { s.ritualRate += 0.15; }, available: s => s.upgrades.has('grand_ritual_prep'), once: false, }, ]; function ritualDeepenings(s: State): number { - return Math.round((s.ritualRate - 2.5) / 0.3); + return Math.round((s.ritualRate - 0.5) / 0.15); }