3885c83294
- 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>
142 lines
5.8 KiB
HTML
142 lines
5.8 KiB
HTML
<!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>
|