Phase 5: four real jobs, radio, and work found in the field
Mission types. Four of them, differing in pacing and risk rather than in the noun on the board: a quick supply drop, a retrieval whose danger is all on the return leg, a long stationary intel transmission in the open, and a cheap survey whose payoff is the map. Retrieval carries cargo that impacts damage, and pays for what arrives rather than what you set off with, which makes the drive home a different problem from the drive out. Radio. Chatter framing the player as an undercover driver being talked at by people busy elsewhere. Every line is triggered by something actually happening in the sim — crossing a border, a road you personally made notorious, a car past saving, or the front genuinely drifting. The enemy-has-other-priorities line fires off measured drift, so the brief's ambient-drift-is-the-mechanism idea is said out loud without ever showing a number. Topics carry cooldowns and never repeat a sentence back to back; a test caught that they could. Field work. Past the line, with nothing in hand, a wounded stranger or a contact or a strippable wreck occasionally turns up. Deliberately not on a board: no route preview, no comparison, no shopping between them. Take the detour in front of you or drive on. They never appear behind your own lines, where a safe errand would defeat the point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+14
-2
@@ -1,6 +1,17 @@
|
||||
import type { Base } from '../sim/bases';
|
||||
import type { HeatLevel } from '../sim/heat';
|
||||
import type { Offer } from '../sim/quests';
|
||||
import { MISSION_SHAPE, type MissionType, type Offer } from '../sim/quests';
|
||||
|
||||
/**
|
||||
* What each job actually asks of you, in the words the people asking would use.
|
||||
* The point is that the four read as different *risks*, not different nouns.
|
||||
*/
|
||||
const BRIEFING: Record<MissionType, string> = {
|
||||
supply: 'drop the load and come back — quick work at the far end',
|
||||
retrieval: 'collect and carry it home; it pays for what arrives intact',
|
||||
intel: 'sit still and transmit, a long minute in the open',
|
||||
recon: 'eyes on the area — you will come back knowing the roads',
|
||||
};
|
||||
|
||||
/**
|
||||
* The quest board. Deliberately words, not numbers: the player is reading a
|
||||
@@ -67,10 +78,11 @@ export function createBoard() {
|
||||
(offer, i) => `
|
||||
<li>
|
||||
<span class="key">[${i + 1}]</span>
|
||||
${offer.type === 'recon' ? 'Survey' : 'Supply run'} —
|
||||
${MISSION_SHAPE[offer.type].label} —
|
||||
${offer.novel ? '<span class="novel">new target</span>' : 'known target'}
|
||||
· ${(offer.route.length / 1000).toFixed(1)} km
|
||||
· ${offer.reward.toFixed(2)} parts
|
||||
<div class="meta">${BRIEFING[offer.type]}</div>
|
||||
<div class="meta">${describe(offer)}</div>
|
||||
</li>`,
|
||||
)
|
||||
|
||||
+9
-5
@@ -1,8 +1,8 @@
|
||||
import { SUBSYSTEMS, type CarCondition } from '../sim/car';
|
||||
import type { HeatLevel } from '../sim/heat';
|
||||
import type { MissionType } from '../sim/quests';
|
||||
import { MISSION_SHAPE, type MissionType } from '../sim/quests';
|
||||
import type { Control } from '../sim/regions';
|
||||
import { WORK_SECONDS } from '../sim/quests';
|
||||
|
||||
|
||||
const BAR_WIDTH = 12;
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface HudModel {
|
||||
stage: 'travel' | 'working' | 'return';
|
||||
novel: boolean;
|
||||
worked: number;
|
||||
cargo: number;
|
||||
distance: number;
|
||||
/** Radians from the car's nose to the target, positive to the left. */
|
||||
bearing: number;
|
||||
@@ -57,13 +58,16 @@ const CONTROL_WORDS: Record<Control, string> = {
|
||||
};
|
||||
|
||||
function questLines(quest: NonNullable<HudModel['quest']>): string[] {
|
||||
const label = quest.type === 'recon' ? 'Survey' : 'Supply run';
|
||||
const shape = MISSION_SHAPE[quest.type];
|
||||
const label = shape.label;
|
||||
const kind = quest.novel ? 'new target' : 'known target';
|
||||
if (quest.stage === 'return') {
|
||||
return [`${label} done — return to any base`];
|
||||
// Only retrieval has anything aboard that can be spoiled on the way home.
|
||||
const cargo = shape.cargoHome ? ` · cargo ${(quest.cargo * 100).toFixed(0)}%` : '';
|
||||
return [`${label} done — return to any base${cargo}`];
|
||||
}
|
||||
if (quest.stage === 'working') {
|
||||
return [`${label} — working ${quest.worked.toFixed(1)}/${WORK_SECONDS}s (hold still)`];
|
||||
return [`${label} — ${shape.working} ${quest.worked.toFixed(1)}/${shape.work}s (hold still)`];
|
||||
}
|
||||
return [
|
||||
`${label} (${kind})`,
|
||||
|
||||
@@ -45,6 +45,8 @@ export interface MinimapView {
|
||||
/** Car heading in radians, matching the world's yaw convention. */
|
||||
heading: number;
|
||||
objective: { x: number; z: number } | null;
|
||||
/** A field contact waiting somewhere, if one has turned up. */
|
||||
opportunity: { x: number; z: number } | null;
|
||||
/** Elapsed time, for ageing observations. */
|
||||
now: number;
|
||||
}
|
||||
@@ -133,6 +135,14 @@ export function createMinimap(roads: RoadNetwork, bases: Base[], worldExtent: nu
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// --- A field contact: marked, but not the same as an assigned target ---
|
||||
if (view.opportunity) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(px(view.opportunity.x), px(view.opportunity.z), 3.5, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#cf6bd6';
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// --- The car, as an arrow, so heading is readable at a glance ---
|
||||
const cx = px(view.x);
|
||||
const cz = px(view.z);
|
||||
|
||||
Reference in New Issue
Block a user