{
  "name": "floor",
  "title": "Floor",
  "type": "registry:component",
  "description": "Flat floor slab with a fixed collider; optional stairwell opening.",
  "dependencies": [
    "@react-three/rapier@^2.2.0",
    "@runek/core@^0.12.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "Floor.tsx",
      "content": "import { RigidBody } from '@react-three/rapier'\nimport { useWorld, type Vec3 } from '@runek/core'\n\n/** A rectangular hole in the slab, e.g. a stairwell. */\nexport interface FloorOpening {\n  /** Hole center offset from the slab center along X, in units. */\n  x?: number\n  /** Hole center offset from the slab center along Z, in units. */\n  z?: number\n  width: number\n  depth: number\n}\n\nexport interface FloorProps {\n  position?: Vec3\n  rotation?: Vec3\n  /** `[width, depth]` in units. The top surface sits at the component origin. */\n  size?: [number, number]\n  thickness?: number\n  /** A hole in the slab (stairwell); the slab splits into strips around it. */\n  opening?: FloorOpening\n  /** Defaults to the world palette's `floor` slot. */\n  color?: string\n}\n\ninterface Slab {\n  x: number\n  z: number\n  w: number\n  d: number\n}\n\nconst EPS = 1e-4\n\nfunction slabs(w: number, d: number, opening?: FloorOpening): Slab[] {\n  if (!opening) return [{ x: 0, z: 0, w, d }]\n\n  const { x = 0, z = 0, width, depth } = opening\n  const left = Math.max(x - width / 2, -w / 2)\n  const right = Math.min(x + width / 2, w / 2)\n  const near = Math.max(z - depth / 2, -d / 2)\n  const far = Math.min(z + depth / 2, d / 2)\n  if (right - left <= EPS || far - near <= EPS) return [{ x: 0, z: 0, w, d }]\n\n  const out: Slab[] = []\n  // Full-depth strips either side of the hole, short strips before/behind it.\n  const leftW = left + w / 2\n  if (leftW > EPS) out.push({ x: -w / 2 + leftW / 2, z: 0, w: leftW, d })\n  const rightW = w / 2 - right\n  if (rightW > EPS) out.push({ x: right + rightW / 2, z: 0, w: rightW, d })\n  const holeW = right - left\n  const nearD = near + d / 2\n  if (nearD > EPS) out.push({ x: (left + right) / 2, z: -d / 2 + nearD / 2, w: holeW, d: nearD })\n  const farD = d / 2 - far\n  if (farD > EPS) out.push({ x: (left + right) / 2, z: far + farD / 2, w: holeW, d: farD })\n  return out\n}\n\nexport function Floor({\n  position,\n  rotation = [0, 0, 0],\n  size = [8, 8],\n  thickness = 0.2,\n  opening,\n  color,\n}: FloorProps) {\n  const { unit, palette, ground } = useWorld()\n  const floorColor = color ?? palette.floor\n  const w = size[0] * unit\n  const d = size[1] * unit\n  const t = thickness * unit\n  const scaled = opening && {\n    x: (opening.x ?? 0) * unit,\n    z: (opening.z ?? 0) * unit,\n    width: opening.width * unit,\n    depth: opening.depth * unit,\n  }\n  // Top surface sits at the world ground baseline by default; explicit position wins.\n  const pos = position ?? [0, ground, 0]\n\n  return (\n    <RigidBody type=\"fixed\" colliders=\"cuboid\" position={pos} rotation={rotation}>\n      {slabs(w, d, scaled).map((s) => (\n        <mesh\n          key={`${s.x.toFixed(3)}:${s.z.toFixed(3)}`}\n          receiveShadow\n          position={[s.x, -t / 2, s.z]}\n        >\n          <boxGeometry args={[s.w, t, s.d]} />\n          <meshStandardMaterial color={floorColor} />\n        </mesh>\n      ))}\n    </RigidBody>\n  )\n}\n",
      "type": "registry:component"
    }
  ]
}
