FastAPI server, replaceable JSON config, tests, Dockerfile. Pairs with ATO via spec/interface.md contract. Co-authored-by: Cursor <cursoragent@cursor.com>
66 lines
2.5 KiB
HTML
66 lines
2.5 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>TrainSim map</title>
|
|
<style>
|
|
body { font-family: sans-serif; background: #0a0e14; color: #cce; margin: 1rem; }
|
|
svg { background: #111820; border: 1px solid #007acc; border-radius: 8px; }
|
|
.train { fill: #0098ff; }
|
|
.edge { stroke: #445; stroke-width: 4; fill: none; }
|
|
.node { fill: #007acc; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>TrainSim — loop track</h1>
|
|
<p id="state">loading…</p>
|
|
<svg id="map" width="420" height="420"></svg>
|
|
<script>
|
|
const svg = document.getElementById('map');
|
|
const stateEl = document.getElementById('state');
|
|
|
|
async function refresh() {
|
|
const [layout, state] = await Promise.all([
|
|
fetch('/track/layout').then(r => r.json()),
|
|
fetch('/train/state').then(r => r.json()),
|
|
]);
|
|
draw(layout, state);
|
|
stateEl.textContent = `speed ${state.speed_mps} m/s · position ${state.position_m} m · running ${state.sim_running}`;
|
|
}
|
|
|
|
function draw(layout, state) {
|
|
svg.innerHTML = '';
|
|
const nodes = Object.fromEntries(layout.nodes.map(n => [n.id, n]));
|
|
layout.edges.forEach(e => {
|
|
const a = nodes[e.from], b = nodes[e.to];
|
|
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
line.setAttribute('x1', a.x); line.setAttribute('y1', a.y);
|
|
line.setAttribute('x2', b.x); line.setAttribute('y2', b.y);
|
|
line.setAttribute('class', 'edge');
|
|
svg.appendChild(line);
|
|
});
|
|
layout.nodes.forEach(n => {
|
|
const c = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
c.setAttribute('cx', n.x); c.setAttribute('cy', n.y); c.setAttribute('r', 8);
|
|
c.setAttribute('class', 'node');
|
|
svg.appendChild(c);
|
|
});
|
|
const t = state.position_m / state.track_length_m;
|
|
const ids = layout.edges.map(e => e.from);
|
|
const idx = Math.floor(t * ids.length) % ids.length;
|
|
const from = nodes[ids[idx]];
|
|
const to = nodes[layout.edges[idx].to];
|
|
const f = t * ids.length - idx;
|
|
const x = from.x + (to.x - from.x) * f;
|
|
const y = from.y + (to.y - from.y) * f;
|
|
const train = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
train.setAttribute('cx', x); train.setAttribute('cy', y); train.setAttribute('r', 12);
|
|
train.setAttribute('class', 'train');
|
|
svg.appendChild(train);
|
|
}
|
|
|
|
setInterval(refresh, 200);
|
|
refresh();
|
|
</script>
|
|
</body>
|
|
</html>
|