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>
This commit is contained in:
2026-04-26 01:05:13 +02:00
parent f811437a35
commit 79ac9dad84
773 changed files with 17482 additions and 541 deletions
+105
View File
@@ -37,6 +37,7 @@
<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>
@@ -135,6 +136,110 @@ const matrix = await LogistikRunner.runMatrix({
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>