The banner keyed on `out || ratio > 1`, so any time draw exceeded generation the panel screamed BROWNOUT — even while the buffer tanks were quietly covering it, the sim's flag was false and THE SCREEN was calm. That state is the factory working as designed, not failing. Only the HUD was panicking. BROWNOUT is now the sim's flag and nothing else. A covered deficit gets its own amber, steady, non-flashing state: "ON RESERVE · 34s", where the seconds are stored/(draw-gen) — real arithmetic, because v3 ruled stored is bandwidth-SECONDS and gen/draw are per-second. Same maths SCREEN uses. Four states (ok / tight / reserve / brownout) now live in a pure power.ts and are pinned by tests, including the exact case that lied: gen 100, draw 134, stored 1000, brownout false -> reserve, not brownout. Also uses v4 bandwidth.capacity to turn the buffer readout into a fuel gauge (0/900) rather than a bare number. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
78 lines
3.1 KiB
TypeScript
78 lines
3.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import type { SimSnapshot } from '../contracts';
|
|
import { formatReserve, powerState, TIGHT_AT } from './power';
|
|
|
|
type BW = SimSnapshot['bandwidth'];
|
|
const bw = (over: Partial<BW> = {}): BW =>
|
|
({ gen: 100, draw: 50, stored: 0, brownout: false, ...over });
|
|
|
|
describe('powerState', () => {
|
|
it('is ok with headroom', () => {
|
|
expect(powerState(bw()).state).toBe('ok');
|
|
});
|
|
|
|
it('goes tight at 85% of generation, before anything is wrong', () => {
|
|
expect(powerState(bw({ draw: 84 })).state).toBe('ok');
|
|
expect(powerState(bw({ draw: TIGHT_AT * 100 })).state).toBe('tight');
|
|
expect(powerState(bw({ draw: 99 })).state).toBe('tight');
|
|
});
|
|
|
|
// ---- the round-3 bug, pinned ------------------------------------------------
|
|
it('a covered deficit is ON RESERVE, not a brownout', () => {
|
|
// draw exceeds gen, tanks have charge, and the sim says no brownout.
|
|
// The old code keyed on `out || ratio > 1` and screamed BROWNOUT at this.
|
|
const p = powerState(bw({ gen: 100, draw: 134, stored: 1000, brownout: false }));
|
|
expect(p.state).toBe('reserve');
|
|
expect(p.state).not.toBe('brownout');
|
|
});
|
|
|
|
it('reports seconds of reserve left, because stored is bandwidth-SECONDS', () => {
|
|
// 1000 bandwidth-seconds stored, bleeding 34/sec => ~29s
|
|
const p = powerState(bw({ gen: 100, draw: 134, stored: 1000, brownout: false }));
|
|
expect(p.reserveSeconds).toBeCloseTo(1000 / 34, 5);
|
|
});
|
|
|
|
it('only the sim gets to say BROWNOUT', () => {
|
|
// Same covered-deficit numbers, but the sim has raised the flag: believe the flag.
|
|
expect(powerState(bw({ gen: 100, draw: 134, stored: 1000, brownout: true })).state)
|
|
.toBe('brownout');
|
|
// ...and a brownout at nominal draw is still a brownout.
|
|
expect(powerState(bw({ gen: 100, draw: 1, brownout: true })).state).toBe('brownout');
|
|
});
|
|
|
|
it('treats an exhausted reserve honestly rather than rounding it up', () => {
|
|
const p = powerState(bw({ gen: 10, draw: 20, stored: 0, brownout: false }));
|
|
expect(p.state).toBe('reserve');
|
|
expect(p.reserveSeconds).toBe(0); // about to become the sim's problem
|
|
});
|
|
|
|
it('handles drawing with no generation at all', () => {
|
|
const p = powerState(bw({ gen: 0, draw: 4, stored: 40, brownout: false }));
|
|
expect(p.state).toBe('reserve');
|
|
expect(p.ratio).toBe(Infinity);
|
|
expect(p.reserveSeconds).toBe(10);
|
|
});
|
|
|
|
it('is ok on an empty floor — no generation, no draw', () => {
|
|
const p = powerState(bw({ gen: 0, draw: 0 }));
|
|
expect(p.state).toBe('ok');
|
|
expect(p.ratio).toBe(0);
|
|
});
|
|
|
|
it('exactly break-even is not a deficit', () => {
|
|
expect(powerState(bw({ gen: 100, draw: 100, stored: 5 })).state).toBe('tight');
|
|
});
|
|
});
|
|
|
|
describe('formatReserve', () => {
|
|
it('floors, because a countdown that rounds up lies at the end', () => {
|
|
expect(formatReserve(29.9)).toBe('29s');
|
|
expect(formatReserve(0.4)).toBe('0s');
|
|
});
|
|
|
|
it('caps absurd numbers instead of printing a wall of digits', () => {
|
|
expect(formatReserve(100000)).toBe('999s');
|
|
expect(formatReserve(Infinity)).toBe('∞');
|
|
});
|
|
});
|