Role system_prompts are inert on mission turns, so a team without a recipe has no default brief and every caller must write one (v2 wrote the wrong one: 0/50 calls read the record). The recipe points the phase at /mission/memory/PROJECT-MEMORY.md, requires verbatim quotes only where a pattern is reported, and names the clean-record and no-record outcomes as passes — the v3 condition failed a correct clean audit for paraphrasing. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
123 lines
4.0 KiB
TypeScript
123 lines
4.0 KiB
TypeScript
// Colour, keyed on what the mission is FOR.
|
|
//
|
|
// A benchmark run and a security sweep were rendered in the same palette as a
|
|
// research mission, so the only thing distinguishing them on screen was the
|
|
// label. Colour is the cheapest signal a viewer reads before any text, and it
|
|
// was carrying no information.
|
|
//
|
|
// This also collapses two uncoordinated kind→colour maps that had drifted
|
|
// apart: `LEVEL_COLOR` in engine.ts (node colour by tier) and the `fireColor`
|
|
// if-chain in `onTouch` (beam colour by id prefix). They answered the same
|
|
// question in different places with different answers.
|
|
|
|
import type { Tier } from "./engine";
|
|
|
|
export type PhaseKind = "research" | "coding" | "benchmark" | "security_scan";
|
|
|
|
export interface Palette {
|
|
/** The mission landmark at the centre. */
|
|
mission: string;
|
|
/** Station colour per phase kind. */
|
|
phase: Record<PhaseKind, string>;
|
|
dir: string;
|
|
file: string;
|
|
tool: string;
|
|
finding: string;
|
|
comm: { message: string; delegate: string; room: string; web: string };
|
|
}
|
|
|
|
/** The default — what the World looked like before palettes, so an unrecognised
|
|
* template renders exactly as it always did rather than as something odd. */
|
|
const BASE: Palette = {
|
|
mission: "#ff8a7a",
|
|
phase: {
|
|
research: "#c98af0",
|
|
coding: "#8ec5ff",
|
|
benchmark: "#f0b866",
|
|
security_scan: "#ff5f57",
|
|
},
|
|
dir: "#5ec8d8",
|
|
file: "#5ec8d8",
|
|
tool: "#5ec8d8",
|
|
finding: "#ff8a7a",
|
|
comm: { message: "#ff5eae", delegate: "#ff8a7a", room: "#c98af0", web: "#5ec8d8" },
|
|
};
|
|
|
|
/** Measurement work reads amber/green — instrument tones, not creative ones. */
|
|
const BENCHMARK: Palette = {
|
|
...BASE,
|
|
mission: "#f0b866",
|
|
phase: { ...BASE.phase, benchmark: "#5fd08a", coding: "#e8b465" },
|
|
dir: "#a8b06a",
|
|
file: "#d8c06a",
|
|
tool: "#5fd08a",
|
|
comm: { ...BASE.comm, web: "#a8b06a" },
|
|
};
|
|
|
|
/** Adversarial work reads crimson/steel. */
|
|
const SECURITY: Palette = {
|
|
...BASE,
|
|
mission: "#ff5f57",
|
|
phase: { ...BASE.phase, security_scan: "#ff5f57", coding: "#8a9af0" },
|
|
dir: "#7a8a9a",
|
|
file: "#9aa8b8",
|
|
tool: "#ff8a7a",
|
|
finding: "#ff3b30",
|
|
comm: { ...BASE.comm, web: "#7a8a9a" },
|
|
};
|
|
|
|
/** Reading and mapping an existing codebase — cooler, quieter. */
|
|
const REFACTOR: Palette = {
|
|
...BASE,
|
|
mission: "#6fd0c0",
|
|
phase: { ...BASE.phase, coding: "#6fd0c0" },
|
|
dir: "#4aa3b8",
|
|
file: "#6fd0c0",
|
|
comm: { ...BASE.comm, web: "#4aa3b8" },
|
|
};
|
|
|
|
/** Pure research — violet and sky, the existing landmark tones. */
|
|
const RESEARCH: Palette = {
|
|
...BASE,
|
|
mission: "#c98af0",
|
|
phase: { ...BASE.phase, research: "#c98af0" },
|
|
dir: "#8a9af0",
|
|
file: "#8ec5ff",
|
|
};
|
|
|
|
const PALETTES: Record<string, Palette> = {
|
|
research_only: RESEARCH,
|
|
research_and_code: BASE,
|
|
security_hardening: SECURITY,
|
|
refactor: REFACTOR,
|
|
benchmark: BENCHMARK,
|
|
self_audit: RESEARCH,
|
|
custom: BASE,
|
|
};
|
|
|
|
export function paletteFor(templateKind?: string | null): Palette {
|
|
return (templateKind && PALETTES[templateKind]) || BASE;
|
|
}
|
|
|
|
/** Node colour for an id + tier. Replaces `LEVEL_COLOR[tier]` for everything a
|
|
* mission scope draws; structural tiers keep their own constants. */
|
|
export function colorFor(p: Palette, nodeId: string, tier: Tier): string | undefined {
|
|
if (tier === "mission") return p.mission;
|
|
if (nodeId.startsWith("finding:")) return p.finding;
|
|
if (nodeId.startsWith("dir:")) return p.dir;
|
|
if (nodeId.startsWith("file:")) return p.file;
|
|
if (nodeId.startsWith("tool:")) return p.tool;
|
|
// `phase` deliberately absent: a station's colour comes from its KIND, which
|
|
// the node id does not carry. `applyMissionPlan` passes it explicitly.
|
|
return undefined;
|
|
}
|
|
|
|
/** Beam colour when a pawn touches a node — the old `fireColor` if-chain. */
|
|
export function fireFor(p: Palette, nodeId: string, weight: number): string | undefined {
|
|
if (nodeId.startsWith("file:")) return weight >= 0.9 ? p.file : p.tool;
|
|
if (nodeId.startsWith("tool:")) return p.tool;
|
|
if (nodeId.startsWith("phase:")) return p.mission;
|
|
if (nodeId.startsWith("finding:")) return p.finding;
|
|
return undefined;
|
|
}
|