// Daily newspaper: headlines picked from templates by how much the indicators // moved over the last few weeks. Selection is deterministic (hash of week + key) // and never touches the game RNG, so reading the paper can't change the game. import { t, tx, getLang, dateParts } from "../i18n/i18n.js"; import { IND_IDS, dateOfDay } from "../engine/index.js"; import { hash, $ } from "./desk.js"; const IND_THRESHOLD = 4; // points moved over the window to make news const VAR_THRESHOLD = { growth: 0.4, unemployment: 0.4, inflation: 0.5, debtRatio: 3 }; // Each entry: [template key, test on the live state, severity score] const LEVELS = [ ["unrest.high", (s, f) => s.indicators.unrest >= 50, 1.7], ["opinion.low", (s, f) => s.indicators.opinion <= 30, 1.6], ["debt.high", (s, f) => f.debtRatio >= 100, 1.6], ["stability.low", (s, f) => s.indicators.stability <= 35, 1.6], ["civilRights.low", (s, f) => s.indicators.civilRights <= 25, 1.5], ["army.low", (s, f) => s.indicators.armyLoyalty <= 35, 1.7], ["inflation.high", (s, f) => s.economy.inflation >= 6, 1.5], ["deficit.high", (s, f) => f.deficit >= 8, 1.5], ["world.slump", (s) => (s.world?.cycle ?? 0) <= -1.8, 1.4], ["world.boom", (s) => (s.world?.cycle ?? 0) >= 1.8, 1.2], ]; function candidates(state, tpl) { const out = []; const h = state.history; const n = h.t.length; if (n >= 2) { const back = Math.min(4, n - 1); const consider = (group, id, threshold) => { const s = h.series[id]; const d = s[n - 1] - s[n - 1 - back]; const dir = d > 0 ? "up" : "down"; const tpls = tpl[group][id]?.[dir]; if (tpls && Math.abs(d) / threshold >= 1) out.push({ score: Math.abs(d) / threshold, key: `${group}.${id}.${dir}`, tpls }); }; for (const id of IND_IDS) consider("ind", id, IND_THRESHOLD); for (const [id, thr] of Object.entries(VAR_THRESHOLD)) consider("var", id, thr); } const debtRatio = h.series.debtRatio?.[n - 1] ?? 0; const deficit = h.series.deficit?.[n - 1] ?? 0; for (const [key, test, score] of LEVELS) { if (tpl.level[key] && test(state, { debtRatio, deficit })) out.push({ score, key: `level.${key}`, tpls: tpl.level[key] }); } return out.sort((a, b) => b.score - a.score); } export function computeHeadlines(state, tpl) { const week = Math.floor(state.day / 7); const pick = (tpls, key) => tpls[hash(`${key}:${week}`) % tpls.length]; if (state.history.t.length < 2 && state.day === 0) return [pick(tpl.intro, "intro")]; const found = candidates(state, tpl); if (!found.length) return [pick(tpl.quiet, "quiet")]; return found.slice(0, 3).map((c) => pick(c.tpls, c.key)); } export function createNewspaper(root, tpl) { root.innerHTML = `

${tx(tpl.name)}

`; let shown = ""; function update(state) { // The date line follows the calendar; headlines only change with the weekly history sample. $(root, ".news__edition").textContent = t("news.edition", { date: dateParts(dateOfDay(state.startDate, state.day).iso).full }); const week = Math.floor(state.day / 7); const key = `${week}|${getLang()}|${state.history.t.length}`; if (key === shown) return; shown = key; root.classList.remove("is-fresh"); void root.offsetWidth; // restart the fade root.classList.add("is-fresh"); const fill = (s) => s.replaceAll("{country}", state.countryName); const [lead, ...rest] = computeHeadlines(state, tpl); $(root, ".news__lead h3").textContent = fill(tx(lead.h)); $(root, ".news__lead p").textContent = fill(tx(lead.d)); const side = $(root, ".news__side"); side.replaceChildren(...rest.map((item) => { const a = document.createElement("article"); const h = document.createElement("h4"); const p = document.createElement("p"); h.textContent = fill(tx(item.h)); p.textContent = fill(tx(item.d)); a.append(h, p); return a; })); } return { update }; }