Initialize environment configuration and enhance API logging

- Added a new .env file for environment variables including database and Redis configurations.
- Updated CLAUDE.md with hard rules for development practices.
- Enhanced package.json with new scripts for development and infrastructure management.
- Integrated Pino for structured logging in the API, replacing the default NestJS logger.
- Implemented OpenTelemetry for tracing and monitoring in the API.
- Added durationMinutes field to the Mission model in Prisma schema and created corresponding migration.
- Updated missions controller and service to handle mission duration and abandonment logic.
- Introduced new logger module for consistent logging across the application.
This commit is contained in:
Maurycy
2026-05-11 08:38:19 +00:00
parent 21f1a5319f
commit 0031ef0a8f
107 changed files with 3948 additions and 725 deletions

View File

@@ -1 +1,2 @@
export * from './lib/encounter-library';
export { SURVIVOR_NAMES } from './lib/survivors';

View File

@@ -1,4 +1,6 @@
import type { EncounterDefinition } from '@fog-explorer/api-interfaces';
import { ALLY_FIRST_NAMES } from './survivors';
import encountersData from './encounters.json';
interface RawEncounter {
key: string;
@@ -13,9 +15,6 @@ interface EncountersFile {
encounters: RawEncounter[];
}
// eslint-disable-next-line @typescript-eslint/no-require-imports
const encountersData = require('./encounters.json') as EncountersFile;
export interface LibraryEncounter extends EncounterDefinition {
tier: 1 | 2 | 3;
flavorSuccess: string[];
@@ -66,5 +65,12 @@ export function pickFlavor(
rng: () => number
): string {
const pool = ctx.success ? encounter.flavorSuccess : encounter.flavorFailure;
return pool[Math.floor(rng() * pool.length)];
const raw = pool[Math.floor(rng() * pool.length)];
return expandAlly(raw, rng);
}
function expandAlly(text: string, rng: () => number): string {
return text.replace(/\{\{ally\}\}/g, () => {
return ALLY_FIRST_NAMES[Math.floor(rng() * ALLY_FIRST_NAMES.length)];
});
}

View File

@@ -1,5 +1,5 @@
{
"version": "1.0.0",
"version": "1.2.0",
"encounters": [
{
"key": "generator_repair",
@@ -7,28 +7,28 @@
"tags": ["generator", "objectives"],
"tier": 1,
"flavorSuccess": [
"The generator sputters to life. Light floods the area.",
"Sparks fly, then hum — the generator catches.",
"Wires connected. The machine breathes again.",
"A deep mechanical thrum. Progress.",
"The gauge climbs. Another generator done.",
"Hands steady, heart racing. The engine turns over.",
"The generator shudders, then roars. One less in the dark.",
"Circuit reconnected. The hum is its own small victory.",
"It takes longer than expected, but the light comes.",
"The generator protests, then yields."
"It catches. I step back before I let myself feel anything.",
"Sparks, then hum. I keep moving.",
"The last wire clicks. One down.",
"It's running. I don't stop to celebrate.",
"My hands stop shaking when the engine turns over.",
"{{ally}} holds the wire steady. It catches.",
"The gauge climbs. I'm already looking for the next one.",
"Takes too long. But it works.",
"Done. The light helps more than I expected.",
"{{ally}} calls out. I make the connection. Done."
],
"flavorFailure": [
"The generator kicks back. Too many watchers in the dark.",
"The mechanism jams. Footsteps echo nearby.",
"A noise betrays the position. The generator goes cold.",
"Something shifts in the fog. The work abandoned.",
"The wiring resists every attempt. No progress tonight.",
"A shadow crosses the generator. Not now.",
"The tool slips. The noise is too loud.",
"The generator sparks wrong. An alarm of sorts.",
"Patience runs out before the generator does.",
"A nearby sound. The repair will have to wait."
"Something moves in the fog. I pull back.",
"A sound behind me. I leave the gen and run.",
"Too loud. I made it too loud.",
"The wiring's wrong. I can't fix it from here.",
"I miscalculate. The whole thing sparks wrong.",
"{{ally}} had to break away. I lose the progress.",
"My hand slips. The noise is too much.",
"A shadow on the gen. I'm already gone.",
"Not tonight. Not this one.",
"{{ally}} pulls back — something's close. I follow."
]
},
{
@@ -37,28 +37,28 @@
"tags": ["totem", "altruistic", "objectives"],
"tier": 1,
"flavorSuccess": [
"The totem crumbles. Its curse lifts from the fog.",
"Bones scatter. The hex dissolves into smoke.",
"The ritual unravels. Something distant screams.",
"The totem falls apart like it was never solid.",
"Ash and splinter. The hex breaks.",
"A soft crack — the bones give way. Something changes.",
"The skull shatters. Whatever watched through it is gone.",
"The cleansing takes. The fog feels thinner.",
"The totem offers no resistance. It was waiting to fall.",
"One less curse in the trial."
"It breaks. Something screams — not here, but somewhere.",
"The bones scatter. I feel the curse lift.",
"One hex gone. The fog feels slightly less wrong.",
"It comes apart like it was never solid.",
"I don't understand what I just broke. I'm glad I broke it.",
"{{ally}} spots it first. I make short work of it.",
"The skull gives. Whatever watched through it is gone.",
"{{ally}} watches my back. The cleanse is fast.",
"It was waiting to fall. I let it.",
"One less curse."
],
"flavorFailure": [
"The totem resists. Its pull is stronger than expected.",
"A presence drives the survivor back before the cleanse completes.",
"The hex holds. Dread thickens the air.",
"Fingers close on the totem — then something warns away.",
"The totem hums with malice. Not today.",
"The ritual is interrupted. The hex burns brighter.",
"The bones refuse to break. Wrong approach.",
"The totem feels wrong. Leave it for now.",
"Whatever keeps the totem standing isn't done yet.",
"The cleanse stalls halfway. The hex endures."
"It pulls back. I can't finish this.",
"Something drives me away before it's done.",
"The hex holds. The air gets thicker.",
"I reach for it and stop. Something warns me off.",
"The totem hums. I'm not strong enough. Not yet.",
"{{ally}} calls wrong. The totem burns brighter.",
"The bones won't break. Wrong approach.",
"{{ally}} needs me. I leave the totem standing.",
"Halfway through. Then something changes. I run.",
"The hex endures. I'll find another way."
]
},
{
@@ -67,28 +67,28 @@
"tags": ["chest", "item"],
"tier": 1,
"flavorSuccess": [
"The chest yields a worn medkit. Small mercies.",
"A flashlight, still charged. The fog recedes slightly.",
"A useful tool among the debris.",
"Something worth having. The chest wasn't empty after all.",
"Buried under detritus — a working toolbox.",
"A medkit, half-depleted but still viable.",
"The chest creaks open. Something useful inside.",
"Luck, this time.",
"Not everything in the fog is hostile. The chest provides.",
"A find. The supplies won't last, but they'll help."
"A medkit. Worn, but real.",
"A flashlight, still charged. I pocket it.",
"Something useful. I don't ask how it got here.",
"Not empty. That's not nothing.",
"A toolbox, buried under rot. I'll take it.",
"Half a medkit. Better than nothing.",
"The chest creaks open. I take what's there.",
"Lucky.",
"The fog gives sometimes. Not often.",
"Supplies. They won't last, but they'll help."
],
"flavorFailure": [
"The chest is empty. Only rust and regret.",
"The lid splinters — nothing useful inside.",
"A noise nearby cuts the search short.",
"Someone got here first.",
"The chest is locked. No time to force it.",
"Debris, nothing more.",
"Empty. Just rust and old regret.",
"Nothing worth taking.",
"A sound cuts the search short.",
"Someone else was here first.",
"Locked. I don't have time to force it.",
"Debris. Nothing more.",
"Whatever was here is long gone.",
"The chest yields nothing. Time wasted.",
"Empty. The trial takes without giving.",
"Nothing. Move on."
"Nothing. Time I don't have, wasted.",
"The trial takes without giving.",
"Move on."
]
},
{
@@ -97,28 +97,28 @@
"tags": ["hook", "survival"],
"tier": 2,
"flavorSuccess": [
"With grim determination, the survivor slips free.",
"Arms aching, the hook releases. Freedom, for now.",
"A desperate push — the chains give way.",
"The hook yields to persistence.",
"I don't know how. I don't care. I'm off.",
"Arms burning. The hook releases. Move.",
"Every instinct says give up. I don't.",
"It gives. I fall. I run.",
"Blood and effort. The barb comes free.",
"The survivor drops to the ground, gasping.",
"Will wins where strength falls short. Free.",
"The hook's grip loosens. A precious second of clarity.",
"Every instinct says stop. The survivor doesn't.",
"The chains snap. Unlikely. Necessary."
"{{ally}}'s voice from out there somewhere. I fight harder. I'm free.",
"Will wins. I'm off the hook.",
"A fraction of movement — and it releases.",
"I stop thinking and just push. It works.",
"{{ally}} is coming. I hold on long enough. Then I'm free."
],
"flavorFailure": [
"The struggle exhausts. The hook holds.",
"Every movement drives the barb deeper. Stay still.",
"The fog presses in. The hook remains.",
"The attempt fails. The chains don't care.",
"No leverage. No escape. Not yet.",
"The energy spent is wasted. Still trapped.",
"The hook was designed for this. It holds.",
"Patience isn't a luxury here — but neither is panic.",
"The struggle costs everything. The hook doesn't move.",
"Every movement makes it worse. I go still.",
"The fog presses in. I'm still up here.",
"I try. The chains don't.",
"No leverage. Not this time.",
"{{ally}} can't get to me. Still stuck.",
"It's designed for exactly this. It holds.",
"Panic and pain in equal measure. Neither helps.",
"The barb shifts but doesn't release.",
"The struggle ends. The hook wins this round."
"I lose this one."
]
},
{
@@ -127,28 +127,28 @@
"tags": ["exit", "objectives"],
"tier": 2,
"flavorSuccess": [
"The gate grinds open. Cold air rushes in.",
"Generators humming, the lock gives. Almost there.",
"The exit yields. Light from outside cuts the fog.",
"The switch holds down. The gate obeys.",
"A long, agonising second — then the gate opens.",
"The exit panel responds. The way out is clear.",
"The gate was always going to open. Tonight it does.",
"The outside world, briefly visible through iron.",
"The switch trips. The gate moves.",
"One last obstacle overcome."
"The gate grinds open. Cold air. Real air.",
"The lock gives. I keep pressure on the switch.",
"Light from outside cuts through the fog.",
"The gate obeys. I don't let go until it does.",
"A long second. Then the mechanism gives.",
"The panel responds. I'm almost out.",
"It opens. It actually opens.",
"Through the iron, I can see outside.",
"The switch trips. The gate moves. I move.",
"One more thing behind me."
],
"flavorFailure": [
"The gate mechanism is jammed. Precious seconds lost.",
"A shadow falls across the panel. The attempt abandoned.",
"The switch is stuck. The gate stays shut.",
"The gate won't cooperate. Not now.",
"The panel sparks and resets. Try again.",
"Something holds the gate. Not mechanical — worse.",
"The generator noise covers the gate's resistance.",
"The lock mechanism fails. The gate holds.",
"Too slow. The gate doesn't open in time.",
"Pressure on the switch, nothing. Wrong timing."
"The gate mechanism jams. I burn seconds I don't have.",
"A shadow at the panel. I abandon it.",
"The switch won't budge.",
"The gate refuses. Not now.",
"The panel sparks and resets. I try again.",
"Something holds it. Nothing mechanical.",
"The gate resists. I can't tell why.",
"The lock fails. The gate stays shut.",
"Too slow. The gate doesn't care.",
"Nothing. Wrong timing."
]
},
{
@@ -157,28 +157,28 @@
"tags": ["stealth", "survival"],
"tier": 1,
"flavorSuccess": [
"Still as stone. The threat passes without noticing.",
"A breath held long — then silence. Safe.",
"The fog swallows the survivor whole. Unseen.",
"The patrol route is predictable. Predictable is avoidable.",
"Close. Too close. But not close enough to matter.",
"The survivor becomes scenery. The patrol sees nothing.",
"A narrow gap in the patrol. Taken.",
"The killer's attention is elsewhere. Move.",
"Footsteps near, then receding. Safe.",
"The locker is cold and cramped and perfect."
"I don't breathe. I don't think. It passes.",
"A long silence — then the footsteps recede.",
"I melt into the fog. Unseen.",
"Predictable. Predictable is avoidable.",
"Close. Too close. But not enough.",
"{{ally}} draws the patrol the other way. I slip through.",
"A gap in the route. I take it.",
"The killer looks elsewhere. I don't waste it.",
"Footsteps near. Then far. Then gone.",
"The locker is cold and tight and it works."
],
"flavorFailure": [
"A twig snaps. Eye contact — then the chase begins.",
"The survivor misjudges the angle. Spotted.",
"Heartbeat too loud. Presence too close.",
"The patrol's route changed. A fatal assumption.",
"No cover. The survivor is seen.",
"A shadow where there should be none. Detected.",
"The instinct to run is wrong. It happens anyway.",
"The fog offers no protection here.",
"Spotted. The trial shifts.",
"The killer turns at the wrong moment."
"A wrong step. Eye contact. The chase starts.",
"I misjudge the angle. Spotted.",
"My heartbeat is too loud. The presence is too close.",
"The route changed. I didn't know.",
"No cover. Nowhere to go.",
"{{ally}} breaks stealth nearby. We're both compromised.",
"Every instinct says run. That instinct is wrong. I run anyway.",
"The fog doesn't protect me here.",
"Spotted. Everything changes.",
"{{ally}}'s noise pulls the killer my way."
]
},
{
@@ -187,28 +187,28 @@
"tags": ["healing", "altruistic", "survival"],
"tier": 1,
"flavorSuccess": [
"Bandages tight, the wound closes. Pain recedes.",
"The medkit does its job. The survivor steadies.",
"A few tense minutes — injuries tended.",
"Not good as new, but functional. Good enough.",
"The bleeding stops. The fog feels slightly less hostile.",
"Clinical precision in a place with no clinics.",
"The medkit is emptied wisely. The injury yields.",
"Hands that shake still heal.",
"The wound is dressed. One less liability.",
"Recovery, however temporary, is still recovery."
"The bandages hold. The pain pulls back.",
"The medkit does what it's supposed to.",
"Not ideal. But the wound closes.",
"Not fixed. Functional. That's enough.",
"The bleeding stops. I breathe.",
"{{ally}}'s hands are steadier than mine. It works.",
"I use the supplies right. The wound yields.",
"{{ally}} moves fast. The treatment holds.",
"One less liability.",
"Better than nothing. Better than before."
],
"flavorFailure": [
"Supplies exhausted before the job is done.",
"Shaking hands fumble the medkit. Time runs out.",
"The wound is worse than it looked. Supplies fall short.",
"The medkit can't do what it wasn't built for.",
"Partial healing is still an open wound.",
"The medkit empties wrong. Wasted.",
"The supplies run out before the job is done.",
"My hands shake too much. Time runs out.",
"Worse than I thought. Supplies fall short.",
"The medkit can't do this.",
"Half-healed is still injured.",
"{{ally}} isn't here. I can't manage it alone.",
"Not enough supplies. Not enough time.",
"A noise ends the attempt prematurely.",
"The injury resists treatment.",
"The medkit fails the survivor. Move anyway."
"A sound. I have to stop.",
"The wound doesn't cooperate.",
"The medkit fails me. I move anyway."
]
},
{
@@ -217,28 +217,28 @@
"tags": ["survival", "escape"],
"tier": 2,
"flavorSuccess": [
"The pallet crashes down. A moment bought.",
"Timber splinters between them. Distance gained.",
"The drop lands true. The chase falters.",
"A calculated throw. The pallet does its job.",
"The killer staggers. A few precious seconds.",
"The pallet drops at the right instant.",
"An obstacle placed. The chase interrupted.",
"The wood holds. That's all it needs to do.",
"A roar of frustration behind the pallet. Run.",
"The drop lands. The gap widens."
"It crashes down between us. A moment bought.",
"Wood and impact. I gain distance.",
"The drop lands right. The chase falters.",
"Timed right. The pallet works.",
"They stagger. I use every second.",
"The drop lands at the right instant.",
"A barrier placed. The chase interrupted.",
"It holds. That's all it needs to do.",
"A roar behind the pallet. I'm already running.",
"The gap widens. Use it."
],
"flavorFailure": [
"The pallet drops wide. No gap created.",
"Too slow — the obstacle proves useless.",
"The throw miscalculated. The pursuit continues.",
"The pallet falls too early. Used for nothing.",
"The killer vaults before the drop lands.",
"The angle was wrong. The pallet wasted.",
"The swing comes before the pallet hits.",
"Not enough distance to matter.",
"The throw panics. The pallet means nothing.",
"A wasted pallet. The chase doesn't stop."
"The pallet drops wide. No gap.",
"Too slow. It means nothing.",
"I miscalculate. The chase continues.",
"Too early. Wasted.",
"They vault before the wood lands.",
"Wrong angle. Wrong moment.",
"The swing comes first.",
"Not enough distance. Never was.",
"I panic. The pallet follows.",
"Wasted. The chase doesn't stop."
]
},
{
@@ -247,28 +247,28 @@
"tags": ["chest", "item", "high-risk"],
"tier": 3,
"flavorSuccess": [
"The basement yields rare supplies. Worth the risk.",
"A pristine toolbox — almost worth dying for.",
"The gamble paid off. The survivor emerges with something valuable.",
"The basement gives up its best. A full medkit.",
"Down there and back. The best supplies in the trial.",
"The risk calculates out. Everything needed, found.",
"Down there and back. Worth it.",
"A pristine toolbox. Almost worth dying for.",
"The gamble paid. I have what I need.",
"A full medkit in the basement. I don't question it.",
"The best supplies in the trial. Found.",
"I run the numbers. The risk pays out.",
"The stairs back up feel like a victory.",
"The basement held back something good tonight.",
"The basement gives up something good.",
"A rare find. The fog occasionally provides.",
"Against instinct, into the basement. Worth it."
"I go against every instinct. It works."
],
"flavorFailure": [
"The basement was a trap. Retreat costs dearly.",
"The stairwell offers no escape. A mistake made clear.",
"The risk was not worth the reward found — nothing.",
"The basement is empty. And now time is lost.",
"Going down is easy. Coming back is not.",
"The killer knows about the basement.",
"The basement was a trap. I pay for it.",
"The stairs offer nothing. Getting back costs more.",
"The risk wasn't worth it. Nothing down here.",
"Empty. And now I'm out of time.",
"Going down is easy. Coming back is the problem.",
"They know about the basement.",
"Nothing down here but dread.",
"The search finds nothing. The stairs up feel longer.",
"I find nothing. The stairs back feel endless.",
"An empty chest in a dangerous room.",
"The basement takes more than it gives tonight."
"The basement takes more than it gives."
]
},
{
@@ -277,28 +277,28 @@
"tags": ["exit", "survival", "high-risk"],
"tier": 3,
"flavorSuccess": [
"The hatch sighs open. One last mercy from the fog.",
"A sound — familiar, haunting. The hatch, just ahead.",
"Against all odds, the escape route reveals itself.",
"The hatch was always here. It waited.",
"The hatch opens. One last way out.",
"That sound — I know that sound. I run toward it.",
"Against everything. The escape route is there.",
"It was here all along. Waiting.",
"The fog breaks once. That's enough.",
"The survivor finds it before the killer does.",
"One last door. It opens.",
"The hatch glows faintly. Run.",
"The sound of it — an invitation.",
"Found. The trial ends here."
"I find it before they do.",
"One door left. It opens.",
"The hatch glows. I don't wait.",
"I hear it. I go.",
"Found. This ends here."
],
"flavorFailure": [
"The hatch is nowhere. Only fog and silence.",
"Nothing. Only fog.",
"Close — so close. Then it closes.",
"The sound was something else entirely.",
"The hatch stays hidden.",
"Searching costs time that isn't there.",
"The killer finds the hatch first.",
"That sound was something else.",
"It stays hidden.",
"Searching burns time I don't have.",
"They find the hatch first.",
"The fog gives nothing freely.",
"The sound led nowhere. A trick of the trial.",
"No hatch. No escape. Not this way.",
"The trial isn't done with the survivor yet."
"The sound led nowhere.",
"No hatch. No escape this way.",
"The trial isn't finished with me yet."
]
},
{
@@ -307,27 +307,27 @@
"tags": ["escape", "survival"],
"tier": 1,
"flavorSuccess": [
"The window frame holds. Through and clear.",
"A clean vault. Momentum kept.",
"The opening is tight but usable.",
"Through the gap before the killer closes.",
"The vault buys distance. Use it.",
"A practiced move. The window yields.",
"Landing clean on the other side.",
"The window is a door when you need it.",
"Fast enough. The gap widens.",
"Clean vault. The frame holds.",
"Through and clear. Momentum kept.",
"Tight, but usable.",
"I make it through before they close the gap.",
"The vault buys distance. I use it.",
"Practiced. The window yields.",
"I land clean on the other side.",
"The window is a door when I need it.",
"Fast enough. The gap opens.",
"Through. The chase resets."
],
"flavorFailure": [
"The window is boarded shut.",
"The vault is too slow. Caught.",
"The frame splinters. Not a clean exit.",
"Misjudged the height. A costly stumble.",
"The window closes the gap but not the killer.",
"Too narrow. No good.",
"The vault fails at the worst moment.",
"The opening was an illusion. Dead end.",
"A bad angle kills the attempt.",
"Boarded. No exit here.",
"Too slow. I'm caught mid-vault.",
"The frame splinters wrong.",
"I misjudge the height. I pay for it.",
"The window doesn't solve the problem.",
"Too narrow.",
"The worst possible moment for this to fail.",
"The opening was an illusion.",
"Bad angle. Bad outcome.",
"The window doesn't help tonight."
]
},
@@ -337,28 +337,28 @@
"tags": ["stealth", "survival"],
"tier": 1,
"flavorSuccess": [
"The locker closes. Footsteps pass. Breathe.",
"Darkness and rust — perfect cover.",
"The killer walks past the locker without a glance.",
"The door closes. Footsteps pass. Breathe.",
"Rust and darkness. It works.",
"They walk past without a glance.",
"Still. Quiet. Safe.",
"The locker is old and cold and hiding works.",
"In and out before the threat turns around.",
"The locker holds its occupant safe.",
"A long minute of nothing. Then clear.",
"The hiding spot works. The patrol moves on.",
"The locker door stays closed. The killer moves on."
"Cold metal. Silence. It holds.",
"{{ally}} draws them off. The locker stays shut.",
"I hold my breath. The footsteps don't stop here.",
"One long minute. Then nothing. Clear.",
"The hiding spot works. They move on.",
"They don't check it. I don't breathe until they're gone."
],
"flavorFailure": [
"The locker door rattles. Discovered.",
"The killer yanks it open. No hiding here.",
"The locker was already checked.",
"The sound of breathing gives the position away.",
"The killer knows these lockers too well.",
"A squeak of the hinge. Fatal.",
"The hiding spot chosen is the wrong one.",
"The killer's instinct is correct tonight.",
"Spotted entering. No time.",
"The locker offers nothing. Pulled out."
"The door rattles. I'm found.",
"It yanks open. No hiding here.",
"Already checked. Bad call.",
"My breathing gives me away.",
"They know these lockers too well.",
"{{ally}} makes noise nearby. They look harder. They find me.",
"The wrong spot. I know it as soon as I'm inside.",
"Their instinct is right tonight.",
"Spotted going in. No time.",
"Pulled out."
]
},
{
@@ -367,28 +367,28 @@
"tags": ["objectives", "altruistic"],
"tier": 2,
"flavorSuccess": [
"The trap clicks disarmed. One less hazard.",
"Patient work. The mechanism yields.",
"The snap of metal, controlled this time.",
"Disarmed without triggering. Clean.",
"The trap comes apart safely.",
"A moment of focus. The trap poses no more threat.",
"It clicks safe. One less hazard.",
"Patience. The mechanism yields.",
"The snap of metal controlled this time.",
"Clean disarm. No mistakes.",
"It comes apart safely. I exhale.",
"One moment of focus. The trap is inert.",
"The floor is safer now.",
"The mechanism was elegant. Now it's inert.",
"One trap gone. Onwards.",
"The disarm holds. The path is clear."
"Elegant mechanism. Now it's nothing.",
"One trap down. Keep moving.",
"Disarmed. Path clear."
],
"flavorFailure": [
"The trap snaps. A brutal reminder.",
"It snaps. A brutal reminder of what this place is.",
"One wrong move. The trap wins.",
"The mechanism was subtler than expected.",
"The disarm attempt triggers it.",
"Fingers too slow. The trap bites.",
"The trap was set with someone capable in mind.",
"Miscalculation. The mechanism triggers.",
"The trap holds and catches.",
"The attempt fails. Pain follows.",
"The trap wasn't meant to be disarmed easily."
"Subtler than I expected.",
"My attempt triggers it.",
"Too slow. The trap bites.",
"Whoever set this knew what they were doing.",
"I miscalculate. The mechanism fires.",
"It catches me.",
"I fail. Pain follows.",
"It wasn't meant to be easy."
]
},
{
@@ -397,26 +397,26 @@
"tags": ["altruistic", "hook"],
"tier": 2,
"flavorSuccess": [
"Off the hook. Both survivors run.",
"The unhook is clean. No one watching.",
"A teammate freed. The trial continues.",
"Quick hands, lucky timing. The rescue holds.",
"The hook releases. Time to move.",
"Pulled free before the killer returns.",
"The rescue works. Move fast.",
"A window of opportunity, taken.",
"The teammate drops free. The trial has two again.",
"Freed. Both running now."
"{{ally}} gets there first. Both of us run.",
"Clean unhook. Nobody watching.",
"I get them down. The trial continues.",
"Fast hands, right timing.",
"They're off the hook. We move.",
"{{ally}} pulls them free before the killer returns.",
"The rescue works. Don't stop moving.",
"The window was there. I took it.",
"{{ally}} gets them clear. Two of us running again.",
"Free. Both running now."
],
"flavorFailure": [
"The killer turns back too soon. Retreat.",
"The rescue fails. Both caught.",
"The timing was wrong.",
"The unhook attempted — the killer was watching.",
"No safe window. The rescue abandoned.",
"The hook held on.",
"The approach was spotted.",
"The teammate is hooked again. Worse now.",
"{{ally}} pulls back. The killer turns too soon.",
"The rescue fails. Both of us in danger.",
"Wrong timing.",
"{{ally}} reaches for the hook — the killer is already watching.",
"No safe window. I leave.",
"The hook holds.",
"{{ally}}'s approach is spotted.",
"They're hooked again. Worse now.",
"The rescue was a trap.",
"The killer was closer than it looked."
]
@@ -427,26 +427,26 @@
"tags": ["totem", "objectives", "altruistic"],
"tier": 2,
"flavorSuccess": [
"The ruin totem falls. Generator speed returns to normal.",
"It falls. Generator speed comes back.",
"The hex breaks. The pressure lifts.",
"Ruin destroyed. The generators are finally cooperating.",
"The totem crumbles. Something screams.",
"The ruin is gone. Work can continue properly.",
"Ruin is gone. Finally.",
"{{ally}} calls out the location. I put it down.",
"One less advantage for the fog.",
"The corrupting influence ends here.",
"Ruin undone. A meaningful contribution.",
"The hex shattered. Every generator benefits.",
"Ruin falls. The trial shifts.",
"The cleansing removes one of the fog's advantages."
"Ruin undone. That mattered.",
"{{ally}} watches while I cleanse. The hex shatters.",
"Ruin falls. The whole trial shifts.",
"One of the fog's edges, removed."
],
"flavorFailure": [
"Ruin holds. The generators resist every effort.",
"The totem is guarded.",
"Ruin endures. Progress is a trickle.",
"The hex is stronger than expected.",
"The cleanse fails. Ruin stays.",
"Guarded or cursed — the totem won't fall.",
"The approach to ruin is cut off.",
"Ruin watches everything. No safe approach.",
"Ruin holds. The generators push back at every touch.",
"It's guarded.",
"Ruin endures. Progress is slower than it should be.",
"The hex is stronger than I am right now.",
"I fail. Ruin stays.",
"{{ally}} couldn't hold them back. Ruin stands.",
"The path to it is cut off.",
"Ruin sees everything. I can't get close.",
"The hex survives the attempt.",
"Ruin stands. The generators punish every mistake."
]
@@ -457,26 +457,26 @@
"tags": ["stealth", "survival", "objectives"],
"tier": 1,
"flavorSuccess": [
"The area is clear. Work continues.",
"A careful sweep — nothing here.",
"All quiet. Safe to proceed.",
"The check takes seconds. Worth every one.",
"Nothing threatening nearby. Focus returned.",
"The perimeter is clean. Move on.",
"A slow look around — all clear.",
"The survivor's instincts were right. Safe.",
"No danger. The noise was nothing.",
"The area is empty. Continue."
"All clear. Back to work.",
"Nothing here. I was right to check.",
"Quiet. Safe to continue.",
"Seconds spent checking. Worth it.",
"Nothing threatening nearby.",
"{{ally}} signals clear. I continue.",
"Slow sweep. Nothing. Move.",
"My instincts were right. Safe.",
"The noise was nothing.",
"Empty. Continue."
],
"flavorFailure": [
"The check reveals what was feared.",
"Not clear. Danger is close.",
"The area isn't empty.",
"The sound was not nothing.",
"A presence, sensed too late.",
"The check confirms the worst.",
"Not safe. Move now.",
"The area is watched.",
"The check confirms what I feared.",
"Not clear. Move now.",
"Not empty.",
"That sound was not nothing.",
"{{ally}}'s signal cuts out. Something is here.",
"Confirmed. Worse than I thought.",
"Get out.",
"I'm being watched.",
"The noise was a warning.",
"Something is here."
]
@@ -487,26 +487,26 @@
"tags": ["item", "objectives"],
"tier": 1,
"flavorSuccess": [
"A map of the trial grounds. The generators are marked.",
"Routes, exits — everything useful on one piece of paper.",
"The map provides context. Decisions improve.",
"A map of the grounds. The generators are marked.",
"Routes, exits — all of it on one piece of paper.",
"The map helps. Decisions get clearer.",
"A schematic of the realm. Useful.",
"The map shows what couldn't be known otherwise.",
"A rare find. The trial becomes navigable.",
"Totem locations, hooks — the map tells all.",
"The map is accurate. That's not guaranteed here.",
"A clearer picture of the trial grounds.",
"The map is old but correct. It will help."
"The map tells me things I couldn't know otherwise.",
"A rare find. I know where I'm going now.",
"Totem locations, hooks — the map knows.",
"Accurate. I didn't expect that.",
"A clearer picture.",
"Old, but correct. I'll take it."
],
"flavorFailure": [
"Nothing useful found.",
"The map, if it existed, is gone.",
"The search yields no information.",
"Fog and confusion. No map here.",
"Nothing useful.",
"No map. If there was one, it's gone.",
"The search gives me nothing.",
"Fog and no information.",
"The grounds give nothing away.",
"The trial keeps its layout to itself.",
"No orientation gained.",
"Blind as before.",
"Still blind.",
"The realm offers no guide tonight.",
"No map. Trust instinct."
]
@@ -517,28 +517,28 @@
"tags": ["stealth", "altruistic"],
"tier": 1,
"flavorSuccess": [
"The trail read correctly. The survivor is found.",
"Scratch marks, followed. Another survivor located.",
"The signs in the fog tell a story.",
"The trail is fresh. Someone was here recently.",
"I read the trail right. Found.",
"{{ally}}'s marks, followed. They're close.",
"The fog leaves clues if you know how to look.",
"{{ally}} was just here. The trail is fresh.",
"The marks lead somewhere useful.",
"Reading the ground pays off.",
"The scratch marks don't lie.",
"A teammate's path, followed to its source.",
"The signs are legible. The survivor is found.",
"The trail ends at something worth finding."
"I follow {{ally}}'s path. It ends well.",
"Legible. I find what I was looking for.",
"The trail ends somewhere worth being."
],
"flavorFailure": [
"The trail goes cold.",
"The marks lead nowhere useful.",
"Misread. The path was wrong.",
"The trail circles back. Disorienting.",
"The signs in the fog are misleading.",
"Too old. The trail is gone.",
"The scratch marks weren't recent.",
"The trail led somewhere dangerous.",
"The signs can't be read clearly.",
"The trail ends without resolution."
"{{ally}}'s marks lead nowhere useful.",
"I read it wrong. Wrong direction.",
"The trail circles. I lose time.",
"The fog misleads. The signs are wrong.",
"Too old. {{ally}}'s trail is gone.",
"The marks weren't recent.",
"The trail leads somewhere I don't want to be.",
"I can't make sense of them.",
"No resolution."
]
},
{
@@ -547,26 +547,26 @@
"tags": ["high-risk", "survival"],
"tier": 3,
"flavorSuccess": [
"The presence recedes. The survivor is not chosen.",
"Whatever stalks the fog passes over.",
"The terror subsides. Not tonight.",
"The entity considers — then moves on.",
"The survivor is overlooked. Lucky.",
"The cold lifts. The entity chose elsewhere.",
"The fog's attention moves away.",
"Stillness. The presence is gone.",
"The presence passes over me. Not chosen. Not tonight.",
"Whatever stalks the fog — it moves on.",
"The terror recedes. I'm still here.",
"It considers. Then looks elsewhere.",
"I'm overlooked. I don't question it.",
"The cold lifts. It chose someone else.",
"The fog's attention slides away.",
"Stillness. It's gone.",
"Not taken. Not yet.",
"The chill passes. The trial continues."
"The chill passes. I continue."
],
"flavorFailure": [
"The fog thickens. Something approaches.",
"The presence locks on. There is no outrunning this.",
"The entity's attention does not waver.",
"Noticed. The trial changes.",
"The chill deepens. The entity is here.",
"No escape from something this deliberate.",
"The fog moves with purpose.",
"The survivor is seen by something that doesn't forget.",
"The fog thickens around me. Something is coming.",
"It locks on. There's no running from this.",
"The attention doesn't move.",
"Noticed. Everything changes.",
"The chill deepens. It's here.",
"Nothing escapes something this deliberate.",
"The fog moves with intent.",
"Something sees me. Something that doesn't forget.",
"The presence descends.",
"Chosen. The hardest part begins."
]
@@ -577,28 +577,28 @@
"tags": ["high-risk", "stealth"],
"tier": 3,
"flavorSuccess": [
"The instinct passes harmlessly. Not found.",
"The killer's sense of where to look is wrong tonight.",
"The prediction fails. Safe.",
"The killer checks elsewhere.",
"Instinct misfires. The survivor is still hidden.",
"Wrong corner checked. The survivor breathes.",
"The killer's certainty was misplaced.",
"The instinct misfires. I'm not found.",
"They look in the wrong place.",
"The prediction fails. I'm safe.",
"They check elsewhere.",
"Wrong corner. I breathe.",
"They're certain. They're wrong.",
"The read was off. I move.",
"Not there. Not tonight.",
"The instinct was wrong. Move quickly.",
"The killer's focus breaks elsewhere."
"The instinct fails them.",
"Their focus breaks. I use it."
],
"flavorFailure": [
"The killer knows exactly where to look.",
"Instinct and experience. The survivor is found.",
"No hiding from something this certain.",
"The killer's read was correct.",
"The position is given away before the search begins.",
"Exactly where expected. No luck tonight.",
"The killer's certainty was earned.",
"Found before the search starts.",
"They know exactly where I am.",
"Instinct and experience. I'm found.",
"There's no hiding from something this certain.",
"The read was right.",
"My position is given away before the search starts.",
"Exactly where expected.",
"Their certainty was earned.",
"Found before the search begins.",
"The instinct is right.",
"The killer walks directly to the survivor."
"They walk directly to me."
]
}
]

View File

@@ -0,0 +1,63 @@
export const SURVIVOR_NAMES: readonly string[] = [
'Dwight Fairfield',
'Meg Thomas',
'Claudette Morel',
'Jake Park',
'Nea Karlsson',
'Laurie Strode',
'Ace Visconti',
'Bill Overbeck',
'Feng Min',
'David King',
'Quentin Smith',
'David Tapp',
'Kate Denson',
'Adam Francis',
'Jeff Johansen',
'Jane Romero',
'Ash Williams',
'Nancy Wheeler',
'Steve Harrington',
'Yui Kimura',
'Zarina Kassir',
'Cheryl Mason',
'Felix Richter',
'Élodie Rakoto',
'Yun-Jin Lee',
'Jill Valentine',
'Leon S. Kennedy',
'Mikaela Reid',
'Jonah Vasquez',
'Yoichi Asakawa',
'Haddie Kaur',
'Ada Wong',
'Rebecca Chambers',
'Vittorio Toscano',
'Thalita Lyra',
'Renato Lyra',
'Gabriel Soma',
'Nicolas Cage',
'Ellen Ripley',
'Alan Wake',
'Sable Ward',
'Aestri Yazar',
'Lara Croft',
'Trevor Belmont',
'Taurie Cain',
'Rick Grimes',
'Michonne Grimes',
'Vee Boonyasak',
];
export const ALLY_FIRST_NAMES: readonly string[] = [
'Dwight', 'Meg', 'Claudette', 'Jake', 'Nea',
'Laurie', 'Ace', 'Bill', 'Feng', 'David',
'Quentin', 'Tapp', 'Kate', 'Adam', 'Jeff',
'Jane', 'Ash', 'Nancy', 'Steve', 'Yui',
'Zarina', 'Cheryl', 'Felix', 'Élodie', 'Yun-Jin',
'Jill', 'Leon', 'Mikaela', 'Jonah', 'Yoichi',
'Haddie', 'Ada', 'Rebecca', 'Vittorio', 'Thalita',
'Renato', 'Gabriel', 'Nicolas', 'Ellen', 'Alan',
'Sable', 'Aestri', 'Lara', 'Trevor',
'Taurie', 'Rick', 'Michonne', 'Vee',
];