2PlayerGame/chapter1/case.js

156 lines
22 KiB
JavaScript
Raw Normal View History

2026-07-23 17:03:26 +03:00
// КЕЙС №1 — "Nightshade Protocol". Всі дані та логіка розв'язання — детерміновані, без AI.
const CASE = {
title: 'Nightshade Protocol',
intro: 'Меридіан, 2087. Маркус Рейз, 44, брокер даних Halcyon Dynamics, знайдений мертвим під балконом свого пентхауса — падіння з 40-го поверху. Офіційна версія: нещасний випадок. Біометричний лог нейроімпланта (незламний) зафіксував смерть о 23:14. У вас одна ніч, щоб довести протилежне.',
roles: {
field: { name: 'Польовий детектив', desc: 'Ти працюєш ногами: місце злочину, фізичні докази, допити віч-на-віч. Цифрові системи для тебе закриті — те, що бачить аналітик, ти дізнаєшся тільки з розмови.' },
analyst: { name: 'Детектив-аналітик', desc: 'Ти працюєш з терміналу: логи доступу, камери, транзакції, біометрія. На місце злочину тебе не пускають — фізичні знахідки напарника ти дізнаєшся тільки з розмови.' }
},
howto: 'Знайдені докази закріплюйте на спільній дошці (📌) — тільки закріплені докази бачить напарник, і тільки вони розблоковують нові питання в допитах та нові запити в системах. Говоріть уголос: жодна роль сама не бачить повної картини.',
suspects: [
{ id: 'elena', name: 'Елена Восс', role: 'Партнерка Рейза по бізнесу', desc: 'Співзасновниця напряму в Halcyon. Холодна, бездоганна, кар\'єра понад усе.' },
{ id: 'kaito', name: 'Кайто Морі', role: 'Екс-коханець', desc: 'Фрилансер-хакер з Нижніх Доків. Розрив із Рейзом був гучним.' },
{ id: 'sable', name: 'Д-р Сейбл', role: 'Біотех-конкурент', desc: 'Голова R&D у конкурентів. Публічно звинувачував Рейза у крадіжці досліджень.' },
{ id: 'rook', name: 'Рук', role: 'Вуличний фіксер, кредитор', desc: 'Тримає майстерню в Доках. Позичає гроші тим, кому банки не позичать.' },
{ id: 'ivy', name: 'Айві Чен', role: 'Асистентка Рейза', desc: 'Повний доступ до пентхауса і графіка жертви. Тиха, виконавча, нервова.' }
],
methods: [
{ id: 'push', label: 'Вимкнення силового поля балкона через інженерний override і поштовх' },
{ id: 'poison', label: 'Отруєння нейротоксином' },
{ id: 'hack', label: 'Злам нейроімпланта (примусова зупинка)' },
{ id: 'drone', label: 'Удар найманого бойового дрона' },
{ id: 'accident', label: 'Нещасний випадок / самогубство' }
],
// tag: що доводить. requires: докази, які мають бути НА ДОШЦІ, щоб це стало доступним.
evidence: {
// — фізичні (польовий) —
fabric: { role: 'field', title: 'Клапоть тканини', tag: 'контакт', detail: 'Темно-сірий клапоть ділового піджака, зачепився за поручень балкона. Дорога корпоративна тканина — таку носять у Шпилі, не в Доках.' },
struggle: { role: 'field', title: 'Сліди боротьби', tag: 'контакт', detail: 'Перекинутий столик, подряпини на покритті балкона біля поручня. Рейз не стояв там сам — його штовхали, і він опирався.' },
fingerprint: { role: 'field', title: 'Частковий відбиток', tag: 'спосіб', detail: 'Частковий відбиток пальця на панелі інженерного override. Потрібна цифрова звірка — закріпи на дошці, щоб аналітик міг прогнати по базі.' },
guard: { role: 'field', title: 'Свідчення охоронця', tag: 'алібі', detail: 'Нічний охоронець Halcyon: «Пані Восс вийшла о 22:15 — бейдж пікнув, я записав. Назад не бачив, після 22:30 я був на обході».' },
ledger: { role: 'field', title: 'Тіньовий гросбух', tag: 'мотив', detail: 'У схованці за панеллю в кабінеті Рейза: роздруківки тіньових рахунків з позначками «Е.Ві нотатка Рейза: «Рада. 9:00. Все їм показати».' },
elena_lie: { role: 'field', title: 'Зізнання Елени', tag: 'алібі', detail: 'Під тиском доказів Елена визнала, що була в пентхаусі Рейза вночі. Стверджує, що пішла, коли він був ще живий.' },
// — цифрові (аналітик) —
override_log:{ role: 'analyst', title: 'Лог override панелі', tag: 'спосіб', detail: 'Інженерний override силового поля балкона, 23:10. Авторизація: ID Елени Восс (пропуск-чіп часів її стосунків з Рейзом досі активний).' },
drone_rec: { role: 'analyst', title: 'Запис патрульного дрона', tag: 'присутність', detail: 'Резервний архів: 22:50, силует на балконі пентхауса. Обличчя не розібрати — зріст і статура дорослої людини в діловому одязі.' },
entry_log: { role: 'analyst', title: 'Лог входу в будівлю Рейза', tag: 'алібі', detail: 'Бейдж Елени Восс: вхід у будівлю Рейза о 22:47. Сама вона каже, що «була в офісі допізна».' },
calendar: { role: 'analyst', title: 'Запрошення в календарі', tag: 'мотив', detail: 'Рейз створив подію на 9:00 ранку: «Термінова рада — комплаєнс Е.В.». Розсилка: рада директорів Halcyon.' },
kaito_wifi: { role: 'analyst', title: 'Мережевий лог Кайто', tag: 'зняття підозри', detail: 'Імплант Кайто Морі підключений до wifi бару "The Hollow Note" безперервно 20:1023:30. На момент смерті (23:14) він був у барі.' },
rook_manifest:{ role: 'analyst', title: 'Маніфест доставки', tag: 'зняття підозри', detail: 'Вантажний під\'їзд будівлі Рейза: Рук, 21:3021:45, накладна на серводеталі для консьєржа. Виїхав за півтори години до смерті.' },
ivy_cam: { role: 'analyst', title: 'Камера лобі', tag: 'зняття підозри', detail: 'Айві Чен виходить з лобі о 22:30, збігається з логом бейджа. До 23:14 не поверталась.' },
sable_conf: { role: 'analyst', title: 'Реєстрація конференції', tag: 'зняття підозри', detail: 'Д-р Сейбл: чекін на конференції «Женева-2», виступ 22:4023:20, сотні свідків і трансляція. Залізне алібі.' },
biometric: { role: 'analyst', title: 'Біометрія імпланта Рейза', tag: 'факт', detail: 'Смерть: 23:14. Сплеск адреналіну 23:1223:14 — боротьба або падіння при свідомості. Це не тихий нещасний випадок.' },
print_match: { role: 'analyst', title: 'Збіг відбитка', tag: 'спосіб', requires: ['fingerprint'], detail: 'Частковий відбиток з панелі override: збіг 87% — Елена Восс.' },
shadow_tx: { role: 'analyst', title: 'Тіньові транзакції', tag: 'мотив', requires: ['ledger'], detail: 'Рахунки з гросбуха: систематичне виведення коштів Halcyon на офшор. Транзакції підписані корпоративним ключем Е.В. Рейз це знайшов.' }
},
// Польовий детектив
locations: [
{ id: 'penthouse', name: 'Пентхаус Рейза (40-й поверх)', desc: 'Місце смерті. Балкон над прірвою Нижніх Доків, кабінет із заблокованим терміналом.',
spots: [
{ id: 's1', label: 'Оглянути поручень балкона', ev: 'fabric' },
{ id: 's2', label: 'Оглянути балкон', ev: 'struggle' },
{ id: 's3', label: 'Оглянути панель override', ev: 'fingerprint' },
{ id: 's4', label: 'Обшукати кабінет', ev: 'ledger' },
{ id: 's5', label: 'Робочий термінал Рейза', flavor: 'Термінал заблоковано корпоративним шифруванням. Цифрові сліди — робота аналітика.' }
], people: [] },
{ id: 'lobby', name: 'Лобі будівлі Рейза', desc: 'Мармур, дрони-консьєржі, камери в кожному куті.',
spots: [ { id: 's6', label: 'Стійка охорони', flavor: 'Журнал відвідувачів повністю цифровий. Хай аналітик перевірить логи доступу будівлі.' } ],
people: ['ivy'] },
{ id: 'halcyon', name: 'Офіс Halcyon Dynamics', desc: 'Верхній Шпиль. Скло, тиша, нічна зміна охорони.',
spots: [], people: ['elena', 'guard', 'sable'] },
{ id: 'bar', name: 'Бар "The Hollow Note"', desc: 'Нижні Доки. Димно, гучно, ніхто нічого не бачив — офіційно.',
spots: [], people: ['kaito', 'barman'] },
{ id: 'workshop', name: 'Майстерня Рука', desc: 'Гараж у Доках, завалений дронами й запчастинами.',
spots: [ { id: 's7', label: 'Оглянути дрони', flavor: 'Вантажні мули для доставки. Жодних слідів бойових модифікацій.' } ],
people: ['rook'] }
],
// Аналітик
systems: [
{ id: 'access', name: 'Логи доступу', queries: [
{ id: 'q1', label: 'Будівля Рейза: входи 20:0000:00', ev: 'entry_log' },
{ id: 'q2', label: 'Панель безпеки пентхауса', ev: 'override_log' },
{ id: 'q3', label: 'Вантажний під\'їзд будівлі', ev: 'rook_manifest' },
{ id: 'q4', label: 'Офіс Halcyon: нічні логи', flavor: 'Нічний архів Halcyon недоступний — планове обслуговування серверів. Лишається усний журнал охорони на місці.' }
]},
{ id: 'video', name: 'Відеоархіви', queries: [
{ id: 'q5', label: 'Камери лобі будівлі Рейза', ev: 'ivy_cam' },
{ id: 'q6', label: 'Патрульні дрони: резервний архів', ev: 'drone_rec' },
{ id: 'q7', label: 'Трансляція конференції «Женева-2»', ev: 'sable_conf' }
]},
{ id: 'net', name: 'Мережа і біометрія', queries: [
{ id: 'q8', label: 'Біометричний лог імпланта жертви', ev: 'biometric' },
{ id: 'q9', label: 'Мережева активність імпланта Кайто Морі', ev: 'kaito_wifi' },
{ id: 'q10', label: 'Звірити відбиток з панелі по базі', ev: 'print_match' }
]},
{ id: 'finance', name: 'Фінанси і записи', queries: [
{ id: 'q11', label: 'Календар жертви', ev: 'calendar' },
{ id: 'q12', label: 'Рахунки жертви', flavor: 'Регулярні перекази на рахунок, пов\'язаний з Руком: борг ~40 000 кредитів, останній платіж прострочений.' },
{ id: 'q13', label: 'Простежити рахунки з гросбуха', ev: 'shadow_tx' }
]}
],
// Допити (веде польовий). requires: id доказів на дошці.
people: {
elena: { name: 'Елена Восс', suspect: true, topics: [
{ id: 't1', q: 'Де ви були цієї ночі?', a: '«Була в офісі допізна. Квартальний звіт. Пішла десь після опівночі — охорона підтвердить.» Дивиться прямо, не кліпає. Занадто рівно.' },
{ id: 't2', q: 'Ваші стосунки з Рейзом?', a: '«Партнери. Дванадцять років. Останнім часом він став… параноїком. Всюди бачив змови.» На слові «параноїком» — коротка пауза.' },
{ id: 't3', q: 'Що за термінова рада о 9:00?', requires: ['calendar'], a: '«Звичайна процедурна рада. Комплаєнс. Нічого особливого.» Пальці стискають планшет так, що біліють кісточки.' },
{ id: 't4', q: 'Охоронець каже: ваш бейдж вийшов о 22:15.', requires: ['guard'], a: '«Виходила подихати. Повернулась і працювала далі. Це не злочин, детективе.» Погляд уперше йде вбік.' },
{ id: 't5', q: 'Лог будівлі Рейза: ваш бейдж, вхід о 22:47.', requires: ['guard', 'entry_log'], gives: 'elena_lie', a: 'Довга пауза. «…Гаразд. Я була в нього. Хотіла поговорити до ради. Ми посперечались, і я пішла. Він був живий, чуєте? Живий.» Голос зривається — вперше за розмову.' }
]},
kaito: { name: 'Кайто Морі', suspect: true, topics: [
{ id: 't6', q: 'Де ви були о 23:14?', a: '«Тут. Весь вечір, до пів на дванадцяту. Спитайте бармена — чи мій імплант, якщо вмієте читати логи.»' },
{ id: 't7', q: 'Рейз злив ваші генетичні дані.', a: 'Стискає склянку. «Продав мою ДНК як рекламну базу. Я його ненавидів. Але між ненавистю і сорока поверхами — велика різниця.»' },
{ id: 't8', q: 'Мережа підтверджує: ви були тут.', requires: ['kaito_wifi'], a: 'Видихає. «Нарешті хоч хтось перевірив. Слухайте… Рейз останній місяць скаржився на Елену. Казав: „вона рахує, що я сліпий". Не знаю, що він знайшов, але воно його зжирало.»' }
]},
sable: { name: 'Д-р Сейбл (відеозв\'язок)', suspect: true, topics: [
{ id: 't9', q: 'Де ви були цієї ночі?', a: '«На „Женеві-2". Виступ о 22:40, чотириста свідків і трансляція. Перевірте — заощадите нам обом час.»' },
{ id: 't10', q: 'Рейз украв ваші дослідження.', a: '«Рейз — падальник, і я не шкодуватиму за ним. Але вбивство — інструмент тих, у кого закінчились юристи. У мене вони не закінчились.»' }
]},
rook: { name: 'Рук', suspect: true, topics: [
{ id: 't11', q: 'Вас бачили біля будівлі Рейза.', a: '«Був поруч. Проїздом. Нічого не бачив, нікого не чіпав.» Дивиться повз тебе, витирає руки ганчіркою, яка чистіша за його відповіді.' },
{ id: 't12', q: 'Рейз був винен вам гроші.', a: '«Сорок штук. І що? Мертвий він мені не заплатить. Подумай головою, детективе: я кредитор, а не гробар.»' },
{ id: 't13', q: 'Маніфест: доставка о 21:3021:45.', requires: ['rook_manifest'], a: '«Ну так, завозив серводеталі консьєржу. Поїхав о 21:45. Задоволений?» Пауза. «Одне скажу: асистентка його виходила пізніше — нервова, як дріт під струмом.»' }
]},
ivy: { name: 'Айві Чен', suspect: true, topics: [
{ id: 't14', q: 'Коли ви пішли від Рейза?', a: '«О 22:30, як завжди. Камера в лобі підтвердить.» Відповідає миттєво — наче репетирувала.' },
{ id: 't15', q: 'Яким він був останні тижні?', a: '«Напруженим. Замкнув частину файлів навіть від мене. Я… я думала, він щось підозрює.» Затинається на слові «підозрює».' },
{ id: 't16', q: 'Камера підтверджує. Але чому ви так нервуєте?', requires: ['ivy_cam'], a: 'Довго мовчить. «Я передавала документи Сейблу. За гроші. Але я не вбивала! Рейз готував на ранок якесь викриття — не про мене. Він сказав: „це її поховає". Її.»' }
]},
guard: { name: 'Нічний охоронець Halcyon', suspect: false, topics: [
{ id: 't17', q: 'Хто виходив з офісу ввечері?', gives: 'guard', a: '«Пані Восс вийшла о 22:15 — бейдж пікнув, я записав у журнал. Назад не бачив: після 22:30 я на обході, а нічні логи в них на обслуговуванні.»' }
]},
barman: { name: 'Бармен "The Hollow Note"', suspect: false, topics: [
{ id: 't18', q: 'Кайто Морі був тут уночі?', a: '«Весь вечір, до пів на дванадцяту точно. Сидів он там і пив так, наче хотів забути чиєсь ім\'я.»' }
]}
},
finale: {
suspect: 'elena', method: 'push', maxEvidence: 4,
recap: 'Елена Восс роками виводила гроші Halcyon через тіньові рахунки. Рейз знайшов гросбух і призначив на 9:00 раду, яка б її поховала. О 22:15 вона вийшла з офісу — охоронець це бачив. О 22:47 увійшла в будівлю Рейза старим пропуском. О 23:10 інженерним override вимкнула силове поле балкона. О 23:14 — після короткої боротьби, що лишила клапоть її піджака на поручні, — Маркус Рейз упав з 40-го поверху. Справу закрито.'
}
};
// Перевірка звинувачення. Повертає {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('override_log') || has('print_match')))
return { ok: false, hint: 'Ланка «спосіб/доступ» не доведена: потрібен доказ, ЧИМ вбивця відкрив шлях до жертви.' };
if (!(has('elena_lie') || (has('guard') && has('entry_log'))))
return { ok: false, hint: 'Ланка «алібі» не зламана: потрібно довести, що підозрюваний бреше про те, де був. Одного доказу тут мало — потрібна суперечність.' };
if (!(has('fabric') || has('struggle')))
return { ok: false, hint: 'Ланка «фізичний контакт» не доведена: що пов\'язує вбивцю з боротьбою на балконі?' };
return { ok: true };
}
if (typeof module !== 'undefined') module.exports = { CASE, checkAccusation };