Add Python TrainSim with loop track map and physics.
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:
parent
45c95836ef
commit
9c5ef3a5cd
18 changed files with 414 additions and 18 deletions
66
static/map.html
Normal file
66
static/map.html
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue