attention / src /components /autonomy /AutonomyStack.tsx
whassan's picture
Add autonomy stack emulator tab
7311a16 verified
Raw History Blame Contribute Delete
12 kB
import { useEffect, useMemo, useState } from "react";
import { Pause, Play, RotateCcw, SkipForward } from "lucide-react";
import {
FLOW_ORDER,
FRAMES,
LEARNING_ORDER,
PLANES,
RUNTIME_ORDER,
SAFETY_ORDER,
STAGES,
type StageId,
} from "../../data/autonomy";
import { FlowArrow, StageCard } from "./StageCard";
import { StageDetail } from "./StageDetail";
type Props = {
openStage: StageId | null;
onOpenStage: (id: StageId | null) => void;
};
const PHASES: { key: string; label: string; stages: StageId[] }[] = [
{ key: "sense", label: "Sense", stages: ["sensors", "fusion"] },
{ key: "understand", label: "Understand", stages: ["perception"] },
{ key: "predict", label: "Predict", stages: ["prediction"] },
{ key: "decide", label: "Decide", stages: ["behavior", "motion"] },
{ key: "act", label: "Act", stages: ["control", "actuators"] },
{ key: "verify", label: "Verify", stages: ["supervisor"] },
{ key: "learn", label: "Learn", stages: ["capture"] },
];
const TICK_MS = 1500;
const CYCLE: StageId[] = [...RUNTIME_ORDER, "world"];
export function AutonomyStack({ openStage, onOpenStage }: Props) {
const [playing, setPlaying] = useState(true);
const [tick, setTick] = useState(0);
useEffect(() => {
if (!playing) return;
const id = window.setInterval(() => setTick((t) => t + 1), TICK_MS);
return () => window.clearInterval(id);
}, [playing]);
const cycle = Math.floor(tick / CYCLE.length);
const activeRuntime = CYCLE[tick % CYCLE.length]!;
const frame = FRAMES[cycle % FRAMES.length]!;
const activeLearning = LEARNING_ORDER[Math.floor(tick / 3) % LEARNING_ORDER.length]!;
const overriding = frame.safety === "override";
const gating = activeRuntime === "control" || activeRuntime === "actuators";
const phase = useMemo(
() => PHASES.find((item) => item.stages.includes(activeRuntime))?.key ?? "sense",
[activeRuntime],
);
const activeIds = useMemo(() => {
const set = new Set<StageId>([activeRuntime, activeLearning]);
if (gating || overriding) set.add("supervisor");
if (overriding) set.add("fallback");
return set;
}, [activeLearning, activeRuntime, gating, overriding]);
const step = () => {
setPlaying(false);
setTick((t) => t + 1);
};
const detail = openStage ? STAGES[openStage] : null;
const detailIndex = openStage ? FLOW_ORDER.indexOf(openStage) : -1;
return (
<section aria-label="Autonomy stack emulator" className="space-y-5">
<div className="rounded-xl border border-white/10 bg-[#0e1624] p-4">
<h2 className="font-mono text-[10px] tracking-[0.22em] text-cyan-200/80 uppercase">
Closed loop
</h2>
<ol className="mt-3 flex flex-wrap items-center gap-x-2 gap-y-2">
{PHASES.map((item, index) => (
<li key={item.key} className="flex items-center gap-2">
<span
className={`rounded-md border px-2.5 py-1 text-xs transition-colors ${
phase === item.key
? "border-cyan-300/70 bg-cyan-400/15 text-white"
: "border-white/10 text-slate-400"
}`}
>
{item.label}
</span>
{index < PHASES.length - 1 ? (
<span aria-hidden className="text-slate-600">
→
</span>
) : null}
</li>
))}
</ol>
<p className="mt-3 text-sm leading-relaxed text-slate-400">
An autonomy stack is not a pipeline that ends. Every action changes
the world, the sensors measure the change, and the loop runs again —
typically ten to twenty times a second.
</p>
</div>
<div className="sticky top-0 z-20 rounded-xl border border-white/10 bg-[#0b1220]/95 p-3 backdrop-blur-md sm:p-4">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="min-w-0">
<p className="font-mono text-[10px] tracking-[0.22em] text-cyan-200/80 uppercase">
Scenario · {frame.label} · cycle {cycle + 1}
</p>
<p className="text-sm font-medium text-white">{frame.headline}</p>
</div>
<div className="flex items-center gap-2">
<span
className={`rounded-md border px-2.5 py-1 font-mono text-[10px] tracking-wide uppercase ${
overriding
? "border-rose-400/60 bg-rose-500/15 text-rose-100"
: "border-emerald-400/40 bg-emerald-500/10 text-emerald-100"
}`}
>
{overriding ? "Safety override" : "Nominal"}
</span>
<button
type="button"
onClick={() => setPlaying((value) => !value)}
aria-label={playing ? "Pause emulator" : "Play emulator"}
className="inline-flex min-h-11 items-center gap-1.5 rounded-md border border-white/15 bg-white/5 px-3 py-2 text-xs text-slate-100 hover:bg-white/10"
>
{playing ? <Pause className="h-3.5 w-3.5" /> : <Play className="h-3.5 w-3.5" />}
{playing ? "Pause" : "Play"}
</button>
<button
type="button"
onClick={step}
aria-label="Step one stage"
className="inline-flex min-h-11 items-center gap-1.5 rounded-md border border-white/15 bg-white/5 px-3 py-2 text-xs text-slate-100 hover:bg-white/10"
>
<SkipForward className="h-3.5 w-3.5" />
Step
</button>
<button
type="button"
onClick={() => {
setTick(0);
setPlaying(true);
}}
aria-label="Restart emulator"
className="inline-flex min-h-11 items-center gap-1.5 rounded-md border border-white/15 bg-white/5 px-3 py-2 text-xs text-slate-100 hover:bg-white/10"
>
<RotateCcw className="h-3.5 w-3.5" />
Restart
</button>
</div>
</div>
</div>
<div className="grid gap-5 lg:grid-cols-[minmax(0,1.25fr)_minmax(15rem,0.85fr)]">
<div className="min-w-0">
<h3 className="mb-3 font-mono text-[10px] tracking-[0.22em] text-slate-500 uppercase">
Autonomy runtime
</h3>
<StageCard
stage={STAGES.mission}
active={false}
readout={frame.readouts.mission}
alwaysShowReadout
onOpen={() => onOpenStage("mission")}
/>
<FlowArrow label="route + constraints" />
{RUNTIME_ORDER.map((id, index) => (
<div key={id}>
<StageCard
stage={STAGES[id]}
active={activeIds.has(id)}
alert={overriding && (id === "control" || id === "actuators")}
readout={frame.readouts[id]}
onOpen={() => onOpenStage(id)}
/>
{index === RUNTIME_ORDER.length - 1 ? null : (
<FlowArrow
label={
id === "motion" ? "proposed trajectory → safety check" : undefined
}
/>
)}
</div>
))}
<FlowArrow label="force applied to the world" />
<StageCard
stage={STAGES.world}
active={activeIds.has("world")}
readout={frame.readouts.world}
onOpen={() => onOpenStage("world")}
/>
<p className="mt-3 rounded-lg border border-dashed border-cyan-300/30 bg-cyan-400/5 px-3 py-2.5 text-xs leading-relaxed text-cyan-100/90">
↺ The world feeds the sensor layer again. This edge is what turns a
perception model into a control system.
</p>
</div>
<div className="space-y-6">
<div>
<h3 className="mb-1 font-mono text-[10px] tracking-[0.22em] text-slate-500 uppercase">
Safety &amp; supervision
</h3>
<p className="mb-3 text-xs leading-relaxed text-slate-500">
Sits between the planner and the actuators with independent
authority to say no.
</p>
<div className="space-y-2">
{SAFETY_ORDER.map((id) => (
<StageCard
key={id}
stage={STAGES[id]}
compact
active={activeIds.has(id)}
alert={overriding}
readout={frame.readouts[id]}
alwaysShowReadout={id === "health"}
onOpen={() => onOpenStage(id)}
/>
))}
</div>
</div>
<div>
<h3 className="mb-1 font-mono text-[10px] tracking-[0.22em] text-slate-500 uppercase">
Data &amp; learning plane
</h3>
<p className="mb-3 text-xs leading-relaxed text-slate-500">
Runs offline, on fleet time rather than frame time. This is the
autonomy development stack.
</p>
{frame.capture ? (
<p className="mb-3 rounded-md border border-emerald-400/40 bg-emerald-500/10 px-3 py-2 font-mono text-[11px] text-emerald-100">
event captured · {frame.capture}
</p>
) : null}
<div className="space-y-2">
{LEARNING_ORDER.map((id) => (
<StageCard
key={id}
stage={STAGES[id]}
compact
active={activeIds.has(id)}
readout={frame.readouts[id]}
onOpen={() => onOpenStage(id)}
/>
))}
</div>
<p className="mt-3 rounded-lg border border-dashed border-emerald-300/30 bg-emerald-400/5 px-3 py-2.5 text-xs leading-relaxed text-emerald-100/90">
↺ Release feeds the runtime. The autonomy stack drives the car;
this loop is what makes next month's car better.
</p>
</div>
</div>
</div>
<div className="rounded-xl border border-white/10 bg-[#0e1624] p-4">
<h3 className="font-mono text-[10px] tracking-[0.22em] text-slate-500 uppercase">
Six interacting planes
</h3>
<div className="mt-3 grid gap-2 sm:grid-cols-2">
{(Object.keys(PLANES) as (keyof typeof PLANES)[]).map((key) => (
<div
key={key}
className="rounded-lg border border-white/10 px-3 py-2"
style={{ boxShadow: `inset 3px 0 0 ${PLANES[key].accent}` }}
>
<p
className="font-mono text-[10px] tracking-[0.18em] uppercase"
style={{ color: PLANES[key].accent }}
>
{PLANES[key].label}
</p>
<p className="mt-1 text-xs leading-relaxed text-slate-400">
{PLANES[key].blurb}
</p>
</div>
))}
</div>
<p className="mt-4 text-sm leading-relaxed text-slate-400">
There is no single "AI" box. Learning shows up in perception, fusion,
the world model, prediction, behavior, motion costs, simulation, and
scenario mining — while safety stays deliberately deterministic around
all of it.
</p>
</div>
{detail ? (
<StageDetail
stage={detail}
live={frame.readouts[detail.id]}
neighbors={{
prev: FLOW_ORDER[(detailIndex - 1 + FLOW_ORDER.length) % FLOW_ORDER.length]!,
next: FLOW_ORDER[(detailIndex + 1) % FLOW_ORDER.length]!,
}}
onOpen={onOpenStage}
onClose={() => onOpenStage(null)}
/>
) : null}
</section>
);
}