{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scout-walker",
  "title": "Scout walker",
  "description": "A two-legged reconnaissance walker whose cab is a mass well above its hips: with one foot down the support is that foot, so every step is paid for by rolling the whole machine over the leg that stays put. Push it past the roll stops and it says it is off balance. Drawn from any of four camera angles.",
  "registryDependencies": [
    "https://robocn.dev/r/walker-kinematics.json",
    "https://robocn.dev/r/robot-kinematics.json",
    "https://robocn.dev/r/robot-style.json",
    "https://robocn.dev/r/use-pointer-target.json",
    "https://robocn.dev/r/use-robot-arm.json",
    "https://robocn.dev/r/use-robot-motion.json"
  ],
  "files": [
    {
      "path": "src/components/ui/scout-walker.tsx",
      "content": "\"use client\"\n\n/**\n * scout-walker — a two-legged reconnaissance walker.\n *\n * The mass is a cab a long way above the hips, and a cab bolted to its hips\n * cannot slide sideways: it rolls. With one foot down the support polygon *is*\n * that foot, so every step is paid for by rolling the whole machine over the\n * leg that is staying put. `solveWalker` works out how much attitude that\n * costs, and this file draws the consequence — head-on, which is the view the\n * rolling is in.\n *\n * Modelled once in world units — x starboard, y up, z toward the nose — with\n * every hull-mounted part going through `walkerHullPoint` so the cab and the\n * solved hips are on the same machine, and the whole thing projected through\n * `robotCamera(view)`.\n *\n * Design note: docs/armoured-walkers.md.\n */\n\nimport * as React from \"react\"\n\nimport { usePointerTarget } from \"@/hooks/use-pointer-target\"\nimport { useEasedPoint } from \"@/hooks/use-robot-arm\"\nimport { useRobotDrag } from \"@/hooks/use-robot-motion\"\nimport { clamp, type Vec2, type Vec3 } from \"@/lib/robocn/kinematics\"\nimport {\n  solveWalker,\n  walkerHullPoint,\n  type WalkerGait,\n  type WalkerLeg,\n  type WalkerPose,\n} from \"@/lib/robocn/walker\"\nimport {\n  boxCorners,\n  capsulePath,\n  fitFrame,\n  px,\n  resolveRobotPalette,\n  resolveRobotSize,\n  robotCamera,\n  robotSizes,\n  robotSurface,\n  slabPath,\n  type RobotPaletteProps,\n  type RobotSize,\n  type RobotVariant,\n  type RobotView,\n} from \"@/lib/robocn/style\"\nimport { cn } from \"@/lib/utils\"\n\n/** How fast a released hull eases back into the gait, in lean units a second. */\nconst LEAN_RATE = 2.6\n\n/** The cab, in hull-local units: origin at the hip centre, y up, z toward the nose. */\nconst CAB = {\n  chin: { halfWidth: 22, bottom: 11, top: 22, back: -15, front: 25 },\n  core: { halfWidth: 25, bottom: 22, top: 45, back: -20, front: 20 },\n  crown: { halfWidth: 18, bottom: 45, top: 49, back: -14, front: 14 },\n  pack: { halfWidth: 15, bottom: 25, top: 41, back: -30, front: -20 },\n} as const\n/** The beam the two hips hang off. */\nconst YOKE = { halfWidth: 21, bottom: -8, top: 6, halfDepth: 9 } as const\n/** Just proud of a face, so a mark sits on the panel rather than inside it. */\nconst SKIN = 0.4\n/** The footplate: a flat pad that stays level with the floor. */\nconst PLATE = { halfWidth: 7.5, fore: 12, aft: 8, height: 3 } as const\n\n/** The whole box the machine works inside, so no camera crops it. */\nconst ENVELOPE = boxCorners({ x: -54, y: -2, z: -56 }, { x: 54, y: 112, z: 56 })\nconst WIDTH = 196\nconst HEIGHT = 208\n\n/** The roll is the mechanism, and a front elevation is where a roll lives. */\nconst NATIVE_VIEW: RobotView = \"front\"\n\nconst viewNames: Record<RobotView, string> = {\n  plan: \"plan view\",\n  front: \"front elevation\",\n  profile: \"side elevation\",\n  iso: \"isometric view\",\n}\n\nexport type ScoutWalkerBehavior = \"patrol\" | \"advance\" | \"watch\" | \"static\"\n\nexport type ScoutWalkerGait = Extract<WalkerGait, \"stand\" | \"walk\" | \"stride\">\n\nexport interface ScoutWalkerProps\n  extends Omit<React.ComponentProps<\"svg\">, \"color\" | \"height\">,\n    RobotPaletteProps {\n  /** Where the camera stands. One walker, four projections. */\n  view?: RobotView\n  /** What it does when `stride` is not supplied. */\n  behavior?: ScoutWalkerBehavior\n  /** Footfall pattern. Omit and `behavior` picks one. */\n  gait?: ScoutWalkerGait\n  /** Controlled gait cycle, 0–1. Supplying it stops the clock. */\n  stride?: number\n  /** Gait cycles per second. */\n  speed?: number\n  animate?: boolean\n  paused?: boolean\n  /** Seconds of offset, so a pair of them breaks step. */\n  phase?: number\n  /** Normalized ride height, foot travel and swing clearance, each 0–1. */\n  height?: number\n  step?: number\n  lift?: number\n  /**\n   * Controlled attitude push, −1..1 of each stop: x rolls it, y pitches it.\n   * Supplying it beats the drag, and the gait still runs underneath.\n   */\n  lean?: Vec2 | null\n  onLeanChange?: (lean: Vec2) => void\n  /** Press and drag to push the cab off its feet; arrow keys nudge it. */\n  interactive?: boolean\n  /** Controlled aim in −1..1: x yaws the cab, y tips the chin pods. */\n  look?: Vec2 | null\n  /** Follow the page pointer while `look` is null. */\n  track?: boolean\n  /** Draw the support, the loaded feet, and the mass over its plumb line. */\n  showSupport?: boolean\n  size?: RobotSize | number\n  variant?: RobotVariant\n  showGround?: boolean\n  signal?: \"idle\" | \"ready\" | \"warning\"\n  label?: string\n}\n\nfunction ScoutWalker({\n  view = NATIVE_VIEW,\n  behavior = \"patrol\",\n  gait,\n  stride,\n  speed = 0.5,\n  animate = true,\n  paused = false,\n  phase = 0,\n  height,\n  step,\n  lift = 0.55,\n  lean,\n  onLeanChange,\n  interactive = true,\n  look = null,\n  track = true,\n  showSupport = false,\n  size = \"md\",\n  variant = \"solid\",\n  showGround = true,\n  signal = \"ready\",\n  label,\n  color,\n  accent,\n  metal,\n  dark,\n  glow,\n  grid,\n  palette: paletteOverride,\n  className,\n  style,\n  role,\n  tabIndex,\n  onKeyDown,\n  onBlur,\n  ...props\n}: ScoutWalkerProps) {\n  const svgRef = React.useRef<SVGSVGElement>(null)\n  const controlled = lean !== undefined && lean !== null\n  const [held, setHeld] = React.useState<Vec2 | null>(null)\n\n  const pinX = controlled ? finiteClamp(lean.x, -1, 1, 0) : 0\n  const pinY = controlled ? finiteClamp(lean.y, -1, 1, 0) : 0\n  const pinned = React.useMemo(\n    () => (controlled ? { x: pinX, y: pinY } : held),\n    [controlled, pinX, pinY, held],\n  )\n  // A behaviour that does nothing needs no clock, so the target is a point and\n  // the loop settles to no renders rather than ticking under a still machine.\n  const running = behavior !== \"static\"\n  const goal = React.useCallback((): Vec2 => pinned ?? { x: 0, y: 0 }, [pinned])\n  const motion = useEasedPoint(running ? goal : (pinned ?? { x: 0, y: 0 }), { x: 0, y: 0 }, {\n    speed: LEAN_RATE,\n    animate,\n    paused,\n    phase,\n  })\n  const bias: Vec2 = controlled && pinned ? pinned : motion.point\n\n  const apply = React.useCallback(\n    (next: Vec2) => {\n      const bounded = { x: finiteClamp(next.x, -1, 1, 0), y: finiteClamp(next.y, -1, 1, 0) }\n      setHeld(bounded)\n      onLeanChange?.(bounded)\n    },\n    [onLeanChange],\n  )\n  // The drag works in the drawing rather than in the machine, so a pointer\n  // pushed to starboard pushes the cab that way from every camera — including\n  // plan view, where starboard is on the other side of the picture.\n  const ground = React.useRef({\n    camera: robotCamera(view),\n    reach: { x: 17, y: 10 },\n    frame: { dx: 0, dy: 0, scale: 1 },\n  })\n  const dragging = useRobotDrag(svgRef, {\n    enabled: interactive,\n    onDrag: React.useCallback(\n      (unitPoint: Vec2, rect: DOMRect) => {\n        const { camera, reach, frame } = ground.current\n        // The drawing is letterboxed inside the element by xMidYMid meet.\n        const fit = Math.min(rect.width / WIDTH, rect.height / HEIGHT)\n        const box = {\n          x: (unitPoint.x * rect.width - (rect.width - WIDTH * fit) / 2) / fit,\n          y: (unitPoint.y * rect.height - (rect.height - HEIGHT * fit) / 2) / fit,\n        }\n        const point = groundPoint(\n          camera,\n          (box.x - frame.dx) / frame.scale,\n          (box.y - frame.dy) / frame.scale,\n        )\n        apply({ x: point.x / Math.max(1, reach.x), y: point.y / Math.max(1, reach.y) })\n      },\n      [apply],\n    ),\n    onDragEnd: React.useCallback(() => setHeld(null), []),\n  })\n\n  const pointer = usePointerTarget(svgRef, {\n    enabled: track && look === null && !paused,\n    within: \"element\",\n    persist: true,\n    toWorld: React.useCallback(\n      (unitPoint: Vec2) => ({\n        x: clamp((unitPoint.x - 0.5) * 2, -1, 1),\n        y: clamp((unitPoint.y - 0.5) * 2, -1, 1),\n      }),\n      [],\n    ),\n  })\n\n  const script = scoutBehaviorPose(behavior, motion.clock)\n  const cycle = stride !== undefined ? finite(stride, 0) : motion.clock * speed * script.rate\n  const pose = solveWalker({\n    legs: 2,\n    gait: gait ?? script.gait,\n    phase: cycle,\n    height: height ?? script.height,\n    step: step ?? script.step,\n    lift,\n    lean: bias,\n  })\n\n  const aim = look ?? pointer.target ?? script.gaze\n  const gaze = { x: finiteClamp(aim.x, -1, 1, 0), y: finiteClamp(aim.y, -1, 1, 0) }\n  /** The cab turns on the hips; the chin pods take the rest of the aim. */\n  const yaw = gaze.x * 34\n\n  const palette = resolveRobotPalette({ color, accent, metal, dark, glow, grid, palette: paletteOverride })\n  const rendered = resolveRobotSize(size)\n  const width = Number.isFinite(rendered) ? rendered : robotSizes.md\n  const shell = robotSurface(\"shell\", variant, palette)\n  const machined = robotSurface(\"metal\", variant, palette)\n  const cast = robotSurface(\"dark\", variant, palette)\n  const trim = robotSurface(\"metal\", variant, palette, 0.7)\n  // The machine reports itself: off its feet lights the warning colour whatever\n  // `signal` was asked for, because that is a fact and not a decoration.\n  const alarmed = !pose.stable\n  const lampColor =\n    alarmed || signal === \"warning\" ? palette.shell : signal === \"ready\" ? palette.accent : palette.metal\n\n  /* -------------------------------------------------------------- camera */\n\n  const camera = robotCamera(view)\n  const frame = fitFrame(ENVELOPE, camera, WIDTH, HEIGHT, 8)\n  const reachX = pose.hull * Math.sin((pose.rollLimit * Math.PI) / 180)\n  const reachY = pose.hull * Math.sin((pose.pitchLimit * Math.PI) / 180)\n  React.useEffect(() => {\n    ground.current = { camera, reach: { x: reachX, y: reachY }, frame }\n  }, [camera, reachX, reachY, frame])\n\n  /** World axes: x starboard, y up, z toward the nose. */\n  const at = (x: number, y: number, z = 0) => camera.project(-x, y, -z)\n  const depthOf = (x: number, y: number, z: number) => camera.depth(-x, y, -z)\n  const corner = (point: Vec3): Vec3 => ({ x: -point.x, y: point.y, z: -point.z })\n  /** A hull point: yawed on the hips, then carried by the hull's attitude. */\n  const onHull = (x: number, y: number, z: number): Vec3 => {\n    const turn = (yaw * Math.PI) / 180\n    return walkerHullPoint(pose, {\n      x: x * Math.cos(turn) + z * Math.sin(turn),\n      y,\n      z: z * Math.cos(turn) - x * Math.sin(turn),\n    })\n  }\n  const hullSolid = (points: readonly Vec3[]) =>\n    slabPath(points.map((point) => corner(onHull(point.x, point.y, point.z))), camera)\n  /** A rectangular box in hull-local units, drawn as the solid it is. */\n  const boxOf = (\n    halfWidth: number,\n    bottom: number,\n    top: number,\n    back: number,\n    front: number,\n  ): Vec3[] =>\n    [-halfWidth, halfWidth].flatMap((x) =>\n      [bottom, top].flatMap((y) => [back, front].map((z) => ({ x, y, z }))),\n    )\n  /** A flat mark standing on a face of the cab, which foreshortens like one. */\n  const face = (x0: number, x1: number, y0: number, y1: number, z: number): Vec3[] => [\n    { x: x0, y: y0, z },\n    { x: x1, y: y0, z },\n    { x: x1, y: y1, z },\n    { x: x0, y: y1, z },\n  ]\n\n  /* ---------------------------------------------------------------- legs */\n\n  const legDrawing = (leg: WalkerLeg) => {\n    const hip = at(leg.hip.x, leg.hipHeight, leg.hip.y)\n    const knee = at(leg.knee.x, leg.kneeHeight, leg.knee.y)\n    const ankle = at(leg.foot.x, leg.clearance + PLATE.height, leg.foot.y)\n    const plate = [-PLATE.halfWidth, PLATE.halfWidth].flatMap((x) =>\n      [leg.clearance, leg.clearance + PLATE.height].flatMap((y) =>\n        [-PLATE.aft, PLATE.fore].map((z) =>\n          corner({ x: leg.foot.x + x, y, z: leg.foot.y + z }),\n        ),\n      ),\n    )\n    return (\n      <g key={leg.id} data-leg={leg.id} data-name={leg.name} data-contact-state={String(leg.contact)}>\n        <path data-part=\"femur\" d={capsulePath(hip, knee, 6.4)} {...shell} />\n        <path data-part=\"tibia\" d={capsulePath(knee, ankle, 5)} {...machined} />\n        <circle cx={px(hip.x)} cy={px(hip.y)} r={5.4} {...cast} />\n        <circle data-knee cx={px(knee.x)} cy={px(knee.y)} r={4.2} {...cast} />\n        <circle cx={px(knee.x)} cy={px(knee.y)} r={1.2} fill={palette.metal} />\n        <path data-part=\"foot\" d={slabPath(plate, camera)} {...cast} />\n      </g>\n    )\n  }\n\n  /* ---------------------------------------------------------------- cab */\n\n  const cabNear = Math.max(\n    ...boxOf(CAB.core.halfWidth, CAB.chin.bottom, CAB.crown.top, CAB.pack.back, CAB.chin.front).map(\n      (point) => {\n        const world = onHull(point.x, point.y, point.z)\n        return depthOf(world.x, world.y, world.z)\n      },\n    ),\n  )\n  const mass = walkerHullPoint(pose, { x: 0, y: pose.hull, z: 0 })\n  const massPoint = at(mass.x, mass.y, mass.z)\n  const plumb = at(pose.centre.x, 0, pose.centre.y)\n\n  /* ------------------------------------------------------------- readouts */\n\n  const contacts = pose.legs.filter((leg) => leg.contact)\n  const attitude = `roll ${Math.round(pose.roll)} degrees, pitch ${Math.round(pose.pitch)} degrees`\n  const state = dragging\n    ? \"held\"\n    : pose.airborne\n      ? \"in the air\"\n      : pose.gait === \"stand\"\n        ? \"standing\"\n        : `walking, ${pose.gait}`\n  const balance = pose.airborne ? \"airborne\" : pose.stable ? \"balanced\" : \"off balance\"\n\n  return (\n    <svg\n      ref={svgRef}\n      role={role ?? (interactive ? \"slider\" : \"img\")}\n      aria-label={`Scout walker, ${state}, ${balance}, ${viewNames[view] ?? viewNames.front}`}\n      aria-valuemin={interactive ? -100 : undefined}\n      aria-valuemax={interactive ? 100 : undefined}\n      aria-valuenow={interactive ? Math.round((pose.roll / Math.max(1, pose.rollLimit)) * 100) : undefined}\n      aria-valuetext={interactive ? `${attitude}, ${balance}` : undefined}\n      tabIndex={tabIndex ?? (interactive ? 0 : undefined)}\n      viewBox={`0 0 ${WIDTH} ${HEIGHT}`}\n      width={width}\n      height={px((width * HEIGHT) / WIDTH)}\n      className={cn(\n        \"max-w-full select-none\",\n        interactive &&\n          \"cursor-grab touch-none focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[currentColor]\",\n        dragging && \"cursor-grabbing\",\n        className,\n      )}\n      style={{ color: palette.foreground, ...style }}\n      onKeyDown={(event) => {\n        onKeyDown?.(event)\n        if (!interactive || event.defaultPrevented) return\n        const nudge = event.shiftKey ? 0.25 : 0.1\n        const next = { ...bias }\n        switch (event.key) {\n          case \"ArrowRight\": next.x += nudge; break\n          case \"ArrowLeft\": next.x -= nudge; break\n          case \"ArrowUp\": next.y += nudge; break\n          case \"ArrowDown\": next.y -= nudge; break\n          case \"Home\": next.x = 0; next.y = 0; break\n          case \"End\": next.x = 1; break\n          default: return\n        }\n        apply(next)\n        event.preventDefault()\n      }}\n      onBlur={(event) => {\n        onBlur?.(event)\n        if (!dragging) setHeld(null)\n      }}\n      {...props}\n    >\n      <g data-walker data-view={view} transform={frame.transform || undefined}>\n        {variant === \"blueprint\" && (\n          <g fill=\"none\" stroke={palette.grid} strokeWidth={0.5} opacity={0.45}>\n            <path\n              d={`M ${px(at(-50, 0, 0).x)} ${px(at(-50, 0, 0).y)} L ${px(at(50, 0, 0).x)} ${px(at(50, 0, 0).y)}`}\n              strokeDasharray=\"3 3\"\n            />\n            {/* How far the mass can be moved before the attitude stops bite. */}\n            <ellipse\n              data-reach\n              cx={px(at(0, 0, 0).x)}\n              cy={px(at(0, 0, 0).y)}\n              rx={px(reachX)}\n              ry={px(Math.max(0.8, reachY * camera.flatten + reachX * camera.flatten))}\n              strokeDasharray=\"2 3\"\n            />\n          </g>\n        )}\n\n        {showGround && (\n          <ellipse\n            cx={px(at(0, 0, 0).x)}\n            cy={px(at(0, 0, 0).y)}\n            rx={38}\n            ry={px(Math.max(2.4, 34 * camera.flatten))}\n            fill={palette.dark}\n            opacity={0.13}\n          />\n        )}\n\n        {showSupport && contacts.length > 1 && (\n          <path\n            data-support\n            d={contacts\n              .map((leg, index) => {\n                const point = at(leg.foot.x, 0, leg.foot.y)\n                return `${index ? \"L\" : \"M\"} ${px(point.x)} ${px(point.y)}`\n              })\n              .join(\" \")}\n            fill=\"none\"\n            stroke={pose.stable ? palette.accent : palette.shell}\n            strokeWidth={1.1}\n            strokeDasharray=\"4 3\"\n          />\n        )}\n\n        {pose.legs\n          .filter((leg) => depthOf(leg.foot.x, 0, leg.foot.y) <= cabNear)\n          .map(legDrawing)}\n\n        <g data-cab data-yaw={px(yaw)}>\n          <path data-part=\"yoke\" d={hullSolid(boxOf(YOKE.halfWidth, YOKE.bottom, YOKE.top, -YOKE.halfDepth, YOKE.halfDepth))} {...cast} />\n          <path data-part=\"pack\" d={hullSolid(boxOf(CAB.pack.halfWidth, CAB.pack.bottom, CAB.pack.top, CAB.pack.back, CAB.pack.front))} {...trim} />\n          <path data-part=\"chin\" d={hullSolid(boxOf(CAB.chin.halfWidth, CAB.chin.bottom, CAB.chin.top, CAB.chin.back, CAB.chin.front))} {...shell} />\n          <path data-part=\"core\" d={hullSolid(boxOf(CAB.core.halfWidth, CAB.core.bottom, CAB.core.top, CAB.core.back, CAB.core.front))} {...shell} />\n          <path data-part=\"crown\" d={hullSolid(boxOf(CAB.crown.halfWidth, CAB.crown.bottom, CAB.crown.top, CAB.crown.back, CAB.crown.front))} {...trim} />\n\n          {/* Two blister pods under the chin, which tip with the aim. */}\n          {([-1, 1] as const).map((side) => {\n            const drop = -gaze.y * 3\n            return (\n              <path\n                key={side}\n                data-pod={side === 1 ? \"starboard\" : \"port\"}\n                d={hullSolid(\n                  [5.5, 16.5].flatMap((across) =>\n                    [CAB.chin.bottom - 7 + drop, CAB.chin.bottom + 1 + drop].flatMap((y) =>\n                      [CAB.chin.front - 9, CAB.chin.front + 4].map((z) => ({\n                        x: side * across,\n                        y,\n                        z,\n                      })),\n                    ),\n                  ),\n                )}\n                {...machined}\n              />\n            )\n          })}\n\n          {/* Flat marks on the cab's own faces: they foreshorten, as marks do. */}\n          {([-1, 1] as const).map((side) => (\n            <path\n              key={side}\n              data-part=\"viewport\"\n              d={hullSolid(\n                face(side * 4, side * 19, CAB.core.bottom + 8, CAB.core.bottom + 17, CAB.core.front + SKIN),\n              )}\n              fill={palette.dark}\n              stroke=\"none\"\n            />\n          ))}\n          <path\n            data-lamp\n            d={hullSolid(face(-4.5, 4.5, CAB.core.top - 6, CAB.core.top - 2, CAB.core.front + SKIN))}\n            fill={lampColor}\n            stroke=\"none\"\n          />\n          {[0, 6].map((row) => (\n            <path\n              key={row}\n              data-part=\"louvre\"\n              d={hullSolid(\n                [-1, 1].flatMap((sx) =>\n                  [CAB.core.bottom + 4 + row, CAB.core.bottom + 7 + row].flatMap((y) =>\n                    [-7, 7].map((z) => ({ x: sx * (CAB.core.halfWidth + SKIN), y, z })),\n                  ),\n                ),\n              )}\n              {...cast}\n            />\n          ))}\n          {/* A whip aerial: the one part that reads from directly above. */}\n          <path\n            data-part=\"aerial\"\n            d={capsulePath(\n              (() => {\n                const base = onHull(8, CAB.crown.top, -9)\n                return at(base.x, base.y, base.z)\n              })(),\n              (() => {\n                const tip = onHull(8, CAB.crown.top + 19, -13)\n                return at(tip.x, tip.y, tip.z)\n              })(),\n              1.1,\n            )}\n            {...machined}\n          />\n        </g>\n\n        {pose.legs\n          .filter((leg) => depthOf(leg.foot.x, 0, leg.foot.y) > cabNear)\n          .map(legDrawing)}\n\n        {showSupport && (\n          <g>\n            {contacts.map((leg) => {\n              const point = at(leg.foot.x, 0, leg.foot.y)\n              return (\n                <circle\n                  key={leg.id}\n                  data-contact={leg.name}\n                  cx={px(point.x)}\n                  cy={px(point.y)}\n                  r={px(2.6 + leg.load * 6)}\n                  fill=\"none\"\n                  stroke={palette.accent}\n                  strokeWidth={1.1}\n                />\n              )\n            })}\n            {/* The mass, and the plumb line that says where it falls. */}\n            <g data-centre data-stable={String(pose.stable)}>\n              <path\n                d={`M ${px(massPoint.x)} ${px(massPoint.y)} L ${px(plumb.x)} ${px(plumb.y)}`}\n                stroke={pose.stable ? palette.accent : palette.shell}\n                strokeWidth={0.9}\n                strokeDasharray=\"2 2\"\n                fill=\"none\"\n              />\n              <circle\n                cx={px(massPoint.x)}\n                cy={px(massPoint.y)}\n                r={3}\n                fill={pose.stable ? palette.accent : palette.shell}\n              />\n              <circle\n                cx={px(plumb.x)}\n                cy={px(plumb.y)}\n                r={2}\n                fill=\"none\"\n                stroke={pose.stable ? palette.accent : palette.shell}\n                strokeWidth={1}\n              />\n            </g>\n          </g>\n        )}\n      </g>\n\n      {label && (\n        <text\n          x={WIDTH / 2}\n          y={HEIGHT - 7}\n          textAnchor=\"middle\"\n          fontFamily=\"ui-monospace, monospace\"\n          fontSize={6}\n          fill={palette.foreground}\n        >\n          {label}\n        </text>\n      )}\n    </svg>\n  )\n}\n\n/**\n * Where a point in the drawing lands on the floor, in plan coordinates.\n *\n * The ground plane projects affinely, so the two basis vectors the camera makes\n * of it invert exactly — no trigonometry, and no chance of disagreeing with the\n * projection it has to be the inverse of.\n */\nfunction groundPoint(camera: ReturnType<typeof robotCamera>, x: number, y: number): Vec2 {\n  const origin = camera.project(0, 0, 0)\n  const across = camera.project(-1, 0, 0)\n  const along = camera.project(0, 0, -1)\n  const e1 = { x: across.x - origin.x, y: across.y - origin.y }\n  const e2 = { x: along.x - origin.x, y: along.y - origin.y }\n  const det = e1.x * e2.y - e1.y * e2.x\n  if (Math.abs(det) < 1e-9) return { x: 0, y: 0 }\n  return {\n    x: (x * e2.y - y * e2.x) / det,\n    y: (-x * e1.y + y * e1.x) / det,\n  }\n}\n\nconst finite = (value: number, fallback: number) => (Number.isFinite(value) ? value : fallback)\n/** A control that has to survive a consumer handing it `NaN`. */\nconst finiteClamp = (value: number, min: number, max: number, fallback: number) =>\n  Number.isFinite(value) ? clamp(value, min, max) : fallback\n\n/**\n * What it does with no timeline on it. Every behaviour is a pure function of\n * the clock, so the cycle can be sampled in a test without faking frames.\n */\nexport function scoutBehaviorPose(behavior: ScoutWalkerBehavior, clock: number) {\n  const time = Number.isFinite(clock) ? clock : 0\n  switch (behavior) {\n    // Head down and quick, on a gait with a flight phase it cannot hold.\n    case \"advance\":\n      return {\n        gait: \"stride\" as ScoutWalkerGait,\n        rate: 1.9,\n        height: 0.3,\n        step: 1,\n        gaze: { x: 0.1 * Math.sin(time * 0.7), y: -0.4 },\n      }\n    // Parked, scanning, breathing on its legs.\n    case \"watch\":\n      return {\n        gait: \"stand\" as ScoutWalkerGait,\n        rate: 0,\n        height: 0.55 + 0.1 * Math.sin(time * 1.05),\n        step: 0.7,\n        gaze: { x: 0.85 * Math.sin(time * 0.42), y: 0.25 * Math.sin(time * 0.27) },\n      }\n    case \"static\":\n      return {\n        gait: \"stand\" as ScoutWalkerGait,\n        rate: 0,\n        height: 0.5,\n        step: 0.7,\n        gaze: { x: 0, y: 0 },\n      }\n    // Pacing a line, looking around as it goes.\n    default:\n      return {\n        gait: \"walk\" as ScoutWalkerGait,\n        rate: 1,\n        height: 0.55,\n        step: 0.78,\n        gaze: { x: 0.55 * Math.sin(time * 0.33), y: 0.2 * Math.cos(time * 0.21) },\n      }\n  }\n}\n\nexport { ScoutWalker }\nexport type { WalkerPose as ScoutWalkerPose }\n",
      "type": "registry:ui",
      "target": "@ui/scout-walker.tsx"
    }
  ],
  "categories": [
    "robotics",
    "droids"
  ],
  "type": "registry:ui"
}