// Fair-play перевірка кейса: node test.js const assert = require('assert'); const { CASE, checkAccusation } = require('./case.js'); // 1. Кожен доказ досяжний рівно одним джерелом (spot / query / topic.gives) const sources = {}; for (const l of CASE.locations) for (const sp of l.spots) if (sp.ev) (sources[sp.ev] ??= []).push('spot:' + sp.id); for (const s of CASE.systems) for (const q of s.queries) if (q.ev) (sources[q.ev] ??= []).push('query:' + q.id); for (const p of Object.values(CASE.people)) for (const t of p.topics) if (t.gives) (sources[t.gives] ??= []).push('topic:' + t.id); for (const id of Object.keys(CASE.evidence)) assert(sources[id]?.length === 1, `evidence "${id}" має ${sources[id]?.length || 0} джерел`); for (const id of Object.keys(sources)) assert(CASE.evidence[id], `джерело посилається на неіснуючий доказ "${id}"`); // 2. Роль джерела збігається з роллю доказу for (const l of CASE.locations) for (const sp of l.spots) if (sp.ev) assert(CASE.evidence[sp.ev].role === 'field'); for (const s of CASE.systems) for (const q of s.queries) if (q.ev) assert(CASE.evidence[q.ev].role === 'analyst'); // 3. Усі requires існують і досяжні без циклів (симуляція повного проходження) const allReq = []; for (const ev of Object.values(CASE.evidence)) if (ev.requires) allReq.push(...ev.requires); for (const p of Object.values(CASE.people)) for (const t of p.topics) if (t.requires) allReq.push(...t.requires); for (const r of allReq) assert(CASE.evidence[r], `requires посилається на неіснуючий доказ "${r}"`); const pinned = new Set(); let progress = true; while (progress) { progress = false; const ok = req => !req || req.every(r => pinned.has(r)); for (const l of CASE.locations) for (const sp of l.spots) if (sp.ev && !pinned.has(sp.ev)) { pinned.add(sp.ev); progress = true; } for (const s of CASE.systems) for (const q of s.queries) if (q.ev && !pinned.has(q.ev) && ok(q.requires ?? CASE.evidence[q.ev].requires)) { pinned.add(q.ev); progress = true; } for (const p of Object.values(CASE.people)) for (const t of p.topics) if (t.gives && !pinned.has(t.gives) && ok(t.requires)) { pinned.add(t.gives); progress = true; } } for (const id of Object.keys(CASE.evidence)) assert(pinned.has(id), `доказ "${id}" недосяжний (deadlock у requires)`); // 4. Звинувачення: правильна комбінація виграє assert(checkAccusation('elena', 'push', ['override_log', 'guard', 'entry_log', 'fabric']).ok); assert(checkAccusation('elena', 'push', ['print_match', 'elena_lie', 'struggle']).ok); // 5. Кожна зламана ланка дає осмислену підказку, а не перемогу assert(!checkAccusation('rook', 'push', ['override_log', 'guard', 'entry_log', 'fabric']).ok); assert(!checkAccusation('elena', 'poison', ['override_log', 'guard', 'entry_log', 'fabric']).ok); assert(!checkAccusation('elena', 'push', ['guard', 'entry_log', 'fabric']).ok); // без способу assert(!checkAccusation('elena', 'push', ['override_log', 'guard', 'fabric']).ok); // суперечність неповна assert(!checkAccusation('elena', 'push', ['override_log', 'guard', 'entry_log']).ok); // без контакту for (const bad of [['rook','push'],['elena','poison']].map(([s,m]) => checkAccusation(s, m, []))) assert(bad.hint && bad.hint.length > 10); // 6. Ключове протиріччя вимагає обох ролей assert(CASE.evidence.guard.role === 'field' && CASE.evidence.entry_log.role === 'analyst'); console.log('OK — кейс розв\'язний, fair-play дотримано.');