Atlas: Logistik-Gerüst + Heli-Fix + Waypoints-Regen + Level-Picker

- Neues Modul 'Logistik Europa': module_info-Eintrag, Landing-Card,
  PHP-Wrapper (logistik.php, modul-logistik.php), Engine-Skelett mit
  Enums + Helpern (travelCost, latePenalty, Bonus, Dijkstra,
  Polyline-Interpolation), Test-Harness, Kompetenzen-Draft,
  5 Seed-Dateien (locations, vehicle-types, cargo-types, railnet,
  contract-templates), 3 Level-Einträge in game_levels, Phase 0 durch
  die Logistik-Instanz geliefert und Atlas-Review bestanden
- Heli-Fix: heli-game.php mit BASE_PATH (production-sicher) +
  Asset-Pfad-Injection, geo_waypoints-Tabelle per Seed-Script
  auffindbar gemacht
- Waypoints-Regen-Tool: regen-waypoints-sql.php synchronisiert
  waypoints.sql aus DB (70 Einträge)
- Design-System: .ggs-level-grid / .ggs-level-card als Standard
  gepromotet (Klimas Muster, iPad-hover-safe)
- Inbox-Nachrichten: Kickoff-Briefings für Heli und Logistik,
  Asset-Map für Heli, DALL-E-Pipeline-Anleitung, Koordinations-
  nachrichten an Fluss, Klima, Glossar, Lehrplan

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-19 20:43:48 +02:00
parent 3931a701e1
commit 3885c83294
54 changed files with 5717 additions and 71 deletions
+141
View File
@@ -0,0 +1,141 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Logistik Europa — Headless-Runner</title>
<style>
body { font-family: 'Segoe UI', system-ui, sans-serif; max-width: 1100px; margin: 24px auto; padding: 0 16px; color: #222; background: #f7f5ef; }
h1 { font-size: 1.4rem; margin-bottom: 8px; }
h2 { font-size: 1.05rem; color: #1f4b37; margin-top: 24px; }
.meta { color: #666; font-size: 0.9rem; margin-bottom: 20px; }
.panel { background: #fff; border: 1px solid #e2ddd1; border-radius: 8px; padding: 14px 18px; margin: 14px 0; }
table { border-collapse: collapse; width: 100%; font-size: 0.88rem; }
th, td { text-align: left; padding: 6px 10px; border-bottom: 1px solid #eee; }
th { background: #f0ece3; font-weight: 600; }
td.num { text-align: right; font-variant-numeric: tabular-nums; }
.ok { color: #2d6d3d; font-weight: 600; }
.fail { color: #b32d2d; font-weight: 600; }
.skip { color: #8a7a4f; font-style: italic; }
.err { color: #b32d2d; font-style: italic; }
button { background: #1f4b37; color: #fff; border: 0; border-radius: 4px; padding: 8px 14px; cursor: pointer; font-size: 0.95rem; margin-right: 8px; }
button:hover { background: #2d6d4d; }
pre { background: #2b2b2b; color: #e6e6e6; padding: 12px; border-radius: 6px; font-size: 0.85rem; overflow-x: auto; }
.legend { font-size: 0.82rem; color: #666; margin-top: 6px; }
details > summary { cursor: pointer; color: #1f4b37; font-weight: 600; }
</style>
</head>
<body>
<h1>Logistik Europa — Headless-Runner</h1>
<p class="meta">
Deterministische Level-Durchläufe für die Balance-Verifikation aus
<code>balance-matrix.md</code> §3. Logik liegt komplett in
<code>headless-runner.js</code> — diese Seite ist nur die UI-Hülle.
</p>
<div class="panel">
<p>
<button id="run-noop">Nur <code>noop</code> (3 Level × 5 Seeds, läuft)</button>
<button id="run-all">Alle Strategien (Stubs werfen)</button>
</p>
<div id="results"><p class="legend">Klick auf einen Button.</p></div>
</div>
<h2>Akzeptanzkorridor (aus balance-matrix.md §3)</h2>
<table>
<thead><tr><th>Level</th><th>noop</th><th>naive</th><th>greedy</th><th>optimal</th></tr></thead>
<tbody>
<tr><td>1</td><td>fail</td><td>success ≥+1.000 €</td><td>success ≥+2.000 €</td><td>success ≥+3.000 €</td></tr>
<tr><td>2</td><td>fail</td><td>fail / negativ</td><td>success ≥0 €</td><td>success ≥+5.000 €</td></tr>
<tr><td>3</td><td>fail</td><td>fail (sicher)</td><td>fail / knapp</td><td>success ≥0 €</td></tr>
</tbody>
</table>
<h2>Strategie-Definitionen</h2>
<ul id="strat-list"><!-- gefüllt aus LogistikRunner.STRATEGIES --></ul>
<h2>API</h2>
<pre>const r = await LogistikRunner.runLevel(1, 1, 'noop');
// { successful, endBalance, completedContracts, lateDeliveries,
// durationHours, hintUsages, log, strategy, level, seed }
const matrix = await LogistikRunner.runMatrix({
levels: [1,2,3], strategies: ['noop','naive','greedy','optimal'],
seeds: [1,2,3,4,5], tolerateErrors: true
});</pre>
<script src="engine.js"></script>
<script src="headless-runner.js"></script>
<script>
(function () {
'use strict';
// Strategie-Liste rendern
const stratList = document.getElementById('strat-list');
Object.entries(LogistikRunner.STRATEGIES).forEach(([name, def]) => {
const li = document.createElement('li');
li.innerHTML = '<strong>' + name + '</strong> — ' + def.description;
stratList.appendChild(li);
});
function renderTable(rows) {
let html = '<table><thead><tr>'
+ '<th>Lvl</th><th>Strategie</th><th>Seed</th>'
+ '<th>Status</th><th class="num">Balance</th>'
+ '<th class="num">Aufträge</th><th class="num">Verspätet</th>'
+ '<th class="num">Dauer (h)</th><th>Notiz</th></tr></thead><tbody>';
rows.forEach(r => {
let cls, label, note = '';
if (r._error) {
cls = 'err'; label = 'ERROR'; note = r._error;
} else if (r.successful === null) {
cls = 'skip'; label = 'SKIP';
} else if (r.successful) {
cls = 'ok'; label = 'success';
} else {
cls = 'fail'; label = 'fail';
}
html += '<tr>'
+ '<td>' + r.level + '</td>'
+ '<td><code>' + r.strategy + '</code></td>'
+ '<td>' + r.seed + '</td>'
+ '<td class="' + cls + '">' + label + '</td>'
+ '<td class="num">' + (r.endBalance ?? '—') + '</td>'
+ '<td class="num">' + (r.completedContracts ?? 0) + '</td>'
+ '<td class="num">' + (r.lateDeliveries ?? 0) + '</td>'
+ '<td class="num">' + (r.durationHours ?? 0) + '</td>'
+ '<td>' + note + '</td>'
+ '</tr>';
});
html += '</tbody></table>';
return html;
}
async function runNoop() {
const root = document.getElementById('results');
root.innerHTML = '<p class="legend">…läuft…</p>';
const rows = await LogistikRunner.runMatrix({
strategies: ['noop'], tolerateErrors: true,
});
root.innerHTML = renderTable(rows)
+ '<p class="legend">' + rows.length + ' Durchläufe — '
+ '<code>noop</code> ist die Phase-0-Demo-Strategie. '
+ 'Alle Level → success=false ist erwartet.</p>';
}
async function runAll() {
const root = document.getElementById('results');
root.innerHTML = '<p class="legend">…läuft…</p>';
const rows = await LogistikRunner.runMatrix({ tolerateErrors: true });
root.innerHTML = renderTable(rows)
+ '<p class="legend">' + rows.length + ' Durchläufe — '
+ '<code>naive/greedy/optimal</code> werfen kontrolliert „Phase 2", '
+ '<code>noop</code> läuft sauber durch.</p>';
}
document.getElementById('run-noop').addEventListener('click', runNoop);
document.getElementById('run-all').addEventListener('click', runAll);
})();
</script>
</body>
</html>