Files
Adminator 79ac9dad84 Atlas: Deploy-Buendel vor Staustufen-Sprint
- Logistik: Musik-Player Playlist-Modus (5 Gruppen, 20 Tracks)
- Heli: End-Screen auf .ggs-endscreen, Mission-Bilder, Voice-Lines, Briefing-Audio
- Logistik: Layout-Tausch Auftraege+Fahrzeuge links, Karte rechts
- Logistik: Tier-1-Staedte ausgebaut, Auto-Timescale-Badge
- Logistik: Roadnet/Railnet Dijkstra-Routing, Balance-Updates
- Atlas: 5 Atlas-Inbox-Nachrichten (Cards-Pattern, DALL-E-Key, Musik-Playlists)
- Status-Updates Heli + Logistik

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 01:05:13 +02:00

247 lines
11 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>
<button id="run-corridor" style="background:#b45900;">Balance-Korridor-Check (R-1/R-2/R-3)</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);
/* ---- Balance-Korridor-Check (siehe balance-corridor.md) ----
R-1 (Runway): mathematisch, ohne Run noetig.
R-2 (Winnable): pro Level mindestens eine Strategie schafft success.
R-3 (Loseable): noop schlaegt 100% fehl; (per Level) auch naive (L2) und greedy (L3).
*/
function r1Check(cfg) {
const vc = cfg.vehicleCount || 1;
const rental = cfg.rentalCostPerDay || 0;
const idle = cfg.idleCostPerHour || 0;
const daily = rental * vc + idle * vc * 12;
const needed = 1.5 * daily;
const have = cfg.startBudget || 0;
return { pass: have >= needed, have, needed, daily };
}
async function runBalanceCorridor() {
const root = document.getElementById('results');
root.innerHTML = '<p class="legend">…laufe Korridor-Check (3 Level × 3 Strategien × 5 Seeds = 45 Durchläufe)…</p>';
// R-1: aus level-config (deterministisch, kein Run noetig)
const levels = (window.LOGISTIK_LEVELS || []).reduce((m, l) => { m[l.level] = l.params; return m; }, {});
const r1 = {};
[1,2,3].forEach(lvl => { if (levels[lvl]) r1[lvl] = r1Check(levels[lvl]); });
// R-2 / R-3: per runMatrix (mit optimal fuer R-2-Hard-Check)
const rows = await LogistikRunner.runMatrix({
levels: [1,2,3], strategies: ['noop','naive','greedy','optimal'], seeds: [1,2,3,4,5], tolerateErrors: true,
});
// Aggregate per (level, strategy): wieviel von 5 Seeds erfolgreich
const agg = {};
rows.forEach(r => {
const key = r.level + ':' + r.strategy;
if (!agg[key]) agg[key] = { total: 0, success: 0, errors: 0, sumBalance: 0 };
agg[key].total++;
if (r._error) agg[key].errors++;
else if (r.successful) { agg[key].success++; agg[key].sumBalance += r.endBalance || 0; }
else agg[key].sumBalance += r.endBalance || 0;
});
// Korridor-Verdikte
const verdicts = [];
[1,2,3].forEach(lvl => {
// R-1
const r1v = r1[lvl];
verdicts.push({
rule: 'R-1', level: lvl, pass: r1v && r1v.pass,
detail: r1v ? ('startBudget=' + r1v.have + '€ vs benoetigt ' + Math.ceil(r1v.needed) + '€ (Tagesfix ' + r1v.daily + '€)') : 'config fehlt',
});
// R-2 (Winnable): mind. eine Strategie schafft Median-success.
const optimal = agg[lvl + ':optimal'] || { success: 0, total: 5 };
const greedy = agg[lvl + ':greedy' ] || { success: 0, total: 5 };
const naive = agg[lvl + ':naive' ] || { success: 0, total: 5 };
const winnable = optimal.success >= 3 || greedy.success >= 3 || (lvl <= 1 && naive.success >= 3);
verdicts.push({
rule: 'R-2', level: lvl, pass: winnable,
detail: 'naive ' + naive.success + '/' + naive.total
+ ' · greedy ' + greedy.success + '/' + greedy.total
+ ' · optimal ' + optimal.success + '/' + optimal.total,
});
// R-3 (Loseable): noop schlaegt zuverlaessig fehl, zusaetzlich level-spezifisch.
const noop = agg[lvl + ':noop'] || { success: 0, total: 5 };
let loseable = noop.success === 0;
let loseDetail = 'noop ' + noop.success + '/' + noop.total;
if (lvl >= 2) {
// L2: naive soll median-fail
loseable = loseable && naive.success <= 2;
loseDetail += ' · naive ' + naive.success + '/' + naive.total + ' (darf fast-fail)';
}
if (lvl === 3) {
// L3: greedy soll auch nicht zuverlaessig gewinnen
loseable = loseable && greedy.success <= 2;
loseDetail += ' · greedy ' + greedy.success + '/' + greedy.total + ' (darf median-fail)';
}
verdicts.push({
rule: 'R-3', level: lvl, pass: loseable,
detail: loseDetail,
});
});
// Render
let html = '<h3 style="margin:14px 0 6px;">Korridor-Verdikte</h3>'
+ '<table><thead><tr><th>Regel</th><th>Level</th><th>Status</th><th>Detail</th></tr></thead><tbody>';
verdicts.forEach(v => {
const cls = v.pass ? 'ok' : 'fail';
const lbl = v.pass ? 'PASS' : 'FAIL';
html += '<tr><td><code>' + v.rule + '</code></td><td>' + v.level + '</td>'
+ '<td class="' + cls + '">' + lbl + '</td>'
+ '<td style="font-size:0.85rem;color:#555;">' + v.detail + '</td></tr>';
});
html += '</tbody></table>';
html += '<details style="margin-top:14px;"><summary>Roh-Daten (' + rows.length + ' Runs)</summary>'
+ renderTable(rows) + '</details>';
const allPass = verdicts.every(v => v.pass);
html += '<p class="legend"><strong>'
+ (allPass ? '✅ Alle Korridor-Regeln erfüllt.' : '⚠ Mindestens eine Regel verletzt.')
+ '</strong> R-2 prüft jetzt mit <code>optimal</code> (Best-Net-Heuristik).</p>';
root.innerHTML = html;
}
document.getElementById('run-corridor').addEventListener('click', runBalanceCorridor);
})();
</script>
</body>
</html>