feat: massive AI generation
This commit is contained in:
@@ -1,6 +1,21 @@
|
||||
import type { WorldDelta, WorldSnapshot } from "./contracts";
|
||||
import type { TelemetrySnapshot } from "./contractsTelemetry";
|
||||
import type { BalanceSettings } from "./contractsBalance";
|
||||
import type { PlayerFactionSnapshot } from "./contractsPlayerFaction";
|
||||
import type { ShipSnapshot } from "./contractsShips";
|
||||
import type {
|
||||
PlayerAssetAssignmentCommandRequest,
|
||||
PlayerAutomationPolicyCommandRequest,
|
||||
PlayerDirectiveCommandRequest,
|
||||
PlayerOrganizationCommandRequest,
|
||||
PlayerOrganizationMembershipCommandRequest,
|
||||
PlayerPolicyCommandRequest,
|
||||
PlayerStrategicIntentCommandRequest,
|
||||
} from "./playerFactionCommands";
|
||||
import type {
|
||||
ShipDefaultBehaviorCommandRequest,
|
||||
ShipOrderCommandRequest,
|
||||
} from "./shipCommands";
|
||||
|
||||
export interface WorldStreamScope {
|
||||
scopeKind?: string;
|
||||
@@ -8,12 +23,16 @@ export interface WorldStreamScope {
|
||||
bubbleId?: string | null;
|
||||
}
|
||||
|
||||
export async function fetchWorldSnapshot(signal?: AbortSignal) {
|
||||
const response = await fetch("/api/world", { signal });
|
||||
async function fetchJson<T>(input: RequestInfo | URL, init?: RequestInit): Promise<T> {
|
||||
const response = await fetch(input, init);
|
||||
if (!response.ok) {
|
||||
throw new Error(`World request failed with ${response.status}`);
|
||||
throw new Error(`${init?.method ?? "GET"} ${typeof input === "string" ? input : input.toString()} failed with ${response.status}`);
|
||||
}
|
||||
return response.json() as Promise<WorldSnapshot>;
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export async function fetchWorldSnapshot(signal?: AbortSignal) {
|
||||
return fetchJson<WorldSnapshot>("/api/world", { signal });
|
||||
}
|
||||
|
||||
export function openWorldStream(
|
||||
@@ -52,39 +71,114 @@ export function openWorldStream(
|
||||
}
|
||||
|
||||
export async function fetchTelemetry(signal?: AbortSignal) {
|
||||
const response = await fetch("/api/telemetry", { signal });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Telemetry request failed with ${response.status}`);
|
||||
}
|
||||
return response.json() as Promise<TelemetrySnapshot>;
|
||||
return fetchJson<TelemetrySnapshot>("/api/telemetry", { signal });
|
||||
}
|
||||
|
||||
export async function fetchBalance(signal?: AbortSignal) {
|
||||
const response = await fetch("/api/balance", { signal });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Balance request failed with ${response.status}`);
|
||||
}
|
||||
return response.json() as Promise<BalanceSettings>;
|
||||
return fetchJson<BalanceSettings>("/api/balance", { signal });
|
||||
}
|
||||
|
||||
export async function updateBalance(settings: BalanceSettings) {
|
||||
const response = await fetch("/api/balance", {
|
||||
return fetchJson<BalanceSettings>("/api/balance", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Balance update failed with ${response.status}`);
|
||||
}
|
||||
return response.json() as Promise<BalanceSettings>;
|
||||
}
|
||||
|
||||
export async function resetWorld() {
|
||||
const response = await fetch("/api/world/reset", {
|
||||
return fetchJson<WorldSnapshot>("/api/world/reset", {
|
||||
method: "POST",
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Reset request failed with ${response.status}`);
|
||||
}
|
||||
return response.json() as Promise<WorldSnapshot>;
|
||||
}
|
||||
|
||||
export async function fetchPlayerFaction(signal?: AbortSignal) {
|
||||
return fetchJson<PlayerFactionSnapshot>("/api/player-faction", { signal });
|
||||
}
|
||||
|
||||
export async function createPlayerOrganization(request: PlayerOrganizationCommandRequest) {
|
||||
return fetchJson<PlayerFactionSnapshot>("/api/player-faction/organizations", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
export async function deletePlayerOrganization(organizationId: string) {
|
||||
return fetchJson<PlayerFactionSnapshot>(`/api/player-faction/organizations/${organizationId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
export async function updatePlayerOrganizationMembership(organizationId: string, request: PlayerOrganizationMembershipCommandRequest) {
|
||||
return fetchJson<PlayerFactionSnapshot>(`/api/player-faction/organizations/${organizationId}/membership`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
export async function upsertPlayerDirective(request: PlayerDirectiveCommandRequest, directiveId?: string | null) {
|
||||
const path = directiveId ? `/api/player-faction/directives/${directiveId}` : "/api/player-faction/directives";
|
||||
return fetchJson<PlayerFactionSnapshot>(path, {
|
||||
method: directiveId ? "PUT" : "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
export async function deletePlayerDirective(directiveId: string) {
|
||||
return fetchJson<PlayerFactionSnapshot>(`/api/player-faction/directives/${directiveId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
export async function upsertPlayerAssignment(assetId: string, request: PlayerAssetAssignmentCommandRequest) {
|
||||
return fetchJson<PlayerFactionSnapshot>(`/api/player-faction/assignments/${assetId}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
export async function upsertPlayerPolicy(request: PlayerPolicyCommandRequest, policyId?: string | null) {
|
||||
const path = policyId ? `/api/player-faction/policies/${policyId}` : "/api/player-faction/policies";
|
||||
return fetchJson<PlayerFactionSnapshot>(path, {
|
||||
method: policyId ? "PUT" : "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
export async function upsertPlayerAutomationPolicy(request: PlayerAutomationPolicyCommandRequest, automationPolicyId?: string | null) {
|
||||
const path = automationPolicyId ? `/api/player-faction/automation-policies/${automationPolicyId}` : "/api/player-faction/automation-policies";
|
||||
return fetchJson<PlayerFactionSnapshot>(path, {
|
||||
method: automationPolicyId ? "PUT" : "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
export async function updatePlayerStrategicIntent(request: PlayerStrategicIntentCommandRequest) {
|
||||
return fetchJson<PlayerFactionSnapshot>("/api/player-faction/strategic-intent", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
export async function enqueueShipOrder(shipId: string, request: ShipOrderCommandRequest) {
|
||||
return fetchJson<ShipSnapshot>(`/api/ships/${shipId}/orders`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateShipDefaultBehavior(shipId: string, request: ShipDefaultBehaviorCommandRequest) {
|
||||
return fetchJson<ShipSnapshot>(`/api/ships/${shipId}/default-behavior`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
}
|
||||
|
||||
206
apps/viewer/src/components/gm/GmGeopoliticsPanel.vue
Normal file
206
apps/viewer/src/components/gm/GmGeopoliticsPanel.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useGmStore } from "../../ui/stores/gmStore";
|
||||
|
||||
const gmStore = useGmStore();
|
||||
|
||||
const factionLabelById = computed(() =>
|
||||
new Map(gmStore.factions.map((faction) => [faction.id, faction.label])),
|
||||
);
|
||||
|
||||
function factionLabel(factionId?: string | null) {
|
||||
if (!factionId) return "—";
|
||||
return factionLabelById.value.get(factionId) ?? factionId;
|
||||
}
|
||||
|
||||
function titleCase(value?: string | null) {
|
||||
if (!value) return "—";
|
||||
return value
|
||||
.replace(/[-_]+/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
.replace(/\b\w/g, (char) => char.toUpperCase());
|
||||
}
|
||||
|
||||
function percent(value?: number | null) {
|
||||
if (value == null || Number.isNaN(value)) return "—";
|
||||
return `${Math.round(value * 100)}%`;
|
||||
}
|
||||
|
||||
const relations = computed(() =>
|
||||
[...(gmStore.geopolitics?.diplomacy.relations ?? [])]
|
||||
.sort((left, right) => right.tensionScore - left.tensionScore || left.id.localeCompare(right.id))
|
||||
.slice(0, 12),
|
||||
);
|
||||
|
||||
const incidents = computed(() =>
|
||||
[...(gmStore.geopolitics?.diplomacy.incidents ?? [])]
|
||||
.sort((left, right) => right.lastObservedAtUtc.localeCompare(left.lastObservedAtUtc))
|
||||
.slice(0, 8),
|
||||
);
|
||||
|
||||
const contestedSystems = computed(() =>
|
||||
[...(gmStore.geopolitics?.territory.controlStates ?? [])]
|
||||
.filter((state) => state.isContested)
|
||||
.sort((left, right) => right.strategicValue - left.strategicValue || left.systemId.localeCompare(right.systemId))
|
||||
.slice(0, 12),
|
||||
);
|
||||
|
||||
const frontLines = computed(() =>
|
||||
[...(gmStore.geopolitics?.territory.frontLines ?? [])]
|
||||
.sort((left, right) => right.pressureScore - left.pressureScore || left.id.localeCompare(right.id))
|
||||
.slice(0, 10),
|
||||
);
|
||||
|
||||
const regions = computed(() =>
|
||||
[...(gmStore.geopolitics?.economyRegions.regions ?? [])]
|
||||
.sort((left, right) => left.label.localeCompare(right.label))
|
||||
.slice(0, 16),
|
||||
);
|
||||
|
||||
const bottlenecks = computed(() =>
|
||||
[...(gmStore.geopolitics?.economyRegions.bottlenecks ?? [])]
|
||||
.sort((left, right) => right.severity - left.severity || left.id.localeCompare(right.id))
|
||||
.slice(0, 12),
|
||||
);
|
||||
|
||||
const corridors = computed(() =>
|
||||
[...(gmStore.geopolitics?.economyRegions.corridors ?? [])]
|
||||
.sort((left, right) => right.riskScore - left.riskScore || left.id.localeCompare(right.id))
|
||||
.slice(0, 12),
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-5 p-4 text-xs text-white/90">
|
||||
<div v-if="!gmStore.geopolitics" class="rounded border border-white/10 bg-white/5 p-4 text-white/60">
|
||||
No geopolitical state loaded.
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<section class="grid gap-3 md:grid-cols-4">
|
||||
<div class="rounded border border-white/10 bg-white/5 p-3">
|
||||
<div class="text-[11px] uppercase tracking-[0.2em] text-white/50">Diplomacy</div>
|
||||
<div class="mt-2 text-lg font-semibold">{{ gmStore.geopolitics.diplomacy.relations.length }}</div>
|
||||
<div class="text-white/60">relations</div>
|
||||
</div>
|
||||
<div class="rounded border border-white/10 bg-white/5 p-3">
|
||||
<div class="text-[11px] uppercase tracking-[0.2em] text-white/50">Wars</div>
|
||||
<div class="mt-2 text-lg font-semibold">{{ gmStore.geopolitics.diplomacy.wars.length }}</div>
|
||||
<div class="text-white/60">active conflicts</div>
|
||||
</div>
|
||||
<div class="rounded border border-white/10 bg-white/5 p-3">
|
||||
<div class="text-[11px] uppercase tracking-[0.2em] text-white/50">Contested</div>
|
||||
<div class="mt-2 text-lg font-semibold">{{ contestedSystems.length }}</div>
|
||||
<div class="text-white/60">systems</div>
|
||||
</div>
|
||||
<div class="rounded border border-white/10 bg-white/5 p-3">
|
||||
<div class="text-[11px] uppercase tracking-[0.2em] text-white/50">Regions</div>
|
||||
<div class="mt-2 text-lg font-semibold">{{ gmStore.geopolitics.economyRegions.regions.length }}</div>
|
||||
<div class="text-white/60">economic regions</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="grid gap-5 xl:grid-cols-2">
|
||||
<div class="rounded border border-white/10 bg-white/5 p-4">
|
||||
<h3 class="text-sm font-semibold">Relations</h3>
|
||||
<div class="mt-3 space-y-2">
|
||||
<div v-for="relation in relations" :key="relation.id" class="rounded border border-white/10 bg-black/20 p-2">
|
||||
<div class="font-medium">{{ factionLabel(relation.factionAId) }} vs {{ factionLabel(relation.factionBId) }}</div>
|
||||
<div class="mt-1 text-white/70">
|
||||
{{ titleCase(relation.posture) }} · tension {{ percent(relation.tensionScore) }} · grievance {{ percent(relation.grievanceScore) }}
|
||||
</div>
|
||||
<div class="text-white/55">
|
||||
Trade {{ relation.tradeAccessPolicy }} · Military {{ relation.militaryAccessPolicy }} · treaties {{ relation.activeTreatyIds.length }} · incidents {{ relation.activeIncidentIds.length }}<span v-if="relation.warStateId"> · war {{ relation.warStateId }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded border border-white/10 bg-white/5 p-4">
|
||||
<h3 class="text-sm font-semibold">Incidents</h3>
|
||||
<div class="mt-3 space-y-2">
|
||||
<div v-for="incident in incidents" :key="incident.id" class="rounded border border-white/10 bg-black/20 p-2">
|
||||
<div class="font-medium">{{ titleCase(incident.kind) }} · {{ incident.systemId ?? "no-system" }}</div>
|
||||
<div class="mt-1 text-white/70">{{ incident.summary }}</div>
|
||||
<div class="text-white/55">
|
||||
Severity {{ incident.severity.toFixed(2) }} · Escalation {{ incident.escalationScore.toFixed(2) }} · {{ factionLabel(incident.sourceFactionId) }} → {{ factionLabel(incident.targetFactionId) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="grid gap-5 xl:grid-cols-2">
|
||||
<div class="rounded border border-white/10 bg-white/5 p-4">
|
||||
<h3 class="text-sm font-semibold">Territory</h3>
|
||||
<div class="mt-3 space-y-2">
|
||||
<div v-for="state in contestedSystems" :key="state.systemId" class="rounded border border-white/10 bg-black/20 p-2">
|
||||
<div class="font-medium">{{ state.systemId }} · {{ titleCase(state.controlKind) }}</div>
|
||||
<div class="mt-1 text-white/70">
|
||||
Control {{ state.controlScore.toFixed(1) }} · strategic {{ state.strategicValue.toFixed(1) }}
|
||||
</div>
|
||||
<div class="text-white/55">
|
||||
Controller {{ factionLabel(state.controllerFactionId) }} · Claimant {{ factionLabel(state.primaryClaimantFactionId) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded border border-white/10 bg-white/5 p-4">
|
||||
<h3 class="text-sm font-semibold">Front Lines</h3>
|
||||
<div class="mt-3 space-y-2">
|
||||
<div v-for="front in frontLines" :key="front.id" class="rounded border border-white/10 bg-black/20 p-2">
|
||||
<div class="font-medium">{{ front.id }}</div>
|
||||
<div class="mt-1 text-white/70">
|
||||
{{ titleCase(front.kind) }} · pressure {{ percent(front.pressureScore) }} · supply {{ percent(front.supplyRisk) }}
|
||||
</div>
|
||||
<div class="text-white/55">
|
||||
{{ front.factionIds.map((id) => factionLabel(id)).join(" vs ") }}<span v-if="front.anchorSystemId"> · anchor {{ front.anchorSystemId }}</span><br>
|
||||
{{ front.systemIds.join(", ") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="grid gap-5 xl:grid-cols-2">
|
||||
<div class="rounded border border-white/10 bg-white/5 p-4">
|
||||
<h3 class="text-sm font-semibold">Economic Regions</h3>
|
||||
<div class="mt-3 space-y-2">
|
||||
<div v-for="region in regions" :key="region.id" class="rounded border border-white/10 bg-black/20 p-2">
|
||||
<div class="font-medium">{{ region.label }}</div>
|
||||
<div class="mt-1 text-white/70">
|
||||
{{ titleCase(region.kind) }} · core {{ region.coreSystemId }} · systems {{ region.systemIds.length }}
|
||||
</div>
|
||||
<div class="text-white/55">
|
||||
Faction {{ factionLabel(region.factionId) }} · fronts {{ region.frontLineIds.length }} · corridors {{ region.corridorIds.length }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded border border-white/10 bg-white/5 p-4">
|
||||
<h3 class="text-sm font-semibold">Bottlenecks And Corridors</h3>
|
||||
<div class="mt-3 space-y-2">
|
||||
<div v-for="bottleneck in bottlenecks" :key="bottleneck.id" class="rounded border border-white/10 bg-black/20 p-2">
|
||||
<div class="font-medium">{{ bottleneck.itemId }} · {{ bottleneck.regionId }}</div>
|
||||
<div class="mt-1 text-white/70">
|
||||
{{ titleCase(bottleneck.cause) }} · severity {{ bottleneck.severity.toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="corridor in corridors" :key="corridor.id" class="rounded border border-white/10 bg-black/20 p-2">
|
||||
<div class="font-medium">{{ corridor.id }}</div>
|
||||
<div class="mt-1 text-white/70">
|
||||
{{ titleCase(corridor.kind) }} · {{ titleCase(corridor.accessState) }} · risk {{ percent(corridor.riskScore) }}
|
||||
</div>
|
||||
<div class="text-white/55">
|
||||
Path {{ corridor.systemPathIds.join(" → ") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -13,7 +13,10 @@ import {
|
||||
} from "@tanstack/vue-table";
|
||||
import { storeToRefs } from "pinia";
|
||||
import GmWindow from "./GmWindow.vue";
|
||||
import GmPlayerFactionPanel from "./GmPlayerFactionPanel.vue";
|
||||
import GmGeopoliticsPanel from "./GmGeopoliticsPanel.vue";
|
||||
import { useGmStore } from "../../ui/stores/gmStore";
|
||||
import { usePlayerFactionStore } from "../../ui/stores/playerFactionStore";
|
||||
import { useViewerSelectionStore } from "../../ui/stores/viewerSelection";
|
||||
import type { ShipSnapshot } from "../../contractsShips";
|
||||
import type { StationSnapshot } from "../../contractsInfrastructure";
|
||||
@@ -74,10 +77,11 @@ const emit = defineEmits<{
|
||||
focus: [id: string, kind: "ship" | "station"];
|
||||
}>();
|
||||
|
||||
type TabId = "ships" | "stations" | "factions";
|
||||
type TabId = "ships" | "stations" | "factions" | "player" | "geopolitics";
|
||||
const activeTab = ref<TabId>("ships");
|
||||
|
||||
const gmStore = useGmStore();
|
||||
const playerFactionStore = usePlayerFactionStore();
|
||||
const selectionStore = useViewerSelectionStore();
|
||||
const { selectedEntityId, selectedEntityKind } = storeToRefs(selectionStore);
|
||||
|
||||
@@ -128,62 +132,51 @@ function formatCargoAmount(value: number | null | undefined) {
|
||||
return value.toFixed(2).replace(/\.?0+$/, "");
|
||||
}
|
||||
|
||||
function formatPercent(value: number | null | undefined) {
|
||||
if (value == null || Number.isNaN(value)) return "—";
|
||||
return `${Math.round(value * 100)}%`;
|
||||
}
|
||||
|
||||
function getLeadCampaign(faction: FactionSnapshot) {
|
||||
return [...faction.strategicState.campaigns]
|
||||
.sort((left, right) => right.priority - left.priority)
|
||||
.find((campaign) => campaign.status !== "completed" && campaign.status !== "cancelled")
|
||||
?? faction.strategicState.campaigns[0];
|
||||
}
|
||||
|
||||
function getLeadObjective(faction: FactionSnapshot) {
|
||||
return [...(faction.objectives ?? [])]
|
||||
return [...faction.strategicState.objectives]
|
||||
.sort((left, right) => right.priority - left.priority)
|
||||
.find((objective) => objective.state !== "Complete" && objective.state !== "Cancelled")
|
||||
?? faction.objectives?.[0];
|
||||
.find((objective) => objective.status !== "completed" && objective.status !== "cancelled")
|
||||
?? faction.strategicState.objectives[0];
|
||||
}
|
||||
|
||||
function getLeadStep(faction: FactionSnapshot) {
|
||||
const objective = getLeadObjective(faction);
|
||||
return [...(objective?.steps ?? [])]
|
||||
.sort((left, right) => right.priority - left.priority)
|
||||
.find((step) => step.status !== "Complete" && step.status !== "Cancelled")
|
||||
?? objective?.steps?.[0];
|
||||
}
|
||||
|
||||
function getLeadTask(faction: FactionSnapshot) {
|
||||
return [...(faction.issuedTasks ?? [])]
|
||||
.sort((left, right) => right.priority - left.priority)
|
||||
.find((task) => task.state !== "Complete" && task.state !== "Cancelled")
|
||||
?? faction.issuedTasks?.[0];
|
||||
function getLatestDecision(faction: FactionSnapshot) {
|
||||
return [...faction.decisionLog]
|
||||
.sort((left, right) => right.occurredAtUtc.localeCompare(left.occurredAtUtc))[0];
|
||||
}
|
||||
|
||||
function describeCommodityState(faction: FactionSnapshot, itemId: string, shortLabel: string) {
|
||||
const signal = faction.blackboard?.commoditySignals.find((entry) => entry.itemId === itemId);
|
||||
const signal = faction.strategicState.economicAssessment.commoditySignals.find((entry) => entry.itemId === itemId);
|
||||
if (!signal) return `${shortLabel} —`;
|
||||
return `${shortLabel} ${titleCaseToken(signal.level)} ${compactRate(signal.projectedNetRatePerSecond)}`;
|
||||
}
|
||||
|
||||
function describeFactionStrategicState(faction: FactionSnapshot) {
|
||||
const campaign = getLeadCampaign(faction);
|
||||
const objective = getLeadObjective(faction);
|
||||
if (!objective) return "No objectives";
|
||||
return `${titleCaseToken(objective.kind)} · ${titleCaseToken(objective.state)}`;
|
||||
}
|
||||
|
||||
function describeFactionLeadStep(faction: FactionSnapshot) {
|
||||
const step = getLeadStep(faction);
|
||||
if (!step) return "No steps";
|
||||
const target = step.commodityId ?? step.moduleId ?? step.targetFactionId ?? step.targetSiteId;
|
||||
return target
|
||||
? `${titleCaseToken(step.kind)} · ${titleCaseToken(step.status)} · ${target}`
|
||||
: `${titleCaseToken(step.kind)} · ${titleCaseToken(step.status)}`;
|
||||
if (!campaign && !objective) return "No campaigns";
|
||||
if (!campaign) return `${titleCaseToken(objective?.kind)} · ${titleCaseToken(objective?.status)}`;
|
||||
return `${titleCaseToken(campaign.kind)} · ${titleCaseToken(campaign.status)}`;
|
||||
}
|
||||
|
||||
function describeFactionLeadTask(faction: FactionSnapshot) {
|
||||
const task = getLeadTask(faction);
|
||||
if (!task) return "No tasks";
|
||||
const target = task.shipRole ?? task.commodityId ?? task.moduleId ?? task.targetFactionId ?? task.targetSiteId;
|
||||
const objective = getLeadObjective(faction);
|
||||
if (!objective) return "No objectives";
|
||||
const target = objective.itemId ?? objective.targetEntityId ?? objective.targetSystemId ?? objective.homeStationId;
|
||||
return target
|
||||
? `${titleCaseToken(task.kind)} · ${titleCaseToken(task.state)} · ${target}`
|
||||
: `${titleCaseToken(task.kind)} · ${titleCaseToken(task.state)}`;
|
||||
}
|
||||
|
||||
function describeFactionPriority(faction: FactionSnapshot) {
|
||||
const priority = [...(faction.strategicPriorities ?? [])]
|
||||
.sort((left, right) => right.priority - left.priority)[0];
|
||||
return priority ? `${titleCaseToken(priority.goalName)} · ${compactNumber(priority.priority, 0)}` : "—";
|
||||
? `${titleCaseToken(objective.kind)} · ${titleCaseToken(objective.status)} · ${target}`
|
||||
: `${titleCaseToken(objective.kind)} · ${titleCaseToken(objective.status)}`;
|
||||
}
|
||||
|
||||
function describeFactionEconomy(faction: FactionSnapshot) {
|
||||
@@ -195,9 +188,57 @@ function describeFactionEconomy(faction: FactionSnapshot) {
|
||||
}
|
||||
|
||||
function describeFactionThreat(faction: FactionSnapshot) {
|
||||
const blackboard = faction.blackboard;
|
||||
if (!blackboard) return "—";
|
||||
return `Enemy ships ${blackboard.enemyShipCount} · stations ${blackboard.enemyStationCount}`;
|
||||
const threat = faction.strategicState.threatAssessment;
|
||||
return `Enemy ships ${threat.enemyShipCount} · stations ${threat.enemyStationCount}`;
|
||||
}
|
||||
|
||||
function describeFactionCommitments(faction: FactionSnapshot) {
|
||||
const economic = faction.strategicState.economicAssessment;
|
||||
return `Mil ${economic.militaryShipCount}/${economic.targetMilitaryShipCount} · Min ${economic.minerShipCount}/${economic.targetMinerShipCount} · Tr ${economic.transportShipCount}/${economic.targetTransportShipCount}`;
|
||||
}
|
||||
|
||||
function describeFactionReserves(faction: FactionSnapshot) {
|
||||
const budget = faction.strategicState.budget;
|
||||
return `Assets ${budget.reservedMilitaryAssets}/${budget.reservedLogisticsAssets}/${budget.reservedConstructionAssets} · Credits ${compactNumber(budget.reservedCredits, 0)}`;
|
||||
}
|
||||
|
||||
function describeFactionBottleneck(faction: FactionSnapshot) {
|
||||
const economic = faction.strategicState.economicAssessment;
|
||||
if (!economic.industrialBottleneckItemId) {
|
||||
return `None · sustain ${formatPercent(economic.sustainmentScore)}`;
|
||||
}
|
||||
return `${economic.industrialBottleneckItemId} · sustain ${formatPercent(economic.sustainmentScore)} · replace ${formatPercent(economic.replacementPressure)}`;
|
||||
}
|
||||
|
||||
function describeFactionIntent(faction: FactionSnapshot) {
|
||||
const latestDecision = getLatestDecision(faction);
|
||||
const leadCampaign = getLeadCampaign(faction);
|
||||
if (!leadCampaign) return latestDecision?.summary ?? "—";
|
||||
const pause = leadCampaign.pauseReason ? ` · ${leadCampaign.pauseReason}` : "";
|
||||
return `${titleCaseToken(leadCampaign.kind)} · ${titleCaseToken(leadCampaign.status)}${pause}`;
|
||||
}
|
||||
|
||||
function describeFactionMemory(faction: FactionSnapshot) {
|
||||
const topSystem = [...faction.memory.systems]
|
||||
.sort((left, right) => (right.frontierPressure + right.routeRisk + right.historicalShortagePressure)
|
||||
- (left.frontierPressure + left.routeRisk + left.historicalShortagePressure))[0];
|
||||
const topCommodity = [...faction.memory.commodities]
|
||||
.sort((left, right) => right.historicalShortageScore - left.historicalShortageScore)[0];
|
||||
if (!topSystem && !topCommodity) return "—";
|
||||
return `${topSystem ? `${topSystem.systemId} fp ${compactNumber(topSystem.frontierPressure, 1)}` : "no-front"}${topCommodity ? ` · ${topCommodity.itemId} hs ${compactNumber(topCommodity.historicalShortageScore, 1)}` : ""}`;
|
||||
}
|
||||
|
||||
function describeFactionDecision(faction: FactionSnapshot) {
|
||||
const latestDecision = getLatestDecision(faction);
|
||||
return latestDecision ? `${titleCaseToken(latestDecision.kind)} · ${latestDecision.summary}` : "—";
|
||||
}
|
||||
|
||||
function describeFactionFronts(faction: FactionSnapshot) {
|
||||
const activeTheaters = faction.strategicState.theaters.filter((theater) => theater.status === "active");
|
||||
const defense = activeTheaters.filter((theater) => theater.kind.includes("defense")).length;
|
||||
const offense = activeTheaters.filter((theater) => theater.kind.includes("offense")).length;
|
||||
const economy = activeTheaters.filter((theater) => theater.kind.includes("economic")).length;
|
||||
return `${activeTheaters.length} active · D ${defense} · O ${offense} · E ${economy}`;
|
||||
}
|
||||
|
||||
// ── Ships table ────────────────────────────────────────────────────────────
|
||||
@@ -210,32 +251,40 @@ type ShipRow = {
|
||||
faction: string;
|
||||
system: string;
|
||||
state: string;
|
||||
objective: string;
|
||||
assignment: string;
|
||||
behavior: string;
|
||||
phase: string;
|
||||
action: string;
|
||||
task: string;
|
||||
orders: string;
|
||||
plan: string;
|
||||
step: string;
|
||||
subtask: string;
|
||||
cargo: number;
|
||||
health: number;
|
||||
};
|
||||
|
||||
const shipRows = computed<ShipRow[]>(() =>
|
||||
gmStore.ships.map((s) => ({
|
||||
id: s.id,
|
||||
label: s.label,
|
||||
class: s.class,
|
||||
factionColor: factionColorMap.value.get(s.factionId) ?? "—",
|
||||
faction: factionMap.value.get(s.factionId) ?? s.factionId,
|
||||
system: s.systemId,
|
||||
state: titleCaseToken(s.state),
|
||||
objective: s.commanderObjective ? titleCaseToken(s.commanderObjective) : "—",
|
||||
behavior: titleCaseToken(s.defaultBehaviorKind),
|
||||
phase: s.behaviorPhase ? titleCaseToken(s.behaviorPhase) : "—",
|
||||
action: s.currentAction ? `${s.currentAction.label} ${Math.round(s.currentAction.progress * 100)}%` : "—",
|
||||
task: titleCaseToken(s.controllerTaskKind),
|
||||
cargo: s.inventory.reduce((sum, e) => sum + e.amount, 0),
|
||||
health: Math.round(s.health),
|
||||
})),
|
||||
gmStore.ships.map((s) => {
|
||||
const topOrder = [...s.orderQueue]
|
||||
.sort((left, right) => right.priority - left.priority || left.createdAtUtc.localeCompare(right.createdAtUtc))[0];
|
||||
const currentStep = s.activePlan?.steps[s.activePlan.currentStepIndex];
|
||||
const currentSubTask = s.activeSubTasks[0];
|
||||
return {
|
||||
id: s.id,
|
||||
label: s.label,
|
||||
class: s.class,
|
||||
factionColor: factionColorMap.value.get(s.factionId) ?? "—",
|
||||
faction: factionMap.value.get(s.factionId) ?? s.factionId,
|
||||
system: s.systemId,
|
||||
state: titleCaseToken(s.state),
|
||||
assignment: s.assignment ? titleCaseToken(s.assignment.kind) : "—",
|
||||
behavior: titleCaseToken(s.defaultBehavior.kind),
|
||||
orders: topOrder ? `${titleCaseToken(topOrder.kind)} · ${s.orderQueue.length}` : "—",
|
||||
plan: s.activePlan ? `${titleCaseToken(s.activePlan.kind)} · ${titleCaseToken(s.activePlan.status)}` : "—",
|
||||
step: currentStep ? `${titleCaseToken(currentStep.kind)} · ${titleCaseToken(currentStep.status)}` : "—",
|
||||
subtask: currentSubTask ? `${titleCaseToken(currentSubTask.kind)} ${Math.round(currentSubTask.progress * 100)}%` : "—",
|
||||
cargo: s.inventory.reduce((sum, e) => sum + e.amount, 0),
|
||||
health: Math.round(s.health),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const shipColumnHelper = createColumnHelper<ShipRow>();
|
||||
@@ -250,11 +299,12 @@ const shipColumns = [
|
||||
shipColumnHelper.accessor("faction", { header: "Faction" }),
|
||||
shipColumnHelper.accessor("system", { header: "System" }),
|
||||
shipColumnHelper.accessor("state", { header: "Ship State" }),
|
||||
shipColumnHelper.accessor("objective", { header: "Commander Objective" }),
|
||||
shipColumnHelper.accessor("assignment", { header: "Assignment" }),
|
||||
shipColumnHelper.accessor("behavior", { header: "Behavior" }),
|
||||
shipColumnHelper.accessor("phase", { header: "Phase" }),
|
||||
shipColumnHelper.accessor("action", { header: "Current Action" }),
|
||||
shipColumnHelper.accessor("task", { header: "Task" }),
|
||||
shipColumnHelper.accessor("orders", { header: "Orders" }),
|
||||
shipColumnHelper.accessor("plan", { header: "Plan" }),
|
||||
shipColumnHelper.accessor("step", { header: "Current Step" }),
|
||||
shipColumnHelper.accessor("subtask", { header: "SubTask" }),
|
||||
shipColumnHelper.accessor("cargo", {
|
||||
header: "Cargo",
|
||||
cell: (info) => formatCargoAmount(info.getValue()),
|
||||
@@ -264,7 +314,7 @@ const shipColumns = [
|
||||
|
||||
const shipFilter = ref("");
|
||||
const shipSorting = ref<SortingState>([]);
|
||||
const shipOrder = useColumnOrder(["label", "class", "factionColor", "faction", "system", "state", "objective", "behavior", "phase", "action", "task", "cargo", "health"]);
|
||||
const shipOrder = useColumnOrder(["label", "class", "factionColor", "faction", "system", "state", "assignment", "behavior", "orders", "plan", "step", "subtask", "cargo", "health"]);
|
||||
|
||||
const shipTable = useVueTable({
|
||||
get data() { return shipRows.value; },
|
||||
@@ -383,14 +433,18 @@ type FactionRow = {
|
||||
label: string;
|
||||
color: string;
|
||||
planCycle: number;
|
||||
priority: string;
|
||||
strategicState: string;
|
||||
leadStep: string;
|
||||
leadTask: string;
|
||||
warReadiness: string;
|
||||
posture: string;
|
||||
fronts: string;
|
||||
leadCampaign: string;
|
||||
leadObjective: string;
|
||||
commitments: string;
|
||||
reserves: string;
|
||||
bottleneck: string;
|
||||
intent: string;
|
||||
decision: string;
|
||||
memory: string;
|
||||
economy: string;
|
||||
threat: string;
|
||||
fleets: string;
|
||||
systems: string;
|
||||
credits: number;
|
||||
population: number;
|
||||
@@ -399,29 +453,29 @@ type FactionRow = {
|
||||
};
|
||||
|
||||
const factionRows = computed<FactionRow[]>(() =>
|
||||
gmStore.factions.map((f) => {
|
||||
const assessment = f.strategicAssessment;
|
||||
const blackboard = f.blackboard;
|
||||
return {
|
||||
id: f.id,
|
||||
label: f.label,
|
||||
color: f.color,
|
||||
planCycle: blackboard?.planCycle ?? 0,
|
||||
priority: describeFactionPriority(f),
|
||||
strategicState: describeFactionStrategicState(f),
|
||||
leadStep: describeFactionLeadStep(f),
|
||||
leadTask: describeFactionLeadTask(f),
|
||||
warReadiness: `Industry ${blackboard?.hasWarIndustrySupplyChain ? "yes" : "no"} · Shipyard ${blackboard?.hasShipyard ? "yes" : "no"}${blackboard?.hasActiveExpansionProject ? ` · Expanding ${blackboard.activeExpansionCommodityId ?? blackboard.activeExpansionModuleId ?? "site"}` : ""}`,
|
||||
economy: describeFactionEconomy(f),
|
||||
threat: describeFactionThreat(f),
|
||||
fleets: assessment ? `M ${assessment.militaryShipCount}/${blackboard?.targetWarshipCount ?? 0} · Mn ${assessment.minerShipCount} · Tr ${assessment.transportShipCount} · Cn ${assessment.constructorShipCount}` : "—",
|
||||
systems: assessment ? `${assessment.controlledSystemCount} / ${assessment.targetSystemCount}` : "—",
|
||||
credits: Math.round(f.credits),
|
||||
population: Math.round(f.populationTotal),
|
||||
shipsBuilt: f.shipsBuilt,
|
||||
shipsLost: f.shipsLost,
|
||||
};
|
||||
}),
|
||||
gmStore.factions.map((f) => ({
|
||||
id: f.id,
|
||||
label: f.label,
|
||||
color: f.color,
|
||||
planCycle: f.strategicState.planCycle,
|
||||
posture: `${titleCaseToken(f.doctrine.strategicPosture)} · ${titleCaseToken(f.doctrine.militaryPosture)} · ${titleCaseToken(f.doctrine.economicPosture)}`,
|
||||
fronts: describeFactionFronts(f),
|
||||
leadCampaign: describeFactionStrategicState(f),
|
||||
leadObjective: describeFactionLeadTask(f),
|
||||
commitments: describeFactionCommitments(f),
|
||||
reserves: describeFactionReserves(f),
|
||||
bottleneck: describeFactionBottleneck(f),
|
||||
intent: describeFactionIntent(f),
|
||||
decision: describeFactionDecision(f),
|
||||
memory: describeFactionMemory(f),
|
||||
economy: describeFactionEconomy(f),
|
||||
threat: describeFactionThreat(f),
|
||||
systems: `${f.strategicState.economicAssessment.controlledSystemCount} / ${f.doctrine.desiredControlledSystems}`,
|
||||
credits: Math.round(f.credits),
|
||||
population: Math.round(f.populationTotal),
|
||||
shipsBuilt: f.shipsBuilt,
|
||||
shipsLost: f.shipsLost,
|
||||
})),
|
||||
);
|
||||
|
||||
const factionColumnHelper = createColumnHelper<FactionRow>();
|
||||
@@ -432,14 +486,18 @@ const factionColumns = [
|
||||
cell: (info) => renderColorCell(info.getValue()),
|
||||
}),
|
||||
factionColumnHelper.accessor("planCycle", { header: "Cycle" }),
|
||||
factionColumnHelper.accessor("priority", { header: "Top Priority" }),
|
||||
factionColumnHelper.accessor("strategicState", { header: "Objective" }),
|
||||
factionColumnHelper.accessor("leadStep", { header: "Lead Step" }),
|
||||
factionColumnHelper.accessor("leadTask", { header: "Issued Task" }),
|
||||
factionColumnHelper.accessor("warReadiness", { header: "Campaign State" }),
|
||||
factionColumnHelper.accessor("posture", { header: "Posture" }),
|
||||
factionColumnHelper.accessor("fronts", { header: "Fronts" }),
|
||||
factionColumnHelper.accessor("leadCampaign", { header: "Lead Campaign" }),
|
||||
factionColumnHelper.accessor("leadObjective", { header: "Lead Objective" }),
|
||||
factionColumnHelper.accessor("commitments", { header: "Commitments" }),
|
||||
factionColumnHelper.accessor("reserves", { header: "Reserves" }),
|
||||
factionColumnHelper.accessor("bottleneck", { header: "Bottleneck" }),
|
||||
factionColumnHelper.accessor("intent", { header: "Strategic Intent" }),
|
||||
factionColumnHelper.accessor("decision", { header: "Recent Decision" }),
|
||||
factionColumnHelper.accessor("memory", { header: "Memory" }),
|
||||
factionColumnHelper.accessor("economy", { header: "Economy" }),
|
||||
factionColumnHelper.accessor("threat", { header: "Threat" }),
|
||||
factionColumnHelper.accessor("fleets", { header: "Fleets" }),
|
||||
factionColumnHelper.accessor("systems", { header: "Systems" }),
|
||||
factionColumnHelper.accessor("credits", { header: "Credits" }),
|
||||
factionColumnHelper.accessor("population", { header: "Pop" }),
|
||||
@@ -449,7 +507,7 @@ const factionColumns = [
|
||||
|
||||
const factionFilter = ref("");
|
||||
const factionSorting = ref<SortingState>([]);
|
||||
const factionOrder = useColumnOrder(["label", "color", "planCycle", "priority", "strategicState", "leadStep", "leadTask", "warReadiness", "economy", "threat", "fleets", "systems", "credits", "population", "shipsBuilt", "shipsLost"]);
|
||||
const factionOrder = useColumnOrder(["label", "color", "planCycle", "posture", "fronts", "leadCampaign", "leadObjective", "commitments", "reserves", "bottleneck", "intent", "decision", "memory", "economy", "threat", "systems", "credits", "population", "shipsBuilt", "shipsLost"]);
|
||||
|
||||
const factionTable = useVueTable({
|
||||
get data() { return factionRows.value; },
|
||||
@@ -472,6 +530,8 @@ const factionTable = useVueTable({
|
||||
// ── Row counts ─────────────────────────────────────────────────────────────
|
||||
|
||||
const tabs: { id: TabId; label: string }[] = [
|
||||
{ id: "player", label: "Player" },
|
||||
{ id: "geopolitics", label: "Geopolitics" },
|
||||
{ id: "ships", label: "Ships" },
|
||||
{ id: "stations", label: "Stations" },
|
||||
{ id: "factions", label: "Factions" },
|
||||
@@ -479,11 +539,15 @@ const tabs: { id: TabId; label: string }[] = [
|
||||
|
||||
const activeFilter = computed({
|
||||
get: () => {
|
||||
if (activeTab.value === "player") return "";
|
||||
if (activeTab.value === "geopolitics") return "";
|
||||
if (activeTab.value === "ships") return shipFilter.value;
|
||||
if (activeTab.value === "stations") return stationFilter.value;
|
||||
return factionFilter.value;
|
||||
},
|
||||
set: (v: string) => {
|
||||
if (activeTab.value === "player") return;
|
||||
if (activeTab.value === "geopolitics") return;
|
||||
if (activeTab.value === "ships") shipFilter.value = v;
|
||||
else if (activeTab.value === "stations") stationFilter.value = v;
|
||||
else factionFilter.value = v;
|
||||
@@ -491,12 +555,24 @@ const activeFilter = computed({
|
||||
});
|
||||
|
||||
const activeRowCount = computed(() => {
|
||||
if (activeTab.value === "player") {
|
||||
return (playerFactionStore.playerFaction?.assetRegistry.shipIds.length ?? 0)
|
||||
+ (playerFactionStore.playerFaction?.assetRegistry.stationIds.length ?? 0);
|
||||
}
|
||||
if (activeTab.value === "geopolitics") {
|
||||
const geopolitics = gmStore.geopolitics;
|
||||
return (geopolitics?.diplomacy.relations.length ?? 0)
|
||||
+ (geopolitics?.territory.controlStates.length ?? 0)
|
||||
+ (geopolitics?.economyRegions.regions.length ?? 0);
|
||||
}
|
||||
if (activeTab.value === "ships") return shipTable.getFilteredRowModel().rows.length;
|
||||
if (activeTab.value === "stations") return stationTable.getFilteredRowModel().rows.length;
|
||||
return factionTable.getFilteredRowModel().rows.length;
|
||||
});
|
||||
|
||||
const activeTotalCount = computed(() => {
|
||||
if (activeTab.value === "player") return activeRowCount.value;
|
||||
if (activeTab.value === "geopolitics") return activeRowCount.value;
|
||||
if (activeTab.value === "ships") return gmStore.ships.length;
|
||||
if (activeTab.value === "stations") return gmStore.stations.length;
|
||||
return gmStore.factions.length;
|
||||
@@ -558,7 +634,7 @@ function hideOrdersTooltip() {
|
||||
|
||||
<template>
|
||||
<GmWindow
|
||||
title="AI States"
|
||||
title="Empire / AI States"
|
||||
:initial-width="980"
|
||||
:initial-height="560"
|
||||
:initial-x="80"
|
||||
@@ -581,7 +657,7 @@ function hideOrdersTooltip() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="relative flex-1">
|
||||
<div v-if="activeTab !== 'player' && activeTab !== 'geopolitics'" class="relative flex-1">
|
||||
<input
|
||||
v-model="activeFilter"
|
||||
class="gm-search-input w-full rounded border py-1 pl-7 pr-7 text-xs"
|
||||
@@ -600,12 +676,30 @@ function hideOrdersTooltip() {
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="flex-1 text-xs opacity-60">
|
||||
{{ activeTab === "player" ? "Player empire control, policy, and observability." : "Diplomacy, territory, and regional economy observability." }}
|
||||
</div>
|
||||
|
||||
<span class="gm-row-count shrink-0 font-mono text-xs tabular-nums opacity-60">
|
||||
{{ activeRowCount }} / {{ activeTotalCount }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Ships table -->
|
||||
<div
|
||||
v-show="activeTab === 'player'"
|
||||
class="gm-table-container min-h-0 flex-1 overflow-auto"
|
||||
>
|
||||
<GmPlayerFactionPanel />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-show="activeTab === 'geopolitics'"
|
||||
class="gm-table-container min-h-0 flex-1 overflow-auto"
|
||||
>
|
||||
<GmGeopoliticsPanel />
|
||||
</div>
|
||||
|
||||
<!-- Ships table -->
|
||||
<div
|
||||
v-show="activeTab === 'ships'"
|
||||
|
||||
1162
apps/viewer/src/components/gm/GmPlayerFactionPanel.vue
Normal file
1162
apps/viewer/src/components/gm/GmPlayerFactionPanel.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -37,3 +37,5 @@ export type {
|
||||
ShipTransitSnapshot,
|
||||
} from "./contractsShips";
|
||||
export type { FactionSnapshot, FactionDelta } from "./contractsFactions";
|
||||
export type { PlayerFactionSnapshot } from "./contractsPlayerFaction";
|
||||
export type { GeopoliticalStateSnapshot } from "./contractsGeopolitics";
|
||||
|
||||
@@ -23,6 +23,10 @@ export interface PolicySetSnapshot {
|
||||
dockingAccessPolicy: string;
|
||||
constructionAccessPolicy: string;
|
||||
operationalRangePolicy: string;
|
||||
combatEngagementPolicy: string;
|
||||
avoidHostileSystems: boolean;
|
||||
fleeHullRatio: number;
|
||||
blacklistedSystemIds: string[];
|
||||
}
|
||||
|
||||
export interface PolicySetDelta extends PolicySetSnapshot {}
|
||||
|
||||
@@ -1,41 +1,81 @@
|
||||
export interface FactionPlanningStateSnapshot {
|
||||
militaryShipCount: number;
|
||||
minerShipCount: number;
|
||||
transportShipCount: number;
|
||||
constructorShipCount: number;
|
||||
controlledSystemCount: number;
|
||||
targetSystemCount: number;
|
||||
hasShipFactory: boolean;
|
||||
oreStockpile: number;
|
||||
refinedMetalsAvailableStock: number;
|
||||
refinedMetalsUsageRate: number;
|
||||
refinedMetalsProjectedProductionRate: number;
|
||||
refinedMetalsProjectedNetRate: number;
|
||||
refinedMetalsLevelSeconds: number;
|
||||
refinedMetalsLevel: string;
|
||||
hullpartsAvailableStock: number;
|
||||
hullpartsUsageRate: number;
|
||||
hullpartsProjectedProductionRate: number;
|
||||
hullpartsProjectedNetRate: number;
|
||||
hullpartsLevelSeconds: number;
|
||||
hullpartsLevel: string;
|
||||
claytronicsAvailableStock: number;
|
||||
claytronicsUsageRate: number;
|
||||
claytronicsProjectedProductionRate: number;
|
||||
claytronicsProjectedNetRate: number;
|
||||
claytronicsLevelSeconds: number;
|
||||
claytronicsLevel: string;
|
||||
waterAvailableStock: number;
|
||||
waterUsageRate: number;
|
||||
waterProjectedProductionRate: number;
|
||||
waterProjectedNetRate: number;
|
||||
waterLevelSeconds: number;
|
||||
waterLevel: string;
|
||||
import type { Vector3Dto } from "./contractsCommon";
|
||||
|
||||
export interface FactionDoctrineSnapshot {
|
||||
strategicPosture: string;
|
||||
expansionPosture: string;
|
||||
militaryPosture: string;
|
||||
economicPosture: string;
|
||||
desiredControlledSystems: number;
|
||||
desiredMilitaryPerFront: number;
|
||||
desiredMinersPerSystem: number;
|
||||
desiredTransportsPerSystem: number;
|
||||
desiredConstructors: number;
|
||||
reserveCreditsRatio: number;
|
||||
expansionBudgetRatio: number;
|
||||
warBudgetRatio: number;
|
||||
reserveMilitaryRatio: number;
|
||||
offensiveReadinessThreshold: number;
|
||||
supplySecurityBias: number;
|
||||
failureAversion: number;
|
||||
reinforcementLeadPerFront: number;
|
||||
}
|
||||
|
||||
export interface FactionStrategicPrioritySnapshot {
|
||||
goalName: string;
|
||||
priority: number;
|
||||
export interface FactionSystemMemorySnapshot {
|
||||
systemId: string;
|
||||
lastSeenAtUtc: string;
|
||||
lastEnemyShipCount: number;
|
||||
lastEnemyStationCount: number;
|
||||
controlledByFaction: boolean;
|
||||
lastRole?: string | null;
|
||||
frontierPressure: number;
|
||||
routeRisk: number;
|
||||
historicalShortagePressure: number;
|
||||
offensiveFailures: number;
|
||||
defensiveFailures: number;
|
||||
offensiveSuccesses: number;
|
||||
defensiveSuccesses: number;
|
||||
lastContestedAtUtc?: string | null;
|
||||
lastShortageAtUtc?: string | null;
|
||||
}
|
||||
|
||||
export interface FactionCommodityMemorySnapshot {
|
||||
itemId: string;
|
||||
historicalShortageScore: number;
|
||||
historicalSurplusScore: number;
|
||||
lastObservedBacklog: number;
|
||||
updatedAtUtc: string;
|
||||
lastCriticalAtUtc?: string | null;
|
||||
}
|
||||
|
||||
export interface FactionOutcomeRecordSnapshot {
|
||||
id: string;
|
||||
kind: string;
|
||||
summary: string;
|
||||
relatedCampaignId?: string | null;
|
||||
relatedObjectiveId?: string | null;
|
||||
occurredAtUtc: string;
|
||||
}
|
||||
|
||||
export interface FactionMemorySnapshot {
|
||||
lastPlanCycle: number;
|
||||
updatedAtUtc: string;
|
||||
lastObservedShipsBuilt: number;
|
||||
lastObservedShipsLost: number;
|
||||
lastObservedCredits: number;
|
||||
knownSystemIds: string[];
|
||||
knownEnemyFactionIds: string[];
|
||||
systems: FactionSystemMemorySnapshot[];
|
||||
commodities: FactionCommodityMemorySnapshot[];
|
||||
recentOutcomes: FactionOutcomeRecordSnapshot[];
|
||||
}
|
||||
|
||||
export interface FactionBudgetSnapshot {
|
||||
reservedCredits: number;
|
||||
expansionCredits: number;
|
||||
warCredits: number;
|
||||
reservedMilitaryAssets: number;
|
||||
reservedLogisticsAssets: number;
|
||||
reservedConstructionAssets: number;
|
||||
}
|
||||
|
||||
export interface FactionCommoditySignalSnapshot {
|
||||
@@ -54,96 +94,196 @@ export interface FactionCommoditySignalSnapshot {
|
||||
reservedForConstruction: number;
|
||||
}
|
||||
|
||||
export interface FactionThreatSignalSnapshot {
|
||||
scopeId: string;
|
||||
scopeKind: string;
|
||||
enemyShipCount: number;
|
||||
enemyStationCount: number;
|
||||
}
|
||||
|
||||
export interface FactionBlackboardSnapshot {
|
||||
export interface FactionEconomicAssessmentSnapshot {
|
||||
planCycle: number;
|
||||
updatedAtUtc: string;
|
||||
targetWarshipCount: number;
|
||||
hasWarIndustrySupplyChain: boolean;
|
||||
hasShipyard: boolean;
|
||||
hasActiveExpansionProject: boolean;
|
||||
activeExpansionCommodityId?: string | null;
|
||||
activeExpansionModuleId?: string | null;
|
||||
activeExpansionSiteId?: string | null;
|
||||
activeExpansionSystemId?: string | null;
|
||||
enemyFactionCount: number;
|
||||
enemyShipCount: number;
|
||||
enemyStationCount: number;
|
||||
militaryShipCount: number;
|
||||
minerShipCount: number;
|
||||
transportShipCount: number;
|
||||
constructorShipCount: number;
|
||||
controlledSystemCount: number;
|
||||
targetMilitaryShipCount: number;
|
||||
targetMinerShipCount: number;
|
||||
targetTransportShipCount: number;
|
||||
targetConstructorShipCount: number;
|
||||
hasShipyard: boolean;
|
||||
hasWarIndustrySupplyChain: boolean;
|
||||
primaryExpansionSiteId?: string | null;
|
||||
primaryExpansionSystemId?: string | null;
|
||||
replacementPressure: number;
|
||||
sustainmentScore: number;
|
||||
logisticsSecurityScore: number;
|
||||
criticalShortageCount: number;
|
||||
industrialBottleneckItemId?: string | null;
|
||||
commoditySignals: FactionCommoditySignalSnapshot[];
|
||||
}
|
||||
|
||||
export interface FactionThreatSignalSnapshot {
|
||||
scopeId: string;
|
||||
scopeKind: string;
|
||||
enemyShipCount: number;
|
||||
enemyStationCount: number;
|
||||
enemyFactionId?: string | null;
|
||||
}
|
||||
|
||||
export interface FactionThreatAssessmentSnapshot {
|
||||
planCycle: number;
|
||||
updatedAtUtc: string;
|
||||
enemyFactionCount: number;
|
||||
enemyShipCount: number;
|
||||
enemyStationCount: number;
|
||||
primaryThreatFactionId?: string | null;
|
||||
primaryThreatSystemId?: string | null;
|
||||
threatSignals: FactionThreatSignalSnapshot[];
|
||||
}
|
||||
|
||||
export interface FactionTheaterSnapshot {
|
||||
id: string;
|
||||
kind: string;
|
||||
systemId: string;
|
||||
status: string;
|
||||
priority: number;
|
||||
supplyRisk: number;
|
||||
friendlyAssetValue: number;
|
||||
targetFactionId?: string | null;
|
||||
anchorEntityId?: string | null;
|
||||
anchorPosition?: Vector3Dto | null;
|
||||
updatedAtUtc: string;
|
||||
campaignIds: string[];
|
||||
}
|
||||
|
||||
export interface FactionPlanStepSnapshot {
|
||||
id: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
priority: number;
|
||||
commodityId?: string | null;
|
||||
moduleId?: string | null;
|
||||
targetFactionId?: string | null;
|
||||
targetSiteId?: string | null;
|
||||
summary?: string | null;
|
||||
blockingReason?: string | null;
|
||||
notes?: string | null;
|
||||
lastEvaluatedCycle: number;
|
||||
dependencyStepIds: string[];
|
||||
requiredFacts: string[];
|
||||
producedFacts: string[];
|
||||
assignedAssets: string[];
|
||||
issuedTaskIds: string[];
|
||||
}
|
||||
|
||||
export interface FactionIssuedTaskSnapshot {
|
||||
export interface FactionCampaignSnapshot {
|
||||
id: string;
|
||||
kind: string;
|
||||
state: string;
|
||||
objectiveId: string;
|
||||
stepId: string;
|
||||
status: string;
|
||||
priority: number;
|
||||
shipRole?: string | null;
|
||||
commodityId?: string | null;
|
||||
moduleId?: string | null;
|
||||
theaterId?: string | null;
|
||||
targetFactionId?: string | null;
|
||||
targetSystemId?: string | null;
|
||||
targetSiteId?: string | null;
|
||||
createdAtCycle: number;
|
||||
updatedAtCycle: number;
|
||||
blockingReason?: string | null;
|
||||
notes?: string | null;
|
||||
assignedAssets: string[];
|
||||
targetEntityId?: string | null;
|
||||
commodityId?: string | null;
|
||||
supportStationId?: string | null;
|
||||
currentStepIndex: number;
|
||||
createdAtUtc: string;
|
||||
updatedAtUtc: string;
|
||||
summary?: string | null;
|
||||
pauseReason?: string | null;
|
||||
continuationScore: number;
|
||||
supplyAdequacy: number;
|
||||
replacementPressure: number;
|
||||
failureCount: number;
|
||||
successCount: number;
|
||||
fleetCommanderId?: string | null;
|
||||
requiresReinforcement: boolean;
|
||||
steps: FactionPlanStepSnapshot[];
|
||||
objectiveIds: string[];
|
||||
}
|
||||
|
||||
export interface FactionObjectiveSnapshot {
|
||||
id: string;
|
||||
campaignId: string;
|
||||
theaterId?: string | null;
|
||||
kind: string;
|
||||
state: string;
|
||||
delegationKind: string;
|
||||
behaviorKind: string;
|
||||
status: string;
|
||||
priority: number;
|
||||
parentObjectiveId?: string | null;
|
||||
targetFactionId?: string | null;
|
||||
commanderId?: string | null;
|
||||
homeSystemId?: string | null;
|
||||
homeStationId?: string | null;
|
||||
targetSystemId?: string | null;
|
||||
targetSiteId?: string | null;
|
||||
targetRegionId?: string | null;
|
||||
targetEntityId?: string | null;
|
||||
targetPosition?: Vector3Dto | null;
|
||||
itemId?: string | null;
|
||||
notes?: string | null;
|
||||
currentStepIndex: number;
|
||||
createdAtUtc: string;
|
||||
updatedAtUtc: string;
|
||||
useOrders: boolean;
|
||||
stagingOrderKind?: string | null;
|
||||
reinforcementLevel: number;
|
||||
steps: FactionPlanStepSnapshot[];
|
||||
reservedAssetIds: string[];
|
||||
}
|
||||
|
||||
export interface FactionReservationSnapshot {
|
||||
id: string;
|
||||
objectiveId: string;
|
||||
campaignId?: string | null;
|
||||
assetKind: string;
|
||||
assetId: string;
|
||||
priority: number;
|
||||
createdAtUtc: string;
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface FactionProductionProgramSnapshot {
|
||||
id: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
priority: number;
|
||||
campaignId?: string | null;
|
||||
commodityId?: string | null;
|
||||
moduleId?: string | null;
|
||||
budgetWeight: number;
|
||||
slotCost: number;
|
||||
createdAtCycle: number;
|
||||
updatedAtCycle: number;
|
||||
invalidationReason?: string | null;
|
||||
blockingReason?: string | null;
|
||||
prerequisiteObjectiveIds: string[];
|
||||
assignedAssets: string[];
|
||||
steps: FactionPlanStepSnapshot[];
|
||||
shipKind?: string | null;
|
||||
targetSystemId?: string | null;
|
||||
targetCount: number;
|
||||
currentCount: number;
|
||||
notes?: string | null;
|
||||
}
|
||||
|
||||
export interface FactionDecisionLogEntrySnapshot {
|
||||
id: string;
|
||||
kind: string;
|
||||
summary: string;
|
||||
relatedEntityId?: string | null;
|
||||
planCycle: number;
|
||||
occurredAtUtc: string;
|
||||
}
|
||||
|
||||
export interface FactionStrategicStateSnapshot {
|
||||
planCycle: number;
|
||||
updatedAtUtc: string;
|
||||
status: string;
|
||||
budget: FactionBudgetSnapshot;
|
||||
economicAssessment: FactionEconomicAssessmentSnapshot;
|
||||
threatAssessment: FactionThreatAssessmentSnapshot;
|
||||
theaters: FactionTheaterSnapshot[];
|
||||
campaigns: FactionCampaignSnapshot[];
|
||||
objectives: FactionObjectiveSnapshot[];
|
||||
reservations: FactionReservationSnapshot[];
|
||||
productionPrograms: FactionProductionProgramSnapshot[];
|
||||
}
|
||||
|
||||
export interface CommanderAssignmentSnapshot {
|
||||
commanderId: string;
|
||||
kind: string;
|
||||
behaviorKind: string;
|
||||
status: string;
|
||||
objectiveId?: string | null;
|
||||
campaignId?: string | null;
|
||||
theaterId?: string | null;
|
||||
parentCommanderId?: string | null;
|
||||
controlledEntityId?: string | null;
|
||||
priority: number;
|
||||
homeSystemId?: string | null;
|
||||
homeStationId?: string | null;
|
||||
targetSystemId?: string | null;
|
||||
targetEntityId?: string | null;
|
||||
targetPosition?: Vector3Dto | null;
|
||||
itemId?: string | null;
|
||||
notes?: string | null;
|
||||
updatedAtUtc?: string | null;
|
||||
activeObjectiveIds: string[];
|
||||
subordinateCommanderIds: string[];
|
||||
}
|
||||
|
||||
export interface FactionSnapshot {
|
||||
@@ -157,11 +297,11 @@ export interface FactionSnapshot {
|
||||
shipsBuilt: number;
|
||||
shipsLost: number;
|
||||
defaultPolicySetId?: string | null;
|
||||
strategicAssessment?: FactionPlanningStateSnapshot | null;
|
||||
strategicPriorities?: FactionStrategicPrioritySnapshot[] | null;
|
||||
blackboard?: FactionBlackboardSnapshot | null;
|
||||
objectives?: FactionObjectiveSnapshot[] | null;
|
||||
issuedTasks?: FactionIssuedTaskSnapshot[] | null;
|
||||
doctrine: FactionDoctrineSnapshot;
|
||||
memory: FactionMemorySnapshot;
|
||||
strategicState: FactionStrategicStateSnapshot;
|
||||
decisionLog: FactionDecisionLogEntrySnapshot[];
|
||||
commanders: CommanderAssignmentSnapshot[];
|
||||
}
|
||||
|
||||
export interface FactionDelta extends FactionSnapshot {}
|
||||
|
||||
307
apps/viewer/src/contractsGeopolitics.ts
Normal file
307
apps/viewer/src/contractsGeopolitics.ts
Normal file
@@ -0,0 +1,307 @@
|
||||
export interface SystemRouteLinkSnapshot {
|
||||
id: string;
|
||||
sourceSystemId: string;
|
||||
destinationSystemId: string;
|
||||
distance: number;
|
||||
isPrimaryLane: boolean;
|
||||
}
|
||||
|
||||
export interface DiplomaticRelationSnapshot {
|
||||
id: string;
|
||||
factionAId: string;
|
||||
factionBId: string;
|
||||
status: string;
|
||||
posture: string;
|
||||
trustScore: number;
|
||||
tensionScore: number;
|
||||
grievanceScore: number;
|
||||
tradeAccessPolicy: string;
|
||||
militaryAccessPolicy: string;
|
||||
warStateId?: string | null;
|
||||
ceasefireUntilUtc?: string | null;
|
||||
updatedAtUtc: string;
|
||||
activeTreatyIds: string[];
|
||||
activeIncidentIds: string[];
|
||||
}
|
||||
|
||||
export interface TreatySnapshot {
|
||||
id: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
tradeAccessPolicy: string;
|
||||
militaryAccessPolicy: string;
|
||||
summary?: string | null;
|
||||
createdAtUtc: string;
|
||||
updatedAtUtc: string;
|
||||
factionIds: string[];
|
||||
}
|
||||
|
||||
export interface DiplomaticIncidentSnapshot {
|
||||
id: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
sourceFactionId: string;
|
||||
targetFactionId: string;
|
||||
systemId?: string | null;
|
||||
borderEdgeId?: string | null;
|
||||
summary: string;
|
||||
severity: number;
|
||||
escalationScore: number;
|
||||
createdAtUtc: string;
|
||||
lastObservedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface BorderTensionSnapshot {
|
||||
id: string;
|
||||
relationId: string;
|
||||
borderEdgeId: string;
|
||||
factionAId: string;
|
||||
factionBId: string;
|
||||
status: string;
|
||||
tensionScore: number;
|
||||
incidentScore: number;
|
||||
militaryPressure: number;
|
||||
accessFriction: number;
|
||||
updatedAtUtc: string;
|
||||
systemIds: string[];
|
||||
}
|
||||
|
||||
export interface WarStateSnapshot {
|
||||
id: string;
|
||||
relationId: string;
|
||||
factionAId: string;
|
||||
factionBId: string;
|
||||
status: string;
|
||||
warGoal: string;
|
||||
escalationScore: number;
|
||||
startedAtUtc: string;
|
||||
ceasefireUntilUtc?: string | null;
|
||||
updatedAtUtc: string;
|
||||
activeFrontLineIds: string[];
|
||||
}
|
||||
|
||||
export interface DiplomaticStateSnapshot {
|
||||
relations: DiplomaticRelationSnapshot[];
|
||||
treaties: TreatySnapshot[];
|
||||
incidents: DiplomaticIncidentSnapshot[];
|
||||
borderTensions: BorderTensionSnapshot[];
|
||||
wars: WarStateSnapshot[];
|
||||
}
|
||||
|
||||
export interface TerritoryClaimSnapshot {
|
||||
id: string;
|
||||
sourceClaimId?: string | null;
|
||||
factionId: string;
|
||||
systemId: string;
|
||||
celestialId?: string | null;
|
||||
status: string;
|
||||
claimKind: string;
|
||||
claimStrength: number;
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface TerritoryInfluenceSnapshot {
|
||||
id: string;
|
||||
systemId: string;
|
||||
factionId: string;
|
||||
claimStrength: number;
|
||||
assetStrength: number;
|
||||
logisticsStrength: number;
|
||||
totalInfluence: number;
|
||||
isContesting: boolean;
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface TerritoryControlStateSnapshot {
|
||||
systemId: string;
|
||||
controllerFactionId?: string | null;
|
||||
primaryClaimantFactionId?: string | null;
|
||||
controlKind: string;
|
||||
isContested: boolean;
|
||||
controlScore: number;
|
||||
strategicValue: number;
|
||||
updatedAtUtc: string;
|
||||
claimantFactionIds: string[];
|
||||
influencingFactionIds: string[];
|
||||
}
|
||||
|
||||
export interface SectorStrategicProfileSnapshot {
|
||||
systemId: string;
|
||||
controllerFactionId?: string | null;
|
||||
zoneKind: string;
|
||||
isContested: boolean;
|
||||
strategicValue: number;
|
||||
securityRating: number;
|
||||
territorialPressure: number;
|
||||
logisticsValue: number;
|
||||
updatedAtUtc: string;
|
||||
economicRegionId?: string | null;
|
||||
frontLineId?: string | null;
|
||||
}
|
||||
|
||||
export interface BorderEdgeSnapshot {
|
||||
id: string;
|
||||
sourceSystemId: string;
|
||||
destinationSystemId: string;
|
||||
sourceFactionId?: string | null;
|
||||
destinationFactionId?: string | null;
|
||||
isContested: boolean;
|
||||
relationId?: string | null;
|
||||
tensionScore: number;
|
||||
corridorImportance: number;
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface FrontLineSnapshot {
|
||||
id: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
anchorSystemId?: string | null;
|
||||
pressureScore: number;
|
||||
supplyRisk: number;
|
||||
updatedAtUtc: string;
|
||||
factionIds: string[];
|
||||
systemIds: string[];
|
||||
borderEdgeIds: string[];
|
||||
}
|
||||
|
||||
export interface TerritoryZoneSnapshot {
|
||||
id: string;
|
||||
systemId: string;
|
||||
factionId?: string | null;
|
||||
kind: string;
|
||||
status: string;
|
||||
reason?: string | null;
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface TerritoryPressureSnapshot {
|
||||
id: string;
|
||||
systemId: string;
|
||||
factionId?: string | null;
|
||||
kind: string;
|
||||
pressureScore: number;
|
||||
securityScore: number;
|
||||
hostileInfluence: number;
|
||||
corridorRisk: number;
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface TerritoryStateSnapshot {
|
||||
claims: TerritoryClaimSnapshot[];
|
||||
influences: TerritoryInfluenceSnapshot[];
|
||||
controlStates: TerritoryControlStateSnapshot[];
|
||||
strategicProfiles: SectorStrategicProfileSnapshot[];
|
||||
borderEdges: BorderEdgeSnapshot[];
|
||||
frontLines: FrontLineSnapshot[];
|
||||
zones: TerritoryZoneSnapshot[];
|
||||
pressures: TerritoryPressureSnapshot[];
|
||||
}
|
||||
|
||||
export interface EconomicRegionSnapshot {
|
||||
id: string;
|
||||
factionId?: string | null;
|
||||
label: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
coreSystemId: string;
|
||||
updatedAtUtc: string;
|
||||
systemIds: string[];
|
||||
stationIds: string[];
|
||||
frontLineIds: string[];
|
||||
corridorIds: string[];
|
||||
}
|
||||
|
||||
export interface SupplyNetworkSnapshot {
|
||||
id: string;
|
||||
regionId: string;
|
||||
throughputScore: number;
|
||||
riskScore: number;
|
||||
updatedAtUtc: string;
|
||||
stationIds: string[];
|
||||
producerItemIds: string[];
|
||||
consumerItemIds: string[];
|
||||
constructionItemIds: string[];
|
||||
}
|
||||
|
||||
export interface LogisticsCorridorSnapshot {
|
||||
id: string;
|
||||
factionId?: string | null;
|
||||
kind: string;
|
||||
status: string;
|
||||
riskScore: number;
|
||||
throughputScore: number;
|
||||
accessState: string;
|
||||
updatedAtUtc: string;
|
||||
systemPathIds: string[];
|
||||
regionIds: string[];
|
||||
borderEdgeIds: string[];
|
||||
}
|
||||
|
||||
export interface RegionalProductionProfileSnapshot {
|
||||
regionId: string;
|
||||
primaryIndustry: string;
|
||||
shipyardCount: number;
|
||||
stationCount: number;
|
||||
updatedAtUtc: string;
|
||||
producedItemIds: string[];
|
||||
scarceItemIds: string[];
|
||||
}
|
||||
|
||||
export interface RegionalTradeBalanceSnapshot {
|
||||
regionId: string;
|
||||
importsRequiredCount: number;
|
||||
exportsSurplusCount: number;
|
||||
criticalShortageCount: number;
|
||||
netTradeScore: number;
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface RegionalBottleneckSnapshot {
|
||||
id: string;
|
||||
regionId: string;
|
||||
itemId: string;
|
||||
cause: string;
|
||||
status: string;
|
||||
severity: number;
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface RegionalSecurityAssessmentSnapshot {
|
||||
regionId: string;
|
||||
supplyRisk: number;
|
||||
borderPressure: number;
|
||||
activeWarCount: number;
|
||||
hostileRelationCount: number;
|
||||
accessFriction: number;
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface RegionalEconomicAssessmentSnapshot {
|
||||
regionId: string;
|
||||
sustainmentScore: number;
|
||||
productionDepth: number;
|
||||
constructionPressure: number;
|
||||
corridorDependency: number;
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface EconomyRegionStateSnapshot {
|
||||
regions: EconomicRegionSnapshot[];
|
||||
supplyNetworks: SupplyNetworkSnapshot[];
|
||||
corridors: LogisticsCorridorSnapshot[];
|
||||
productionProfiles: RegionalProductionProfileSnapshot[];
|
||||
tradeBalances: RegionalTradeBalanceSnapshot[];
|
||||
bottlenecks: RegionalBottleneckSnapshot[];
|
||||
securityAssessments: RegionalSecurityAssessmentSnapshot[];
|
||||
economicAssessments: RegionalEconomicAssessmentSnapshot[];
|
||||
}
|
||||
|
||||
export interface GeopoliticalStateSnapshot {
|
||||
cycle: number;
|
||||
updatedAtUtc: string;
|
||||
routes: SystemRouteLinkSnapshot[];
|
||||
diplomacy: DiplomaticStateSnapshot;
|
||||
territory: TerritoryStateSnapshot;
|
||||
economyRegions: EconomyRegionStateSnapshot;
|
||||
}
|
||||
289
apps/viewer/src/contractsPlayerFaction.ts
Normal file
289
apps/viewer/src/contractsPlayerFaction.ts
Normal file
@@ -0,0 +1,289 @@
|
||||
import type { Vector3Dto } from "./contractsCommon";
|
||||
import type { ShipOrderTemplateSnapshot } from "./contractsShips";
|
||||
|
||||
export interface PlayerAssetRegistrySnapshot {
|
||||
shipIds: string[];
|
||||
stationIds: string[];
|
||||
commanderIds: string[];
|
||||
claimIds: string[];
|
||||
constructionSiteIds: string[];
|
||||
policySetIds: string[];
|
||||
marketOrderIds: string[];
|
||||
fleetIds: string[];
|
||||
taskForceIds: string[];
|
||||
stationGroupIds: string[];
|
||||
economicRegionIds: string[];
|
||||
frontIds: string[];
|
||||
reserveIds: string[];
|
||||
}
|
||||
|
||||
export interface PlayerStrategicIntentSnapshot {
|
||||
strategicPosture: string;
|
||||
economicPosture: string;
|
||||
militaryPosture: string;
|
||||
logisticsPosture: string;
|
||||
desiredReserveRatio: number;
|
||||
allowDelegatedCombatAutomation: boolean;
|
||||
allowDelegatedEconomicAutomation: boolean;
|
||||
notes?: string | null;
|
||||
}
|
||||
|
||||
export interface PlayerFleetSnapshot {
|
||||
id: string;
|
||||
label: string;
|
||||
status: string;
|
||||
role: string;
|
||||
commanderId?: string | null;
|
||||
frontId?: string | null;
|
||||
homeSystemId?: string | null;
|
||||
homeStationId?: string | null;
|
||||
policyId?: string | null;
|
||||
automationPolicyId?: string | null;
|
||||
reinforcementPolicyId?: string | null;
|
||||
assetIds: string[];
|
||||
taskForceIds: string[];
|
||||
directiveIds: string[];
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface PlayerTaskForceSnapshot {
|
||||
id: string;
|
||||
label: string;
|
||||
status: string;
|
||||
role: string;
|
||||
fleetId?: string | null;
|
||||
commanderId?: string | null;
|
||||
frontId?: string | null;
|
||||
policyId?: string | null;
|
||||
automationPolicyId?: string | null;
|
||||
assetIds: string[];
|
||||
directiveIds: string[];
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface PlayerStationGroupSnapshot {
|
||||
id: string;
|
||||
label: string;
|
||||
status: string;
|
||||
role: string;
|
||||
economicRegionId?: string | null;
|
||||
policyId?: string | null;
|
||||
automationPolicyId?: string | null;
|
||||
stationIds: string[];
|
||||
directiveIds: string[];
|
||||
focusItemIds: string[];
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface PlayerEconomicRegionSnapshot {
|
||||
id: string;
|
||||
label: string;
|
||||
status: string;
|
||||
role: string;
|
||||
sharedEconomicRegionId?: string | null;
|
||||
policyId?: string | null;
|
||||
automationPolicyId?: string | null;
|
||||
systemIds: string[];
|
||||
stationGroupIds: string[];
|
||||
directiveIds: string[];
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface PlayerFrontSnapshot {
|
||||
id: string;
|
||||
label: string;
|
||||
status: string;
|
||||
priority: number;
|
||||
posture: string;
|
||||
sharedFrontLineId?: string | null;
|
||||
targetFactionId?: string | null;
|
||||
systemIds: string[];
|
||||
fleetIds: string[];
|
||||
reserveIds: string[];
|
||||
directiveIds: string[];
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface PlayerReserveGroupSnapshot {
|
||||
id: string;
|
||||
label: string;
|
||||
status: string;
|
||||
reserveKind: string;
|
||||
homeSystemId?: string | null;
|
||||
policyId?: string | null;
|
||||
assetIds: string[];
|
||||
frontIds: string[];
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface PlayerFactionPolicySnapshot {
|
||||
id: string;
|
||||
label: string;
|
||||
scopeKind: string;
|
||||
scopeId?: string | null;
|
||||
policySetId?: string | null;
|
||||
allowDelegatedCombat: boolean;
|
||||
allowDelegatedTrade: boolean;
|
||||
reserveCreditsRatio: number;
|
||||
reserveMilitaryRatio: number;
|
||||
tradeAccessPolicy: string;
|
||||
dockingAccessPolicy: string;
|
||||
constructionAccessPolicy: string;
|
||||
operationalRangePolicy: string;
|
||||
combatEngagementPolicy: string;
|
||||
avoidHostileSystems: boolean;
|
||||
fleeHullRatio: number;
|
||||
blacklistedSystemIds: string[];
|
||||
notes?: string | null;
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface PlayerAutomationPolicySnapshot {
|
||||
id: string;
|
||||
label: string;
|
||||
scopeKind: string;
|
||||
scopeId?: string | null;
|
||||
enabled: boolean;
|
||||
behaviorKind: string;
|
||||
useOrders: boolean;
|
||||
stagingOrderKind?: string | null;
|
||||
maxSystemRange: number;
|
||||
knownStationsOnly: boolean;
|
||||
radius: number;
|
||||
waitSeconds: number;
|
||||
preferredItemId?: string | null;
|
||||
notes?: string | null;
|
||||
repeatOrders: ShipOrderTemplateSnapshot[];
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface PlayerReinforcementPolicySnapshot {
|
||||
id: string;
|
||||
label: string;
|
||||
scopeKind: string;
|
||||
scopeId?: string | null;
|
||||
shipKind: string;
|
||||
desiredAssetCount: number;
|
||||
minimumReserveCount: number;
|
||||
autoTransferReserves: boolean;
|
||||
autoQueueProduction: boolean;
|
||||
sourceReserveId?: string | null;
|
||||
targetFrontId?: string | null;
|
||||
notes?: string | null;
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface PlayerProductionProgramSnapshot {
|
||||
id: string;
|
||||
label: string;
|
||||
status: string;
|
||||
kind: string;
|
||||
targetShipKind?: string | null;
|
||||
targetModuleId?: string | null;
|
||||
targetItemId?: string | null;
|
||||
targetCount: number;
|
||||
currentCount: number;
|
||||
stationGroupId?: string | null;
|
||||
reinforcementPolicyId?: string | null;
|
||||
notes?: string | null;
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface PlayerDirectiveSnapshot {
|
||||
id: string;
|
||||
label: string;
|
||||
status: string;
|
||||
kind: string;
|
||||
scopeKind: string;
|
||||
scopeId: string;
|
||||
targetEntityId?: string | null;
|
||||
targetSystemId?: string | null;
|
||||
targetPosition?: Vector3Dto | null;
|
||||
homeSystemId?: string | null;
|
||||
homeStationId?: string | null;
|
||||
sourceStationId?: string | null;
|
||||
destinationStationId?: string | null;
|
||||
behaviorKind: string;
|
||||
useOrders: boolean;
|
||||
stagingOrderKind?: string | null;
|
||||
itemId?: string | null;
|
||||
preferredNodeId?: string | null;
|
||||
preferredConstructionSiteId?: string | null;
|
||||
preferredModuleId?: string | null;
|
||||
priority: number;
|
||||
radius: number;
|
||||
waitSeconds: number;
|
||||
maxSystemRange: number;
|
||||
knownStationsOnly: boolean;
|
||||
patrolPoints: Vector3Dto[];
|
||||
repeatOrders: ShipOrderTemplateSnapshot[];
|
||||
policyId?: string | null;
|
||||
automationPolicyId?: string | null;
|
||||
notes?: string | null;
|
||||
createdAtUtc: string;
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface PlayerAssignmentSnapshot {
|
||||
id: string;
|
||||
assetKind: string;
|
||||
assetId: string;
|
||||
fleetId?: string | null;
|
||||
taskForceId?: string | null;
|
||||
stationGroupId?: string | null;
|
||||
economicRegionId?: string | null;
|
||||
frontId?: string | null;
|
||||
reserveId?: string | null;
|
||||
directiveId?: string | null;
|
||||
policyId?: string | null;
|
||||
automationPolicyId?: string | null;
|
||||
role: string;
|
||||
status: string;
|
||||
updatedAtUtc: string;
|
||||
}
|
||||
|
||||
export interface PlayerDecisionLogEntrySnapshot {
|
||||
id: string;
|
||||
kind: string;
|
||||
summary: string;
|
||||
relatedEntityKind?: string | null;
|
||||
relatedEntityId?: string | null;
|
||||
occurredAtUtc: string;
|
||||
}
|
||||
|
||||
export interface PlayerAlertSnapshot {
|
||||
id: string;
|
||||
kind: string;
|
||||
severity: string;
|
||||
summary: string;
|
||||
assetKind?: string | null;
|
||||
assetId?: string | null;
|
||||
relatedDirectiveId?: string | null;
|
||||
status: string;
|
||||
createdAtUtc: string;
|
||||
}
|
||||
|
||||
export interface PlayerFactionSnapshot {
|
||||
id: string;
|
||||
label: string;
|
||||
sovereignFactionId: string;
|
||||
status: string;
|
||||
createdAtUtc: string;
|
||||
updatedAtUtc: string;
|
||||
assetRegistry: PlayerAssetRegistrySnapshot;
|
||||
strategicIntent: PlayerStrategicIntentSnapshot;
|
||||
fleets: PlayerFleetSnapshot[];
|
||||
taskForces: PlayerTaskForceSnapshot[];
|
||||
stationGroups: PlayerStationGroupSnapshot[];
|
||||
economicRegions: PlayerEconomicRegionSnapshot[];
|
||||
fronts: PlayerFrontSnapshot[];
|
||||
reserves: PlayerReserveGroupSnapshot[];
|
||||
policies: PlayerFactionPolicySnapshot[];
|
||||
automationPolicies: PlayerAutomationPolicySnapshot[];
|
||||
reinforcementPolicies: PlayerReinforcementPolicySnapshot[];
|
||||
productionPrograms: PlayerProductionProgramSnapshot[];
|
||||
directives: PlayerDirectiveSnapshot[];
|
||||
assignments: PlayerAssignmentSnapshot[];
|
||||
decisionLog: PlayerDecisionLogEntrySnapshot[];
|
||||
alerts: PlayerAlertSnapshot[];
|
||||
}
|
||||
@@ -1,5 +1,140 @@
|
||||
import type { InventoryEntry, Vector3Dto } from "./contractsCommon";
|
||||
|
||||
export interface ShipSkillProfileSnapshot {
|
||||
navigation: number;
|
||||
trade: number;
|
||||
mining: number;
|
||||
combat: number;
|
||||
construction: number;
|
||||
}
|
||||
|
||||
export interface ShipOrderSnapshot {
|
||||
id: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
priority: number;
|
||||
interruptCurrentPlan: boolean;
|
||||
createdAtUtc: string;
|
||||
label?: string | null;
|
||||
targetEntityId?: string | null;
|
||||
targetSystemId?: string | null;
|
||||
targetPosition?: Vector3Dto | null;
|
||||
sourceStationId?: string | null;
|
||||
destinationStationId?: string | null;
|
||||
itemId?: string | null;
|
||||
nodeId?: string | null;
|
||||
constructionSiteId?: string | null;
|
||||
moduleId?: string | null;
|
||||
waitSeconds: number;
|
||||
radius: number;
|
||||
maxSystemRange?: number | null;
|
||||
knownStationsOnly: boolean;
|
||||
failureReason?: string | null;
|
||||
}
|
||||
|
||||
export interface ShipOrderTemplateSnapshot {
|
||||
kind: string;
|
||||
label?: string | null;
|
||||
targetEntityId?: string | null;
|
||||
targetSystemId?: string | null;
|
||||
targetPosition?: Vector3Dto | null;
|
||||
sourceStationId?: string | null;
|
||||
destinationStationId?: string | null;
|
||||
itemId?: string | null;
|
||||
nodeId?: string | null;
|
||||
constructionSiteId?: string | null;
|
||||
moduleId?: string | null;
|
||||
waitSeconds: number;
|
||||
radius: number;
|
||||
maxSystemRange?: number | null;
|
||||
knownStationsOnly: boolean;
|
||||
}
|
||||
|
||||
export interface DefaultBehaviorSnapshot {
|
||||
kind: string;
|
||||
homeSystemId?: string | null;
|
||||
homeStationId?: string | null;
|
||||
areaSystemId?: string | null;
|
||||
targetEntityId?: string | null;
|
||||
preferredItemId?: string | null;
|
||||
preferredNodeId?: string | null;
|
||||
preferredConstructionSiteId?: string | null;
|
||||
preferredModuleId?: string | null;
|
||||
targetPosition?: Vector3Dto | null;
|
||||
waitSeconds: number;
|
||||
radius: number;
|
||||
maxSystemRange: number;
|
||||
knownStationsOnly: boolean;
|
||||
patrolPoints: Vector3Dto[];
|
||||
patrolIndex: number;
|
||||
repeatOrders: ShipOrderTemplateSnapshot[];
|
||||
repeatIndex: number;
|
||||
}
|
||||
|
||||
export interface ShipAssignmentSnapshot {
|
||||
commanderId: string;
|
||||
parentCommanderId?: string | null;
|
||||
kind: string;
|
||||
behaviorKind: string;
|
||||
status: string;
|
||||
objectiveId?: string | null;
|
||||
campaignId?: string | null;
|
||||
theaterId?: string | null;
|
||||
priority: number;
|
||||
homeSystemId?: string | null;
|
||||
homeStationId?: string | null;
|
||||
targetSystemId?: string | null;
|
||||
targetEntityId?: string | null;
|
||||
targetPosition?: Vector3Dto | null;
|
||||
itemId?: string | null;
|
||||
notes?: string | null;
|
||||
updatedAtUtc?: string | null;
|
||||
}
|
||||
|
||||
export interface ShipSubTaskSnapshot {
|
||||
id: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
summary: string;
|
||||
targetEntityId?: string | null;
|
||||
targetSystemId?: string | null;
|
||||
targetNodeId?: string | null;
|
||||
targetPosition?: Vector3Dto | null;
|
||||
itemId?: string | null;
|
||||
moduleId?: string | null;
|
||||
threshold: number;
|
||||
amount: number;
|
||||
progress: number;
|
||||
elapsedSeconds: number;
|
||||
totalSeconds: number;
|
||||
blockingReason?: string | null;
|
||||
}
|
||||
|
||||
export interface ShipPlanStepSnapshot {
|
||||
id: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
summary: string;
|
||||
blockingReason?: string | null;
|
||||
currentSubTaskIndex: number;
|
||||
subTasks: ShipSubTaskSnapshot[];
|
||||
}
|
||||
|
||||
export interface ShipPlanSnapshot {
|
||||
id: string;
|
||||
sourceKind: string;
|
||||
sourceId: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
summary: string;
|
||||
currentStepIndex: number;
|
||||
createdAtUtc: string;
|
||||
updatedAtUtc: string;
|
||||
interruptReason?: string | null;
|
||||
failureReason?: string | null;
|
||||
steps: ShipPlanStepSnapshot[];
|
||||
}
|
||||
|
||||
export interface ShipSnapshot {
|
||||
id: string;
|
||||
label: string;
|
||||
@@ -10,33 +145,33 @@ export interface ShipSnapshot {
|
||||
localVelocity: Vector3Dto;
|
||||
targetLocalPosition: Vector3Dto;
|
||||
state: string;
|
||||
orderKind: string | null;
|
||||
defaultBehaviorKind: string;
|
||||
behaviorPhase: string | null;
|
||||
controllerTaskKind: string;
|
||||
commanderObjective: string | null;
|
||||
orderQueue: ShipOrderSnapshot[];
|
||||
defaultBehavior: DefaultBehaviorSnapshot;
|
||||
assignment?: ShipAssignmentSnapshot | null;
|
||||
skills: ShipSkillProfileSnapshot;
|
||||
activePlan?: ShipPlanSnapshot | null;
|
||||
currentStepId?: string | null;
|
||||
activeSubTasks: ShipSubTaskSnapshot[];
|
||||
controlSourceKind: string;
|
||||
controlSourceId?: string | null;
|
||||
controlReason?: string | null;
|
||||
lastReplanReason?: string | null;
|
||||
lastAccessFailureReason?: string | null;
|
||||
celestialId?: string | null;
|
||||
dockedStationId?: string | null;
|
||||
commanderId?: string | null;
|
||||
policySetId?: string | null;
|
||||
cargoCapacity: number;
|
||||
|
||||
travelSpeed: number;
|
||||
travelSpeedUnit: string;
|
||||
inventory: InventoryEntry[];
|
||||
factionId: string;
|
||||
health: number;
|
||||
history: string[];
|
||||
currentAction?: ShipActionProgressSnapshot | null;
|
||||
spatialState: ShipSpatialStateSnapshot;
|
||||
}
|
||||
|
||||
export interface ShipDelta extends ShipSnapshot { }
|
||||
|
||||
export interface ShipActionProgressSnapshot {
|
||||
label: string;
|
||||
progress: number;
|
||||
}
|
||||
export interface ShipDelta extends ShipSnapshot {}
|
||||
|
||||
export interface ShipSpatialStateSnapshot {
|
||||
spaceLayer: string;
|
||||
|
||||
@@ -21,6 +21,8 @@ import type {
|
||||
PolicySetSnapshot,
|
||||
MarketOrderSnapshot,
|
||||
} from "./contractsEconomy";
|
||||
import type { PlayerFactionSnapshot } from "./contractsPlayerFaction";
|
||||
import type { GeopoliticalStateSnapshot } from "./contractsGeopolitics";
|
||||
import type {
|
||||
ShipDelta,
|
||||
ShipSnapshot,
|
||||
@@ -44,6 +46,8 @@ export interface WorldSnapshot {
|
||||
policies: PolicySetSnapshot[];
|
||||
ships: ShipSnapshot[];
|
||||
factions: FactionSnapshot[];
|
||||
playerFaction?: PlayerFactionSnapshot | null;
|
||||
geopolitics?: GeopoliticalStateSnapshot | null;
|
||||
}
|
||||
|
||||
export interface WorldDelta {
|
||||
@@ -63,6 +67,8 @@ export interface WorldDelta {
|
||||
policies: PolicySetDelta[];
|
||||
ships: ShipDelta[];
|
||||
factions: FactionDelta[];
|
||||
playerFaction?: PlayerFactionSnapshot | null;
|
||||
geopolitics?: GeopoliticalStateSnapshot | null;
|
||||
scope?: ObserverScope | null;
|
||||
}
|
||||
|
||||
|
||||
124
apps/viewer/src/playerFactionCommands.ts
Normal file
124
apps/viewer/src/playerFactionCommands.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import type { Vector3Dto } from "./contractsCommon";
|
||||
import type { ShipOrderTemplateSnapshot } from "./contractsShips";
|
||||
|
||||
export interface PlayerOrganizationCommandRequest {
|
||||
kind: string;
|
||||
label: string;
|
||||
parentOrganizationId?: string | null;
|
||||
frontId?: string | null;
|
||||
homeSystemId?: string | null;
|
||||
homeStationId?: string | null;
|
||||
policyId?: string | null;
|
||||
automationPolicyId?: string | null;
|
||||
reinforcementPolicyId?: string | null;
|
||||
targetFactionId?: string | null;
|
||||
priority?: number | null;
|
||||
role?: string | null;
|
||||
reserveKind?: string | null;
|
||||
systemIds?: string[] | null;
|
||||
focusItemIds?: string[] | null;
|
||||
notes?: string | null;
|
||||
}
|
||||
|
||||
export interface PlayerOrganizationMembershipCommandRequest {
|
||||
assetIds?: string[] | null;
|
||||
childOrganizationIds?: string[] | null;
|
||||
systemIds?: string[] | null;
|
||||
frontIds?: string[] | null;
|
||||
replace?: boolean;
|
||||
}
|
||||
|
||||
export interface PlayerDirectiveCommandRequest {
|
||||
label: string;
|
||||
kind: string;
|
||||
scopeKind: string;
|
||||
scopeId: string;
|
||||
behaviorKind: string;
|
||||
useOrders: boolean;
|
||||
stagingOrderKind?: string | null;
|
||||
targetEntityId?: string | null;
|
||||
targetSystemId?: string | null;
|
||||
targetPosition?: Vector3Dto | null;
|
||||
homeSystemId?: string | null;
|
||||
homeStationId?: string | null;
|
||||
sourceStationId?: string | null;
|
||||
destinationStationId?: string | null;
|
||||
itemId?: string | null;
|
||||
preferredNodeId?: string | null;
|
||||
preferredConstructionSiteId?: string | null;
|
||||
preferredModuleId?: string | null;
|
||||
priority: number;
|
||||
radius?: number | null;
|
||||
waitSeconds?: number | null;
|
||||
maxSystemRange?: number | null;
|
||||
knownStationsOnly?: boolean | null;
|
||||
patrolPoints?: Vector3Dto[] | null;
|
||||
repeatOrders?: ShipOrderTemplateSnapshot[] | null;
|
||||
policyId?: string | null;
|
||||
automationPolicyId?: string | null;
|
||||
notes?: string | null;
|
||||
}
|
||||
|
||||
export interface PlayerPolicyCommandRequest {
|
||||
label: string;
|
||||
scopeKind: string;
|
||||
scopeId?: string | null;
|
||||
policySetId?: string | null;
|
||||
allowDelegatedCombat: boolean;
|
||||
allowDelegatedTrade: boolean;
|
||||
reserveCreditsRatio: number;
|
||||
reserveMilitaryRatio: number;
|
||||
notes?: string | null;
|
||||
tradeAccessPolicy?: string | null;
|
||||
dockingAccessPolicy?: string | null;
|
||||
constructionAccessPolicy?: string | null;
|
||||
operationalRangePolicy?: string | null;
|
||||
combatEngagementPolicy?: string | null;
|
||||
avoidHostileSystems?: boolean | null;
|
||||
fleeHullRatio?: number | null;
|
||||
blacklistedSystemIds?: string[] | null;
|
||||
}
|
||||
|
||||
export interface PlayerAutomationPolicyCommandRequest {
|
||||
label: string;
|
||||
scopeKind: string;
|
||||
scopeId?: string | null;
|
||||
enabled: boolean;
|
||||
behaviorKind: string;
|
||||
useOrders: boolean;
|
||||
stagingOrderKind?: string | null;
|
||||
maxSystemRange: number;
|
||||
knownStationsOnly: boolean;
|
||||
radius: number;
|
||||
waitSeconds: number;
|
||||
preferredItemId?: string | null;
|
||||
notes?: string | null;
|
||||
repeatOrders?: ShipOrderTemplateSnapshot[] | null;
|
||||
}
|
||||
|
||||
export interface PlayerAssetAssignmentCommandRequest {
|
||||
assetKind: string;
|
||||
assetId: string;
|
||||
fleetId?: string | null;
|
||||
taskForceId?: string | null;
|
||||
stationGroupId?: string | null;
|
||||
economicRegionId?: string | null;
|
||||
frontId?: string | null;
|
||||
reserveId?: string | null;
|
||||
directiveId?: string | null;
|
||||
policyId?: string | null;
|
||||
automationPolicyId?: string | null;
|
||||
role: string;
|
||||
clearConflicts?: boolean;
|
||||
}
|
||||
|
||||
export interface PlayerStrategicIntentCommandRequest {
|
||||
strategicPosture: string;
|
||||
economicPosture: string;
|
||||
militaryPosture: string;
|
||||
logisticsPosture: string;
|
||||
desiredReserveRatio: number;
|
||||
allowDelegatedCombatAutomation: boolean;
|
||||
allowDelegatedEconomicAutomation: boolean;
|
||||
notes?: string | null;
|
||||
}
|
||||
41
apps/viewer/src/shipCommands.ts
Normal file
41
apps/viewer/src/shipCommands.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { Vector3Dto } from "./contractsCommon";
|
||||
import type { ShipOrderTemplateSnapshot } from "./contractsShips";
|
||||
|
||||
export interface ShipOrderCommandRequest {
|
||||
kind: string;
|
||||
priority: number;
|
||||
interruptCurrentPlan: boolean;
|
||||
label?: string | null;
|
||||
targetEntityId?: string | null;
|
||||
targetSystemId?: string | null;
|
||||
targetPosition?: Vector3Dto | null;
|
||||
sourceStationId?: string | null;
|
||||
destinationStationId?: string | null;
|
||||
itemId?: string | null;
|
||||
nodeId?: string | null;
|
||||
constructionSiteId?: string | null;
|
||||
moduleId?: string | null;
|
||||
waitSeconds?: number | null;
|
||||
radius?: number | null;
|
||||
maxSystemRange?: number | null;
|
||||
knownStationsOnly?: boolean | null;
|
||||
}
|
||||
|
||||
export interface ShipDefaultBehaviorCommandRequest {
|
||||
kind: string;
|
||||
homeSystemId?: string | null;
|
||||
homeStationId?: string | null;
|
||||
areaSystemId?: string | null;
|
||||
targetEntityId?: string | null;
|
||||
preferredItemId?: string | null;
|
||||
preferredNodeId?: string | null;
|
||||
preferredConstructionSiteId?: string | null;
|
||||
preferredModuleId?: string | null;
|
||||
targetPosition?: Vector3Dto | null;
|
||||
waitSeconds?: number | null;
|
||||
radius?: number | null;
|
||||
maxSystemRange?: number | null;
|
||||
knownStationsOnly?: boolean | null;
|
||||
patrolPoints?: Vector3Dto[] | null;
|
||||
repeatOrders?: ShipOrderTemplateSnapshot[] | null;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import type { ShipSnapshot } from "../../contractsShips";
|
||||
import type { StationSnapshot } from "../../contractsInfrastructure";
|
||||
import type { FactionSnapshot } from "../../contractsFactions";
|
||||
import type { MarketOrderSnapshot } from "../../contractsEconomy";
|
||||
import type { GeopoliticalStateSnapshot } from "../../contractsGeopolitics";
|
||||
|
||||
export const useGmStore = defineStore("gm", {
|
||||
state: () => ({
|
||||
@@ -10,6 +11,7 @@ export const useGmStore = defineStore("gm", {
|
||||
stations: [] as StationSnapshot[],
|
||||
factions: [] as FactionSnapshot[],
|
||||
marketOrders: [] as MarketOrderSnapshot[],
|
||||
geopolitics: null as GeopoliticalStateSnapshot | null,
|
||||
}),
|
||||
actions: {
|
||||
updateWorld(
|
||||
@@ -17,11 +19,21 @@ export const useGmStore = defineStore("gm", {
|
||||
stations: StationSnapshot[],
|
||||
factions: FactionSnapshot[],
|
||||
marketOrders: MarketOrderSnapshot[],
|
||||
geopolitics: GeopoliticalStateSnapshot | null,
|
||||
) {
|
||||
this.ships = ships;
|
||||
this.stations = stations;
|
||||
this.factions = factions;
|
||||
this.marketOrders = marketOrders;
|
||||
this.geopolitics = geopolitics;
|
||||
},
|
||||
upsertShip(ship: ShipSnapshot) {
|
||||
const index = this.ships.findIndex((candidate) => candidate.id === ship.id);
|
||||
if (index >= 0) {
|
||||
this.ships.splice(index, 1, ship);
|
||||
return;
|
||||
}
|
||||
this.ships.push(ship);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
43
apps/viewer/src/ui/stores/playerFactionStore.ts
Normal file
43
apps/viewer/src/ui/stores/playerFactionStore.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { defineStore } from "pinia";
|
||||
import type { PlayerFactionSnapshot } from "../../contractsPlayerFaction";
|
||||
|
||||
export const usePlayerFactionStore = defineStore("playerFaction", {
|
||||
state: () => ({
|
||||
playerFaction: null as PlayerFactionSnapshot | null,
|
||||
selectedOrganizationId: null as string | null,
|
||||
selectedDirectiveId: null as string | null,
|
||||
}),
|
||||
actions: {
|
||||
setPlayerFaction(snapshot: PlayerFactionSnapshot | null) {
|
||||
this.playerFaction = snapshot;
|
||||
if (snapshot == null) {
|
||||
this.selectedOrganizationId = null;
|
||||
this.selectedDirectiveId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const knownOrganizationIds = new Set<string>([
|
||||
...snapshot.fleets.map((entry) => entry.id),
|
||||
...snapshot.taskForces.map((entry) => entry.id),
|
||||
...snapshot.stationGroups.map((entry) => entry.id),
|
||||
...snapshot.economicRegions.map((entry) => entry.id),
|
||||
...snapshot.fronts.map((entry) => entry.id),
|
||||
...snapshot.reserves.map((entry) => entry.id),
|
||||
]);
|
||||
if (this.selectedOrganizationId && !knownOrganizationIds.has(this.selectedOrganizationId)) {
|
||||
this.selectedOrganizationId = null;
|
||||
}
|
||||
|
||||
const knownDirectiveIds = new Set(snapshot.directives.map((entry) => entry.id));
|
||||
if (this.selectedDirectiveId && !knownDirectiveIds.has(this.selectedDirectiveId)) {
|
||||
this.selectedDirectiveId = null;
|
||||
}
|
||||
},
|
||||
selectOrganization(organizationId: string | null) {
|
||||
this.selectedOrganizationId = organizationId;
|
||||
},
|
||||
selectDirective(directiveId: string | null) {
|
||||
this.selectedDirectiveId = directiveId;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -7,29 +7,59 @@ import type {
|
||||
OpsStationCardState,
|
||||
OpsStripState,
|
||||
} from "./viewerHudState";
|
||||
import { describeShipCurrentAction, describeShipLocation, describeShipObjective, describeShipState } from "./viewerSelection";
|
||||
import { describeShipCurrentAction, describeShipLocation, describeShipState } from "./viewerSelection";
|
||||
import type { CameraMode, Selectable, WorldState, PovLevel } from "./viewerTypes";
|
||||
|
||||
function buildFactionCard(faction: FactionSnapshot): OpsFactionCardState {
|
||||
const state = faction.strategicAssessment;
|
||||
const blackboard = faction.blackboard;
|
||||
const leadTask = [...(faction.issuedTasks ?? [])]
|
||||
function buildFactionCard(world: WorldState, faction: FactionSnapshot): OpsFactionCardState {
|
||||
const playerFaction = world.playerFaction;
|
||||
if (playerFaction && playerFaction.sovereignFactionId === faction.id) {
|
||||
const selectedDirective = playerFaction.directives[0];
|
||||
return {
|
||||
kind: "faction",
|
||||
id: faction.id,
|
||||
label: `${faction.label} Command`,
|
||||
stateLines: [
|
||||
`Player ${playerFaction.assetRegistry.shipIds.length} ships · ${playerFaction.assetRegistry.stationIds.length} stations`,
|
||||
`Groups ${playerFaction.fleets.length + playerFaction.taskForces.length + playerFaction.stationGroups.length + playerFaction.economicRegions.length + playerFaction.fronts.length + playerFaction.reserves.length}`,
|
||||
`Intent ${playerFaction.strategicIntent.strategicPosture} · ${playerFaction.strategicIntent.economicPosture}`,
|
||||
`Alerts ${playerFaction.alerts.length} · Decisions ${playerFaction.decisionLog.length}`,
|
||||
`Lead ${selectedDirective ? `${selectedDirective.behaviorKind} · ${selectedDirective.scopeKind}` : "no active directives"}`,
|
||||
],
|
||||
priorities: [
|
||||
{ label: "Reserve", value: `${Math.round(playerFaction.strategicIntent.desiredReserveRatio * 100)}%` },
|
||||
{ label: "Auto", value: `${Number(playerFaction.strategicIntent.allowDelegatedEconomicAutomation)}/${Number(playerFaction.strategicIntent.allowDelegatedCombatAutomation)}` },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const strategicState = faction.strategicState;
|
||||
const economic = strategicState.economicAssessment;
|
||||
const activeCampaigns = strategicState.campaigns.filter((campaign) => campaign.status === "active");
|
||||
const activeTheaters = strategicState.theaters.filter((theater) => theater.status === "active");
|
||||
const activeWars = world.geopolitics?.diplomacy.wars.filter((war) => war.factionAId === faction.id || war.factionBId === faction.id).length ?? 0;
|
||||
const contestedSystems = world.geopolitics?.territory.controlStates.filter((state) =>
|
||||
state.isContested && (state.controllerFactionId === faction.id || state.primaryClaimantFactionId === faction.id || state.claimantFactionIds.includes(faction.id))).length ?? 0;
|
||||
const leadCampaign = [...strategicState.campaigns]
|
||||
.sort((left, right) => right.priority - left.priority)[0];
|
||||
const leadTheater = [...strategicState.theaters]
|
||||
.sort((left, right) => right.priority - left.priority)[0];
|
||||
const latestDecision = [...faction.decisionLog]
|
||||
.sort((left, right) => right.occurredAtUtc.localeCompare(left.occurredAtUtc))[0];
|
||||
return {
|
||||
kind: "faction",
|
||||
id: faction.id,
|
||||
label: faction.label,
|
||||
stateLines: state ? [
|
||||
`Military ${state.militaryShipCount} · Miners ${state.minerShipCount}`,
|
||||
`Transport ${state.transportShipCount} · Constructors ${state.constructorShipCount}`,
|
||||
`Systems ${state.controlledSystemCount} / ${state.targetSystemCount}`,
|
||||
`Shipyard ${blackboard?.hasShipyard ? "yes" : "no"} · War industry ${blackboard?.hasWarIndustrySupplyChain ? "yes" : "no"}`,
|
||||
leadTask ? `Task ${leadTask.kind}${leadTask.shipRole ? ` · ${leadTask.shipRole}` : ""}` : `Ore ${state.oreStockpile.toFixed(0)}`,
|
||||
] : [],
|
||||
priorities: (faction.strategicPriorities ?? []).map((entry) => ({
|
||||
label: entry.goalName,
|
||||
value: entry.priority.toFixed(0),
|
||||
})),
|
||||
stateLines: [
|
||||
`Posture ${faction.doctrine.strategicPosture} · ${faction.doctrine.militaryPosture}`,
|
||||
`Campaigns ${activeCampaigns.length} · Fronts ${activeTheaters.length} · Wars ${activeWars}`,
|
||||
`Commit ${economic.militaryShipCount}/${economic.targetMilitaryShipCount} mil · ${economic.minerShipCount}/${economic.targetMinerShipCount} min`,
|
||||
`Reserve ${strategicState.budget.reservedMilitaryAssets} mil · ${strategicState.budget.reservedLogisticsAssets} log`,
|
||||
`Bottleneck ${economic.industrialBottleneckItemId ?? "none"} · Contested ${contestedSystems}${latestDecision ? ` · ${latestDecision.kind}` : ""}`,
|
||||
],
|
||||
priorities: [
|
||||
...(leadCampaign ? [{ label: leadCampaign.kind, value: leadCampaign.priority.toFixed(0) }] : []),
|
||||
...(leadTheater ? [{ label: leadTheater.kind, value: leadTheater.priority.toFixed(0) }] : []),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -69,6 +99,9 @@ function buildShipCard(
|
||||
const shipLocation = describeShipLocation(world, ship);
|
||||
const shipState = describeShipState(world, ship);
|
||||
const shipAction = describeShipCurrentAction(ship);
|
||||
const currentStep = ship.activePlan?.steps[ship.activePlan.currentStepIndex];
|
||||
const topOrder = [...ship.orderQueue]
|
||||
.sort((left, right) => right.priority - left.priority || left.createdAtUtc.localeCompare(right.createdAtUtc))[0];
|
||||
|
||||
return {
|
||||
kind: "ship",
|
||||
@@ -84,9 +117,10 @@ function buildShipCard(
|
||||
],
|
||||
action: shipAction ? buildProgressBar(shipAction.label, shipAction.progress) : undefined,
|
||||
aiLines: [
|
||||
...(ship.commanderObjective ? [`Objective ${describeShipObjective(ship.commanderObjective)}`] : []),
|
||||
`Behavior ${ship.defaultBehaviorKind}${ship.behaviorPhase ? ` · ${ship.behaviorPhase}` : ""}`,
|
||||
`Task ${ship.controllerTaskKind}`,
|
||||
`Assignment ${ship.assignment?.kind ?? "unassigned"}`,
|
||||
`Behavior ${ship.defaultBehavior.kind}`,
|
||||
`Plan ${ship.activePlan ? `${ship.activePlan.kind}${currentStep ? ` · ${currentStep.kind}` : ""}` : "none"}`,
|
||||
`Orders ${topOrder ? `${topOrder.kind} +${Math.max(0, ship.orderQueue.length - 1)}` : "none"}`,
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -111,7 +145,7 @@ export function buildOpsStripState(
|
||||
|
||||
const factions = [...world.factions.values()]
|
||||
.sort((left, right) => left.label.localeCompare(right.label))
|
||||
.map(buildFactionCard);
|
||||
.map((faction) => buildFactionCard(world, faction));
|
||||
|
||||
const stations = [...world.stations.values()]
|
||||
.filter((station) => !isSystemFiltered || station.systemId === activeSystemId)
|
||||
|
||||
@@ -212,26 +212,37 @@ function formatStorageWithInventory(
|
||||
}
|
||||
|
||||
function renderSystemOwnership(world: WorldState, systemId: string): string {
|
||||
const claims = [...world.claims.values()].filter((claim) =>
|
||||
claim.systemId === systemId && claim.state !== "destroyed");
|
||||
if (claims.length === 0) {
|
||||
return "Ownership none";
|
||||
const control = world.geopolitics?.territory.controlStates.find((state) => state.systemId === systemId);
|
||||
const zone = world.geopolitics?.territory.zones.find((entry) => entry.systemId === systemId);
|
||||
const profile = world.geopolitics?.territory.strategicProfiles.find((entry) => entry.systemId === systemId);
|
||||
const pressure = world.geopolitics?.territory.pressures.find((entry) => entry.systemId === systemId);
|
||||
const region = world.geopolitics?.economyRegions.regions.find((entry) => entry.systemIds.includes(systemId));
|
||||
const front = world.geopolitics?.territory.frontLines.find((entry) => entry.systemIds.includes(systemId));
|
||||
|
||||
if (!control) {
|
||||
const claims = [...world.claims.values()].filter((claim) =>
|
||||
claim.systemId === systemId && claim.state !== "destroyed");
|
||||
return claims.length === 0 ? "Territory unknown" : `Claims ${claims.length}`;
|
||||
}
|
||||
|
||||
const ownershipByFaction = new Map<string, number>();
|
||||
for (const claim of claims) {
|
||||
ownershipByFaction.set(claim.factionId, (ownershipByFaction.get(claim.factionId) ?? 0) + 1);
|
||||
const controllerLabel = control.controllerFactionId
|
||||
? (world.factions.get(control.controllerFactionId)?.label ?? control.controllerFactionId)
|
||||
: "none";
|
||||
const claimantLabel = control.primaryClaimantFactionId
|
||||
? (world.factions.get(control.primaryClaimantFactionId)?.label ?? control.primaryClaimantFactionId)
|
||||
: "none";
|
||||
const lines = [
|
||||
`Control ${control.controlKind} · Controller ${controllerLabel}`,
|
||||
`Claimant ${claimantLabel} · Zone ${zone?.kind ?? profile?.zoneKind ?? "unknown"}`,
|
||||
`Pressure ${Math.round((pressure?.pressureScore ?? profile?.territorialPressure ?? 0) * 100)}% · Security ${Math.round((pressure?.securityScore ?? profile?.securityRating ?? 0) * 100)}%`,
|
||||
];
|
||||
if (front) {
|
||||
lines.push(`Front ${front.id} · ${front.factionIds.join(" vs ")}`);
|
||||
}
|
||||
|
||||
return [...ownershipByFaction.entries()]
|
||||
.sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
|
||||
.map(([factionId, count]) => {
|
||||
const faction = world.factions.get(factionId);
|
||||
const label = faction?.label ?? factionId;
|
||||
const share = Math.round((count / claims.length) * 100);
|
||||
return `${label} ${count}/${claims.length} (${share}%)`;
|
||||
})
|
||||
.join("<br>");
|
||||
if (region) {
|
||||
lines.push(`Region ${region.label} · ${region.kind}`);
|
||||
}
|
||||
return lines.join("<br>");
|
||||
}
|
||||
|
||||
export function buildDetailPanelState(params: DetailPanelParams) {
|
||||
@@ -284,14 +295,32 @@ export function buildDetailPanelState(params: DetailPanelParams) {
|
||||
const shipBehavior = describeShipBehavior(ship);
|
||||
const shipOrder = describeShipOrder(ship);
|
||||
const shipAction = describeShipCurrentAction(ship);
|
||||
const currentStep = ship.activePlan?.steps[ship.activePlan.currentStepIndex];
|
||||
const orderQueue = ship.orderQueue.length > 0
|
||||
? ship.orderQueue.slice(0, 4).map((order) => `${order.kind} [${order.status}]`).join("<br>")
|
||||
: "none";
|
||||
const subTaskList = ship.activeSubTasks.length > 0
|
||||
? ship.activeSubTasks.slice(0, 4).map((subTask) => `${subTask.summary || subTask.kind} · ${subTask.status}`).join("<br>")
|
||||
: "none";
|
||||
const playerAssignment = world.playerFaction?.assignments.find((assignment) => assignment.assetKind === "ship" && assignment.assetId === ship.id);
|
||||
const playerDirective = playerAssignment?.directiveId
|
||||
? world.playerFaction?.directives.find((directive) => directive.id === playerAssignment.directiveId)
|
||||
: undefined;
|
||||
return {
|
||||
title: ship.label,
|
||||
bodyHtml: `
|
||||
<p>Parent ${parent}</p>
|
||||
<p>Behavior ${shipBehavior}</p>
|
||||
<p>Command ${ship.controlSourceKind}${ship.controlSourceId ? `<br>ID ${ship.controlSourceId}` : ""}${ship.controlReason ? `<br>${ship.controlReason}` : ""}</p>
|
||||
<p>Assignment ${ship.assignment?.kind ?? "unassigned"}${ship.assignment?.campaignId ? `<br>Campaign ${ship.assignment.campaignId}` : ""}</p>
|
||||
${playerAssignment ? `<p>Player ${playerAssignment.role}${playerDirective ? `<br>Directive ${playerDirective.label}` : ""}${playerAssignment.fleetId ? `<br>Fleet ${playerAssignment.fleetId}` : ""}${playerAssignment.taskForceId ? `<br>Task Force ${playerAssignment.taskForceId}` : ""}${playerAssignment.frontId ? `<br>Front ${playerAssignment.frontId}` : ""}</p>` : ""}
|
||||
<p>State ${shipState}</p>
|
||||
<p>Order ${shipOrder}</p>
|
||||
<p>Task ${ship.controllerTaskKind}</p>
|
||||
<p>Queue ${orderQueue}</p>
|
||||
<p>Plan ${ship.activePlan ? `${ship.activePlan.kind} · ${ship.activePlan.status}` : "none"}${currentStep ? `<br>Step ${currentStep.kind} · ${currentStep.status}` : ""}</p>
|
||||
<p>Subtasks ${subTaskList}</p>
|
||||
${ship.lastReplanReason ? `<p>Last replan ${ship.lastReplanReason}</p>` : ""}
|
||||
${ship.lastAccessFailureReason ? `<p>Access ${ship.lastAccessFailureReason}</p>` : ""}
|
||||
${shipAction ? `
|
||||
<div class="detail-progress">
|
||||
<div class="detail-progress-label">
|
||||
@@ -322,11 +351,16 @@ export function buildDetailPanelState(params: DetailPanelParams) {
|
||||
? station.dockedShipIds.map((shipId) => world.ships.get(shipId)?.label ?? shipId).join("<br>")
|
||||
: "none";
|
||||
const stationStorage = formatStorageWithInventory(station.storageUsage, station.inventory);
|
||||
const playerAssignment = world.playerFaction?.assignments.find((assignment) => assignment.assetKind === "station" && assignment.assetId === station.id);
|
||||
const playerDirective = playerAssignment?.directiveId
|
||||
? world.playerFaction?.directives.find((directive) => directive.id === playerAssignment.directiveId)
|
||||
: undefined;
|
||||
return {
|
||||
title: station.label,
|
||||
bodyHtml: `
|
||||
<p>${station.category} · ${station.systemId}</p>
|
||||
<p>Parent ${parent}</p>
|
||||
${playerAssignment ? `<p>Player ${playerAssignment.role}${playerDirective ? `<br>Directive ${playerDirective.label}` : ""}${playerAssignment.stationGroupId ? `<br>Group ${playerAssignment.stationGroupId}` : ""}${playerAssignment.economicRegionId ? `<br>Region ${playerAssignment.economicRegionId}` : ""}</p>` : ""}
|
||||
<p>Docked ${station.dockedShips} / ${station.dockingPads}
|
||||
<br>
|
||||
${dockedShipLabels}</p>
|
||||
|
||||
@@ -320,8 +320,9 @@ export function renderSystemDetails(
|
||||
|
||||
export function describeShipState(world: WorldState | undefined, ship: ShipSnapshot): string {
|
||||
const baseState = ship.state;
|
||||
const currentSubTask = ship.activeSubTasks[0];
|
||||
if (baseState === "capacitor-starved") {
|
||||
return `${baseState} while ${describeControllerTask(ship.controllerTaskKind)}`;
|
||||
return currentSubTask ? `${baseState} while ${titleTask(currentSubTask.kind)}` : baseState;
|
||||
}
|
||||
|
||||
if (!world || (baseState !== "ftl" && baseState !== "spooling-ftl" && baseState !== "warping" && baseState !== "spooling-warp")) {
|
||||
@@ -347,75 +348,52 @@ export function describeShipState(world: WorldState | undefined, ship: ShipSnaps
|
||||
return `${baseState} -> ${destinationSystem?.label ?? destinationCelestial.systemId}`;
|
||||
}
|
||||
|
||||
function describeControllerTask(taskKind: string): string {
|
||||
switch (taskKind) {
|
||||
case "travel":
|
||||
return "travel";
|
||||
case "extract":
|
||||
return "mining";
|
||||
case "dock":
|
||||
return "docking";
|
||||
case "unload":
|
||||
return "transfer";
|
||||
case "deliver-construction":
|
||||
return "material delivery";
|
||||
case "build-construction-site":
|
||||
return "site construction";
|
||||
case "construct-module":
|
||||
return "module construction";
|
||||
case "undock":
|
||||
return "undocking";
|
||||
case "load-workers":
|
||||
return "worker loading";
|
||||
case "unload-workers":
|
||||
return "worker unloading";
|
||||
default:
|
||||
return taskKind;
|
||||
}
|
||||
}
|
||||
|
||||
export function describeShipObjective(objective: string): string {
|
||||
switch (objective) {
|
||||
case "set-mining-objective": return "mine resources";
|
||||
case "set-patrol-objective": return "patrol";
|
||||
case "set-construction-objective": return "build station";
|
||||
case "set-idle-objective": return "idle";
|
||||
default: return objective;
|
||||
}
|
||||
return objective.replace(/[-_]+/g, " ");
|
||||
}
|
||||
|
||||
export function describeShipBehavior(ship: ShipSnapshot): string {
|
||||
return ship.behaviorPhase
|
||||
? `${ship.defaultBehaviorKind} · ${ship.behaviorPhase}`
|
||||
: ship.defaultBehaviorKind;
|
||||
const parts = [ship.defaultBehavior.kind];
|
||||
if (ship.assignment?.kind) {
|
||||
parts.push(ship.assignment.kind);
|
||||
}
|
||||
return parts.join(" · ");
|
||||
}
|
||||
|
||||
export function describeShipOrder(ship: ShipSnapshot): string {
|
||||
const orderParts: string[] = [];
|
||||
if (ship.orderKind) {
|
||||
orderParts.push(ship.orderKind);
|
||||
}
|
||||
if (ship.commanderObjective) {
|
||||
orderParts.push(describeShipObjective(ship.commanderObjective));
|
||||
}
|
||||
if (orderParts.length > 0) {
|
||||
return orderParts.join(" · ");
|
||||
const activeOrder = [...ship.orderQueue]
|
||||
.sort((left, right) => right.priority - left.priority || left.createdAtUtc.localeCompare(right.createdAtUtc))[0];
|
||||
if (activeOrder) {
|
||||
return activeOrder.label ?? activeOrder.kind;
|
||||
}
|
||||
|
||||
return describeControllerTask(ship.controllerTaskKind);
|
||||
if (ship.assignment?.kind) {
|
||||
return describeShipObjective(ship.assignment.kind);
|
||||
}
|
||||
|
||||
if (ship.activePlan) {
|
||||
return ship.activePlan.summary || ship.activePlan.kind;
|
||||
}
|
||||
|
||||
return ship.defaultBehavior.kind;
|
||||
}
|
||||
|
||||
export function describeShipCurrentAction(ship: ShipSnapshot): { label: string; progress: number } | undefined {
|
||||
if (!ship.currentAction) {
|
||||
const subTask = ship.activeSubTasks[0];
|
||||
if (!subTask) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
label: ship.currentAction.label,
|
||||
progress: Math.max(0, Math.min(ship.currentAction.progress, 1)),
|
||||
label: subTask.summary || titleTask(subTask.kind),
|
||||
progress: Math.max(0, Math.min(subTask.progress, 1)),
|
||||
};
|
||||
}
|
||||
|
||||
function titleTask(value: string): string {
|
||||
return value.replace(/[-_]+/g, " ");
|
||||
}
|
||||
|
||||
export function describeShipLocation(world: WorldState | undefined, ship: ShipSnapshot): { system: string; local?: string } {
|
||||
const systemId = ship.spatialState.currentSystemId || ship.systemId;
|
||||
const system = world?.systems.get(systemId);
|
||||
|
||||
@@ -49,6 +49,8 @@ export function createWorldState(snapshot: WorldSnapshot): WorldState {
|
||||
policies: new Map(snapshot.policies.map((policy) => [policy.id, policy])),
|
||||
ships: new Map(snapshot.ships.map((ship) => [ship.id, ship])),
|
||||
factions: new Map(snapshot.factions.map((faction) => [faction.id, faction])),
|
||||
playerFaction: snapshot.playerFaction ?? null,
|
||||
geopolitics: snapshot.geopolitics ?? null,
|
||||
recentEvents: [],
|
||||
};
|
||||
}
|
||||
@@ -88,8 +90,14 @@ export function applyDeltaToWorld(world: WorldState, delta: WorldDelta): boolean
|
||||
for (const faction of delta.factions) {
|
||||
world.factions.set(faction.id, faction);
|
||||
}
|
||||
if (delta.playerFaction !== undefined) {
|
||||
world.playerFaction = delta.playerFaction ?? null;
|
||||
}
|
||||
if (delta.geopolitics !== undefined) {
|
||||
world.geopolitics = delta.geopolitics ?? null;
|
||||
}
|
||||
|
||||
return delta.factions.length > 0;
|
||||
return delta.factions.length > 0 || delta.playerFaction !== undefined || delta.geopolitics !== undefined;
|
||||
}
|
||||
|
||||
export function recordDeltaStats(networkStats: NetworkStats, delta: WorldDelta, rawBytes: number): void {
|
||||
@@ -102,10 +110,11 @@ export function recordDeltaStats(networkStats: NetworkStats, delta: WorldDelta,
|
||||
+ delta.marketOrders.length
|
||||
+ delta.policies.length
|
||||
+ delta.factions.length;
|
||||
const changedEntitiesWithPlayer = changedEntities + (delta.playerFaction ? 1 : 0) + (delta.geopolitics ? 1 : 0);
|
||||
networkStats.deltasReceived += 1;
|
||||
networkStats.deltaBytes += rawBytes;
|
||||
networkStats.lastDeltaBytes = rawBytes;
|
||||
networkStats.lastEntityChanges = changedEntities;
|
||||
networkStats.lastEntityChanges = changedEntitiesWithPlayer;
|
||||
networkStats.eventsReceived += delta.events.length;
|
||||
networkStats.lastDeltaAtMs = performance.now();
|
||||
networkStats.throughputSamples.push({ atMs: performance.now(), bytes: rawBytes });
|
||||
|
||||
@@ -14,6 +14,8 @@ import type {
|
||||
StationSnapshot,
|
||||
SystemSnapshot,
|
||||
OrbitalSimulationSnapshot,
|
||||
PlayerFactionSnapshot,
|
||||
GeopoliticalStateSnapshot,
|
||||
} from "./contracts";
|
||||
|
||||
export type PovLevel = "local" | "system" | "galaxy";
|
||||
@@ -152,6 +154,8 @@ export interface WorldState {
|
||||
policies: Map<string, PolicySetSnapshot>;
|
||||
ships: Map<string, ShipSnapshot>;
|
||||
factions: Map<string, FactionSnapshot>;
|
||||
playerFaction: PlayerFactionSnapshot | null;
|
||||
geopolitics: GeopoliticalStateSnapshot | null;
|
||||
recentEvents: SimulationEventRecord[];
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { fetchWorldSnapshot, openWorldStream } from "./api";
|
||||
import type { ViewerHudState } from "./viewerHudState";
|
||||
import { buildOpsStripState } from "./viewerOpsStrip";
|
||||
import { useGmStore } from "./ui/stores/gmStore";
|
||||
import { usePlayerFactionStore } from "./ui/stores/playerFactionStore";
|
||||
import { viewerPinia } from "./ui/stores/pinia";
|
||||
import { buildDetailPanelState } from "./viewerPanels";
|
||||
import { applyDeltaToWorld, cloneFactions, createWorldState, recordDeltaStats } from "./viewerState";
|
||||
@@ -205,7 +206,9 @@ export class ViewerWorldLifecycle {
|
||||
[...world.stations.values()],
|
||||
[...world.factions.values()],
|
||||
[...world.marketOrders.values()],
|
||||
world.geopolitics,
|
||||
);
|
||||
usePlayerFactionStore(viewerPinia).setPlayerFaction(world.playerFaction);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user