- {entry.entityName}
+
+ {entry.entityName}
+
- Turn {entry.turn} ·{" "}
- {formatSimTime(entry.timestamp)}
+ Turn {entry.turn} · {formatSimTime(entry.timestamp)}
{showMenu && (
@@ -105,7 +119,9 @@ function LogEntryCard({
)}
-
{entry.narrativeProse}
+
+ {entry.narrativeProse}
+
{entry.intents.map((intent, i) => (
@@ -114,6 +130,25 @@ function LogEntryCard({
);
}
+function MobileSidebarClose() {
+ const { isMobile, setOpenMobile } = useSidebar();
+ if (!isMobile) return null;
+ return (
+
+
+ Menu
+
+
+
+ );
+}
export function PlayView() {
const router = useRouter();
@@ -125,7 +160,9 @@ export function PlayView() {
const [playerInput, setPlayerInput] = useState("");
const [error, setError] = useState("");
const [statusText, setStatusText] = useState("");
- const [selectedEntryForModal, setSelectedEntryForModal] = useState
(null);
+ const [selectedEntryForModal, setSelectedEntryForModal] = useState<
+ SimSnapshot["log"][number] | null
+ >(null);
const logEndRef = useRef(null);
const steppingRef = useRef(false);
@@ -147,79 +184,79 @@ export function PlayView() {
scrollToBottom();
}, [snapshot, scrollToBottom]);
- const runSteps = useCallback(
+ const runSteps = useCallback(async (id: string) => {
+ if (steppingRef.current) return;
+ steppingRef.current = true;
+ setLoading(true);
+ setError("");
+ pauseRequestedRef.current = false;
+
+ try {
+ let current = snapshotRef.current;
+ while (true) {
+ if (pauseRequestedRef.current) {
+ break;
+ }
+ const result = await stepSimulation({ simId: id });
+ if (!result.ok) {
+ setError(result.error);
+ break;
+ }
+ current = result.snapshot;
+ setSnapshot(current);
+
+ if (
+ current.status === "waiting_player" ||
+ current.status === "done" ||
+ current.status === "error"
+ ) {
+ break;
+ }
+
+ const entityName =
+ current.entities[current.entityIndex ?? 0]?.name || "";
+ setStatusText(
+ `Turn ${current.turn} — processing ${entityName || "next step"}...`,
+ );
+ }
+ } catch (err) {
+ setError(
+ err instanceof Error ? err.message : "Failed during simulation step.",
+ );
+ } finally {
+ steppingRef.current = false;
+ setLoading(false);
+ setStatusText("");
+ }
+ }, []);
+
+ const handleResume = useCallback(
async (id: string) => {
- if (steppingRef.current) return;
- steppingRef.current = true;
setLoading(true);
setError("");
- pauseRequestedRef.current = false;
-
try {
- let current = snapshotRef.current;
- while (true) {
- if (pauseRequestedRef.current) {
- break;
- }
- const result = await stepSimulation({ simId: id });
- if (!result.ok) {
- setError(result.error);
- break;
- }
- current = result.snapshot;
- setSnapshot(current);
-
- if (
- current.status === "waiting_player" ||
- current.status === "done" ||
- current.status === "error"
- ) {
- break;
- }
-
- const entityName =
- current.entities[current.entityIndex ?? 0]?.name || "";
- setStatusText(
- `Turn ${current.turn} — processing ${entityName || "next step"}...`,
- );
+ const res = await resumeSimulation(id);
+ if (!res.ok) {
+ setError(res.error);
+ setLoading(false);
+ return;
+ }
+ setSnapshot(res.snapshot);
+ if (res.snapshot.status === "running") {
+ await runSteps(res.snapshot.id);
+ } else {
+ setLoading(false);
}
} catch (err) {
setError(
- err instanceof Error
- ? err.message
- : "Failed during simulation step.",
+ err instanceof Error ? err.message : "Failed to resume session.",
);
- } finally {
- steppingRef.current = false;
setLoading(false);
- setStatusText("");
}
},
- [],
+ [runSteps],
);
- const handleResume = useCallback(async (id: string) => {
- setLoading(true);
- setError("");
- try {
- const res = await resumeSimulation(id);
- if (!res.ok) {
- setError(res.error);
- setLoading(false);
- return;
- }
- setSnapshot(res.snapshot);
- if (res.snapshot.status === "running") {
- await runSteps(res.snapshot.id);
- } else {
- setLoading(false);
- }
- } catch (err) {
- setError(err instanceof Error ? err.message : "Failed to resume session.");
- setLoading(false);
- }
- }, [runSteps]);
-
// Load simulation on mount
useEffect(() => {
if (!simId) {
@@ -256,11 +293,7 @@ export function PlayView() {
await runSteps(result.snapshot.id);
}
} catch (err) {
- setError(
- err instanceof Error
- ? err.message
- : "Failed to submit action.",
- );
+ setError(err instanceof Error ? err.message : "Failed to submit action.");
setLoading(false);
}
};
@@ -286,7 +319,9 @@ export function PlayView() {
return (
- Initializing simulation...
+
+ Initializing simulation...
+
);
}
@@ -305,66 +340,79 @@ export function PlayView() {
if (!snapshot) return null;
return (
-
- {/* Sidebar Navigation */}
-
-
- {/* Main Content Area */}
-
- {/* Sticky Header */}
-
-
-
-
{snapshot.scenarioName}
-
{snapshot.scenarioDescription}
+
+
+ {/* Sidebar Navigation */}
+
+
+
+
+
+ Simulation
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Main Content Area */}
+
+ {/* Sticky Header */}
+
+
+
+
+
+
+ {snapshot.scenarioName}
+
+
+ {snapshot.scenarioDescription}
+
+
+
{/* Simulation Global Controls */}
{snapshot.status !== "done" && snapshot.status !== "error" && (
<>
- {snapshot.status === "running" && (
- loading ? (
+ {snapshot.status === "running" &&
+ (loading ? (
- )
- )}
+ ))}
- Status: {snapshot.status.toUpperCase()}
- Turn: {snapshot.turn}
+
+ Status:{" "}
+
+ {snapshot.status.toUpperCase()}
+
+
+
+ Turn:{" "}
+ {snapshot.turn}
+
{loading && "⏳ "}
@@ -435,42 +490,73 @@ export function PlayView() {
{/* Simulation Info */}
-
Simulation Info
+
+ Simulation Info
+
- Session ID
- {snapshot.id}
+
+ Session ID
+
+
+ {snapshot.id}
+
- Max Turns
- {snapshot.maxTurns}
+
+ Max Turns
+
+
+ {snapshot.maxTurns}
+
- Turn Count
- {snapshot.turn}
+
+ Turn Count
+
+
+ {snapshot.turn}
+
- Entities Registered
- {snapshot.entities.length}
+
+ Entities Registered
+
+
+ {snapshot.entities.length}
+
{/* Entities Involved */}
-
Entities Involved
+
+ Entities Involved
+
{snapshot.entities.map((ent) => (
-
+
- {ent.name}
- ID: {ent.id}
+
+ {ent.name}
+
+
+ ID: {ent.id}
+
{ent.isPlayer ? (
- PLAYER
+
+ PLAYER
+
) : (
- NPC
+
+ NPC
+
)}
@@ -485,18 +571,24 @@ export function PlayView() {
{activeTab === "interact" && (