// casegen/junk.js — фейкові сліди, дрібниці, плітки const { pick, shuffle, chance } = require('./rng'); const N = require('./names'); function setupJunk(ctx) { const { r, citizens, victim, killer, herring, caseType, district, add, E, g, witnessFrom, dna2 } = ctx; const nb = () => witnessFrom(citizens); const dogOwner = citizens.find(c => c.dog && c.id !== victim.id); const junkTexts = shuffle(r, [ { t: 'Чек із бару', d: 'Два еспресо і коньяк, 21:47. На звороті олівцем — номер телефону без імені. Половина цифр затерта.' }, { t: 'Стара запальничка', d: `Бензинова, з гравіюванням «${pick(r, ['С.', 'М.', 'В.', 'Т.'])}». Давно не заправлялась.` }, { t: 'Флаєр', d: '«Ремонт взуття за годину». Усі відривні номери відірвані — популярний швець.' }, { t: 'Порожня пляшка', d: 'З-під дешевого вина, ще липка. Поруч — два пластикові стаканчики.' }, { t: 'Список покупок', d: 'Молоко, батарейки, свічки, скотч. Почерк квапливий.' }, { t: 'Одна рукавичка', d: 'Шкіряна, права, добротна. Ліва, вочевидь, досі гріє чиюсь кишеню.' }, { t: 'Прострочена квитанція', d: `Комуналка на ім'я «${nb().name}», третє нагадування. Хтось не любить платити вчасно.` }, { t: 'Записник із адресами', d: `Половина сторінок вирвана. Серед уцілілих записів — «${nb().name}» і закреслений номер.` }, { t: 'Білети в кіно', d: 'Два білети двомісячної давності, сеанс 21:30, місця поруч. І обгортка від шоколаду.' }, { t: 'Іржавий ключ', d: 'Не підходить до жодних дверей поблизу. Хтось носив його «про всяк випадок» роками.' }, { t: 'Оголошення про пса', d: dogOwner ? `«Загубився пес на кличку ${dogOwner.dog}. Винагорода.» Судячи з дати — пес давно вдома.` : '«Загубився пес. Винагорода.» Без прикмет — оптимістично.' }, { t: 'Стос старих газет', d: 'В одній обведено оголошення про оренду квартири. Олівець аж продавив папір.' }, { t: 'Візитка таксі', d: 'Від руки написано час: «23:15». Ні дати, ні імені.' }, { t: 'Розбитий парасоль', d: 'І чек на новий — того ж дня, з маркету. Практична людина.' }, { t: 'Гребінець із монограмою', d: 'Кістяний, старий, доглянутий. Літера затерта до нечитабельності.' }, { t: 'Театральна програмка', d: 'Гастролі обласного театру, третій ряд. На полях — сердечко олівцем.' }, { t: 'Ключниця з трьома ключами', d: 'Жоден не підписаний. Хтось досі відчиняє двері навпомацки.' }, { t: 'Обгортка від валідолу', d: 'Свіжа. Комусь тут нещодавно було недобре — або дуже нервово.' }, { t: 'Дитячий малюнок', d: 'Будинок, сонце і три постаті. Складений учетверо і затертий на згинах — його носили з собою.' }, { t: 'Лотерейний білет', d: 'Не виграшний. Закреслений хрест-навхрест із таким натиском, що порвався папір.' }, { t: 'Окуляри з тріснутим склом', d: 'Сильні діоптрії. Власник без них як без рук — але по них не повернувся.' }, { t: 'Розклад електричок', d: 'Обведено останній нічний рейс. Двічі.' }, { t: 'Порожній футляр від скрипки', d: 'Оксамит витертий. Або інструмент продали, або носять у ньому щось інше.' }, { t: 'Пакетик насіння', d: 'Чорнобривці. Строк придатності вийшов три роки тому. Хтось усе не наважиться посадити.' }, { t: 'Квитанція з ломбарду', d: `Здано «годинник чол. позолоч.». Викуп — до кінця місяця. Сума немала.` }, { t: 'Зламана рація', d: 'Дитяча, з наліпкою блискавки. Друга з пари десь чекає на звʼязок.' }, { t: 'Листівка без марки', d: '«Вітаю з ювілеєм!» — написано, підписано, не надіслано. Вже роки як.' }, { t: 'Шматок пакувального шпагату', d: 'Свіжий зріз, гострим ножем. Таким перевʼязують коробки. Або щось інше.' }, { t: 'Кишеньковий календарик', d: 'Позаминулий рік. Пʼятниці обведені. Усі до однієї.' }, { t: 'Металева фляжка', d: 'Порожня, з увігнутим боком. Пахне не чаєм.' } ]); const junkSpots = []; for (const b of district.buildings) for (const sp of (b.spots || [])) junkSpots.push({ type: 'spot', building: b.id, spot: sp }); const chosen = shuffle(r, junkSpots).slice(0, 8); const usedSlots = new Set(Object.values(E).filter(e => e.at.type === 'unit').map(e => e.at.unit + '|' + e.at.slot)); for (const [unit, n] of [[victim.home, 2], [killer.home, 1], [herring.home, 1]]) { let placed = 0; for (const slot of shuffle(r, district.homeSlots)) { if (placed >= n) break; if (usedSlots.has(unit + '|' + slot)) continue; usedSlots.add(unit + '|' + slot); chosen.push({ type: 'unit', unit, slot }); placed++; } } chosen.forEach((at, i) => { const j = junkTexts[i % junkTexts.length]; add({ id: 'junk_' + i, side: 'field', at, title: j.t, tag: 'дрібниця', detail: j.d }); }); // Плітки const GOSSIP = [ () => `Кажуть, ${nb().first} ${nb().last} і ${nb().first} не вітаються після сварки за паркування.`, () => `${nb().name} нібито у великих боргах за комуналку. Але то таке, люди всяке кажуть.`, () => `Подейкують, ${nb().name} шукає нову роботу — на старій щось не склалося.`, () => `${nb().name} щовечора в барі. Щовечора!`, () => `Бачили, як ${nb().name} кричить у телефон просто посеред вулиці. Аж луна йшла.`, () => `У «Каштані» хтось заливає сусідів знизу, і всі чомусь думають на ${nb().name}.`, () => 'На складі маркету вночі інколи світиться. Сторож каже — миші. Ага, миші з ліхтариком.', () => `${nb().name} нібито бачили на набережній о третій ночі. Безсоння, кажуть.`, () => 'У гуртожитку знову міняли замки — хтось утретє загубив ключі.', () => `Подейкують, ${nb().name} збирає гроші на авто. Або на переїзд. Або на весілля. Версії різняться.`, () => `${nb().name} виносить сміття о п'ятій ранку. Щодня. Хто взагалі стільки смітить?`, () => { const p = nb(); return `Кажуть, ${p.name} писа${g(p, 'в', 'ла')} скарги на пів району. Пише красиво, з посиланнями на закони.`; }, () => `У ${nb().first} з ${nb().first} щось було. Або є. Або буде. Двір слідкує.`, () => `${nb().name} тримає на балконі голубів. Сусіди знизу мають окрему думку про це.`, () => 'На набережній вечорами хтось грає на трубі. Гарно грає. Сумно.', () => { const p = nb(); return `${p.name} нібито ви${g(p, 'грав', 'грала')} у лотерею і мовчить. Мовчить підозріло добре.`; }, () => { const p = nb(); return `Бачили, як ${p.name} годує всіх котів району. Клички ${g(p, 'дав', 'дала')} кожному. Веде список.`; }, () => 'У клініці кажуть, що нічний охоронець спить. Охоронець каже, що медитує.', () => { const p = nb(); return `${p.name} посвари${g(p, 'вся', 'лася')} з усім будинком через антену на даху. Війна триває.`; }, () => `Подейкують, ${nb().name} ходить до бару, але не п'є — сидить і слухає чужі розмови.`, () => 'В автомайстерні беруть «за терміновість» удвічі. Терміново тепер усе.', () => `${nb().name} щонеділі кудись їздить першим автобусом. Питати незручно, а цікаво.`, () => 'Хтось ночами переставляє лавку в парку. Комунальники здалися.' ]; for (const c of citizens) { if (c.id === victim.id) { c.gossip = []; continue; } c.gossip = [pick(r, GOSSIP)()]; if (chance(r, 0.4)) c.gossip.push(pick(r, GOSSIP)()); } const wit = nb(); wit.gossip.push(caseType === 'kidnap' ? `Пізно того вечора хтось у ${killer.coat} тягнув щось важке в бік проїзду. ${g(wit, 'Подумав', 'Подумала')} — килим. А тепер думаю — не килим.` : `Пізно того вечора хтось у ${killer.coat} швидко йшов від місця, де потім знайшли тіло. Може, здалося.`); nb().gossip.push(`У квартирі ${killer.home} останнім часом світло горить до третьої ночі. Комусь не спиться.`); if (dna2 && dna2.why === 'brawl') nb().gossip.push(`Удень перед тим бачили, як ${dna2.owner.name} ${g(dna2.owner, 'зчепився', 'зчепилася')} з ${victim.first} ${victim.last} у барі — ${dna2.reason}. Розборонили швидко, обоє розійшлися своїми ногами.`); } module.exports = { setupJunk };