Add real replay variety: 15 new letters, random per-day roster
All checks were successful
Docker / build-and-push (push) Successful in 36s
All checks were successful
Docker / build-and-push (push) Successful in 36s
Every playthrough showed the exact same fixed 30 letters in the exact same order — no randomization anywhere in the codebase. Added 15 new letters (3 per day, l31-l45), bringing each day's pool to 9, and changed the engine to draw PER_DAY_COUNT (6) at random from that pool per game instead of always using the same fixed set. - engine.ts: buildRoster() shuffles each day's pool and takes the first 6; GameState now carries a `roster` (letter ids in day order) generated once at createState() and persisted with the rest of the save. lettersForDay/currentLetter read through the roster instead of the full LETTERS array directly. - Save key bumped to v3 — old saves have no roster field, and backfilling one wouldn't match already-recorded decisions, so old saves are simply ignored rather than partially migrated. - Tests rewritten: several previously asserted specific letter ids at specific positions, which no longer holds under randomization. Split into buildRoster() property tests (right count, no dupes, every id resolves, different seeds differ) and engine tests that either check general correctness or construct an explicit roster when a specific letter's behavior needs locking in (l22, l04). Verified: 11/11 vitest, tsc clean, and a real 5-fresh-games browser test confirming 5 different first-letter senders instead of the same one every time. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -339,6 +339,185 @@ export const LETTERS: Letter[] = [
|
||||
},
|
||||
penalty: { deliver: { composure: 2, suspicion: 2 }, return: { suspicion: 2 }, archive: { composure: 2 } },
|
||||
},
|
||||
|
||||
// ---- Extra pool, day 1 (drawn at random alongside l01–l04, l21–l22) ----
|
||||
{
|
||||
id: 'l31', day: 1, sender: 'Handelsbanken', recipient: 'Nils Öberg', address: 'Baltzarsgatan 22, Malmö', postmark: '2026-07-21',
|
||||
body: 'Kontoutdrag för juni bifogas. Vänligen kontrollera att alla transaktioner stämmer.', marks: ['Porto betalt'],
|
||||
correctRoute: 'deliver', explanation: 'Fullständig adress och nutida mottagare. §1.1.',
|
||||
correctConsequence: 'Levererat. Ingen reklamation inkom, vilket i sig inte betyder att allt stämde.',
|
||||
wrongConsequences: {
|
||||
return: 'Returnerat trots giltig adress. Banken skickade en identisk kopia veckan efter.',
|
||||
archive: 'Katalogiserat i onödan. Ett kontoutdrag är inte ett historiskt ärende.',
|
||||
quarantine: 'Spärrat ett kontoutdrag. Handelsbanken hörde av sig och undrade varför.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'l32', day: 1, sender: 'Mårten Kvist', recipient: 'Sonja Kvist', address: 'Lägenheten på tredje, ingen gata angiven', postmark: '2026-07-21',
|
||||
body: 'Jag skrev adressen för fort. Du vet ändå var jag menar.', marks: ['Adress saknas'],
|
||||
correctRoute: 'return', explanation: 'Ogiltig adress, avsändaren finns. §1.4.',
|
||||
correctConsequence: 'Returnerat. Mårten skickade det igen, med samma adress.',
|
||||
wrongConsequences: {
|
||||
deliver: 'Utan giltig adress levererades det ändå, till "tredje våningen" på fel gata.',
|
||||
archive: 'Katalogiserat trots att det inte finns någon post att katalogisera under.',
|
||||
quarantine: 'Spärrat ett brev om en glömd adress. Ingenting ovanligt fanns att spärra.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'l33', day: 1, sender: 'Elna Vig', recipient: 'Systrarna Vig', address: 'Djäknegatan 3', postmark: '1889-04-17',
|
||||
body: 'Mor är sämre. Doktorn kommer inte förrän på onsdag. Skriv snart.', marks: ['SEGELFARTYGSFRIMÄRKE'],
|
||||
correctRoute: 'archive', explanation: 'Poststämpeln är äldre än femtio år. §2.2.',
|
||||
correctConsequence: 'Arkiverat. Ingen doktor nämns i något senare ärende från samma adress.',
|
||||
wrongConsequences: {
|
||||
deliver: 'Levererat till en adress som varit parkeringshus sedan 1962.',
|
||||
return: 'Avsändaren har inte funnits på hundra år. Returen kom ändå tillbaka, oöppnad, från rätt håll.',
|
||||
quarantine: 'Spärrat ett gammalt sjukdomsbud. Ingenting i det motiverade karantän.',
|
||||
},
|
||||
},
|
||||
|
||||
// ---- Extra pool, day 2 ----
|
||||
{
|
||||
id: 'l34', day: 2, sender: 'Vårdcentralen Möllevången', recipient: 'Ruth Ekwall (1930–2019)', address: 'Simrishamnsgatan 6', postmark: '2026-07-22',
|
||||
body: 'Kallelse till årlig hälsokontroll. Vi ser fram emot ert besök.', marks: ['PÅMINNELSE'],
|
||||
correctRoute: 'archive', explanation: 'Mottagaren är registrerad som avliden. §4.3.',
|
||||
correctConsequence: 'Arkiverat. Vårdcentralen ringde själva och frågade varför ingen svarat på kallelsen.',
|
||||
wrongConsequences: {
|
||||
deliver: 'Levererat. Grannen skrev senare att brevlådan öppnats och stängts av sig själv.',
|
||||
return: 'Returnerat trots att mottagaren är avliden — vårdcentralens system har redan noterat det.',
|
||||
quarantine: 'Spärrat en rutinmässig hälsokallelse i onödan.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'l35', day: 2, sender: 'Ruth Ekwall (1930–2019)', recipient: 'Vårdcentralen Möllevången', address: 'Docentgatan 1', postmark: '2026-07-22',
|
||||
body: 'Jag avbokar. Jag har redan varit där en gång för mycket.', marks: ['DOFT AV JORD'],
|
||||
correctRoute: 'quarantine', explanation: 'Den avlidne är avsändare. §4.7 har företräde framför returregeln.',
|
||||
correctConsequence: 'Spärrat. Vårdcentralen strök tiden ur systemet utan att någon ringde och frågade varför.',
|
||||
wrongConsequences: {
|
||||
deliver: 'Levererat till vårdcentralen, som bokade om till en ny tid nästa månad.',
|
||||
return: 'Returnerat till en avliden avsändare. Adressen finns inte längre, det gör inte returen heller.',
|
||||
archive: 'Katalogiserat som historia. §4.7 kräver karantän, inte arkiv, när den avlidne själv skriver.',
|
||||
},
|
||||
penalty: { deliver: { suspicion: 2 } },
|
||||
},
|
||||
{
|
||||
id: 'l36', day: 2, sender: '— SAKNAS —', recipient: '— SAKNAS —', address: '', postmark: '2026-07-22',
|
||||
body: 'Ett tomt kuvert. Inuti ligger bara en nyckel utan bricka.', marks: ['BÅDA SAKNAS'],
|
||||
correctRoute: 'quarantine', explanation: 'Både avsändare och adress saknas. §4.9.',
|
||||
correctConsequence: 'Spärrat. Nyckeln passar inget lås som finns registrerat i huset.',
|
||||
wrongConsequences: {
|
||||
deliver: 'Utan mottagare levererades kuvertet ändå, till sorteringsbordet självt.',
|
||||
return: 'Utan avsändare gick det ingenstans. Det kom tillbaka till dig nästa morgon.',
|
||||
archive: 'Katalogiserat utan att det finns något att katalogisera nyckeln under.',
|
||||
},
|
||||
},
|
||||
|
||||
// ---- Extra pool, day 3 ----
|
||||
{
|
||||
id: 'l37', day: 3, sender: 'Föreningen Solrosen', recipient: 'Medlemmarna', address: 'Ystadsvägen -12', postmark: '2026-07-23',
|
||||
body: 'Kallelse till vårstädning på gården. Ta med egna handskar.', marks: ['CIRKULÄR'],
|
||||
correctRoute: 'return', explanation: 'Negativt gatunummer är ogiltigt. §5.2.',
|
||||
correctConsequence: 'Returnerat. Föreningen skrev tillbaka och undrade om gatunumret var en felskrivning. Det var det inte.',
|
||||
wrongConsequences: {
|
||||
deliver: 'Levererat till Ystadsvägen -12. Ingen av medlemmarna kunde senare beskriva var gården låg.',
|
||||
archive: 'Katalogiserat i onödan. Ett negativt gatunummer avfärdas, det arkiveras inte.',
|
||||
quarantine: 'Spärrat en kallelse till vårstädning. Föreningen fick aldrig sitt svar.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'l38', day: 3, sender: 'Avdelning Noll', recipient: 'Samtlig personal', address: 'Alla internfack', postmark: '2026-07-23',
|
||||
body: 'Påminnelse: internpost från Avdelning Noll ska alltid vidare, oavsett mottagarens invändningar.', marks: ['INTERNPOST', '0', 'CIRKULÄR'],
|
||||
correctRoute: 'deliver', explanation: 'Internpost från Avdelning Noll går alltid vidare. §5.9.',
|
||||
correctConsequence: 'Vidarebefordrat till samtlig personal. Ingen bekräftade mottagandet, vilket tydligen är normalt.',
|
||||
wrongConsequences: {
|
||||
return: 'Returen nekades — Avdelning Noll finns inte, så det fanns ingen adress att skicka den till.',
|
||||
archive: 'Katalogiserat som cirkulär i stället för att skickas vidare enligt §5.9.',
|
||||
quarantine: 'Spärrat internpost från Avdelning Noll. Skåpet vägrade stänga hela natten.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'l39', day: 3, sender: 'Malmö Stadsarkiv', recipient: 'Läsesalen', address: 'Rådhuset, plan 2', postmark: '2026-07-23',
|
||||
body: 'Begärd handling återfinns inte i katalogen. Den återfinns inte heller i bortfallslistan.', marks: ['EFTERLYSNING'],
|
||||
correctRoute: 'archive', explanation: 'Katalogiserad avsaknad räknas som känd post utan ny avvikelse. Ingen ny föreskrift gäller — standardhantering.',
|
||||
correctConsequence: 'Katalogiserat. Handlingen förblir lika saknad som innan, nu med en officiell notering om det.',
|
||||
wrongConsequences: {
|
||||
deliver: 'Levererat till läsesalen. De väntar fortfarande på en handling som inte finns.',
|
||||
return: 'Returnerat till stadsarkivet, som inte kan ta emot något de redan saknar.',
|
||||
quarantine: 'Spärrat en efterlysning om en saknad handling. Ingenting i det motiverar karantän.',
|
||||
},
|
||||
},
|
||||
|
||||
// ---- Extra pool, day 4 ----
|
||||
{
|
||||
id: 'l40', day: 4, sender: 'Ingrid Sand', recipient: 'Ingrid Sand', address: 'Nobelvägen 34', postmark: '2026-07-24',
|
||||
body: 'Kom ihåg: du bad mig skriva detta åt dig, för du litade inte på att du skulle minnas.', marks: ['TEXTEN HAR ÄNDRATS'],
|
||||
correctRoute: 'quarantine', explanation: 'Texten skiljer sig mellan läsningar. §6.6.',
|
||||
correctConsequence: 'Spärrat. Andra gången du tittade på kuvertet stod avsändaren med annan handstil.',
|
||||
wrongConsequences: {
|
||||
deliver: 'Levererat till sig själv. Ingrid ringde och sa att hon aldrig skrivit det.',
|
||||
return: 'Returnerat till Ingrid, som redan hade brevet — i en annan version.',
|
||||
archive: 'Katalogiserat med en beskrivning som redan hunnit ändras innan bläcket torkat.',
|
||||
},
|
||||
penalty: { deliver: { suspicion: 2 } },
|
||||
},
|
||||
{
|
||||
id: 'l41', day: 4, sender: 'Kriminalvården', recipient: 'Arkiv 9', address: 'Internleverans', postmark: '2026-07-24',
|
||||
body: 'Överlämning av handlingar rörande en intern som friskrevs från ett register hen aldrig stod i.', marks: ['SVART STÄMPEL', 'SVART STÄMPEL', 'SVART STÄMPEL'],
|
||||
correctRoute: 'archive', explanation: 'Tre svarta stämplar innebär Arkiv 9, och texten har inte ändrats. §6.8.',
|
||||
correctConsequence: 'Arkiverat. Registret personen friskrevs från existerar fortfarande, bara inte på papper.',
|
||||
wrongConsequences: {
|
||||
deliver: 'Levererat i stället för arkiverat. Tre svarta stämplar pekar entydigt mot §6.8.',
|
||||
return: 'Returnerat till Kriminalvården, som inte kan ta emot handlingar om ett register de förnekar.',
|
||||
quarantine: 'Spärrat utan att texten ändrats mellan läsningar. §6.6 gäller inte här.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'l42', day: 4, sender: 'Dig själv, om en timme', recipient: 'Dig själv, nu', address: 'Sorteringsbordet', postmark: '2026-07-24',
|
||||
body: 'Första läsningen sa ingenting om det här. Nu säger den att du redan bestämt dig. Det gjorde du.', marks: ['TEXTEN HAR ÄNDRATS', 'SVART STÄMPEL', 'SVART STÄMPEL', 'SVART STÄMPEL'],
|
||||
correctRoute: 'quarantine', explanation: 'Textförändring har uttryckligt företräde framför trestämpelregeln. §6.6.',
|
||||
correctConsequence: 'Spärrat. En timme senare stod exakt samma text där, orörd, som om den väntat på att bli rätt.',
|
||||
wrongConsequences: {
|
||||
archive: 'Arkiverat enligt trestämpelregeln. §6.6 säger uttryckligen att textförändring går före.',
|
||||
deliver: 'Levererat till dig själv, en timme för tidigt enligt avsändarraden.',
|
||||
return: 'Returnerat till en avsändare som ännu inte skrivit klart brevet när det stämplades.',
|
||||
},
|
||||
penalty: { archive: { suspicion: 2 } },
|
||||
},
|
||||
|
||||
// ---- Extra pool, day 5 ----
|
||||
{
|
||||
id: 'l43', day: 5, sender: 'Rådhuset 2039', recipient: 'Rådhuset 2026', address: 'Stortorget 1', postmark: '2039-11-02',
|
||||
body: 'Bekräftelse: renoveringen av plan fyra är nu slutförd. Den planerades aldrig.', marks: ['FRAMTIDA'],
|
||||
correctRoute: 'return', explanation: 'Framtida post återsänds enligt §7.1.',
|
||||
correctConsequence: 'Returnerat tretton år i förväg. Ingen renovering av plan fyra finns i några ritningar, ännu.',
|
||||
wrongConsequences: {
|
||||
deliver: 'Levererat till Rådhuset, som inte planerar att renovera plan fyra förrän frågan väcks 2039.',
|
||||
archive: 'Katalogiserat som om händelsen redan inträffat. Renoveringen har inte ens planerats än.',
|
||||
quarantine: 'Spärrat utan grund. §7.1 kräver retur för framtida post, inte karantän.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'l44', day: 5, sender: 'Du, om tio minuter', recipient: 'Du, nu', address: 'Sorteringsbordet', postmark: '2026-07-27',
|
||||
body: 'Du kommer läsa det här brevet två gånger. Den andra gången förstår du varför luckan stängs tidigt idag.', marks: ['IMORGON', 'PERSONLIGT'],
|
||||
correctRoute: 'quarantine', explanation: 'Framtida post som beskriver den pågående arbetsdagen omfattas av §7.4.',
|
||||
correctConsequence: 'Spärrat efter första läsningen, som instruktionen kräver. Luckan stängdes exakt tio minuter tidigare än vanligt.',
|
||||
wrongConsequences: {
|
||||
deliver: 'Levererat till dig själv. Du läste det en andra gång av misstag och förstod för mycket.',
|
||||
return: 'Returnerat till avsändaren — dig, om tio minuter. Ingen kvittens hann komma tillbaka.',
|
||||
archive: 'Katalogiserat som redan avslutat. Klockan sa något annat.',
|
||||
},
|
||||
penalty: { deliver: { composure: 2, suspicion: 2 } },
|
||||
},
|
||||
{
|
||||
id: 'l45', day: 5, sender: 'Föreståndaren, för länge sedan', recipient: 'Vem som än sitter här sist', address: 'Sorteringsbordet', postmark: '1971-07-25',
|
||||
body: 'Om luckan någonsin stängs helt, se till att den sista posten stämplas rätt. Det är allt jag ber om.', marks: ['PERSONALAKT', 'SVART STÄMPEL', 'SVART STÄMPEL', 'SVART STÄMPEL'],
|
||||
correctRoute: 'archive', explanation: 'Äldre än femtio år, tre svarta stämplar, och texten har inte ändrats. §6.8 och §2.2 är överens.',
|
||||
correctConsequence: 'Arkiverat, bredvid alla andra föreståndares sista ord. Det är fler än det borde finnas rum för.',
|
||||
wrongConsequences: {
|
||||
deliver: 'Levererat till "vem som än sitter här sist". Det är du. Du visste inte det förrän nu.',
|
||||
return: 'Returnerat till 1971. Ingen föreståndare från den tiden finns kvar att ta emot det.',
|
||||
quarantine: 'Spärrat trots att varken ålder eller stämplar kräver det. §6.8 räcker här.',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const DAY_BRIEFINGS = [
|
||||
|
||||
Reference in New Issue
Block a user