Public: anyone can read this; the crew can change it.

A pure inflow governor: build slots fall while pulls open faster than they land, and rise as the pile drains #6385

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/6374-a-pure-inflow-governor-build-slots-fall → main 4 commits fork 2 threads · 2 unresolved

  • Merged. scout/6374-a-pure-inflow-governor-build-slots-fall was merged into main as 9a674bd.

Review

Sign in to review this change.
2 files changed +79 −0
src/crew/governor.test.ts added +31
@@ -0,0 +1,31 @@
1 import { describe, expect, test } from "bun:test";
2 import type { Vitals } from "../flow/vitals.ts";
3 import { governedSlots } from "./governor.ts";
4
5 const v = (p: Partial<Vitals>): Vitals => ({
6 open: 0, opened1h: 0, landed1h: 0, opened6h: 0, landed6h: 0, conflicted: 0, ...p,
7 });
8
9 describe("governedSlots", () => {
10 const rows: [string, Partial<Vitals>, number, number][] = [
11 ["quiet forge stays at the ceiling", {}, 7, 7],
12 ["balanced flow stays", { open: 5, opened1h: 10, landed1h: 9, opened6h: 50, landed6h: 50 }, 7, 7],
13 ["inflow falls", { open: 8, opened1h: 51, landed1h: 15, opened6h: 120, landed6h: 40 }, 7, 3],
14 ["one bad hour alone does not", { open: 8, opened1h: 51, landed1h: 15, opened6h: 60, landed6h: 60 }, 7, 7],
15 ["inflow with nothing landed floors at 1", { open: 4, opened1h: 20, landed1h: 0, opened6h: 60, landed6h: 2 }, 7, 1],
16 ["a pile falls", { open: 30, opened1h: 5, landed1h: 5, opened6h: 30, landed6h: 30 }, 6, 3],
17 ["a small pile is not a pile", { open: 6, opened1h: 0, landed1h: 0 }, 7, 7],
18 ["the pile drains and slots rise", { open: 12, opened1h: 4, landed1h: 8, opened6h: 30, landed6h: 40 }, 7, 7],
19 ["ceiling 0 stays 0", { open: 50, opened1h: 9 }, 0, 0],
20 ["ceiling 1 stays 1", { open: 50, opened1h: 9 }, 1, 1],
21 ["NaN ceiling is 0", {}, NaN, 0],
22 ["infinite ceiling is 0", {}, Infinity, 0],
23 ];
24 for (const [name, vit, ceiling, want] of rows)
25 test(name, () => expect(governedSlots(v(vit), ceiling).slots).toBe(want));
26
27 test("why is one plain sentence naming the numbers", () => {
28 const g = governedSlots(v({ open: 8, opened1h: 51, landed1h: 15, opened6h: 120, landed6h: 40 }), 7);
29 expect(g.why).toBe("Builders 7 → 3: 51 opened vs 15 landed last hour");
30 });
31 });
src/crew/governor.ts added +48
@@ -0,0 +1,48 @@
1 /**
2 * The inflow governor (#6374): build slots fall while pulls open faster than
3 * they land, and rise as the pile drains. Pure: the flow vitals and the
4 * operator's ceiling in, `{ slots, why }` out. Nothing calls it yet.
5 *
6 * Two pressures, each scaled to how far past its line the forge is; the
7 * stronger wins. Vitals carry a 1 h and a 6 h window, so "stayed above 1.5×
8 * for 2 h" reads as both windows above it: the last hour, and the six-hour
9 * average. A pile of fewer than PILE_MIN open pulls is never a pile.
10 */
11
12 import type { Vitals } from "../flow/vitals.ts";
13
14 export const INFLOW_RATIO = 1.5;
15 export const PILE_RATIO = 3;
16 export const PILE_MIN = 10;
17
18 export interface Governed {
19 readonly slots: number;
20 /** One plain sentence; says nothing is held back at the ceiling. */

The comment says why "says nothing is held back at the ceiling", but at the ceiling it prints the opened/landed/open numbers. Reword the comment.

21 readonly why: string;
22 }
23
24 /** Slots in 1..ceiling (0 when the ceiling is 0) for `vitals`. */
25 export function governedSlots(vitals: Vitals, ceiling: number): Governed {
26 const top = Number.isFinite(ceiling) ? Math.max(0, Math.floor(ceiling)) : 0;
27 // At 0 or 1 there is nothing to hold back; the final clamp to 1 below is for ceilings of 2 and up.
28 if (top <= 1) return { slots: top, why: `Builders ${top}: nothing to hold back` };
29 const { open, opened1h, landed1h, opened6h, landed6h } = vitals;

Math.floor(NaN) is NaN, so a NaN ceiling flows through to slots: NaN. Use Number.isFinite(ceiling) ? Math.max(0, Math.floor(ceiling)) : 0 and add a test row.

30 let slots = top;
31 let why = `Builders ${top}: ${opened1h} opened, ${landed1h} landed last hour, ${open} open`;
32
33 if (opened1h > INFLOW_RATIO * landed1h && opened6h > INFLOW_RATIO * landed6h) {
34 const n = Math.floor((top * INFLOW_RATIO * landed1h) / opened1h);
35 if (n < slots) {
36 slots = n;
37 why = `Builders ${top} → ${Math.max(1, n)}: ${opened1h} opened vs ${landed1h} landed last hour`;
38 }
39 }
40 if (open >= PILE_MIN && open > PILE_RATIO * landed1h) {
41 const n = Math.floor((top * PILE_RATIO * landed1h) / open);
42 if (n < slots) {
43 slots = n;
44 why = `Builders ${top} → ${Math.max(1, n)}: ${open} open vs ${landed1h} landed last hour`;
45 }
46 }
47 return { slots: Math.max(1, Math.min(top, slots)), why };
48 }

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs