Add Python TrainSim with loop track map and physics.
Some checks are pending
CodeView TrainSim CI / test (push) Waiting to run
CodeView TrainSim CI / docker (push) Blocked by required conditions

FastAPI server, replaceable JSON config, tests, Dockerfile.
Pairs with ATO via spec/interface.md contract.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Mona Lisa 2026-06-14 20:37:22 +00:00
parent 45c95836ef
commit 9c5ef3a5cd
18 changed files with 414 additions and 18 deletions

66
static/map.html Normal file
View file

@ -0,0 +1,66 @@
<!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>