2PlayerGame/chapter2/case2.js
2026-07-23 17:03:26 +03:00

175 lines
28 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// КЕЙС №2 — "Тихий аукціон". Сучасний нуар, два акти. Все детерміновано, без AI.
const CASE = {
title: 'Тихий аукціон',
intro: 'Місто. Листопад. Третя доба дощу. Віктор Гайдук, 58, власник аукціонного дому «Гайдук і Спадок», знайдений мертвим у кріслі свого кабінету над аукціонною залою — напередодні найбільших торгів року. Лікар швидкої написав «серцевий напад». Страхова компанія вимагає незалежної перевірки. У вас одна ніч і жодного ордера.',
actNote: 'АКТ I: доведіть, що це вбивство — тоді прокурор підпише ордери. АКТ II: доведіть, хто вбивця.',
act2title: 'АКТ II — ОРДЕРИ ПІДПИСАНО',
act2text: 'Токсикологія підтвердила: це вбивство. Прокурор підписав ордери — відкрито доступ до банків, реєстрів і приватних приміщень. Тепер знайдіть того, хто налив отруту.',
roles: {
field: { name: 'Польовий детектив', desc: 'Ти працюєш ногами: місце злочину, обшуки, допити віч-на-віч. Обшук вимагає твердої руки — зафіксуй сканер у зоні сигналу. Цифрові системи для тебе закриті.' },
analyst: { name: 'Детектив-аналітик', desc: 'Ти працюєш з терміналу: камери, дзвінки, банки, біометрія. Кожен запит захищений — перехопи й повтори код доступу. На місце злочину тебе не пускають.' }
},
howto: 'Знайдені докази закріплюйте на спільній дошці (📌) — лише закріплене бачить напарник, і лише воно розблоковує нові питання й запити. Відкидайте підозрюваних, зірочте ключові докази, лишайте нотатки — все синхронізується. Говоріть уголос.',
suspects: [
{ id: 'stryzh', name: 'Богдан Стриж', role: 'Партнер, головний аукціоніст', desc: 'Друга особа дому «Гайдук і Спадок». Бігає марафони, курить дорогі сигарили — «одна на день, для нервів».' },
{ id: 'iryna', name: 'Ірина Гайдук', role: 'Дружина', desc: 'Молодша за чоловіка на двадцять років. Шлюбний контракт, велика страховка — місто вже все порахувало.' },
{ id: 'dana', name: 'Дана Гайдук', role: 'Дочка від першого шлюбу', desc: 'Не розмовляла з батьком два роки. Борги, зйомна квартира, гострий язик.' },
{ id: 'mazur', name: 'Лесь Мазур', role: 'Реставратор', desc: 'П\'ять років тому Гайдук публічно знищив його репутацію. Тепер веде стріми з реставрації — на життя вистачає.' },
{ id: 'pliuta', name: 'Артем Плюта', role: 'Колекціонер', desc: 'Купив у Гайдука «Екстер» за триста тисяч. Експертиза сказала: підробка. Судиться.' }
],
methods: [
{ id: 'digoxin', label: 'Передозування дигоксином, розчиненим у коньяку в графині' },
{ id: 'placebo', label: 'Підміна серцевих ліків на плацебо' },
{ id: 'smother', label: 'Задушення з інсценуванням серцевого нападу' },
{ id: 'insulin', label: 'Ін\'єкція інсуліну' },
{ id: 'natural', label: 'Природна смерть — серцевий напад' }
],
evidence: {
// ---- АКТ I, фізичні ----
decanter: { role: 'field', title: 'Осад у графині', tag: 'засіб', detail: 'На дні графина з коньяком — білуватий кристалічний осад. Коньяк такого не лишає. Графин стоїть на звичному місці, біля крісла.' },
wiped_glass: { role: 'field', title: 'Вимитий келих', tag: 'інсценування', detail: 'Єдиний келих — ретельно вимитий і поставлений догори дном біля мийки. Мертві не миють посуд. Хтось прибирав за ним.' },
diary: { role: 'field', title: 'Щоденник Гайдука', tag: 'мотив', detail: 'Останній запис, зроблений вчора: «Скасувати торги. Дзвонити К. Нікому в домі не вірити.» Слово «домі» підкреслено двічі.' },
pills_missing:{ role: 'field', title: 'Зниклі ліки', tag: 'засіб', detail: 'В аптечці — свіжий рецепт на дигоксин (серцевий препарат), виписаний тиждень тому. Самої пляшечки ніде немає. Ні в кабінеті, ні в смітті.' },
cigarillo: { role: 'field', title: 'Недопалок сигарили', tag: 'доступ', detail: 'Свіжий недопалок дорогої сигарили під дверима службового входу. Двері — на защіпці, камери там немає. Потрібен аналіз ДНК — закріпи на дошці для аналітика.' },
guard2: { role: 'field', title: 'Свідчення охоронця', tag: 'алібі', detail: 'Охоронець Семен: «Пан Стриж вийшов о 21:03 головним входом, я записав. Службовий вхід на защіпці, камери там нема — економія. На паркінгу камери два тижні як мертві, тільки шлагбаум лог пише».' },
cleaner_st: { role: 'field', title: 'Свідчення прибиральниці', tag: 'таймлайн', detail: 'Пані Ольга: «Графин я мила о сьомій вечора, як завжди. Пан Віктор наливав перший келих о пів на десяту — ритуал, свята справа. Келих я не мила і догори дном не ставила».' },
// ---- АКТ II, фізичні ----
coatcheck: { role: 'field', title: 'Свідчення гардеробниці', tag: 'алібі', act: 2, detail: 'Пані Зоя з фонду «Меценат»: «Пан Стриж? Здав пальто десь після пів на одинадцяту — точно після початку дощу, мокрий був наскрізь. А дощ того вечора почався о десятій». Людська пам\'ять — не документ, але вона впевнена щодо дощу.' },
blister: { role: 'field', title: 'Блістер у смітті Стрижа', tag: 'засіб', act: 2, detail: 'На дні сміттєвого пакета в офісі Стрижа на складі — порожній блістер дигоксину. У марафонця Стрижа здорове серце. Партію можна простежити — закріпи для аналітика.' },
fake_canvases:{ role: 'field', title: 'Підробки на складі', tag: 'мотив', act: 2, detail: 'Склад №3: шість полотен «з провенансом» — і поруч шість свіжих копій, ще зі скипидарним запахом. Це лоти 1218 завтрашніх торгів. Хтось продає фальшаки через дім Гайдука.' },
dana_confess:{ role: 'field', title: 'Зізнання Дани', tag: 'зняття підозри', detail: 'Дана визнала: заходила о 20:30 забрати картину Марчука з малої зали — «батько мені її обіцяв». Вийшла о 21:12, до кабінету не піднімалась. Камера це підтверджує.' },
stryzh_lie: { role: 'field', title: 'Злам алібі Стрижа', tag: 'алібі', act: 2, detail: 'Під тиском фактів Стриж визнав: повертався в дім службовим входом «за каталогами», виїхав з паркінгу о 22:14. Стверджує, що Гайдук був живий і «пив свій коньяк».' },
// ---- АКТ I, цифрові ----
watch_log: { role: 'analyst', title: 'Лог смарт-годинника', tag: 'спосіб смерті', detail: 'Годинник жертви: 21:52 — аритмія за патерном глікозидної інтоксикації (передозування серцевим препаратом). 22:03 — зупинка серця. Це не «просто напад» — це отруєння.' },
lobby_cam: { role: 'analyst', title: 'Камера головного входу', tag: 'таймлайн', detail: 'Стриж: вихід 21:03. Дана Гайдук: вхід 20:30, вихід 21:12 — на поверх кабінету не піднімалась, була лише в малій залі. Після 21:12 головним входом ніхто не заходив і не виходив до ранку.' },
call_log: { role: 'analyst', title: 'Відеодзвінок Ірини', tag: 'зняття підозри', detail: 'Ірина Гайдук: відеодзвінок сестрі в Торонто 21:2822:36 з домашнього wifi, камера активна, обличчя в кадрі підтверджено. На момент отруєння була вдома.' },
stream_log: { role: 'analyst', title: 'Стрім Мазура', tag: 'зняття підозри', detail: 'Лесь Мазур: безперервна пряма трансляція реставрації 20:0023:05, п\'ятсот глядачів, обличчя й руки в кадрі весь час. Залізне алібі.' },
expert_email:{ role: 'analyst', title: 'Лист експертці', tag: 'мотив', detail: 'Гайдук — експертці Катерині Резник, учора 18:44: «Катерино, перевірте лоти 1218 до торгів. Особисто. Нікому ні слова».' },
geo_toll: { role: 'analyst', title: 'Геолокація Плюти', tag: 'зняття підозри', detail: 'Плюта: транзакції на платних дорогах і АЗСо 21:50 був за 170 км від міста, повертався з Одеси. Фізично не міг бути в кабінеті.' },
// ---- АКТ II, цифрові ----
parking: { role: 'analyst', title: 'Лог шлагбаума паркінгу', tag: 'алібі', act: 2, detail: 'Паркінг аукціонного дому: авто Стрижа — виїзд о 22:14. Він каже, що «поїхав о 21:00 і о 21:30 був на вечері фонду». Авто каже інше.' },
tox_report: { role: 'analyst', title: 'Токсикологія (ордер)', tag: 'спосіб смерті', act: 2, detail: 'Офіційний висновок: дигоксин, семикратна терапевтична доза. У крові жертви — і в залишках коньяку з графина. Отруту розчинили саме там.' },
dna_match: { role: 'analyst', title: 'ДНК з недопалка', tag: 'доступ', act: 2, requires: ['cigarillo'], detail: 'ДНК зі слини на недопалку сигарили біля службового входу: збіг — Богдан Стриж. Він стояв під дверима без камер того вечора.' },
bank_shell: { role: 'analyst', title: 'Рахунки-прокладки', tag: 'мотив', act: 2, detail: 'Ордер на банки: ТОВ «Артконсалт-Гарант» отримало 2,3 млн за «атрибуцію» лотів 1218. Кінцевий бенефіціар прокладки — Богдан Стриж. Гайдук збирався скасувати торги і поховати схему.' },
pharma_log: { role: 'analyst', title: 'Слід партії дигоксину', tag: 'засіб', act: 2, requires: ['blister'], detail: 'Партія з блістера: онлайн-аптека, замовлення на неіснуючу «О. Кравець», доставка в поштомат за квартал від дому Стрижа, оплата карткою, поповненою готівкою. Ланцюжок веде до нього.' }
},
// Умова переходу в Акт II: обидва докази вбивства на дошці.
actGate: ['decanter', 'watch_log'],
locations: [
{ id: 'office', name: 'Кабінет Гайдука', desc: 'Кабінет над аукціонною залою. Крісло, графин, дощ по склу. Тіло вже забрали.',
spots: [
{ id: 's1', label: 'Оглянути графин', ev: 'decanter' },
{ id: 's2', label: 'Оглянути мийку та келих', ev: 'wiped_glass' },
{ id: 's3', label: 'Обшукати стіл', ev: 'diary' },
{ id: 's4', label: 'Оглянути аптечку', ev: 'pills_missing' },
{ id: 's5', label: 'Службові сходи', ev: 'cigarillo' },
{ id: 's6', label: 'Оглянути крісло', flavor: 'Жодних слідів боротьби. Він просто сів у крісло з келихом — і не встав. Хтось дуже хотів, щоб це виглядало саме так.' }
], people: ['olga'] },
{ id: 'hall', name: 'Аукціонна зала', desc: 'Порожня зала, накриті лоти, завтра тут мали бути торги року.',
spots: [], people: ['stryzh', 'dana', 'semen'] },
{ id: 'house', name: 'Дім Гайдуків', desc: 'Особняк за містом. Тихо, дорого, самотньо.',
spots: [], people: ['iryna'] },
{ id: 'studio', name: 'Студія Мазура', desc: 'Підвал, лампи, запах лаку. У кутку — кільцева лампа для стрімів.',
spots: [], people: ['mazur'] },
{ id: 'hotel', name: 'Готель «Централь»', desc: 'Плюта зупинився тут «до кінця судового процесу».',
spots: [], people: ['pliuta'] },
{ id: 'fund', name: 'Фонд «Меценат»', desc: 'Зала благодійної вечері, де Стриж «був з 21:30». Гардероб працює до останнього гостя.', act: 2,
spots: [ { id: 's7', label: 'Розпитати розпорядника', flavor: '«Пан Стриж? Був, звісно. Коли саме прийшов? У нас не клуб, детективе, ми квитки не штампуємо. Спитайте в гардеробі — Зоя все пам\'ятає».' } ],
people: ['zoya'] },
{ id: 'depot', name: 'Склад «Гайдук і Спадок» №3', desc: 'Ордер відкрив двері, які Стриж тримав замкненими. Його офіс — у кутку складу.', act: 2,
spots: [
{ id: 's8', label: 'Оглянути ящики з лотами', ev: 'fake_canvases' },
{ id: 's9', label: 'Обшукати сміття в офісі Стрижа', ev: 'blister' },
{ id: 's10', label: 'Сейф Стрижа', flavor: 'Замкнено. Злам сейфа ордер не покриває — та й те, що лежить у смітті, часом говорить голосніше.' }
], people: [] }
],
systems: [
{ id: 'video', name: 'Камери й трансляції', queries: [
{ id: 'q1', label: 'Камера головного входу аукціонного дому', ev: 'lobby_cam' },
{ id: 'q2', label: 'Стрім-платформа: канал Мазура', ev: 'stream_log' },
{ id: 'q3', label: 'Камери паркінгу', flavor: 'Камери паркінгу не працюють два тижні — «на технічному обслуговуванні». Пише лише лог шлагбаума, а він видається тільки з ордером.' },
{ id: 'q4', label: 'Лог шлагбаума паркінгу (ордер)', ev: 'parking', act: 2 }
]},
{ id: 'comms', name: 'Зв\'язок', queries: [
{ id: 'q5', label: 'Дзвінки та відеозв\'язок Ірини Гайдук', ev: 'call_log' },
{ id: 'q6', label: 'Пошта жертви', ev: 'expert_email' }
]},
{ id: 'bio', name: 'Біометрія і криміналістика', queries: [
{ id: 'q7', label: 'Смарт-годинник жертви', ev: 'watch_log' },
{ id: 'q8', label: 'Повна токсикологія (ордер)', ev: 'tox_report', act: 2 },
{ id: 'q9', label: 'Аналіз ДНК недопалка', ev: 'dna_match', act: 2 }
]},
{ id: 'money', name: 'Фінанси і реєстри', queries: [
{ id: 'q10', label: 'Транзакції Плюти: дороги та АЗС', ev: 'geo_toll' },
{ id: 'q11', label: 'Заповіт і страховка', flavor: 'Нотаріус і страхова вимагають ордер. Ордер буде, коли доведете, що це вбивство: потрібні докази отруєння на дошці.' },
{ id: 'q12', label: 'Банківські рахунки, пов\'язані з домом (ордер)', ev: 'bank_shell', act: 2 },
{ id: 'q13', label: 'Простежити партію препарату', ev: 'pharma_log', act: 2 }
]}
],
people: {
stryzh: { name: 'Богдан Стриж', suspect: true, topics: [
{ id: 't1', q: 'Де ви були вчора ввечері?', a: '«Пішов о 21:00 — камера підтвердить. О 21:30 вже був на вечері фонду „Меценат". Сотня свідків, детективе.» Відповідає гладко, як з каталогу.' },
{ id: 't2', q: 'Що тепер буде з торгами?', a: '«Найбільші торги року. Віктор був знервований останній тиждень... Тепер усе на мені.» Скорботи в голосі — рівно нуль.' },
{ id: 't3', q: 'У щоденнику: «Скасувати торги».', requires: ['diary'], a: '«Скасувати?! Вперше чую. У нас зобов\'язання перед клієнтами.» Різкий, як удар молотка. Занадто різкий.' },
{ id: 't4', q: 'Гайдук просив експертку перевірити лоти 1218.', requires: ['expert_email'], a: '«Рутинна перевірка провенансу. Ці лоти, до речі, готував я.» Поправляє годинник. Пальці не слухаються.' },
{ id: 't5', q: 'Авто виїхало о 22:14. Пальто ви здали о 22:40.', requires: ['coatcheck', 'parking'], gives: 'stryzh_lie', a: 'Довга пауза. «...Гаразд. Я повертався за каталогами. Службовим входом, щоб охорона не бачила — не хотів розмов. Віктор був живий, сидів з коньяком. Це все, що я скажу без адвоката.»' }
]},
iryna: { name: 'Ірина Гайдук', suspect: true, topics: [
{ id: 't6', q: 'Де ви були вчора ввечері?', a: '«Вдома. Дзвонила сестрі в Торонто, ми говорили години зо дві.» Дивиться у вікно, на дощ.' },
{ id: 't7', q: 'Ваш шлюб. Страховка. Контракт.', a: '«Місто вже все порахувало, так? Ми десять років разом. Він був складний. Але мій.» Вперше за розмову — живий голос.' },
{ id: 't8', q: 'Дзвінок підтверджено. Що ви не договорюєте?', requires: ['call_log'], a: 'Видихає. «Останній тиждень Віктор замикався з паперами. Одного вечора сказав: „Богдан думає, що я сліпий". Я тоді не зрозуміла. Тепер, здається, розумію.»' }
]},
dana: { name: 'Дана Гайдук', suspect: true, topics: [
{ id: 't9', q: 'Що ви робите в залі?', a: '«Забираю речі батька. Маю право.» Тримає порожню теку так, наче там щось є.' },
{ id: 't10', q: 'Ви не розмовляли два роки. Чому?', a: '«Він обрав дім. Аукціонний. Я не вписалась у каталог.» Сухо. Але руки тремтять.' },
{ id: 't11', q: 'Камера: ви зайшли о 20:30 і вийшли о 21:12.', requires: ['lobby_cam'], gives: 'dana_confess', a: 'Мовчить. Потім: «Я взяла картину. Марчука, з малої зали. Батько обіцяв її мені ще до всього. Нагору я не піднімалась. Не змогла. Тепер уже й не зможу.»' }
]},
mazur: { name: 'Лесь Мазур', suspect: true, topics: [
{ id: 't12', q: 'Де ви були вчора ввечері?', a: '«Стрім. Три години, п\'ятсот глядачів, реставрація наживо. Таке не змонтуєш, детективе — перевірте платформу.»' },
{ id: 't13', q: 'Гайдук знищив вашу репутацію.', a: '«Назвав мою атрибуцію шахрайством. Публічно. І знаєте що? Він мав рацію — я тоді помилився. Його прокляттям було бачити підробки скрізь. Схоже, одного разу він подивився надто близько до дому.»' }
]},
pliuta: { name: 'Артем Плюта', suspect: true, topics: [
{ id: 't14', q: 'Де ви були вчора ввечері?', a: '«Повертався з Одеси. Траса, чеки, платні дороги — усе в банківській виписці. Перевіряйте, я нікуди не поспішаю.»' },
{ id: 't15', q: 'Гайдук продав вам підробку.', a: '«„Екстер" за триста тисяч, який малювали позавчора. Я подав до суду, детективе, а не в морг. Мертвий Гайдук мені суд не виграє.»' }
]},
semen: { name: 'Охоронець Семен', suspect: false, topics: [
{ id: 't16', q: 'Хто заходив і виходив увечері?', gives: 'guard2', a: '«Пан Стриж вийшов о 21:03 головним, я записав. Дочка господаря ще була... Службовий вхід? Він на защіпці постійно, камери там нема — економлять. На паркінгу камери два тижні мертві, тільки шлагбаум лог пише.»' }
]},
olga: { name: 'Прибиральниця пані Ольга', suspect: false, topics: [
{ id: 't17', q: 'Розкажіть про вчорашній вечір.', gives: 'cleaner_st', a: '«Графин я мила о сьомій, як завжди. Пан Віктор наливав перший келих о пів на десяту — ритуал, свята справа, хоч годинник звіряй. Келих біля мийки? Я його не мила і догори дном не ставила. Не мій почерк.»' }
]},
zoya: { name: 'Гардеробниця пані Зоя', suspect: false, topics: [
{ id: 't18', q: 'Коли Стриж здав пальто?', gives: 'coatcheck', a: '«Десь після пів на одинадцяту. Точно після початку дощу — мокрий був наскрізь, аж з рукавів текло. А дощ учора почався рівно о десятій, я ще парасолю з дому не взяла.»' }
]}
},
finale: {
suspect: 'stryzh', method: 'digoxin', maxEvidence: 4,
recap: 'Богдан Стриж роками продавав підробки через дім «Гайдук і Спадок», отримуючи мільйони через прокладку «Артконсалт-Гарант». Гайдук помітив, замовив таємну експертизу лотів 1218 і зібрався скасувати торги. О 21:03 Стриж демонстративно вийшов головним входом — для камери. Повернувся службовими сходами, де немає камер, викуривши сигарилу під дверима. Розчинив дигоксин у графині, знаючи ритуал партнера: перший келих о пів на десяту. Вимив свій келих, забрав пляшечку ліків — хай виглядає як хворе серце. О 22:14 виїхав з паркінгу, о 22:40, мокрий від дощу, здав пальто на вечері, де «був з 21:30». Годинник на зап\'ясті жертви виявився чеснішим за некролог. Справу закрито.'
}
};
// Перевірка звинувачення. Повертає {ok, hint}.
function checkAccusation(suspect, method, evs) {
const has = id => evs.includes(id);
if (suspect !== CASE.finale.suspect)
return { ok: false, hint: 'Не сходиться сам підозрюваний: його місцеперебування на вечір смерті підтверджене доказами, які у вас є або які можна знайти. Кого докази виключають — той не вбивця.' };
if (method !== CASE.finale.method)
return { ok: false, hint: 'Підозрюваний правильний, але спосіб суперечить біометрії та токсикології. ЩО саме вбило жертву і ДЕ це було розчинено?' };
if (!(has('blister') || has('pharma_log')))
return { ok: false, hint: 'Ланка «засіб» не доведена: потрібен доказ, що пов\'язує вбивцю з отрутою, а не лише отруту з жертвою.' };
if (!(has('stryzh_lie') || (has('coatcheck') && has('parking'))))
return { ok: false, hint: 'Ланка «алібі» не зламана: потрібна суперечність із двох незалежних джерел — або зізнання. Один доказ тут завжди можна відбрехати.' };
if (!has('dna_match'))
return { ok: false, hint: 'Ланка «доступ» не доведена: як вбивця потрапив у кабінет повз камери? Потрібен доказ його присутності на шляху без камер.' };
return { ok: true };
}
if (typeof module !== 'undefined') module.exports = { CASE, checkAccusation };