diff --git a/tui/monitor.tsx b/tui/monitor.tsx
index f657bc0..546c57a 100644
--- a/tui/monitor.tsx
+++ b/tui/monitor.tsx
@@ -140,105 +140,73 @@ function foodColor(food: number | undefined) {
// --- components -------------------------------------------------------------
-function Header({ snapshot, connectedIpc, width, startedAt }: { snapshot: Snapshot; connectedIpc: boolean; width: number; startedAt: number }) {
+function StatusHeader({ snapshot, connectedIpc, width, startedAt }: { snapshot: Snapshot; connectedIpc: boolean; width: number; startedAt: number }) {
const pos = snapshot.position
- ? `(${Math.round(snapshot.position.x)}, ${Math.round(snapshot.position.y)}, ${Math.round(snapshot.position.z)})`
+ ? `(${Math.round(snapshot.position.x)},${Math.round(snapshot.position.y)},${Math.round(snapshot.position.z)})`
: "?";
const hp = snapshot.health;
const food = snapshot.food;
- const day = snapshot.isDay ? "☀ day" : "🌙 night";
+ const day = snapshot.isDay ? "☀" : "🌙";
const session = formatDuration(Date.now() - startedAt);
const mcOnline = snapshot.connected;
+ const story = snapshot.storyStep;
+ const idx = story?.index ?? 0;
+ const cur = story?.step;
+ const want = story?.suggestion?.skillId;
+ // 11-step progress bar in 11 cells
+ const bar = Array.from({ length: 11 }, (_, i) => {
+ if (i < idx) return "▓";
+ if (i === idx) return "▒";
+ return "░";
+ }).join("");
return (
- pepa-monitor
- ·
- {mcOnline ? "● MC" : "○ MC"}
- ·
- {connectedIpc ? "● IPC" : "○ IPC"}
- ·
- session {session}
- ·
- user {snapshot.username ?? "?"}
-
-
- pos {pos}
-
- HP
- {hp ?? "?"}/20
-
- food
- {food ?? "?"}/20
-
+ pepa
+ ·
+ {mcOnline ? "●MC" : "○MC"}
+ ·
+ {connectedIpc ? "●IPC" : "○IPC"}
+ ·
+ {session}
+ ·
+ {snapshot.username ?? "?"}
+ ·
+ {pos}
+ ·
+ HP {hp ?? "?"}
+ ·
+ food {food ?? "?"}
+ ·
{day}
- hostiles
- 0 ? "red" : "gray"}>{snapshot.hostileCount ?? 0}
- {snapshot.closestHostile ? closest {snapshot.closestHostile.name}@{snapshot.closestHostile.distance}b : null}
+ {(snapshot.hostileCount ?? 0) > 0 ? (
+ <>
+ ·
+ ⚔{snapshot.hostileCount}
+ {snapshot.closestHostile ? ({snapshot.closestHostile.name}@{Math.round(snapshot.closestHostile.distance)}b) : null}
+ >
+ ) : null}
-
- );
-}
-
-function StorylinePanel({ snapshot, width }: { snapshot: Snapshot; width: number }) {
- const story = snapshot.storyStep;
- if (!story) {
- return (
-
- storyline not available (bot offline or storyline disabled)
-
- );
- }
- const STEPS = [
- "orient_self", "first_wood", "crafting_basics", "first_tools", "first_food",
- "shelter_minimal", "stone_tier", "food_security", "iron_age", "settle_base", "village_grow",
- ];
- const TITLES: Record = {
- orient_self: "Понять где я",
- first_wood: "Собрать 8 поленьев",
- crafting_basics: "Сделать верстак и палки",
- first_tools: "Деревянные орудия",
- first_food: "Найти первую еду",
- shelter_minimal: "Простой шелтер с кроватью",
- stone_tier: "Каменные орудия",
- food_security: "Запас еды на 16+",
- iron_age: "Железо и печь",
- settle_base: "Постоянная база",
- village_grow: "Развивать деревню",
- };
- const idx = story.index ?? 0;
- const cur = story.step;
- const want = story.suggestion?.skillId;
- const emergency = story.emergency;
- return (
-
- storyline
- step
+ story
+ {bar}
+
{idx + 1}/11
-
- {cur?.id}
- {cur?.title}
- {emergency ? [EMERGENCY PAUSE] : null}
-
- {want ? (
-
- wants:
- {want}
-
- ) : null}
-
- {STEPS.map((id, i) => {
- const done = i < idx;
- const current = i === idx;
- const mark = done ? "✓" : current ? "→" : "○";
- const color = done ? "green" : current ? "yellow" : "gray";
- return (
-
- {` ${mark} ${(i + 1).toString().padStart(2)}. ${id.padEnd(18)} ${TITLES[id] ?? ""}`}
-
- );
- })}
+
+ {cur ? (
+ <>
+ {cur.id}
+ ·
+ {cur.title}
+ >
+ ) : (no story)}
+ {want ? (
+ <>
+ →
+ {want}
+ >
+ ) : null}
+ {story?.emergency ? [EMERGENCY] : null}
);
@@ -300,17 +268,20 @@ function AdvisorPanel({ recs, width, height }: { recs: any[]; width: number; hei
const ok = r.outcome_ok;
const outcomeMark = ok == null ? "·" : ok ? "✓" : "✗";
const outcomeColor = ok == null ? "gray" : ok ? "green" : "red";
- const tail = `${r.tokens_in ?? "?"}/${r.tokens_out ?? "?"}t ${r.latency_ms ?? "?"}ms`;
+ const target = r.recommended_skill ?? r.action;
+ const trigger = String(r.trigger_reason ?? "?");
+ // One line per recommendation: " ✓ wedged_60s → survive.flee 802t 1900ms"
+ const lhs = `${outcomeMark} ${trigger} → ${target}`;
+ const rhs = `${r.tokens_in ?? "?"}t ${r.latency_ms ?? "?"}ms`;
+ const free = Math.max(20, width - rhs.length - 6);
return (
-
-
- {outcomeMark}
- {r.trigger_reason}
- →
- {r.recommended_skill ?? r.action}
-
- {tail}{r.rationale ? ` · ${String(r.rationale).slice(0, width - 14)}` : ""}
-
+
+ {outcomeMark}
+ {trigger}
+ →
+ {String(target).slice(0, Math.max(8, free - trigger.length - 6))}
+ {rhs}
+
);
})}
{visible.length === 0 ? (no advisor calls yet) : null}
@@ -322,19 +293,14 @@ function ImprovementsPanel({ items, width, height }: { items: any[]; width: numb
const visible = items.slice(0, height);
return (
- Improvements (open: {items.length})
+ Improvements (open {items.length})
{visible.map((r) => (
-
-
- #{r.id}
- P{r.priority}
- ×{r.votes}
- {String(r.title).slice(0, width - 14)}
-
- {r.description ? (
- {String(r.description).slice(0, width - 8)}
- ) : null}
-
+
+ #{r.id}
+ P{r.priority}
+ ×{r.votes}
+ {String(r.title).slice(0, Math.max(20, width - 14))}
+
))}
{visible.length === 0 ? (no improvement requests yet) : null}
@@ -477,17 +443,20 @@ function App() {
});
}
- // Layout math: full-width header & storyline, then split 50/50.
+ // Layout math: compact 4-section vertical stack.
+ // row budget:
+ // header (story + status) ≈ 4 rows
+ // middle activity/chat ≈ floor((rows - 4 - 4 - 4) / 2)
+ // bottom advisor/improvements ≈ same
+ // footer ≈ 4 rows
const totalWidth = Math.max(80, cols);
const halfWidth = Math.floor(totalWidth / 2);
- const totalRows = Math.max(24, rows);
- // rough allocation: header 4, storyline ~14, footer 4. Rest split for middle panels.
- const middleRows = Math.max(8, Math.floor((totalRows - 4 - 14 - 4) / 2));
+ const totalRows = Math.max(20, rows);
+ const middleRows = Math.max(5, Math.floor((totalRows - 4 - 4 - 4) / 2));
return (
-
-
+