Phase 8: recognition, pursuit, and losing them

Cover is now a state you can lose. Suspicion builds while enemy eyes are on you,
squared with proximity so one patrol beside the car outweighs a crowd across the
street, and blocked by buildings like everything else. It cannot build at all
behind your own lines. Ramming, being shot at and running somebody over skip the
meter and land in full.

At the top of it you are hunted: nearby enemies drop what they were doing and
come, and keep joining as you drive past fresh patrols, since a chase you could
outrun by passing more of them would be no chase. Being hunted is also now the
only reason the enemy shoots at you — a target is a state, not a property of the
road you happen to be on.

Two ways out. Break line of sight for fourteen seconds, with any glimpse
resetting the clock; hunters drive to where they last saw you rather than where
you are, so turning a corner and changing direction is the move. Or let your own
side occupy them — hunters are patrols, not fanatics, and enough peeling off
ends the chase outright. Escaping leaves suspicion at 0.6 rather than zero: they
know there is a car worth looking for.

Tuning a test caught: the post-escape value sat a hair above the "suspicious"
threshold, so the afterglow expired within a second and the rule may as well not
have existed. Decay slowed to match, so cover is earned back by being dull
rather than by turning one corner.

Verified end to end in the running game: loitering beside a patrol in occupied
ground filled the meter and pulled three hunters, fleeing out of sight cleared it
after exactly fourteen seconds, and a visible player was closed on from 61m to
12m while taking hits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
dejvino
2026-08-08 12:20:47 +02:00
co-authored by Claude Opus 5
parent 5e557dc93a
commit d10033d79e
7 changed files with 662 additions and 9 deletions
+27
View File
@@ -39,6 +39,14 @@ export interface HudModel {
bearing: number;
} | null;
control: Control;
pursuit: {
/** 0..1 toward being recognised. */
meter: number;
alert: 'clear' | 'suspicious' | 'hunted';
hunters: number;
/** Seconds of staying out of sight before they give up. */
losingIn: number;
};
/** Rounds in the air nearby. Not a health bar — a reason to keep moving. */
danger: number;
completed: number;
@@ -57,6 +65,24 @@ const arrowFor = (bearing: number): string => {
return ARROWS[((sector % 8) + 8) % 8]!;
};
/**
* The one meter the player is meant to watch. Deliberately not a number: it is
* "how close am I to being made", and it wants reading at a glance while
* driving.
*/
function pursuitLines(pursuit: HudModel['pursuit']): string[] {
if (pursuit.alert === 'hunted') {
return [
`HUNTED — ${pursuit.hunters} on you`,
pursuit.losingIn > 0
? `break line of sight · ${pursuit.losingIn.toFixed(0)}s to lose them`
: 'they can see you',
];
}
if (pursuit.meter <= 0.02) return ['cover intact'];
return [`noticed ${bar(pursuit.meter)}${pursuit.alert === 'suspicious' ? ' — being watched' : ''}`];
}
const CONTROL_WORDS: Record<Control, string> = {
liberated: 'liberated — nobody is watching the roads here',
contested: 'contested — the roads remember, slowly',
@@ -105,6 +131,7 @@ export function createHud(seed: number) {
...(model.quest ? questLines(model.quest) : ['no mission — find a base']),
'',
CONTROL_WORDS[model.control],
...pursuitLines(model.pursuit),
...(model.danger > 0 ? [`⚠ rounds in the air nearby (${model.danger})`] : []),
'',
// Debug only. The real game signals heat through the road itself —