+
+ {SLICES.map((slice) => {
+ const rad = (SLICE_ANGLE_DEG[slice] * Math.PI) / 180;
+ const x = Math.sin(rad) * LABEL_RADIUS;
+ const y = -Math.cos(rad) * LABEL_RADIUS;
+ const isActive = slice === activeTool;
+ const isHovered = slice === hovered;
+ return (
+
+ {t(slice).toUpperCase()}
+
+ );
+ })}
+
+ );
+}
diff --git a/src/ui/TouchControls.tsx b/src/ui/TouchControls.tsx
index 8012d82..2443f48 100644
--- a/src/ui/TouchControls.tsx
+++ b/src/ui/TouchControls.tsx
@@ -1,6 +1,7 @@
import { useRef, useCallback, useEffect, useState } from 'react';
import { useStore } from '../store';
-import { setTouchJoystick, clearTouchJoystick, setTouchThrottle } from '../ship/shipInput';
+import { setTouchJoystick, clearTouchJoystick, setTouchThrottle, setTouchRoll } from '../ship/shipInput';
+import { setTouchFiring } from '../ship/spaceMining';
import { addCameraLook, endCameraLook } from '../ship/cameraLook';
const JOYSTICK_SIZE = 120;
@@ -131,6 +132,89 @@ function ThrottleSlider({ onActivity }: { onActivity: () => void }) {
);
}
+/** A simple hold button: press sets a value, release/cancel clears it. Used
+ * for roll (±1) and fire (true/false) — same one-touch-id-per-control
+ * pattern as Joystick/ThrottleSlider above, just without drag tracking. */
+function HoldButton({
+ className,
+ glyph,
+ onPress,
+ onRelease,
+ onActivity,
+}: {
+ className: string;
+ glyph: string;
+ onPress: () => void;
+ onRelease: () => void;
+ onActivity: () => void;
+}) {
+ const touchId = useRef