74 lines
8.6 KiB
TypeScript
74 lines
8.6 KiB
TypeScript
import type { ActivityEvent, Archetype, Group, MissionState, Operator, Task, Vehicle } from '../../domain/model';
|
|
import { seededRandom } from './seededRandom';
|
|
|
|
const groupSpecs = [
|
|
['group:alpha', 'ALPHA', 12, 'task:alpha', 'operator:alex', 82, 'nominal', 'Area Search', 'Return', 'wedge'],
|
|
['group:bravo', 'BRAVO', 10, 'task:bravo', 'operator:sara', 71, 'nominal', 'Route Recon', 'Survey pass', 'column'],
|
|
['group:charlie', 'CHARLIE', 8, 'task:charlie', 'operator:david', 64, 'nominal', 'Communications Relay', 'Hold station', 'orbit'],
|
|
['group:delta', 'DELTA', 12, 'task:delta', 'operator:maria', 35, 'attention', 'Reserve / Support', 'Awaiting allocation', 'staging'],
|
|
['group:reserve', 'RESERVE', 8, undefined, 'operator:ken', 100, 'nominal', 'Available', 'Available', 'staging'],
|
|
] as const;
|
|
const archetypes: Archetype[] = ['quadcopter', 'fixed-wing', 'tiltrotor-vtol', 'helicopter', 'rover', 'surface-vessel'];
|
|
|
|
export function createScenario(seed = 17): MissionState {
|
|
const random = seededRandom(seed);
|
|
const tasks: Record<string, Task> = {
|
|
'task:alpha': { id: 'task:alpha', name: 'TASK-143 · Alpha Search', kind: 'Area Search', progress: 82, required: 12, assignedGroupId: 'group:alpha', state: 'active', etaMinutes: 18 },
|
|
'task:bravo': { id: 'task:bravo', name: 'TASK-144 · Coastal Route', kind: 'Route Recon', progress: 71, required: 10, assignedGroupId: 'group:bravo', state: 'active', etaMinutes: 24 },
|
|
'task:charlie': { id: 'task:charlie', name: 'TASK-145 · Relay Mesh', kind: 'Communications Relay', progress: 64, required: 8, assignedGroupId: 'group:charlie', state: 'active', etaMinutes: 31 },
|
|
'task:delta': { id: 'task:delta', name: 'TASK-146 · Support Readiness', kind: 'Reserve / Support', progress: 35, required: 8, assignedGroupId: 'group:delta', state: 'active', etaMinutes: 45 },
|
|
};
|
|
const groups: Record<string, Group> = Object.fromEntries(groupSpecs.map(([id, name, count, taskId, operatorId, progress, health, currentIntent, nextIntent, formation]) => [id, {
|
|
id, name, count, taskId, operatorId, progress, health, currentIntent, nextIntent, formation, attentionCount: name === 'DELTA' ? 1 : 0,
|
|
}]));
|
|
const operatorsArray: Operator[] = [
|
|
{ id: 'operator:alex', name: 'Alex', role: 'Mission operator · ALPHA', presence: 'online', color: '#45c7ef', cursor: { x: 36, y: 64, lat: 41.08, lon: 29.06 }, selectedEntityId: 'group:alpha' },
|
|
{ id: 'operator:sara', name: 'Sara', role: 'Group operator · BRAVO', presence: 'online', color: '#70d56c', cursor: { x: 47, y: 25, lat: 41.13, lon: 29.08 }, selectedEntityId: 'group:bravo' },
|
|
{ id: 'operator:david', name: 'David', role: 'Mission planner', presence: 'online', color: '#ed9a55', cursor: { x: 72, y: 18, lat: 41.15, lon: 29.13 } },
|
|
{ id: 'operator:maria', name: 'Maria', role: 'Safety observer', presence: 'online', color: '#a77ae4', cursor: { x: 46, y: 76, lat: 41.04, lon: 29.08 } },
|
|
{ id: 'operator:ken', name: 'Ken', role: 'Engineering observer', presence: 'online', color: '#ead365', cursor: { x: 58, y: 80, lat: 41.03, lon: 29.10 } },
|
|
];
|
|
const operators = Object.fromEntries(operatorsArray.map(operator => [operator.id, operator]));
|
|
const vehicles: Record<string, Vehicle> = {};
|
|
let ordinal = 1;
|
|
for (const [groupId, groupName, count, taskId] of groupSpecs) {
|
|
for (let index = 0; index < count; index += 1) {
|
|
const id = `vehicle:${String(ordinal).padStart(3, '0')}`;
|
|
const alpha = groupName === 'ALPHA';
|
|
const archetype = groupName === 'CHARLIE' ? (index % 2 ? 'tiltrotor-vtol' : 'quadcopter') : archetypes[(ordinal - 1) % archetypes.length];
|
|
const centerX = 25 + groupSpecs.findIndex(spec => spec[0] === groupId) * 12;
|
|
const centerY = 27 + groupSpecs.findIndex(spec => spec[0] === groupId) * 11;
|
|
vehicles[id] = {
|
|
id, name: alpha ? `UAV-${String(index + 1).padStart(2, '0')}` : `${groupName.slice(0, 3)}-${String(index + 1).padStart(2, '0')}`,
|
|
groupId, archetype, capabilities: archetype === 'rover' || archetype === 'surface-vessel' ? ['support', 'mapping'] : ['ISR', archetype === 'tiltrotor-vtol' ? 'relay' : 'imaging'],
|
|
payload: archetype === 'surface-vessel' ? 'Sonar (simulated)' : 'EO/IR (simulated)',
|
|
position: { x: centerX + (index % 4) * 3 + random() * 2, y: centerY + Math.floor(index / 4) * 4 + random() * 2, lat: 41.02 + random() * 0.14, lon: 28.98 + random() * 0.19 },
|
|
heading: Math.round(random() * 359), speed: Math.round(8 + random() * 22), altitude: archetype === 'rover' || archetype === 'surface-vessel' ? 0 : Math.round(90 + random() * 230),
|
|
energy: Math.round(61 + random() * 34), link: Math.round(68 + random() * 31), health: groupName === 'DELTA' && index < 2 ? 'attention' : 'nominal', freshnessMs: 0, available: groupName === 'RESERVE', binding: 'simulated-bound', taskId,
|
|
};
|
|
ordinal += 1;
|
|
}
|
|
}
|
|
const events: ActivityEvent[] = [
|
|
{ id: 'activity:1', timestamp: Date.UTC(2026, 7, 17, 11, 30, 2), actor: 'System', category: 'source', severity: 'info', message: 'Deterministic scenario ALPHA-17 connected', entityId: 'source:fake', acknowledged: true, target: { kind: 'source', entityId: 'source:fake', zoom: 1, inspector: 'event' } },
|
|
{ id: 'activity:2', timestamp: Date.UTC(2026, 7, 17, 11, 31, 8), actor: 'Sara', category: 'operator', severity: 'info', message: 'Updated BRAVO coastal route', entityId: 'group:bravo', acknowledged: true, target: { kind: 'group', entityId: 'group:bravo', zoom: 2, inspector: 'summary' } },
|
|
{ id: 'activity:3', timestamp: Date.UTC(2026, 7, 17, 11, 32, 11), actor: 'System', category: 'alert', severity: 'attention', message: 'BRAVO approaching constraint R-210B', entityId: 'group:bravo', acknowledged: false, target: { kind: 'constraint', entityId: 'constraint:r210b', position: { x: 73, y: 47, lat: 41.08, lon: 29.15 }, zoom: 2, inspector: 'event' } },
|
|
];
|
|
return {
|
|
mission: { id: 'mission:alpha17', name: 'Coastal Survey', code: 'ALPHA-17', revision: 184, elapsedSeconds: 6137 }, tasks, groups, vehicles, operators,
|
|
leases: Object.fromEntries(groupSpecs.map(([id, , , , operatorId]) => [`lease:${id}`, { id: `lease:${id}`, entityId: id, holderId: operatorId, state: 'held', expiresAt: Date.UTC(2026, 7, 17, 11, 35, 0), observers: operatorsArray.filter(operator => operator.id !== operatorId).slice(0, 2).map(operator => operator.id) }])),
|
|
queue: [
|
|
{ id: 'intent:1', title: 'Transit', detail: 'to Northern Sector', ownerId: 'operator:alex', groupId: 'group:alpha', taskId: 'task:alpha', durationMinutes: 8, state: 'completed', dependencies: [], resources: ['group:alpha'], validation: 'valid' },
|
|
{ id: 'intent:2', title: 'Search Area A', detail: 'coverage pattern', ownerId: 'operator:alex', groupId: 'group:alpha', taskId: 'task:alpha', durationMinutes: 18, state: 'executing', dependencies: ['intent:1'], resources: ['group:alpha'], validation: 'valid' },
|
|
{ id: 'intent:3', title: 'Loiter', detail: '6 minute observation', ownerId: 'operator:alex', groupId: 'group:alpha', durationMinutes: 6, state: 'queued', dependencies: ['intent:2'], resources: ['group:alpha'], validation: 'valid' },
|
|
{ id: 'intent:4', title: 'Return', detail: 'staging point', ownerId: 'operator:alex', groupId: 'group:alpha', durationMinutes: 12, state: 'queued', dependencies: ['intent:3'], resources: ['group:alpha'], validation: 'valid' },
|
|
{ id: 'intent:5', title: 'Recharge', detail: 'base service', ownerId: 'operator:alex', groupId: 'group:alpha', durationMinutes: 35, state: 'queued', dependencies: ['intent:4'], resources: ['group:alpha'], validation: 'valid' },
|
|
],
|
|
alerts: [
|
|
{ id: 'alert:bravo', severity: 'attention', title: 'BRAVO approaching constraint', detail: 'R-210B coastal limit · ETA 04:12', entityId: 'group:bravo', acknowledged: false, resolved: false, target: { kind: 'constraint', entityId: 'constraint:r210b', position: { x: 73, y: 47, lat: 41.08, lon: 29.15 }, zoom: 2, inspector: 'event' } },
|
|
{ id: 'alert:alpha-eta', severity: 'attention', title: 'ALPHA task behind schedule', detail: 'ETA impact +00:08:45', entityId: 'task:alpha', acknowledged: false, resolved: false, target: { kind: 'task', entityId: 'task:alpha', position: { x: 39, y: 38, lat: 41.09, lon: 29.05 }, zoom: 2, inspector: 'task' } },
|
|
{ id: 'alert:delta-readiness', severity: 'attention', title: 'DELTA readiness below plan', detail: '2 support vehicles require review', entityId: 'group:delta', acknowledged: false, resolved: false, target: { kind: 'group', entityId: 'group:delta', zoom: 2, inspector: 'summary' } },
|
|
], events, source: { state: 'connecting', message: 'Connecting deterministic simulation', invalidEvents: 0, duplicates: 0, outOfOrder: 0 }, simulated: true, simulation: { paused: false, speed: 1, tick: 0 },
|
|
};
|
|
}
|