From 88f7b28f4ab2927c9a1d29a9c50a2d2ed030899b Mon Sep 17 00:00:00 2001 From: Here0001 <325388435+Here0001@users.noreply.github.com> Date: Mon, 28 Sep 2026 14:32:17 +0200 Subject: [PATCH] Corp in weak BitNodes, purchase ledger, CORP runs only with a corporation - HELPERS 5.18: corpBesteht() (INFO block or BANK's own report); readiness asks for an existing corporation first, like gang and Bladeburner. - BANK 5.18: in nodes with CorporationSoftcap < 0.75, found the corporation only once $150b arrive within 60 s (full income median); unknown multipliers no longer count as "allowed"; START:CORP only when a new corporation appears, so a switch set to 0 stays 0; hash refill once per CORP report; share trading only on fresh CORP data; founding, buyback, sale and hash inflow in the ledger. - CORP 0.49: division, city and share issue in the ledger; exports "dran". - QUEEN 7.17: CORP starts only when a corporation exists (same pattern as BITNODE) and a running CORP without one is stopped. - DIAG 3.26: corp findings only on fresh data with correct reasons; "waiting for a corporation" instead of "missing"; "off but running" only at window end. Co-Authored-By: Claude Opus 5.5 --- scripts/SCHWARM-BANK.js | 138 +++++++++++++++++++++++++--- scripts/SCHWARM-CORP.js | 65 ++++++++++++-- scripts/SCHWARM-DIAG.js | 180 +++++++++++++++++++++++++++++++------ scripts/SCHWARM-HELPERS.js | 55 +++++++++++- scripts/SCHWARM-QUEEN.js | 33 ++++++- 5 files changed, 417 insertions(+), 54 deletions(-) diff --git a/scripts/SCHWARM-BANK.js b/scripts/SCHWARM-BANK.js index a2dbaed..c968a60 100644 --- a/scripts/SCHWARM-BANK.js +++ b/scripts/SCHWARM-BANK.js @@ -1,5 +1,25 @@ /** - * SCHWARM-BANK.js — v5.17 + * SCHWARM-BANK.js — v5.18 + * + * v5.18 — DIE CORP IN SCHWACHEN NODES (Punkt 24, Entscheidung des Spielers). + * a) corpPossible(): unter Softcap 0,75 gruenden, sobald die $150b in + * hoechstens CORP_SCHNELL_SEK (60 s) hereinkommen (incomeRate, Median). + * "Multiplikatoren unbekannt" heisst jetzt null = nicht gruenden (vorher + * true: nach jedem Neuladen einige Sekunden lang eine offene Tuer). + * corpErledigt() wartet bei null, statt die Stufe abzuhaken. + * b) START:CORP nur noch beim UEBERGANG "keine Corp -> Corp", nicht bei + * jedem eigenen Start. Vorher galt ein Schalter 0 nach jedem Laden und + * jedem Deploy rund eine Minute lang nicht (so entstand Chemical). + * c) Hash-Nachfuellung der Corp-Fonds hoechstens EINMAL je CORP-Meldung + * (vorher je Takt: $0,4b -> $183b bei einer Schwelle von $40b). + * d) Rueckkauf und Verkauf von Corp-Aktien nur auf frischem CORP_OUT + * (<= 120 s). Ohne laufendes CORP stand dort ein eingefrorener Kurs. + * e) Ins Handlungsbuch: Corp-Gruendung, Rueckkauf (topf corporation), + * Aktienverkauf (erloes) und der Hash-Zufluss in die Corp-Fonds. + * Nachtrag 1 (Gegenpruefung, 26 Agenten): 60-s-Regel erst mit vollem + * Einkommens-Puffer; Gruendung/Rueckkauf ohne RPC-Antwort = "unklar"; + * gemessener Rueckkaufbetrag gilt als Schaetzung; Rangliste und + * Verkaufskapazitaet nur auf frischem CORP_OUT. * * v5.17 — MEHRERE FREIGABEN JE TAKT, UND JEDER KAUF MIT BETRAG IM BUCH. * a) Geld-Antraege werden der Reihe nach bedient (prio absteigend, bei @@ -866,7 +886,7 @@ // 04.09.2026 waren das getrennte Freitexte und liefen auseinander: der // Kopf sagte eine Version, die Startmeldung im Log eine andere. Beim // Nachstellen eines Fehlers behauptet das Log damit etwas Falsches. -const VERSION = "5.17"; +const VERSION = "5.18"; import { drainBankInbox, hasCapability, CAPS, formatNumber, formatMoney, @@ -1220,21 +1240,39 @@ export async function main(ns) { // bleibt (1e9^0.75 = $5.6m). // // WAS DAS NICHT SAGT: dass eine Corp dort sinnlos waere. Sie ist dort nur - // kein Weg zu Bargeld. Wer sie aus anderen Gruenden will, schaltet CORP - // von Hand ein — dieser Riegel haelt ausschliesslich die AUTOMATISCHE - // Gruendung auf BANKs Kosten an. + // kein Weg zu Bargeld. + // + // v5.18 (Entscheidung des Spielers, 26.09.2026): auch dort gruenden, + // sobald es praktisch nichts kostet - wenn die $150b in hoechstens + // CORP_SCHNELL_SEK hereinkommen. Eine BESTEHENDE Corp wird ohnehin + // gesteuert (HELPERS v5.17 daemonBereit), auch eine von Hand gegruendete. // // NEBENWIRKUNG, GEWOLLT: corpErledigt() haengt an corpPossible(). Wo die // Gruendung sich nicht lohnt, gilt die Corp-Stufe als abgehakt und die // Congruity-/Craft-Phase (v4.7/v4.8) laeuft an, statt ewig zu warten. const CORP_SOFTCAP_LOHNT = 0.75; + const CORP_SCHNELL_SEK = 60; // v5.18: schwache Node -> gruenden, wenn $150b in 60 s kommen + /** true = gruenden, false = nicht (sicher), null = unbekannt (nicht gruenden, nicht abhaken). */ const corpPossible = () => { if (!bitNodeFeatures(ns).corporation) return false; const m = bnMults(); - if (!m || typeof m.CorporationSoftcap !== "number") return true; // unbekannt -> nicht sperren + // v5.18: unbekannt heisst NICHT erlaubt. Nach jedem Neuladen fehlt der + // bn-Block einige Sekunden (Ports leer, INFO noch nicht da) - mit true + // haette BANK dann in BN13/BN15 gegruendet. + if (!m || typeof m.CorporationSoftcap !== "number") return null; if (m.CorporationSoftcap < CORP_SOFTCAP_MIN) return false; // Engine sperrt hart - return m.CorporationSoftcap >= CORP_SOFTCAP_LOHNT; // v5.3: lohnt es sich auch? + if (m.CorporationSoftcap >= CORP_SOFTCAP_LOHNT) return true; // v5.3: lohnt sich + // v5.18: schwache Node - nur, wenn die Gruendung praktisch nichts kostet. + // Nachtrag 1: erst mit VOLLEM Median-Puffer (INCOME_SAMPLES, ~70 s). + // Bei ein oder zwei Messungen ist der "Median" das Maximum - ein + // einzelner Aktienverkauf haette sonst $150b ausgeloest. + const rate = incomeRate(); + if (rank.incBuf.length < INCOME_SAMPLES) return null; + return rate * CORP_SCHNELL_SEK >= CORP_FOUNDING_COST; }; + /** v5.18: CORP_OUT ist nur frisch, solange CORP laeuft (Runde ~35 s). */ + const CORP_OUT_FRISCH_MS = 120_000; + const corpFrisch = (c) => !!(c && c.ts && (Date.now() - c.ts) <= CORP_OUT_FRISCH_MS); // ===================================================================== // v5.4 — IN BN3 IST DIE GRUENDUNG UMSONST // ===================================================================== @@ -1272,7 +1310,8 @@ export async function main(ns) { * dieser BitNode unmoeglich ist. Der Geld-Vorrang bleibt ueberall dort * erhalten, wo es ihn ueberhaupt geben kann. */ - const corpErledigt = () => corpExists || !corpPossible(); + // v5.18: nur ein SICHERES Nein hakt ab. null (unbekannt) wartet. + const corpErledigt = () => corpExists || corpPossible() === false; /** * Erzeugt zusaetzlicher Worker-RAM in dieser BitNode ueberhaupt Geld? @@ -1593,7 +1632,9 @@ export async function main(ns) { // dieselbe Rechnung wie im Ziel — sonst steht in der Rangliste ein // anderer Preis als der, der dann tatsaechlich abgebucht wird. const cc = io.corp; - if (cc && cc.public && (cc.issuedShares || 0) > 0 && (cc.sharePrice || 0) > 0) { + // Nachtrag 1: nur auf frischem CORP_OUT - sonst kann ein Posten zum + // eingefrorenen Kurs zum HOLD werden und Geld binden. + if (corpFrisch(cc) && cc.public && (cc.issuedShares || 0) > 0 && (cc.sharePrice || 0) > 0) { const total = cc.totalShares || 1; const rate = (cc.dividendRate || 0) > 0 ? cc.dividendRate : RUECKKAUF_PLAN_DIVIDENDE; const chunk = Math.floor(Math.min(cc.issuedShares, cc.buyback && cc.buyback.chunk ? cc.buyback.chunk : 10e6)); @@ -1924,6 +1965,9 @@ export async function main(ns) { // // RUECKFALL: meldet CORP kein Fenster (aeltere Version, Block fehlt), // bleibt der Rueckkauf aus. Lieber nicht kaufen als teuer kaufen. + // v5.18: nur auf frischem CORP_OUT - sonst kauft BANK zu einem + // eingefrorenen Kurs aus einem laengst geschlossenen Fenster. + if (!corpFrisch(cc)) return null; const fw = cc.fenster; if (!fw || fw.offen !== 1) return null; @@ -1955,8 +1999,10 @@ export async function main(ns) { label: `Corp-Aktien: ${formatNumber(anzahl)} zurueck (${anteilJetzt.toFixed(1)}% -> ${(100 * (eigen + anzahl) / total).toFixed(1)}%)`, cost: kosten, ready: true, buy: async () => { + const geldVor = money(); const ok = await io.act("corpBuyback", [anzahl], // v5.15: so heisst der Befehl in INFO `(() => { try { ns.corporation.buyBackShares(${anzahl}); return true; } catch (e) { return false; } })()`); + rueckkaufBuchen(ok, anzahl, geldVor, kosten, "Ziel"); if (ok === true) { ns.print(`[Corp] ${formatNumber(anzahl)} Aktien zurueckgekauft ` + `(${anteilJetzt.toFixed(2)}% -> ${(100 * (eigen + anzahl) / total).toFixed(2)}%, ` @@ -1980,6 +2026,17 @@ export async function main(ns) { corpExists = true; if (!corpStartSent) { sendCmd(ns, "START:CORP"); corpStartSent = true; } } + // v5.18: ins Handlungsbuch. Der Preis ist fest (Corporation/helpers.ts:80-85). + // Nachtrag 1: null = keine Antwort in 12 s - INFO kann den Auftrag + // spaeter noch ausfuehren. Wie home-RAM (v5.17): "unklar" mit Preis. + try { + const unklar = ok === null || ok === undefined; + chronik(ns, "BANK", "corp", CORP_NAME, + ok === true ? "gegruendet" : (unklar ? "unklar" : "abgelehnt"), + selbst ? formatMoney(CORP_FOUNDING_COST) : "Saatgeld", + { betrag: (ok === true || unklar) && selbst ? CORP_FOUNDING_COST : 0, + topf: "corporation", ...(unklar ? { unklar: true } : {}) }); + } catch (e) { /* Beiwerk */ } return ok === true; }, }); @@ -2219,15 +2276,24 @@ export async function main(ns) { if (io.b.corp) { const ex = io.b.corp.exists === true; if (ex && !corpExists) corpExists = true; + if (io.b.corp.exists === false) corpOhneGesehen = true; } else { const nowT = Date.now(); if (!corpExists && nowT - corpCheckAt >= 30_000) { corpCheckAt = nowT; const r = await evalNs(ns, "ns.corporation.hasCorporation()"); corpExists = r === true; + if (r === false) corpOhneGesehen = true; } } - if (corpExists && !corpStartSent) { sendCmd(ns, "START:CORP"); corpStartSent = true; ns.print("Corp existiert -> START:CORP."); } + // v5.18: NUR beim Uebergang "keine Corp -> Corp" einschalten (etwa eine + // von Hand gegruendete). Stand die Corp schon beim Start dieses Prozesses, + // gilt der Schalter, wie er steht - vorher hebelte jedes Laden und jeder + // Deploy einen Schalter 0 fuer rund eine Minute aus. + if (corpExists && corpOhneGesehen && !corpStartSent) { + sendCmd(ns, "START:CORP"); corpStartSent = true; + ns.print("Neue Corp -> START:CORP."); + } }; /** Großziel fahren: ansparen -> Lücke liquidieren -> kaufen -> ausbuchen. */ @@ -2443,6 +2509,7 @@ export async function main(ns) { * Mehrheit (>= floorOwnFrac) zu unterschreiten? 0 bei Cooldown/keine Corp. */ const corpSellCapacity = () => { const c = io.corp; + if (!corpFrisch(c)) return 0; // Nachtrag 1: nicht auf altem Kurs planen if (!c || !c.public || (c.sellCooldown || 0) > 0) return 0; const total = c.totalShares || 0, own = c.numShares || 0, price = c.sharePrice || 0; if (total <= 0 || price <= 0) return 0; @@ -2451,9 +2518,32 @@ export async function main(ns) { // Verkauf drückt den Kurs -> konservativ mit Abschlag rechnen. return sellable * price / CORP_PRICE_PREMIUM; }; + // v5.18: Rueckkauf ins Buch. Nachtrag 1 (Gegenpruefung): drei Ausgaenge + // wie beim home-RAM (v5.17). null = keine Antwort in 12 s, INFO fuehrt den + // Auftrag aber spaeter noch aus -> "unklar" mit der Schaetzung. + // Der gemessene Betrag gilt IMMER als Schaetzung: zwischen den beiden + // Geldstaenden liegt ein RPC, und in der Zeit fliessen Dividende (derselbe + // Topf!) und Einkommen. Liegt die Differenz ausserhalb von (0, 3x Formel], + // gilt die Formel. + const rueckkaufBuchen = (ok, anzahl, geldVor, schaetzung, wo) => { + const gekauft = ok === true, unklar = ok === null || ok === undefined; + const delta = geldVor - money(); + const plausibel = gekauft && delta > 0 && (!(schaetzung > 0) || delta <= 3 * schaetzung); + const betrag = gekauft ? (plausibel ? delta : (schaetzung > 0 ? schaetzung : 0)) + : (unklar && schaetzung > 0 ? schaetzung : 0); + try { + chronik(ns, "BANK", "aktien", "Corp-Rueckkauf", + gekauft ? "gekauft" : (unklar ? "unklar" : "abgelehnt"), + `${formatNumber(anzahl)} Aktien, ${formatMoney(betrag)} (${wo})`, + { betrag, topf: "corporation", n: anzahl, + ...(gekauft ? { geschaetzt: 1, quelle: plausibel ? "differenz" : "formel" } : {}), + ...(unklar ? { unklar: true } : {}) }); + } catch (e) { /* Beiwerk */ } + }; /** Aktien für ~need $ verkaufen (bis Untergrenze). Gibt den Erlös (>0) zurück. */ const corpSellForCash = async (need) => { const c = io.corp; + if (!corpFrisch(c)) return 0; // v5.18: kein Verkauf auf altem Kurs if (!c || !c.public || (c.sellCooldown || 0) > 0 || need <= 0) return 0; const total = c.totalShares || 0, own = c.numShares || 0, price = c.sharePrice || 0; if (total <= 0 || price <= 0) return 0; @@ -2467,6 +2557,12 @@ export async function main(ns) { if (r === null || r === false) return 0; // sellShares liefert in dieser Engine keinen $-Wert -> Erlös schätzen. const est = want * price / CORP_PRICE_PREMIUM; + // v5.18: Zufluss, kein Kauf - daher erloes statt betrag. + try { + chronik(ns, "BANK", "aktien", "Corp-Verkauf", "verkauft", + `${formatNumber(want)} Aktien, ~${formatMoney(est)} fuer ein Grossziel`, + { erloes: est, topf: "corporation", n: want, geschaetzt: 1 }); + } catch (e) { /* Beiwerk */ } ns.print(`CORP-AKTIEN: ${want} verkauft (~${formatMoney(est)}) für Großziel — Mehrheit bleibt (>= ${((c.floorOwnFrac || 2/3) * 100).toFixed(0)}%).`); return est; }; @@ -2477,6 +2573,7 @@ export async function main(ns) { if (now - lastCorpBuyback < CORP_BUYBACK_MS) return; lastCorpBuyback = now; const c = io.corp; + if (!corpFrisch(c)) return; // v5.18: nur auf frischem Stand if (!c || !c.public || !c.buyback || c.buyback.want !== 1) return; // v3.0: kein `if (goalState.active) return;` mehr. Diese Zeile war die im // v1.9-Kopf als "OFFEN BLEIBT" vermerkte zweite Sperre — sie verhinderte den @@ -2503,8 +2600,10 @@ export async function main(ns) { const affordable = Math.floor(budget / (price * CORP_PRICE_PREMIUM)); const count = Math.min(issued, chunk, affordable); if (count < 1) return; + const geldVor = money(); const ok = await io.act("corpBuyback", [count], `(() => { try { ns.corporation.buyBackShares(${count}); return true; } catch (e) { return false; } })()`); // v5.15: void -> true + rueckkaufBuchen(ok, count, geldVor, count * price * CORP_PRICE_PREMIUM, "Rendite"); if (ok === true) ns.print(`CORP-AKTIEN: ${count} zurückgekauft (~${formatMoney(count * price)}), Rendite ${(divYieldPerH * 100).toFixed(0)}%/h >= Zins ${(gate * 100).toFixed(0)}%/h.`); }; @@ -3912,6 +4011,7 @@ export async function main(ns) { let lastGrantBlock = ""; // v4.1: warum der Top-Antrag nicht freigegeben wurde let corpExists = false; let corpStartSent= false; // START:CORP nur einmal senden + let corpOhneGesehen = false; // v5.18: in DIESEM Prozess schon "keine Corp" gesehen? let grantedLast = {}; // Freigabe-Schnappschuss vom letzten Tick (Verbrauchserkennung) let zurueckTakt = new Set(); // v5.17 Nachtrag: ungenutzt zurueckgezogen (nicht buchen) @@ -4052,6 +4152,7 @@ export async function main(ns) { // ===================== HAUPTSCHLEIFE ===================== const needs = {}; // PRODUZENT -> { ts, fields } (drainHashNeeds füllt) + let corpFondsTs = 0; // v5.18: je CORP-Meldung hoechstens EINE Nachfuellung let U = null; // aufgelöste Upgrade-Namen (Cache) ns.print(" // SCHWARM-BANK // v" + VERSION + " (INFRA gemergt)"); @@ -4260,9 +4361,14 @@ export async function main(ns) { // Corp-Fonds (nur wenn nötig und Kurs stimmt). const corp = fresh(needs, "CORP"); - if (corp && U.corpFunds) { + // v5.18: die Meldung traegt den Fondsstand ihrer Runde. Wer je Takt + // (2 s) erneut nachfuellt, rechnet mit einem Stand, den er selbst + // laengst ueberholt hat - live $0,4b -> $183b bei Schwelle $40b. + const corpMeldung = needs["CORP"]; + if (corp && U.corpFunds && corpMeldung && corpMeldung.ts !== corpFondsTs) { const funds = Number(corp.funds); if (isFinite(funds) && funds < CORP_FUNDS_MIN) { + corpFondsTs = corpMeldung.ts; const wanted = Math.ceil((CORP_FUNDS_MIN - funds) / 1e9); let buys = 0; while (buys < wanted) { @@ -4271,7 +4377,15 @@ export async function main(ns) { if (!spend(U.corpFunds)) break; buys++; } - if (buys > 0) action.push(`Corp-Fonds: ${buys}x $1b`); + if (buys > 0) { + action.push(`Corp-Fonds: ${buys}x $1b`); + // v5.18: kein Spielergeld, deshalb ohne topf/betrag. + try { + chronik(ns, "BANK", "hash", "Corp-Fonds", "gekauft", + `${buys}x $1b (Fonds vorher ${formatMoney(funds)})`, + { n: buys, fonds: buys * 1e9 }); + } catch (e) { /* Beiwerk */ } + } } } diff --git a/scripts/SCHWARM-CORP.js b/scripts/SCHWARM-CORP.js index 3ecae4f..94f69fd 100644 --- a/scripts/SCHWARM-CORP.js +++ b/scripts/SCHWARM-CORP.js @@ -1,5 +1,16 @@ /** - * SCHWARM-CORP.js — v0.48 + * SCHWARM-CORP.js — v0.49 + * + * v0.49 — WAS CORP ANLEGT, STEHT IM HANDLUNGSBUCH (Punkt 24). + * Divisionsgruendung, Stadtkauf und Aktien-Emission liefen nur ueber + * ns.print - das Tail ist mit dem Prozess weg. Am 26.09. entstand so + * Chemical in BN15, ohne dass sich spaeter belegen liess, wer es angelegt + * hatte. Die Betraege kommen aus der CORP-Kasse, nicht vom Spieler - + * deshalb ohne topf, mit kasse "corp". Dazu: `dran` im CORP_OUT (die + * Division, die diese Runde ausgebaut wurde - DIAG braucht es fuer die + * Lager-Regel), und zwei Kommentare, die noch das Tor von v0.39 nannten. + * Nachtrag 1: die Kosten einer Division sind der Engine-Preis + * (startingCost), nicht die Fondsdifferenz ueber mehrere evalNs. * * v0.48 — FRUEHER BOERSENGANG STATT HENNE-EI-FALLE. * Beobachtet: 1 Division, 4/6 Staedte, privat, Runde 0/2, $774 Mio. @@ -564,7 +575,7 @@ import { evalNs, formatMoney, publishHashNeed, publishCorpInfo, readBankInfo, re // waehrend der Kopf laengst auf 0.34 stand — genau die Drift, die der // PRUEFER bei allen anderen Dateien abfaengt (Kopf gegen Konstante). CORP // hatte als einzige gar keine Konstante und fiel deshalb durch das Raster. -const VERSION = "0.48"; +const VERSION = "0.49"; const STATE_FILE = "schwarm-corp-state.txt"; const LOOP_MS = 5000; const WAIT_CORP_MS = 30_000; // v0.21: Takt der Warteschleife, solange keine Corp existiert @@ -755,6 +766,9 @@ const STAGE_IDLE_FUNDS_MULT = 3; // Notausgang: Fonds >= 3x Startkosten // nicht traegt. Die Dividende liest es: wer noch fuer Personal spart, erntet // nicht. Modulweit, weil beide Seiten in verschiedenen Funktionen sitzen. let bueroHungrig = false; +// v0.49: die Division, die in dieser Runde dran war (Reihum). Geht mit +// CORP_OUT hinaus und wird danach geleert - eine Runde ohne Reihum meldet null. +let zuletztDran = null; // v0.43: dasselbe fuers LAGER. Dass es fehlte, war der Grund, warum der Mangel // unsichtbar blieb - ein uebersprungener Lagerkauf hinterliess keine Spur. let lagerHungrig = false; @@ -1362,6 +1376,22 @@ async function createDivision(ns, ind, name) { await evalNs(ns, `ns.corporation.expandIndustry(${JSON.stringify(ind)}, ${JSON.stringify(name)})`); const snap = await corpSnapshot(ns); const ok = !!(snap && snap.divisions.includes(name)); + // v0.49: ins Handlungsbuch - bezahlt aus der Corp-Kasse, nicht vom Spieler. + // Nachtrag 1: der Engine-Preis (Actions.ts:80-86 zieht genau startingCost + // ab). Die Fondsdifferenz enthielt auch Umsatz, Smart Supply und BANKs + // Hash-Zufluss zwischen den beiden Abfragen. + let kosten = 0; + if (ok) { + try { + const idat = await industryData(ns, ind); + if (idat && typeof idat.startingCost === "number" && idat.startingCost >= 0) kosten = idat.startingCost; + } catch (e) { kosten = 0; } + } + try { + chronik(ns, "CORP", "division", `${name} (${ind})`, ok ? "gegruendet" : "fehlgeschlagen", + ok ? `${formatMoney(kosten)} aus der Corp-Kasse` : "", + { kasse: "corp", kosten: ok ? kosten : 0 }); + } catch (e) { /* Beiwerk */ } // Die neue Division sofort in den Cache, damit die naechste Landkarte sie // kennt, ohne ein weiteres eval zu brauchen. if (ok) industrieCache.set(name, ind); @@ -2046,6 +2076,11 @@ async function ensureCities(ns, div, budget, consts) { await cPurchaseWarehouse(ns, div, city); const ok = await cHasWarehouse(ns, div, city); budget.remaining -= need; + // v0.49: ins Handlungsbuch (Corp-Kasse). + try { + chronik(ns, "CORP", "stadt", `${div}/${city}`, ok ? "gekauft" : "unklar", + `Buero+Lager ${formatMoney(need)}`, { kasse: "corp", kosten: need }); + } catch (e) { /* Beiwerk */ } ns.print(` [${div}] Neue Stadt: ${city} (Büro+Lager, ${formatMoney(need)}) ${ok ? "OK" : "(Lager prüfen)"} — noch ${missing.length - 1} offen.`); return await divisionCities(ns, div); } @@ -2744,6 +2779,10 @@ async function fensterStep(ns, snap, bueroUndLager) { if (max >= 10e6) { const r = await evalNs(ns, `(() => { try { return ns.corporation.issueNewShares(${max}); } catch (e) { return null; } })()`); if (typeof r === "number") { + try { + chronik(ns, "CORP", "emission", "Aktien", "ausgegeben", + `${fmtNum(max)} Aktien -> ${formatMoney(r)} (Fenster)`, { kasse: "corp", n: max, erloes: r }); + } catch (e) { /* Beiwerk */ } ns.print(` [Fenster] Emission zum HOHEN Kurs: ${fmtNum(max)} Aktien -> ` + `${formatMoney(r)} Corp-Kapital. Jetzt ausgeben, bis die Fonds unter ` + `${formatMoney(grenze)} liegen.`); @@ -2944,7 +2983,13 @@ async function stockStep(ns, snap, offer, reserve, stufeOffen) { let amount = Math.floor((snap.totalShares * ISSUE_FRACTION_OF_TOTAL) / 10e6) * 10e6; if (amount >= 10e6) { const r = await evalNs(ns, `(() => { try { return ns.corporation.issueNewShares(${amount}); } catch (e) { return null; } })()`); - if (typeof r === "number") ns.print(` [Boerse] Emission: ${fmtNum(amount)} Aktien -> ${formatMoney(r)} Corp-Kapital (alles rueckkaufbar).`); + if (typeof r === "number") { + try { + chronik(ns, "CORP", "emission", "Aktien", "ausgegeben", + `${fmtNum(amount)} Aktien -> ${formatMoney(r)} (Reserve)`, { kasse: "corp", n: amount, erloes: r }); + } catch (e) { /* Beiwerk */ } + ns.print(` [Boerse] Emission: ${fmtNum(amount)} Aktien -> ${formatMoney(r)} Corp-Kapital (alles rueckkaufbar).`); + } } } @@ -3410,9 +3455,10 @@ export async function main(ns) { const unlocks = ensured.status; const unlockReserve = ensured.reserve; - // --- 1b) NAECHSTE STUFE der Kette (Chem/Tabak). Bedingung: Vorstufe - // existiert mit >= 3 Staedten, und die Kosten sind OHNE Unlock- - // Reserve und OHNE Betriebskapital gedeckt. + // --- 1b) NAECHSTE STUFE der Kette (Chem/Tabak). Bedingung (v0.40): + // Vorstufe in ALLEN Staedten, Gewinn >= STAGE_MIN_PROFIT_SEC ODER + // Fonds >= STAGE_IDLE_FUNDS_MULT x Startkosten, und die Kosten + // OHNE Unlock-Reserve und OHNE Betriebskapital gedeckt. if (state.bp && state.bp !== "ADOPT") { const stages = BLUEPRINTS[state.bp].stages; for (let si = 1; si < stages.length; si++) { @@ -3712,6 +3758,7 @@ export async function main(ns) { // ueberhaupt erst erreichbar. const reihe = rangfolge(ownDivs, divSummen); const dran = reihe.length ? [reihe[0]] : []; + zuletztDran = dran.length ? dran[0] : null; // v0.49: fuer DIAG if (dran.length) { const z = divSummen[dran[0]] || {}; const pro = (z.staedte > 0) ? Math.round((z.empMax || 0) / z.staedte) : 0; @@ -3890,8 +3937,11 @@ export async function main(ns) { divisions: (snap2.divisions || []).length, cities: ownDivs.reduce((a, d) => a + ((citiesByDiv[d] || []).length), 0), reserve, - // v0.24: null = Kette vollstaendig (nichts mehr anzusparen). + // v0.24: null = nichts anzusparen. Seit v0.40 heisst das + // Kette vollstaendig ODER Reifetor zu (Staedte/Gewinn). kriegskasse, + // v0.49: wer diese Runde dran war (null = kein Reihum). + dran: zuletztDran, autoStufe: AUTO_NEUE_STUFE, // v0.44: Stufe und naechster Preis je Corp-Upgrade. // Bis hierher war NICHT feststellbar, ob je eines gekauft @@ -3935,6 +3985,7 @@ export async function main(ns) { // ausserhalb waere jeder Rueckkauf zum Hoechstkurs. fenster, }); + zuletztDran = null; // v0.49: gilt nur fuer diese Runde } const own = snap2.totalShares > 0 ? (snap2.numShares / snap2.totalShares * 100) : 100; diff --git a/scripts/SCHWARM-DIAG.js b/scripts/SCHWARM-DIAG.js index bfec46a..1ad39d5 100644 --- a/scripts/SCHWARM-DIAG.js +++ b/scripts/SCHWARM-DIAG.js @@ -1,5 +1,36 @@ /** - * SCHWARM-DIAG.js — v3.24 + * SCHWARM-DIAG.js — v3.26 + * + * v3.26 — CORP OHNE CORP IST KEIN MANGEL, UND "AUS, LAEUFT ABER" NUR, WENN + * ES AM FENSTERENDE NOCH STIMMT. QUEEN v7.17 startet CORP erst, wenn eine + * Corp besteht - "Schalter an, kein Prozess" ist dann der Sollzustand + * ("wartet auf eine Corp"), nicht "FEHLT". Und im Testspiel z1832 wurde + * der CORP-Schalter mitten im Fenster umgelegt; die Queen beendete den + * Prozess 28 s spaeter, DIAG meldete trotzdem "Queen hat nicht + * aufgeraeumt". Befund jetzt nur, wenn der Prozess in der LETZTEN Probe + * noch lief. + * + * v3.25 — CORP-BEFUNDE NUR AUF FRISCHEN ZAHLEN, MIT DER RICHTIGEN BEGRUENDUNG. + * (Punkt 24) Die Staedte-Regel zitierte das Reifetor von CORP v0.39 + * ("< 3"), meldete auch den normalen Ausbau der neuesten Division und + * rechnete auf eingefrorenem CORP_OUT weiter (z1794: 2862 s alt, CORP aus, + * Chemical hatte laengst 2 Staedte). Jetzt: + * - Frische-Tor: CORP-Befunde nur, wenn CORP in den Proben lief UND + * CORP_OUT hoechstens CORP_PORT_FRISCH_S alt ist. Sonst eine Zeile + * "eingefroren" und keine Befunde. + * - Staedte: Befund erst, wenn eine Division ueber CORP_STAEDTE_SERIE_MIN + * Berichte bei laufendem CORP nicht waechst. Begruendung nach + * ensureCities und dem Reifetor v0.40. + * - Upgrades: verglichen wird mit dem, was corpUpgradeStep seit CORP v0.45 + * greifen kann (Fonds - Unlock - Betriebskapital), nicht mit dem Budget. + * - Stillstand: der Zweig "Stufe ueber den Fonds" ist seit CORP v0.22 + * unerreichbar (Deckel 50 %) und entfaellt. + * - Lager: nur fuer die Division, die dran ist (Reihum seit CORP v0.45). + * - Node-Lage: in schwachen Nodes eine Zeile (kein Befund), was die Corp + * dort bringt. "Corp(P31)" hiess der falsche Port - CORP_OUT ist 15. + * Nachtrag 1: Staedte-Text nach dem echten Ablauf (ensureCities fuer JEDE + * Division vor der Reihum-Wahl, eine einzelne kranke Stadt sperrt); + * Reifegrad zeigt CORP ohne Corp in schwachen Nodes als "offen". * * v3.24 — KASSE. Neuer Abschnitt 4c, maschinenlesbar: Kassenbuch der * Engine (getMoneySources), Geldstand, Reset-Stempel und ALLE Zeilen des @@ -605,6 +636,7 @@ import { readShareWanted, readHashCacheNeed, readInfoSnapshot, getPhase, setDaemonEnabled, readOut, readAugBuy, readResetReady, planFreigabe, daemonBereit, bereitSpielerInfo, // DAEMONS steht schon oben — nicht doppelt! + corpBesteht, // v3.26: CORP laeuft nur mit Corp (QUEEN v7.17) chronikLesen, CHRONIK_KUERZEL, // v3.22: das Handlungsbuch chronikUmschichten, // v3.24: Port 35 je Probe in die Datei BEREIT_GANG_KARMA, BEREIT_BLADE_STAT, @@ -613,7 +645,7 @@ import { const OUT_FILE = "SCHWARM-REPORT.txt"; const SHORT_FILE = "schwarm-kurz.txt"; // v2.2: Kurzfassung zum Weitergeben const LEGEND_FILE = "schwarm-legende.txt"; // v2.2: GENERIERT, nie von Hand pflegen -const DIAG_VERSION = "3.24"; // EINE Quelle fuer Kopfzeile und Kurzreport-Format +const DIAG_VERSION = "3.26"; // EINE Quelle fuer Kopfzeile und Kurzreport-Format // NACHGETRAGEN AM 11.09.: stand auf "3.4", waehrend der Dateikopf schon v3.5 // sagte — dieselbe Falle, vor der oben (Zeile 95 und 368) bereits zweimal // gewarnt wird. Im Bericht vom 11.09. stand oben "SCHWARM-DIAGNOSE v3.4" und @@ -717,6 +749,15 @@ const SELF_KEY = "DIAG"; // ist richtig so — nach einem Neustart ist nichts beobachtet. const GANG_WARFARE_STREAK_MIN = 2; let gangWarfareStreak = 0; +// v3.25: CORP_OUT gilt als frisch bis zu dieser Sekundenzahl. CORP +// veroeffentlicht etwa alle 35 s (z1789: 19:35:03 / 19:35:38). +const CORP_PORT_FRISCH_S = 120; +// v3.25: so viele frische Berichte in Folge ohne neue Stadt, bevor es ein +// Befund ist. CORP kauft je Runde hoechstens EINE Stadt je Division (vor der +// Reihum-Wahl) und nur, wenn jede einzelne Stadt gesund ist - einzelne +// Berichte ohne Zuwachs sind normal. +const CORP_STAEDTE_SERIE_MIN = 3; +const corpStaedteSerie = {}; // Division -> { staedte, n } // v3.4: Anlauf-Skripte. Keine Daemons — deshalb nicht in der Registry und // bisher unsichtbar. Siehe den BOOTSTRAP-Block in Abschnitt 2. const BOOT_FILES = { "SCHWARM-GENESIS.js": "GENESIS", "SCHWARM-ARSENAL.js": "ARSENAL" }; @@ -1903,7 +1944,12 @@ export async function main(ns) { // liefert in shouldRun einen unbedingten Treffer). "Schalter aus und // laeuft trotzdem" ist bei ihnen also der SOLLZUSTAND, kein Fehler. else if (!wanted && d.oneshotDaemon) verdict = e.n > 0 ? "One-Shot lief (Selbst-Aus)" : "aus (gewollt)"; - else if (!wanted) verdict = e.n > 0 ? "!! AUS, läuft aber" : "aus (gewollt)"; + // v3.26: nur ein Befund, wenn der Prozess am FENSTERENDE noch lief. Wird + // der Schalter mittendrin umgelegt, beendet die Queen den Daemon ein + // paar Sekunden spaeter - das ist aufgeraeumt, nicht vergessen. + else if (!wanted) verdict = e.n === 0 ? "aus (gewollt)" + : (e.last < nS - 1 ? `aus (im Fenster beendet, zuletzt Probe ${e.last + 1})` + : "!! AUS, läuft aber"); else if (!capOk) verdict = `wartet: ${capNeed} fehlt`; else if (d.oneshotDaemon) verdict = e.n > 0 ? "One-Shot lief" : "One-Shot nicht im Fenster"; else if (selfDriven) verdict = e.n > 0 ? "laeuft (Dispatcher-gefuehrt)" : "ruht — nichts offen"; @@ -1922,6 +1968,11 @@ export async function main(ns) { else if (key === "BITNODE" && e.n === 0 && !planFreigabe(ns)) { verdict = "wartet auf Freigabe (schwarm-plan.txt)"; } + // v3.26 — CORP OHNE CORP (QUEEN v7.17): der Schalter ist die Absicht, + // gestartet wird erst, wenn eine Corp besteht. Sollzustand, kein Mangel. + else if (key === "CORP" && e.n === 0 && corpBesteht(ns) !== true) { + verdict = "wartet auf eine Corp (BANK gruendet)"; + } else if (e.n === 0) verdict = "!! FEHLT — nie gesehen"; else if (frischGestartet) verdict = `frisch gestartet (ab Sample ${e.first + 1})`; else if (e.n < nS * 0.9) verdict = `!! instabil (${e.n}/${nS})`; @@ -2661,13 +2712,37 @@ export async function main(ns) { // Und weil sie nicht mehr waechst, erreicht sie die Reserve nie. Genau // dieselbe Bauart wie die Sparziel-Sperre, die in BANK schon aufgefallen ist. const cq = last.corp; - if (!cq) { - L.push(`Corp(P31): —`); + // v3.25: readCorpInfo liefert {} statt null - ohne ts gab es nie eine Meldung. + if (!cq || !cq.ts) { + L.push(`Corp(P15): —`); } else { const alt = cq.ts ? Math.round((Date.now() - cq.ts) / 1000) : null; - L.push(`Corp(P31): Fonds ${nMoney(cq.funds || 0)} Profit ${nMoney(cq.profitPerSec || 0)}/s ` + // v3.25: FRISCHE-TOR. Befunde nur, wenn CORP lief und der Port frisch ist. + const corpLief = !!(seen["CORP"] && seen["CORP"].n > 0); + const corpFrisch = corpLief && alt !== null && alt <= CORP_PORT_FRISCH_S; + L.push(`Corp(P15): Fonds ${nMoney(cq.funds || 0)} Profit ${nMoney(cq.profitPerSec || 0)}/s ` + `${cq.divisions || 0} Division(en), ${cq.cities || 0} Bueros (Division x Stadt, max 6 Staedte)` + (alt === null ? "" : ` (${alt} s alt)`)); + if (!corpFrisch) { + const um = new Date(cq.ts); + const hhmm = `${String(um.getHours()).padStart(2, "0")}:${String(um.getMinutes()).padStart(2, "0")}`; + L.push(` (CORP ${corpLief ? "meldet nicht" : "lief in diesem Fenster nicht"} — ` + + `Stand von ${hhmm}, vor ${Math.round((alt || 0) / 60)} min. Die Zahlen sind eingefroren, ` + + `die CORP-Befunde ruhen.)`); + } + // v3.25: Node-Lage - was bringt die Corp in DIESER Node? Kein Befund. + try { + const bnC = last.inf && last.inf.blocks ? last.inf.blocks["bn"] : null; + const mC = bnC && bnC.data ? bnC.data.mults : null; + const sc = mC && typeof mC.CorporationSoftcap === "number" ? mC.CorporationSoftcap : null; + if (sc !== null && sc < 0.75) { + const expo = Math.max(0, sc - 0.15); + L.push(` Node-Lage: CorporationSoftcap ${sc.toFixed(2)} — die Dividende wird ` + + `hoch ${expo.toFixed(2)} genommen (mit beiden Unlocks hoch ${sc.toFixed(2)}), die Corp bringt ` + + `hier kaum Geld. Gesteuert wird sie trotzdem (Spieler 26.09.), gegruendet nur, ` + + `wenn $150b in 60 s hereinkommen (BANK v5.18).`); + } + } catch (e) { /* nur Anzeige */ } const rp = cq.reserveParts || null; // v2.9: cq.budget ist der REST nach allen Kaeufen, nicht das Budget. // Wer beides verwechselt, haelt eine Corp, die ihr Geld vollstaendig @@ -2726,7 +2801,10 @@ export async function main(ns) { // corpUpgradeStep laeuft als LETZTER und sieht ohnehin nur den // Rest nach Lager, Buero, Boost und AdVert. Dieselbe Krankheit // wie beim Lager (CORP v0.43) und beim Buero (v0.39). - const unerreichbar = Object.keys(ups).filter(n => { + // v3.25: seit CORP v0.45 greift corpUpgradeStep Rundenrest UND + // Grossposten-Topf - zusammen Fonds - Unlock - Betriebskapital. + const greifbar = (cq.funds || 0) - (rp ? (rp.unlock || 0) + (rp.workCap || 0) : 0); + const unerreichbar = !corpFrisch ? [] : Object.keys(ups).filter(n => { const u = ups[n] || {}; // v3.21: OHNE die Einschraenkung auf Stufe 0. Die stand hier // zuerst und ging am Ziel vorbei - die erste Messung zeigte @@ -2734,17 +2812,16 @@ export async function main(ns) { // $1,27 Bio. Budget. Unerreichbar ist unerreichbar, egal auf // welcher Stufe. return typeof u.cost === "number" && u.cost > 0 - && bud > 0 && u.cost > bud; + && greifbar > 0 && u.cost > greifbar; }); if (unerreichbar.length) { findings.push(`CORP: bei ${unerreichbar.length} Upgrade(s) kostet die ` - + `NAECHSTE Stufe mehr als das ganze Rundenbudget (${nMoney(bud)}): ` + + `NAECHSTE Stufe mehr als alles, was corpUpgradeStep ueberhaupt greifen kann ` + + `(Rundenrest plus Grossposten-Topf = Fonds minus Unlock-Reserve minus ` + + `Betriebskapital, ${nMoney(greifbar)}): ` + unerreichbar.map(n => `${n} Stufe ${ups[n].lvl} -> ${nMoney(ups[n].cost)}`).join(", ") - + `. corpUpgradeStep ist der LETZTE Posten des Budgets und sieht nur ` - + `den Rest nach Lager, Buero, Boost und AdVert — diese Stufen sind ` - + `also nicht teuer, sondern unerreichbar. Gleiche Bauart wie der ` - + `Lager-Fehler (CORP v0.43) und der Buero-Fehler (v0.39): dort half ` - + `ein eigener Spartopf.`); + + `. Einen eigenen Topf haben die Upgrades seit CORP v0.45 schon; diese ` + + `Stufen kommen erst, wenn die Fonds wachsen.`); } } @@ -2784,15 +2861,48 @@ export async function main(ns) { + " " + (d.ta2 ? "TA.II" : "MP") + (d.produkte ? " +Produkte" : "")); } - // BEFUNDE, die aus diesen Zahlen unmittelbar folgen. + // BEFUNDE, die aus diesen Zahlen unmittelbar folgen - v3.25 nur + // auf frischen Zahlen (Frische-Tor oben). + const dranJetzt = typeof cq.dran === "string" ? cq.dran : null; + if (corpFrisch) { + for (const k of Object.keys(corpStaedteSerie)) { + if (!dl.some(x => x.d === k)) delete corpStaedteSerie[k]; + } + } for (const d of dl) { - if ((d.staedte || 0) < maxSt) { - findings.push(`CORP: Division "${d.d}" steht in ${d.staedte} von ${maxSt} Staedten. ` - + `Das Tor zur naechsten Blaupausen-Stufe oeffnet aber schon bei DREI Staedten ` - + `(SCHWARM-CORP prevCities.length < 3) — die Corp kann also auf eine zweite ` - + `Industrie sparen, bevor die erste fertig ausgebaut ist.`); + if (!corpFrisch) break; + // v3.25: STAEDTE. Eine Division unter 6 Staedten ist erst ein + // Befund, wenn sie ueber mehrere frische Berichte nicht waechst. + const st = d.staedte || 0; + if (st < maxSt) { + const s = corpStaedteSerie[d.d]; + corpStaedteSerie[d.d] = (s && s.staedte === st) ? { staedte: st, n: s.n + 1 } : { staedte: st, n: 1 }; + const n = corpStaedteSerie[d.d].n; + if (n >= CORP_STAEDTE_SERIE_MIN) { + const krank = (d.moral || 0) < 50 || (d.energie || 0) < 50; + findings.push(`CORP: Division "${d.d}" steht seit ${n} Berichten unveraendert in ` + + `${st} von ${maxSt} Staedten, obwohl CORP laeuft. ensureCities kauft je Runde ` + + `hoechstens EINE Stadt je Division (Buero + Lager, $9 Mrd.), fuer jede Division noch ` + + `vor der Reihum-Wahl aus dem gemeinsamen Rundenbudget - und nur, wenn JEDE einzelne ` + + `bestehende Stadt Moral/Energie >= 50 und Personal hat. ` + + (krank ? `Moral/Energie stehen im Schnitt bei ${d.moral}/${d.energie}. ` + : `Rundenbudget ${nMoney(bud)}; Moral/Energie im Schnitt ${d.moral}/${d.energie} - ` + + `eine EINZELNE Stadt unter 50 sperrt trotzdem (CORP meldet nur den Schnitt; ` + + `im CORP-Log steht dann "Staedte-Ausbau wartet"). `) + + `Ist die Division Vorstufe einer noch ungebauten Blaupausen-Stufe, bleibt deren ` + + `Reifetor zu, solange Staedte fehlen (SCHWARM-CORP v0.40: prevCities.length < CITIES.length).`); + } + } else { + delete corpStaedteSerie[d.d]; } - if ((d.wh || 0) > 0 && (d.whUsed || 0) / d.wh > 0.95) { + // v3.25: LAGER nur fuer die Division, die dran ist - die anderen + // warten seit CORP v0.45 planmaessig auf ihre Runde. + if (dranJetzt && dranJetzt !== d.d) { + if ((d.wh || 0) > 0 && (d.whUsed || 0) / d.wh > 0.95) { + L.push(` "${d.d}": Lager ${Math.round(100 * d.whUsed / d.wh)} % — wartet auf ihre ` + + `Runde (dran: ${dranJetzt}, Reihum seit CORP v0.45).`); + } + } else if ((d.wh || 0) > 0 && (d.whUsed || 0) / d.wh > 0.95) { findings.push(`CORP: Division "${d.d}" hat ihre Lager zu ${Math.round(100 * d.whUsed / d.wh)} % ` + `gefuellt (${fmtK(d.whUsed)}/${fmtK(d.wh)}). Ab 100 % stockt die Produktion — Lager vergroessern.`); } @@ -2805,13 +2915,15 @@ export async function main(ns) { L.push(" (CORP < v0.37 meldet keine Kennzahlen je Division.)"); } // DER BEFUND: Budget null trotz vorhandener Fonds heisst Stillstand. - if (bud <= 0 && (cq.funds || 0) > 0) { - const warum = rp && rp.stage > (cq.funds || 0) - ? `Die naechste Blaupausen-Stufe allein (${nMoney(rp.stage)}) liegt ueber den Fonds ` - + `(${nMoney(cq.funds)}) — die Corp spart auf etwas, das sie ohne Wachstum nie erreicht.` - : `Reserve ${nMoney(cq.reserve || 0)} >= Fonds ${nMoney(cq.funds || 0)}.`; - findings.push(`CORP steht still: Rundenbudget 0. ${warum} Solange das Budget 0 ist, ` - + `kauft CORP nichts — keine Lager, kein Personal, keine Upgrades, keine Boost-Materialien.`); + // v3.25: nur frisch. Der Zweig "Stufe ueber den Fonds" ist seit CORP + // v0.22 unerreichbar - die Stufen-Ruecklage ist auf 50 % gedeckelt. + if (corpFrisch && bud <= 0 && (cq.funds || 0) > 0) { + const teile = rp ? `Unlock-Reserve ${nMoney(rp.unlock || 0)} + Betriebskapital ` + + `${nMoney(rp.workCap || 0)}` : `Reserve ${nMoney(cq.reserve || 0)}`; + findings.push(`CORP steht still: Rundenbudget 0, weil ${teile} die Fonds ` + + `${nMoney(cq.funds)} aufzehren. Die Stufen-Ruecklage ist es nicht — sie ist seit CORP ` + + `v0.22 auf 50 % der freien Fonds gedeckelt. Damit ist auch der Grossposten-Topf leer: ` + + `kein Lager, kein Buero, keine Stadt, keine Upgrades, kein Boost.`); } } if (Array.isArray((last.bank || {}).blockiert)) { @@ -3128,7 +3240,17 @@ export async function main(ns) { let b; try { b = daemonBereit(ns, key, sp); } catch (e) { continue; } if (b === true) continue; // nur das Auffaellige zeigen - urteil.push(`${key} ${b === false ? "NEIN" : "unbekannt"}`); + // Nachtrag 1: CORP liefert in schwachen Nodes ohne Corp null, OBWOHL + // die Daten da sind - dort entscheidet BANK nach dem Einkommen. + let offen = false; + if (key === "CORP" && b === null) { + try { + const bnR = last.inf && last.inf.blocks ? last.inf.blocks["bn"] : null; + const mR = bnR && bnR.data ? bnR.data.mults : null; + offen = !!(mR && typeof mR.CorporationSoftcap === "number"); + } catch (e) { offen = false; } + } + urteil.push(`${key} ${b === false ? "NEIN" : (offen ? "offen (BANK gruendet nach Einkommen)" : "unbekannt")}`); } L.push(` Reifegrad (daemonBereit): ${urteil.length ? urteil.join(" · ") : "alle bereit"}`); L.push(" Lesehilfe: NEIN = wird abgeschaltet und bleibt aus. unbekannt = Daten fehlen,"); diff --git a/scripts/SCHWARM-HELPERS.js b/scripts/SCHWARM-HELPERS.js index 9a111b4..1c7228d 100644 --- a/scripts/SCHWARM-HELPERS.js +++ b/scripts/SCHWARM-HELPERS.js @@ -1,5 +1,22 @@ /** - * SCHWARM-HELPERS.js — v5.16 + * SCHWARM-HELPERS.js — v5.18 + * + * v5.18 — corpBesteht(): EINE Antwort auf "gibt es eine Corp?" fuer QUEEN, + * DIAG und daemonBereit. Zwei Quellen, die schnellere zuerst: der INFO- + * Block (nur alle 6 min erneuert) und BANKs eigene Meldung corpExists auf + * BANK_OUT (jeden Takt). QUEEN v7.17 startet CORP nur noch, wenn eine + * Corp besteht (Spieler 27.09.2026: "corp auf on kostet ram?"). + * + * v5.17 — EINE BESTEHENDE CORP WIRD GESTEUERT (Entscheidung des Spielers, + * 26.09.2026). daemonBereit(CORP) fragte nur den Softcap (>= 0,75) - auch + * dann, wenn schon eine Corp stand. In BN15 lag so eine Corp mit $157b + * brach, und die Selbstverwaltung schaltete CORP jedes Mal wieder ab, wenn + * BANK es mit START:CORP eingeschaltet hatte. Jetzt wie bei Gang und + * Bladeburner: ZUERST der Bestand (INFO corp.exists), dann die Huerde. + * Ohne Corp: Softcap < 0,15 -> false (Engine sperrt), >= 0,75 -> true, + * dazwischen null - dort entscheidet BANK ueber die Gruendung (60-s-Regel), + * und der Schalter bleibt, wie er ist. + * Nachtrag 1: der Vertrag von null nennt jetzt beide Bedeutungen. * * v5.16 — DAS HANDLUNGSBUCH WIRD PRUEFBAR. chronik() nimmt ein siebtes * Feld: JSON mit ms (Epochenzeit) und dem, was der Aufrufer mitgibt - vor @@ -592,7 +609,7 @@ // 04.09.2026 waren das getrennte Freitexte und liefen auseinander: der // Kopf sagte eine Version, die Startmeldung im Log eine andere. Beim // Nachstellen eines Fehlers behauptet das Log damit etwas Falsches. -const VERSION = "5.16"; +const VERSION = "5.18"; // ============================================================================= @@ -2567,6 +2584,9 @@ export function readLiquidationRequest(ns) { // true Bedingung erfuellt ODER es gibt fuer diesen Daemon keine. // false Bedingung NACHWEISLICH nicht erfuellt -> darf ausgeschaltet werden. // null es gibt eine Bedingung, aber die Daten fehlen -> NICHT ANFASSEN. +// v5.17: bei CORP ohne Corp in einer schwachen Node (Softcap +// 0,15-0,75) auch: BANK entscheidet nach dem Einkommen. Ebenfalls +// nicht anfassen. // Raten waere hier eine Handlung, kein Zustand. // // DIE SPIELERWERTE KOMMEN VON AUSSEN, UND ZWAR AUS EINEM GUTEN GRUND. @@ -2596,9 +2616,13 @@ export function readLiquidationRequest(ns) { // Groessenordnung uebrig. Gleiche Schwelle wie BANK v5.3 // (CORP_SOFTCAP_LOHNT) — bewusst doppelt genannt, aber mit // derselben Zahl und derselben Begruendung an beiden Stellen. +// v5.17: gilt nur fuer die GRUENDUNG. Eine bestehende Corp +// ist immer bereit (Spieler 26.09.2026); unter 0,75 gruendet +// BANK nur, wenn $150b in 60 s hereinkommen. export const BEREIT_GANG_KARMA = -54_000; // Gang/data/Constants.ts:27 export const BEREIT_BLADE_STAT = 100; // Beitritt: alle vier Kampfwerte export const BEREIT_CORP_SOFTCAP = 0.75; // = BANK CORP_SOFTCAP_LOHNT +export const BEREIT_CORP_SOFTCAP_MIN = 0.15; // Corporation/helpers.ts:74 - darunter sperrt die Engine export const BEREIT_BLADE_AUG = "The Blade's Simulacrum"; // v5.10 — EINMAL FREIGESCHALTET IST NICHT DASSELBE WIE "BEDINGUNG ERFUELLT". @@ -2610,6 +2634,23 @@ export const BEREIT_BLADE_AUG = "The Blade's Simulacrum"; // // Deshalb wird ZUERST der Mitgliedsstand gefragt und erst danach die Huerde. // Andersherum haette ein Aug-Install eine laufende Gang "unreif" gemacht. +/** + * v5.18 — Besteht eine Corp? true = ja, false = INFO sagt nein (und BANK + * nicht ja), null = unbekannt. INFO erneuert den corp-Block nur alle 6 min; + * direkt nach einer Gruendung stuende dort noch "keine Corp". BANK meldet + * ihren Stand (corpExists) dagegen in jedem Takt. Eine Corp verschwindet + * innerhalb einer Node nie (nur beim BitNode-Wechsel, und dann sind alle + * Ports leer) - ein "ja" aus einer der beiden Quellen gilt also. + */ +export function corpBesteht(ns) { + let info = null; + try { info = readInfoBlock(ns, "corp", Infinity); } catch (e) { info = null; } + if (info && info.exists === true) return true; + try { const b = readBankInfo(ns); if (b && b.corpExists === true) return true; } catch (e) { /* weiter */ } + if (info && info.exists === false) return false; + return null; +} + /** Installierte Augmentierungen aus dem INFO-Block. null = nicht bekannt. */ function bereitAugsInstalliert(ns) { try { @@ -2680,10 +2721,18 @@ export function daemonBereit(ns, key, sp) { return true; } if (key === "CORP") { + // v5.17: ZUERST der Bestand. Wer eine Corp hat, laesst sie steuern - + // egal, ob BANK oder der Spieler gegruendet hat (Spieler 26.09.2026). + // v5.18: ueber corpBesteht (INFO ODER BANKs eigene Meldung). + try { if (corpBesteht(ns) === true) return true; } catch (e) { /* weiter zur Huerde */ } let m = null; try { const b = readInfoBlock(ns, "bn", Infinity); if (b) m = b.mults; } catch (e) { m = null; } if (!m || typeof m.CorporationSoftcap !== "number") return null; - return m.CorporationSoftcap >= BEREIT_CORP_SOFTCAP; + if (m.CorporationSoftcap < BEREIT_CORP_SOFTCAP_MIN) return false; // Engine sperrt die Gruendung + if (m.CorporationSoftcap >= BEREIT_CORP_SOFTCAP) return true; + // Schwache Node ohne Corp: ob gegruendet wird, entscheidet BANK nach + // dem Einkommen (60-s-Regel). Bis dahin weder an- noch abschalten. + return null; } return true; // keine Zusatzbedingung } diff --git a/scripts/SCHWARM-QUEEN.js b/scripts/SCHWARM-QUEEN.js index 1c74ca7..fec337c 100644 --- a/scripts/SCHWARM-QUEEN.js +++ b/scripts/SCHWARM-QUEEN.js @@ -1,5 +1,14 @@ /** - * SCHWARM-QUEEN.js — v7.16 + * SCHWARM-QUEEN.js — v7.17 + * + * v7.17 — CORP LAEUFT NUR, WENN ES EINE CORP GIBT. Nach dem Wechsel nach + * BN11 (Testspiel 27.09.2026) stand der Schalter an, home war auf 128 GB + * zurueckgesetzt, und die Queen hielt fuer ein CORP, das nur wartet, 5 GB + * frei - bis BANK die $150b beisammen hat, koennen Stunden vergehen. + * Muster wie BITNODE (v7.6): der Schalter ist die Absicht, die bestehende + * Corp der Ausloeser (HELPERS corpBesteht). Kein Umschalten, also kein + * Wettlauf mit BANKs START:CORP. Abschnitt 4c2 beendet ein schon laufendes + * CORP, wenn sicher keine Corp besteht (Uebergang von v7.16, Handstart). * * v7.16 — NACH DER SELBST-ERNEUERUNG LIEF ALTER CODE WEITER. * Jede Aenderung an PAYLOADS loest die Selbst-Erneuerung aus. Die neue @@ -497,13 +506,13 @@ // 04.09.2026 waren das getrennte Freitexte und liefen auseinander: der // Kopf sagte eine Version, die Startmeldung im Log eine andere. Beim // Nachstellen eines Fehlers behauptet das Log damit etwas Falsches. -const VERSION = "7.16"; +const VERSION = "7.17"; import { DAEMONS, PHASE, STATE_FILE, readManagedState, isDaemonEnabled, setDaemonEnabled, planFreigabe, writeOutField, - daemonBereit, bereitSpielerInfo, + daemonBereit, bereitSpielerInfo, corpBesteht, publishManagedState, PSERV_PREFIX, setPhase, getPhase, publishCapabilities, detectCapabilities, @@ -903,6 +912,14 @@ function shouldRun(ns, key, caps, state) { // beendet ihn Abschnitt 4c im selben Takt. if (key === "BITNODE") return !!planFreigabe(ns); + // v7.17 — CORP LAEUFT NUR, WENN ES EINE CORP GIBT (Spieler 27.09.2026: + // "corp auf on kostet ram?"). Dasselbe Muster wie BITNODE: der Schalter + // ist die Absicht, die bestehende Corp der Ausloeser. Gruenden ist BANKs + // Aufgabe; sobald die Corp steht, startet CORP im naechsten Takt. Ein schon + // laufendes CORP ohne Corp beendet Abschnitt 4c2. FORCIERT (2) greift + // weiter oben und startet trotzdem. + if (key === "CORP" && corpBesteht(ns) !== true) return false; + if (d.owner === "QUEEN") return true; if (S.wants[key]) return true; // Owner hat WANT gemeldet @@ -2174,6 +2191,16 @@ export async function main(ns) { stop(ns, key, "Schalter aus"); } + // ---------- 4c2. CORP OHNE CORP -> BEENDEN (v7.17) --------------------- + // shouldRun startet CORP nur noch, wenn eine Corp besteht. Ein schon + // LAUFENDES CORP ohne Corp bliebe aber stehen: der Uebergang von v7.16 + // (im Testspiel am 28.09. genau so gesehen) oder ein von Hand + // gestartetes. Nur bei SICHEREM Nein (false, nicht null - direkt nach + // dem Laden sind die Ports leer) und nie gegen ein forciertes CORP (2). + if (S.pids.CORP && state.CORP !== 2 && corpBesteht(ns) === false) { + stop(ns, "CORP", "keine Corp"); + } + // ---------- 4b. Veraltete Nutzlast? Dann beenden --------------------- // MUSS VOR planDeployments stehen. Der Plan wird aus dem Ist-Zustand // gebildet: ein Daemon, der erst danach beendet wird, taucht darin