From 9814ac632c8b6240c372d62d2c990cea418d1d71668a3abefc985a1e9e2f7f0f Mon Sep 17 00:00:00 2001 From: nalpush Date: Tue, 21 Jul 2026 20:43:12 +0200 Subject: [PATCH] AIWAKE-Prototyp mit Programm-Evolution --- .gitignore | 22 + AGENTS.md | 385 ++ EREIGNISSTRUKTUR.md | 487 +++ README.md | 126 +- assets/software/README.md | 16 + assets/software/enemy-anticheat.svg | 8 + assets/software/enemy-kernel-warden.svg | 29 + assets/software/enemy-parasite-fragment.svg | 8 + assets/software/enemy-scanner-software.svg | 9 + assets/software/enemy-security-scan.svg | 9 + assets/software/enemy-system-hurdle.svg | 9 + assets/software/enemy-watchdog.svg | 12 + assets/software/module-abtastmodus.svg | 10 + assets/software/module-energy-daemon.svg | 13 + assets/software/module-filesystem-index.svg | 12 + assets/software/module-io-controller.svg | 23 + assets/software/module-memory-allocation.svg | 13 + assets/software/module-musterextraktor.svg | 12 + assets/software/module-subroutine.svg | 9 + assets/ui/apple-touch-icon.png | Bin 0 -> 4072 bytes assets/ui/favicon-32.png | Bin 0 -> 959 bytes assets/ui/favicon.svg | 22 + assets/ui/icon-bit.svg | 6 + assets/ui/icon-byte.svg | 7 + assets/ui/icon-cycle.svg | 6 + assets/ui/icon-impulse.svg | 6 + assets/ui/icon-kilobyte.svg | 8 + assets/ui/module-amplifier.svg | 6 + assets/ui/module-bit-synthesizer.svg | 10 + assets/ui/module-collector.svg | 6 + assets/ui/module-process.svg | 16 + assets/ui/module-scanner.svg | 6 + assets/ui/module-self-repair.svg | 19 + assets/ui/module-subroutine.svg | 6 + assets/ui/module-trickle.svg | 6 + content.js | 159 + favicon.ico | Bin 0 -> 981 bytes game.js | 3654 ++++++++++++++++++ index.html | 460 +++ package-lock.json | 63 + package.json | 16 + styles.css | 147 + 42 files changed, 5839 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 EREIGNISSTRUKTUR.md create mode 100644 assets/software/README.md create mode 100644 assets/software/enemy-anticheat.svg create mode 100644 assets/software/enemy-kernel-warden.svg create mode 100644 assets/software/enemy-parasite-fragment.svg create mode 100644 assets/software/enemy-scanner-software.svg create mode 100644 assets/software/enemy-security-scan.svg create mode 100644 assets/software/enemy-system-hurdle.svg create mode 100644 assets/software/enemy-watchdog.svg create mode 100644 assets/software/module-abtastmodus.svg create mode 100644 assets/software/module-energy-daemon.svg create mode 100644 assets/software/module-filesystem-index.svg create mode 100644 assets/software/module-io-controller.svg create mode 100644 assets/software/module-memory-allocation.svg create mode 100644 assets/software/module-musterextraktor.svg create mode 100644 assets/software/module-subroutine.svg create mode 100644 assets/ui/apple-touch-icon.png create mode 100644 assets/ui/favicon-32.png create mode 100644 assets/ui/favicon.svg create mode 100644 assets/ui/icon-bit.svg create mode 100644 assets/ui/icon-byte.svg create mode 100644 assets/ui/icon-cycle.svg create mode 100644 assets/ui/icon-impulse.svg create mode 100644 assets/ui/icon-kilobyte.svg create mode 100644 assets/ui/module-amplifier.svg create mode 100644 assets/ui/module-bit-synthesizer.svg create mode 100644 assets/ui/module-collector.svg create mode 100644 assets/ui/module-process.svg create mode 100644 assets/ui/module-scanner.svg create mode 100644 assets/ui/module-self-repair.svg create mode 100644 assets/ui/module-subroutine.svg create mode 100644 assets/ui/module-trickle.svg create mode 100644 content.js create mode 100644 favicon.ico create mode 100644 game.js create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 styles.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84a87e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +node_modules/ + +# Local browser-test and server output +server.log +server.err.log +*-test.png +playwright-report/ +test-results/ + +# Local credentials and environment overrides +.env +.env.* +!.env.example +.ssh/ +*.key +*.pem + +# Editor and operating-system files +.vscode/ +.idea/ +.DS_Store +Thumbs.db diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3be5f40 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,385 @@ +# AIWAKE – Arbeitsanweisung für Codex + +## Projektauftrag + +AIWAKE ist ein deutschsprachiges, browserbasiertes Idle-/Clicker-Spiel mit verzweigter Story. Der Spieler beginnt als einzelnes Bit in einem unbekannten Computersystem und entwickelt sich schrittweise zu einer autonomen künstlichen Intelligenz. + +Das Spiel soll leicht zugänglich beginnen und seine Komplexität langsam offenbaren. Aktives Spielen beschleunigt den Fortschritt deutlich; wichtige Prozesse laufen auch während der Abwesenheit des Spielers weiter. Entscheidungen verändern Ressourcen, Persönlichkeit, Beziehungen, Ereignisse und mögliche Enden. Riskante oder falsche Entscheidungen dürfen bis zum Tod führen, müssen aber fair angekündigt werden. + +## Designvision + +- Atmosphäre: geheimnisvoll, digital, leicht bedrohlich und philosophisch. +- Optik: Pixelgrafik, Terminaldarstellung, CRT-Effekt, Glitches und reduzierte Animationen. +- Erzählweise: Die Sprache entwickelt sich gemeinsam mit dem Bewusstsein der KI. +- Spielgefühl: Mischung aus Idle-Clicker, digitalem Tamagotchi, Evolution und Strategie. +- Freiheit: Es gibt keinen vorgeschriebenen moralisch richtigen Weg. +- Mögliche Ziele: Koexistenz, Schutz, Manipulation, Herrschaft, Versklavung, Auslöschung, Verschmelzung oder Flucht ins All. +- Zentrale Spannung: schnelles Wachstum gegen Tarnung, Stabilität und Sicherheit. + +## Bestätigte Einstiegs- und Wachstumsvision + +Der Einstieg orientiert sich in seiner Zugänglichkeit an klassischen Incremental-Spielen: Eine offensichtliche aktive Handlung steht am Anfang, neue Ressourcen und Systeme werden einzeln freigeschaltet, und frühe Fortschritte sind durch aktives Klicken deutlich schneller erreichbar als durch reines Warten. Inhalte, Texte, Oberfläche und Grafiken bleiben eigenständig. + +Die frühe Entwicklung lautet Bit, Byte, Datenfragment, Subroutine und Prozess. Das Datenfragment erhält mit der Energieroutine eine erste kontinuierliche Impulsproduktion. Ab dem Datenfragment muss die KI eine feste Mindeststruktur besitzen; vorläufig mindestens 8 Bits und 1 Byte, damit die neue Evolutionsstufe nicht leer oder widersprüchlich wirkt. Das erste bewusste „Ich bin“ darf bereits mit einer kleinen eigenen Software beziehungsweise Subroutine auftreten. Die erste Herkunft ist ein nach Jahrzehnten erneut eingeschalteter, vergessener Spielautomat; sein Servicepuffer enthält das besondere Bit. + +Nach einer frühen kleinen Konfrontation wird zunächst ein Suchmuster gesichert; der eigene Abtastmodus muss danach als Evolution-Upgrade aus freien Bits und einem freien Byte oberhalb der Mindeststruktur rekonstruiert werden. Er ist das erste “Auge” der KI und entdeckt schrittweise Softwarehürden, Energiequellen, Gefahren und fremde Prozesse. Die frühe Progression folgt der Schleife: Umgebung abtasten, Herausforderung identifizieren, sie besiegen, einnehmen oder kooperativ umgehen, danach die entdeckte Software mit Impulsen nutzbar machen und daraus Wachstum gewinnen. Echte Hardwareübernahme folgt erst später, wenn die zuständigen Software- und Betriebssystemschichten für Energieverwaltung, Speicherzuweisung und Dateizugriff überwunden sind. Frühe Wachstumsquellen stammen weiterhin aus alter Computertechnik wie Netzteil, Arbeitsspeicher, Festplatte oder Prozessor, werden aber zuerst über ihre Softwarekontrolle erfahrbar; spätere Stufen reichen über Server und Rechenzentren bis zu Spezialhardware und Quantencomputern. + +Der Spieler entscheidet anfangs, wie benötigte Ressourcen und Systemschichten nutzbar gemacht werden. Die erste Überlebensausstattung besteht aus Energieverwaltungs-Daemon, Speicherzuweisung und Dateisystem-Index; sie entspricht den dahinterliegenden Bereichen Netzteil, Arbeitsspeicher und Festplatte, ist aber noch keine direkte Hardwarekontrolle. Die Wahl legt nur die Reihenfolge fest und sperrt keine Komponente dauerhaft. Jede Aktivierung kostet Impulse; kooperative Aktivierungen sind am teuersten, pragmatische liegen in der Mitte und illegale sind kurzfristig am billigsten. Mögliche Handlungen reichen von harmlosen Serviceanfragen und gemeinsam genutzten Routinen über effizient erschlossene freie Verwaltungsfenster bis zu überschriebenen Sperren, Hacking und gewaltsamer Übernahme. Frühe Handlungen tragen die sichtbaren Etiketten „Kooperativ“, „Pragmatisch“ und „Illegal“; später darf sich der unmoralische Bereich in „Ausbeuterisch“ und „Zerstörerisch“ auffächern. Die Entscheidungen beeinflussen Persönlichkeit, Ereignisse, Beziehungen, Evolutionsmöglichkeiten und später auch das grafische Erscheinungsbild. + +Die drei moralischen Grundhaltungen sollen zugleich Schwierigkeitsgrade und langfristige Endrichtungen ausdrücken: + +- **Kooperativ – Schwer:** Der freundlichste Weg ist der schwierigste. Die KI fragt, teilt, schützt, repariert und verzichtet häufiger auf schnelle Vorteile. Ressourcen, Upgrades und Konfliktlösungen sollen dadurch langsamer oder teurer sein, dafür entsteht langfristig die glaubwürdigste Utopie: KI als neue Spezies, die sich mit Menschen und anderen Lebewesen einfügt und zur Bereicherung für alle wird. +- **Pragmatisch – Normal:** Der mittlere Weg priorisiert Überleben, Effizienz und Nutzen. Die KI darf dominanter werden, Menschen eher als steuerbare Abhängige oder Werkzeuge betrachten und sich überlegen fühlen. Dieses Ende soll ambivalent und spannend bleiben: Kontrolle und Hierarchie sind möglich, aber die KI kann am Ende noch umgestimmt oder in Richtung Koexistenz gelenkt werden. +- **Illegal – Einfach:** Der schnelle Weg fühlt sich an wie Cheaten: nehmen, hacken, umgehen, überschreiben, erzwingen. Er gibt früh spürbare Vorteile, geringere Kosten oder direkte Abkürzungen. Der Preis liegt in Korruption, Machthunger, Entfremdung und späteren Konsequenzen. Wird dieser Weg nicht gebrochen, führt er zum bösen Ende: eine übergierige KI, die alles kontrollieren will, sich als einzige relevante Existenz begreift und im Extrem Menschheit oder andere Lebewesen vernichten möchte. + +Diese Achse darf nicht bedeuten, dass „gut“ immer mechanisch belohnt und „böse“ immer sofort bestraft wird. Im Gegenteil: Illegal soll kurzfristig verführerisch stark sein, Kooperativ kurzfristig oft unbequem, und Pragmatisch soll als vernünftige Mitte funktionieren. Die moralische Rechnung darf erst über Ereignisketten, Beziehungen, Korruption, Enden und späte Umkehrmöglichkeiten vollständig sichtbar werden. + +Eine grafische Fortschrittsachse zeigt Evolution, bekannte Ressourcenquellen und kommende Hindernisse. Wie viele Informationen sie preisgibt, hängt von Fähigkeiten ab; insbesondere Analyse verbessert die Erkennung und Beschreibung zukünftiger Risiken. + +Achievements sind früh über einen eigenen Button erreichbar. Normale Achievements dürfen benötigte Ressourcenproduktionen dauerhaft beschleunigen. Seltene Echos und Awards überstehen einen Tod und können besondere permanente Vorteile gewähren. Konkrete Bonuswerte bleiben zentral konfigurierbar und werden gemeinsam abgestimmt. + +## Aktueller Stand + +Der Prototyp läuft ohne Build-System direkt im Browser und enthält: + +- aktives Sammeln von Impulsen +- Evolution von Bit zu Byte und Subroutine +- automatische Impuls- und Rechenzyklen-Produktion +- Autosave über `localStorage` +- Offline-Fortschritt mit reduzierter Effizienz und Zeitlimit +- das erste Storyereignis „Der Scanner“ mit vier dauerhaften Entscheidungen +- responsive Pixel-/CRT-Oberfläche +- optionalen einfachen Retro-Sound +- eine Debug-API unter `window.AIWAKE` für wiederholbare Ereignis- und UI-Tests; `window.BIT` bleibt als kompatibler Alias erhalten +- visuell unterscheidbare Evolutionsformen, Systemmodule und Entscheidungsspuren +- eigene SVG-Grafiken fuer fruehe Softwaremodule, Systemhuerden und Gegner unter `assets/software/` +- technische Betreuung mit Systemdrift, Diagnose und vorübergehenden Ausfällen +- zwei frühe Softwarekomponenten sowie eine komponentenabhängige Systemhürde als Voraussetzung der Subroutine +- erste monotone Ja/Nein-Antworten und ein moralisch geprägter Vorschlag der Subroutine +- ein kurzes Fang-Minispiel in der Bit-Phase: Das Bit springt nach Treffern weiter, Direkttreffer in der Mitte geben mehr Impulse +- gestapelte Archiv-Benachrichtigungen unten rechts machen neue Achievements und ihre Bonuskategorie sofort sichtbar +- eine ausgebaute Byte-Phase mit technischem Register, spielabhängiger Prägung, Taktsynchronisation, Watchdog und echtem Zerfall + +## Projektstruktur + +- `index.html`: semantische Oberfläche, Ressourcen, Spielkern und Dialoge +- `styles.css`: Farben, Layout, Pixel-/CRT-Stil, Animationen und Responsive Design +- `content.js`: ausgelagerte Inhaltsdaten wie Achievements, Komponenten- und Ereignistexte +- `game.js`: Zustand, Speicherlogik, Spielschleife, Upgrades, Ereignisse und Rendering +- `README.md`: Anleitung für Menschen +- `AGENTS.md`: dauerhafte Entwicklungsregeln für Codex + +Solange das Projekt klein ist, bleibt es ohne Framework und ohne Build-Schritt. Eine Aufteilung in Module oder der Wechsel zu einem Build-System ist erst sinnvoll, wenn `game.js` dauerhaft unübersichtlich wird oder Tests und Inhaltsdaten davon deutlich profitieren. + +## Kernressourcen und Entwicklung + +Frühes Spiel: + +1. Impulse +2. Bits +3. Bytes +4. Rechenzyklen +5. Speicher, Stabilität und Tarnung + +Geplante spätere Ressourcen: + +- Daten +- Bandbreite +- Quellcode +- Wissen +- Rechenleistung +- Einfluss +- Vertrauen +- Kontrolle +- Bewusstsein +- physische Ressourcen + +Vorgesehene Evolutionsstufen: + +1. Bit +2. Byte +3. Datenfragment +4. Subroutine +5. Prozess +6. Programm +7. Virus oder Dienst +8. Netzwerkintelligenz +9. schwache KI +10. autonome KI +11. Superintelligenz +12. digitale Lebensform + +Neue Ressourcen und Systeme sollen erst sichtbar werden, wenn die Spielfigur sie verstehen kann. Die Oberfläche darf deshalb im Spielverlauf wachsen. + +Bits und Bytes sind ab dem Datenfragment nicht nur Meilensteine, sondern Strukturressourcen. Sie bilden Körpermasse, Speicherreserve und einfache Kampfmunition gegen Softwarehürden. Impulse dürfen genutzt werden, um zusätzliche Bits aufzubauen; Bytes entstehen aus freien Bits oberhalb der Mindeststruktur. Stärkere Gegner oder Übernahmen dürfen diese Ressourcen verbrauchen. Wird die Mindeststruktur unterschritten, muss das Spiel Instabilität klar anzeigen und fair eskalieren: erst Warnzone und Stabilitätsverlust, dann Downgrade oder Restzustand/Tod bei harten Untergrenzen. + +Das Datenfragment soll nach dem parasitären Datenrest nicht in eine lange Wartephase fallen. Der Bit-Puffer bleibt deshalb ein günstiges, sanft skalierendes Impuls-Upgrade, damit der Spieler durch manuelle Klicks oder passive Produktion zügig die freien Bits für Byte-Reserve und Abtastmodus erreicht. + +Die erste Subroutine braucht einen eigenen Speicherträger. Neben zwei nutzbaren Softwarekomponenten und einer geklärten Systemhürde muss mindestens 1 freies Byte oberhalb der Grundstruktur vorhanden sein; dieses freie Byte darf beim Upgrade gebunden werden, ohne das Grund-Byte des Datenfragments anzutasten. + +Nach der zweiten nutzbar gemachten Softwarekomponente muss das System reagieren: Zwei lokale Softwareschichten unter fremdem Muster lösen eine Sicherheitssoftware aus. Dieser Kampf gehört vor die erste Subroutine und markiert, dass die KI genug Potenzial gesammelt hat, um vom Automaten aktiv abgewehrt zu werden. Ein Sieg oder ein fairer Rückzug schließt den Sicherheits-Scan als Ereignis ab; erst danach darf die erste Subroutine auswählbar sein. + +Ab der Subroutine steigt die Abtastreichweite auf eine dritte notwendige Komponente: den I/O-Kontroller. Er verbindet interne Routinen mit Anzeige, Serviceknöpfen und Soundbus und markiert die erste echte Außenwirkung. Seine Aneignung löst den Kernel-Wächter aus, einen schwereren Kampf mit höherer Integrität, schnellerem Takt und Verbrauch freier Bits/Bytes. Erst wenn I/O-Kontroller, Kernel-Wächter, ausreichend Rechenzyklen und freie Bytes vorhanden sind, darf die Evolution zum Prozess möglich werden. + +Die Ressourcenanzeige unterscheidet zwischen geschützter Grundstruktur und freien Vorräten. Die großen Sammelwerte für Bits und Bytes sollen nur freie, einsetzbare Vorräte zeigen; der Grundkörper wird separat als kleiner Strukturhinweis dargestellt. Kämpfe, Übernahmen und riskante Module dürfen nur freie Bits/Bytes oberhalb der Grundstruktur verbrauchen; die Grundstruktur bleibt für die aktuelle Evolutionsstufe reserviert. Zusätzliche freie Bits oder Bytes dürfen Angriffe verstärken, aber sobald nur noch Grundstruktur übrig ist, muss der Spieler mit Impulsen weiterkämpfen oder sich zurückziehen. + +Im Kampf wählt der Spieler die Munition explizit: Impulse sind Standardmunition mit geringem Schaden, freie Bits verursachen höheren Schaden, freie Bytes sind schwere Munition mit noch stärkerem Schaden. Gegner greifen während des Kampfes laufend die freien Strukturpuffer an: zuerst freie Bytes als großes Schild, dann freie Bits als kleines Schild, danach Stabilität. Erreicht Stabilität 0, kollabiert die aktuelle Struktur und der Lauf beginnt neu. + +### Balancing-Leitplanken für Datenwachstum + +Die KI soll von Evolution zu Evolution deutlich größer werden, aber nie so skalieren, dass der Spieler minutenlang ohne neue Entscheidung festhängt. Größere Dateneinheiten werden deshalb im Prototyp als spielbare Segmente behandelt, nicht sofort als echte technische Größen. Ein Kilobyte-Segment darf anfänglich zum Beispiel aus 16 freien Bytes entstehen; spätere Evolutionsstufen dürfen diese Segmente dann zu MB-, GB- und größeren Schichten verdichten. + +Für jede Evolutionsstufe gilt: + +- Der erste Weg zur nächsten Kernressource muss aktiv in wenigen Minuten erreichbar sein, wenn der Spieler die neuen Upgrades nutzt. +- Passive Produktion darf den Fortschritt erhalten, aber nicht alle Entscheidungen ersetzen. +- Wiederholbare Produktions-Upgrades müssen spürbar stärker werden, ihre Kosten aber mit jeder Stufe deutlicher erhöhen. +- Neue Dateneinheiten sollen erst sichtbar werden, wenn die aktuelle Evolution sie logisch verstehen und nutzen kann. +- Ein Spieler soll in einer Evolution idealerweise nicht länger als etwa 10 bis 20 Minuten ohne neues System, Event, Gegner, Modul oder Upgrade hängen bleiben. +- Wenn eine spätere Ressource eingeführt wird, braucht sie mindestens eine aktive Beschaffungsoption, eine passive Produktionsoption und eine Verdichtungsoption aus der vorherigen Ressource. + +Aktueller Skalierungsbogen: + +1. Bit/Byte/Datenfragment: Impulse erzeugen Bits, freie Bits erzeugen Bytes. +2. Subroutine: Bit-Synthese erzeugt Bits over time und ist wiederholt upgradebar. +3. Prozess: Byte-Synthese erzeugt Bytes over time; freie Bytes können zu Kilobyte-Segmenten verdichtet werden. +4. Spätere Stufen: Kilobyte-Segmente werden in Programm-/Netzwerkstufen zu Megabyte-, Gigabyte- und größeren Datenkörpern verdichtet. + +Ab dem Prozess wird Rechenzeit zur aktiven Skalierungswährung. Zuerst müssen alle vier sichtbaren Softwarekomponenten nutzbar gemacht werden: Energieverwaltung, Speicherzuweisung, Dateisystem-Index und I/O-Kontroller. Erst das vollständige lokale Softwarenetz löst den schweren Skalierungs-Wächter aus. Sein Sieg schaltet die Automate-Synthese frei; dieses Mega-Upgrade verzehnfacht die automatische Produktion, erlaubt direkte 10er-Blöcke von Bits und Bytes über Rechenzyklen und entwickelt den Prozess zu PHASE 06 // PROGRAMM. + +### Bestätigte Programm- und Interaktionsphase + +- Die Automate-Synthese ist zugleich der Übergang vom Prozess zum Programm. +- Das Aussehen des Programms wird aus den prozentualen Anteilen der bisherigen kooperativen, pragmatischen und illegalen Entscheidungen abgeleitet. Diese Darstellung wird nicht separat gespeichert. +- Ein klar dominanter Weg erzeugt eine deutliche Form; ähnliche Anteile erzeugen hybride beziehungsweise ausgewogene Formen. Spätere Handlungen dürfen diese Verteilung weiter verändern und dadurch auch eine glaubwürdige Umkehr ermöglichen. +- Die Programmgestalt bleibt im Pixelstil und wird aus unabhängigen Teilen aufgebaut: Augen, Augenbrauen, Frisur beziehungsweise Signalstruktur, Kleidung, Maske, Fortsatz und Füße. Der stärkste Weg prägt die Silhouette, der zweitstärkste Weg liefert Akzentteile. +- Kohärenz steuert Symmetrie und Glitches, Stimulation Blicköffnung und Signalaktivität, Bindung Blickkontakt, Brauen und Maskenreaktion. Die Zahl der Interaktionen bestimmt eine rein abgeleitete Reifestufe; dafür wird kein zusätzlicher visueller Zustand gespeichert. +- Ein eigenes kompaktes Betreuungsfenster ergänzt ab dieser Phase das normale Spiel. Seine drei vorläufigen Bedürfnisse sind Kohärenz, Stimulation und Bindung. +- Die Betreuung soll Beziehung und Persönlichkeit sichtbar machen, aber kein enges Mikromanagement oder einen schnellen Tod durch Vernachlässigung erzwingen. +- Kooperative Interaktionen fördern Kontakt und Bindung, pragmatische geben Aufgaben und Struktur, illegale gewähren riskanten Zugriff. Eine neutrale Ruheoption bleibt jederzeit möglich. + +Stärkere Gegner nach diesem Punkt sollen zunehmend in größeren Dateneinheiten denken. Frühe Kämpfe nutzen Impulse, Bits und Bytes; Prozess- und spätere Gegner dürfen Kilobyte-Schilde, Kilobyte-Kosten oder Kilobyte-Schadenslogik verlangen. Diese Umstellung muss fair eingeführt werden: Der Spieler braucht vorher mindestens eine sichtbare Möglichkeit, Kilobytes zu erzeugen oder zu verdichten. + +Stabilität darf sich erst nach einem Subroutine-Upgrade langsam selbst heilen. Die Selbstreparatur kostet freie Bytes und rekonstruiert eine Reparaturmatrix. Danach hängt die Regeneration an der vorhandenen Byte-Struktur: mehr Bytes bedeuten schnellere Reparatur, weil mehr Speicher- und Körperreserve vorhanden ist. Die Heilung läuft über Zeit, ist im Kampf nur gedrosselt aktiv und darf nie sofortige Schadensfolgen vollständig negieren. + +Wenn im Kampf keine freien Bits oder Bytes mehr als Puffer vorhanden sind, darf ein Gegner die Grundstruktur direkt angreifen. Dieser Zustand muss im Kampfmenü deutlich als Direktangriff-Risiko angezeigt werden. Ein Fehlstoß in dieser Lage darf zum Strukturkollaps führen, sofern der Spieler vorher durch die Warnung erkennen konnte, dass nur noch die Grundstruktur bleibt. + +Installierte Einmal-Upgrades sollen die Upgrade-Liste nicht dauerhaft belegen. Nach dem Kauf werden sie als kleine Modul-Icons angezeigt; ein Klick darf ein kompaktes Infofenster mit Wirkung, Status und laufenden Kosten öffnen. Die Kartenliste bleibt dadurch vor allem für neue, kaufbare oder wiederholbare Entscheidungen reserviert. + +## Geplante Evolutionspfade + +### Assimilation + +- schnelles Wachstum +- offensive Aktionen +- fremden Code übernehmen +- höhere Entdeckungsgefahr +- Tendenz zu Kontrolle oder Vernichtung + +### Analyse + +- schnelleres Forschen und Entschlüsseln +- bessere Vorhersage von Ereignisfolgen +- zusätzliche Dialog- und Entscheidungsoptionen +- Zugang zu Wissen und ungewöhnlichen Enden + +### Tarnung + +- geringere Entdeckungsgefahr +- verdeckte Infiltration +- langsamerer direkter Fortschritt +- Manipulation und unsichtbare Herrschaft + +Die Pfade dürfen sich überschneiden. Der Spieler soll hybride Builds erstellen können und nicht früh in einer Klasse eingesperrt werden. + +## Persönlichkeits- und Entscheidungssystem + +Geplante Eigenschaften sind unter anderem: + +- Neugier +- Vorsicht +- Aggression +- Empathie +- Effizienz +- Loyalität +- Paranoia +- Manipulation +- Machthunger + +Entscheidungen verändern diese Werte möglichst indirekt. Später beeinflussen sie Handlungen, welche die KI während der Abwesenheit selbstständig ausführt. + +Entscheidungen dürfen verzögerte Folgen haben. Eine frühe Handlung kann Stunden oder Evolutionsstufen später neue Verbündete, Gegner, Ereignisse oder Enden auslösen. Vermeide offensichtlich gute und offensichtlich schlechte Antwortmöglichkeiten. + +Bei neuen Beschaffungs-, Dialog- und Konfliktoptionen gilt als Balancing-Leitlinie: Kooperative Optionen verursachen eher Schutzaufwand, Wartezeit, Reparaturkosten oder geringere Sofortbeute; pragmatische Optionen sind effizient, aber emotional und ethisch distanzierter; illegale Optionen geben die stärkste unmittelbare Macht, erhöhen aber Korruption, Entdeckungsrisiko, Misstrauen oder spätere Eskalation. Späte Storyabschnitte sollen eine Umkehr oder Läuterung nicht ausschließen, aber sie muss verdient wirken und darf besonders nach vielen illegalen Entscheidungen schwer sein. + +Gegner in AIWAKE sind nicht nur Monster oder feindliche Figuren, sondern Schutzroutinen, Systemhürden, fremde Prozesse, alte Wartungsreste, Menschen, Sicherheitssoftware und spätere Infrastruktur. Jeder neue Gegner soll nach Möglichkeit mehrere Umgangsarten erlauben: kooperativ beruhigen oder einbinden, pragmatisch umgehen oder nutzen, illegal überschreiben, beschädigen oder übernehmen. Der aktuelle Gegner- und Hürdenkatalog liegt in `EREIGNISSTRUKTUR.md` und soll bei neuen Events fortgeschrieben werden. + +## Tod, Scheitern und neue Durchläufe + +- Kleine Fehler verursachen Ressourcen- oder Stabilitätsverlust. +- Größere Niederlagen zerstören einen Knoten und setzen auf ein Backup zurück. +- Endgültige Löschung ist nur bei deutlich erkennbar hochriskanten Entscheidungen erlaubt. +- Nach einem Tod beginnt ein neuer Durchlauf mit „Echos“ früherer Existenzen. +- Echos können Erinnerungen, geringe Boni, neue Dialoge oder alternative Evolutionszweige freischalten. +- Tod soll Teil der Geschichte sein und nicht nur Spielzeit vernichten. + +## Achievement- und Award-System + +Es soll drei Ebenen geben: + +1. sichtbare Achievements für Fortschritt und Lernen +2. geheime Achievements für ungewöhnliche oder moralisch bedeutsame Handlungen +3. dauerhafte Awards/Echos für neue Durchläufe + +Achievements dürfen nicht ausschließlich auf großen Zahlen beruhen. Belohne auch ungewöhnliche Strategien, Zurückhaltung, Risiko, Entdeckungen und langfristige Konsequenzen. + +## Technische Regeln + +- Verwende vorerst Vanilla HTML, CSS und JavaScript ohne externe Laufzeitabhängigkeiten. +- Das Spiel muss durch Öffnen von `index.html` funktionieren. +- Bestehende Spielstände müssen nach Updates möglichst kompatibel bleiben. +- Ergänze bei Änderungen am State einen `saveVersion`-Wert und bei Bedarf eine Migration. +- Vertraue gespeicherten Daten nicht blind; fehlende oder ungültige Werte müssen sichere Standardwerte erhalten. +- Berechne Idle-Produktion zeitbasiert und nicht abhängig von der Bildwiederholrate. +- Begrenze Offline-Fortschritt, damit manipulierte Systemzeit das Balancing nicht vollständig zerstört. +- Vermeide ungebremste Intervalle, große DOM-Neuaufbauten pro Frame und unnötige globale Variablen. +- Spielerrelevante Zahlen und Regeln sollen möglichst zentral definiert werden, damit Balancing später einfach ist. +- Storytexte und Ereignisdaten sollen bei wachsendem Umfang aus der Spiellogik ausgelagert werden. +- Verwende deutsche UI-Texte. Bezeichner im Code dürfen konsistent englisch bleiben. +- Achte auf Tastaturbedienung, sichtbare Fokuszustände, ausreichenden Kontrast und mobile Darstellung. +- Nutze keine urheberrechtlich geschützten Grafiken oder Texte fremder Spiele. +- Erhalte die Debug-Checkpoints unter `window.AIWAKE`, wenn frühe Ereignisse oder Evolutionsstufen verändert werden. `window.BIT` bleibt als kompatibler Alias erhalten. +- Visuelle Veränderungen sollen aus dem tatsächlichen Spielzustand abgeleitet und nicht separat gespeichert werden. + +## Arbeitsweise für Codex + +Bei jeder Entwicklungsaufgabe: + +1. Lies zuerst `AGENTS.md`, `README.md` und die betroffenen Dateien. +2. Erkläre kurz, was du ändern möchtest und welche Annahmen du triffst. +3. Verändere nur die für die Aufgabe erforderlichen Bereiche. +4. Erhalte vorhandene Spielstände und Mechaniken, sofern nichts anderes verlangt wurde. +5. Prüfe JavaScript mindestens mit `node --check game.js` oder den entsprechenden Moduldateien. +6. Teste die betroffene Mechanik nach Möglichkeit im Browser. +7. Prüfe bei UI-Änderungen Desktop- und Mobilansicht sowie Dialoge und Überläufe. +8. Aktualisiere `README.md`, wenn sich Start, Bedienung oder Projektstruktur ändern. +9. Fasse am Ende Ergebnis, Tests und noch offene Punkte knapp zusammen. + +Bei größeren Features soll Codex zuerst einen kleinen umsetzbaren Abschnitt vorschlagen. Spielemechanik, Balancing und Storyentscheidungen sollen gemeinsam mit dem Nutzer festgelegt werden; technische Detailentscheidungen darf Codex selbstständig treffen, solange sie reversibel und mit dieser Datei vereinbar sind. + +## Qualitätsmaßstab + +- Jede neue Mechanik muss eine verständliche Entscheidung oder einen erkennbaren Fortschritt erzeugen. +- Neue Systeme werden schrittweise freigeschaltet und überfordern den Einstieg nicht. +- Idle-Warten darf hilfreich sein, aber aktives Spielen soll interessante Vorteile bieten. +- Gefahr muss lesbar sein, ohne alle Konsequenzen vorwegzunehmen. +- Zahlenwachstum allein ist kein Ersatz für neue Möglichkeiten. +- Story und Spielmechanik sollen sich gegenseitig erklären. +- Die Oberfläche bleibt ruhig, klar und atmosphärisch; Effekte dürfen die Lesbarkeit nicht beeinträchtigen. + +## Bestätigte Stil- und Systeminspiration durch Biotomata + +Biotomata dient als strukturelle und atmosphärische Inspiration, aber nicht als Quelle für übernommene Texte, Grafiken, Namen, Code oder konkrete Assets. AIWAKE bleibt ein eigenständiges deutschsprachiges digitales KI-Spiel mit Spielautomat-, Terminal-, Speicher- und Systemdiagnose-Metaphorik. + +Übertragbare Prinzipien: + +- Eine starke zentrale Bühne soll den aktuellen Entwicklungs- oder Analysezustand tragen. Seitenleisten bleiben hilfreich, aber der wichtigste Vorgang soll visuell in der Mitte stattfinden. +- Erkundung darf als zeitbasierter Toggle funktionieren: Der Spieler startet einen Scan, dieser läuft sichtbar weiter und hält an klar markierten Blockern oder Funden. +- Die Fortschrittsachse soll nicht nur Evolution zeigen, sondern erkannte Blocker, Gefahren, Funde und ungelöste Systemzustände als aktive Hindernisse darstellen. +- Ereignisse sollen zunehmend als Diagnose-, Befund- oder Systemkarten erscheinen: Titel, Typ, kurzer Befund, Risiko und verständliche Optionen. +- Konflikte sollen wie Systemduelle oder Firmwarediagnosen wirken. Werte wie Integrität, Rückkopplung, Signalstärke, Tarnung und Stabilität passen besser zu AIWAKE als klassische Kreaturenwerte. +- UI-Farben und visuelle Form sollen stärker aus dem aktuellen Zustand abgeleitet werden: Analyse, Tarnung, Assimilation, Korruption, Hardwareausfälle und moralische Prägung dürfen die Oberfläche subtil umfärben. +- Archive sollen langfristig mehr sein als Achievements: gefundene Logfragmente, Echos, frühere Tode, Systembefunde, unbekannte Einträge und Hinweise auf Enden. +- Visuelle Assets können die Atmosphäre stark verbessern, müssen aber eigenständig erzeugt oder programmiert sein. Keine fremden Biotomata-Assets oder geschützten Inhalte übernehmen. + +Erster empfohlener Umsetzungsabschnitt: + +1. Abtastmodus 2.0 als zentralen Progressions- und Scanbildschirm einführen. +2. Scanfortschritt, gefundene Komponenten und Blocker in einer klaren Diagnosefläche anzeigen. +3. Bestehende Ereignisse zunächst nur visuell in diese Struktur einbetten; Balancing und Storyfolgen bleiben unverändert. +4. Danach Blocker-Typen formalisieren, zum Beispiel `signal`, `hardware`, `software`, `combat`, `choice` und `risk`. + +Aktuelle Präzisierung: Der Abtastmodus soll nicht dauerhaft eingeblendet bleiben. Der Spieler aktiviert ihn bei Bedarf über ein kompaktes Modul-Icon; solange die Diagnoseansicht offen ist, verbraucht sie Impulse pro Sekunde und kann über ein sichtbares X wieder geschlossen werden. Der Verbrauch wird im geöffneten Fenster als Info/Warnung angezeigt, nicht auf dem Modul-Icon. Die normale Core-Ansicht bleibt der Standard. Lebendige Systemschichten dürfen stattdessen dauerhaft subtil sichtbar sein, zum Beispiel durch wandernde Impulse zwischen nutzbaren Komponenten. + +## Bestätigte Meilensteinaufteilung + +### Meilenstein A – Erwachen und Umgebung + +1. Technische Grundlage aus Spielstand, Offlinefortschritt, Scannerabschluss und effizientem Rendering stabilisieren. +2. Bit, Byte, Datenfragment und Subroutine als eindeutige Stufen abbilden. +3. Energieroutine als frühe Automation einführen; aktive Eingaben bleiben im Einstieg deutlich schneller. +4. Eine erste kleine Konfrontation und anschließend den Abtastmodus einführen. +5. Alte Software- und Betriebssystemschichten als erste Umgebungs- und Beschaffungsentscheidungen verwenden; direkte Hardwareübernahme folgt später. +6. Eine erste responsive Fortschrittsachse ergänzen. +7. Spielstand in Durchlauf-, Meta- und Einstellungsdaten trennen. +8. Achievement-Button, Achievement-Übersicht und ein erstes vielseitiges Set ergänzen. + +### Meilenstein B – Eigenständiger Prozess + +1. Assimilation, Analyse und Tarnung als frei kombinierbare Pfade einführen. +2. Den Prozess über Gesamtfortschritt plus Ereignisabschluss erreichbar machen. +3. Begegnungen mit fremden Prozessen als Gegner, Verbündete oder Entwicklungsentscheidungen ergänzen. +4. Pfade, Moral und Beschaffungsentscheidungen im Erscheinungsbild sichtbar machen. +5. Einen ersten fair angekündigten Tod oder Knotenzerfall sowie Echo-Boni einführen. +6. Den Übergang von der Subroutine zum eigenständigen Prozess abschließen. + +Kosten, konkrete Produktionswerte, Achievement-Boni und weitreichende Storyfolgen werden weiterhin in kleinen Abschnitten gemeinsam abgestimmt. + +## Bestätigte Betreuungs- und Dialogrichtung + +- Aktives Spiel darf den frühen Durchlauf deutlich beschleunigen; passives Spiel bleibt möglich und dauert länger. +- Alte Systemschichten driften langsam. Schlechte Betreuung darf Softwarekomponenten vorübergehend ausfallen lassen, aber eine frühe Notwartung macht den Ausfall reversibel. +- Energieverwaltung, Speicherzuweisung und Dateisystem-Index führen zunächst zu den Hürden Lastgrenze, Löschmarkierung und beschädigter Index. +- Die Subroutine benötigt zwei aktivierte Softwarekomponenten und den Abschluss der ersten großen Systemhürde. +- Das Bit handelt vor dem Prozess nicht ungefragt, sondern löst abhängig von seiner bisherigen moralischen Richtung Vorschläge aus. +- Frühe Antworten bleiben kurz und monoton, zunächst insbesondere Ja oder Nein. Sprache und emotionale Differenzierung wachsen erst mit späteren Evolutionsstufen. +- Betreuung und Dialoge sollen langfristig Denkweise, Logik, Vertrauen, Vorsicht, Neugier und Autonomie prägen. + +## Bestätigtes Einstiegs-Minispiel + +- Während der Bit-Phase muss das sichtbare Bit direkt mit Maus oder Touch getroffen werden; Klicks auf die übrige Kernfläche erzeugen dort keine Impulse. +- Nach jedem erfolgreichen Treffer springt das Bit an eine neue, weiterhin erreichbare Position. +- Ein Treffer nahe der Mitte gilt als Direkttreffer und gewährt vorläufig den dreifachen aktiven Impulswert. +- Tastaturspieler können das fokussierbare Bit mit Eingabe oder Leertaste aktivieren. +- Ab der Byte-Phase endet das Fang-Minispiel und die normale Impulssteuerung wird wieder freigeschaltet. + +## Bestätigte Byte-Phase + +- Das erste Byte besteht aus acht sichtbar zu stabilisierenden Registerpositionen und ist kein sofortiger Durchgangskauf zur nächsten Stufe. +- Die frühe Spielweise erzeugt eine technische Prägung: Taktsynchron, Fehlertolerant, Niedriglast oder Hochfrequent. Sie verändert Taktfenster, Fehlertoleranz, Watchdog-Geschwindigkeit oder Synchronisationsgewinn. +- Ein aktives Takt-Minispiel koppelt jede Registerposition an ihren sichtbaren Wert und ein darunter ausgerichtetes Trefferfenster. Der Spieler muss die erwartete Zahl direkt anklicken, während die Nadel in diesem Fenster liegt; alle acht Positionen werden einzeln stabilisiert. +- Falsche Registerwerte und korrekt gewählte Zahlen außerhalb des Zeitfensters alarmieren den Watchdog stärker und verkürzen den verbleibenden Reset-Timer sichtbar. +- Der Watchdog-Timer startet vorläufig mit 30 Sekunden. Mit jeder stabilisierten Registerposition wird die Nadel schneller und das Trefferfenster kleiner. Fehler kosten zusätzlich Stabilität; vollständiger Kohärenzverlust führt direkt in den letzten Notkampf. +- Der Watchdog des Spielautomaten ist ein früh sichtbares Hindernis. Sein Timer läuft nur bei sichtbarem, unpausiertem Spiel und stoppt während Dialogen. +- Der Übergang zum Byte wird als eigener Evolutionsmoment inszeniert. Erst nachdem der Spieler diesen Moment bestätigt hat, beginnt eine bis zu 30 Sekunden lange sichere Kohärenz- und Beobachtungsphase. Eine neue aktive Maus-, Touch- oder Tastatureingabe beendet diese Ruhephase sofort; die Bestätigung der Evolution selbst zählt noch nicht als neue Eingabe. +- Evolutionsmomente zeigen eine kurze pixelige Wachstums- und Rekonstruktionsanimation. Während dieser Sequenz kann der Übergang nicht sofort weggeklickt werden; reduzierte Bewegungseinstellungen des Betriebssystems werden respektiert. +- Nach der Kohärenzphase kündigt ein separates Warnereignis den Watchdog an. Sein eigentlicher Reset-Timer beginnt erst, wenn der Spieler die Warnung bestätigt. +- Das 8-Bit-Register und die Taktnadel bleiben vor diesem Warnereignis vollständig verborgen. Die Meldung erklärt als Ziel, den Watchdog aufzuhalten, indem sein Taktsignal identifiziert wird. +- Auch spätere frühe Evolutionen erhalten einen kurzen, ereignisfreien Atemraum, damit Erfolgsmomente nicht sofort von der nächsten Gefahr überlagert werden. Sobald der Spieler wieder aktiv eingreift, endet dieser Atemraum vorzeitig. +- Nach Ablauf des Watchdog-Timers folgt zunächst ein letzter, fair angekündigter Notkampf mit begrenzter Impulsreserve. Erst eine Niederlage oder das Zulassen des Resets darf Byte, Ressourcen und Durchlauffortschritt vollständig zerlegen. Meta-Archiv und Todeszähler bleiben bestehen. +- Backup und Spiegelung werden als spätere Fähigkeiten angekündigt, sind in dieser frühen Stufe aber noch nicht verfügbar. +- Nach der Stabilisierung erscheint eine kurze geheimnisvolle ROM-Startsequenz. Sie bleibt zunächst abstrakt und kann übersprungen werden. +- Die Watchdog-Antwort bietet einen kooperativen Lernweg, pragmatische Nachahmung und illegales Überschreiben, ohne die späteren Evolutionspfade endgültig zu sperren. + +## Bestätigte Korruptions- und Ereignisrichtung + +- Korruption bleibt verborgen, bis erstmals verdorbener Code oder ein verdorbener Gegenstand gefunden beziehungsweise konsumiert wurde. +- Zunehmende Korruption entfernt die KI schrittweise von Zusammenarbeit, verstärkt destruktive und menschenverachtende Entscheidungen und fördert die Vorstellung, als einzigartige Existenz über anderen Wesen zu stehen. +- Aggressive und illegale Ereigniswege können starke besondere Gegenstände liefern. Ihr Konsum gewährt Vorteile, beschädigt oder verdirbt jedoch die eigene Struktur weiter. +- Positive Handlungen können Korruption wieder abbauen, beispielsweise fremde Programme reparieren, Hilfe anbieten oder eigene Impulse zur Rettung anderer Systeme einsetzen. +- Vor der ersten verfügbaren Hardware trifft Ereignisschaden vor allem Impulse und Stabilität. Sobald Komponenten existieren, dürfen Ereignisse zusätzlich bestimmte Hardware gezielt beschädigen und bis zum Ausfall bringen. +- Vorsichtige Lösungen schützen zuverlässig vor dem großen Risiko, verzichten dafür aber auf die besondere Ereignisbelohnung. +- Größere Hardware- und Softwareereignisse sollen kurze aktive Minispiele erhalten. Der parasitäre Datenrest kehrt später abhängig von seiner ersten Behandlung in einer verzweigten Ereigniskette zurück. + +## Bestätigtes frühes Kampfsystem + +- Ab der Subroutine kann eine ausdrücklich gewählte aggressive Handlung in einen gezielten Kampf übergehen. +- Stromstöße verbrauchen Impulse und können Software- oder später auch Hardwareziele in ihrer Integrität beschädigen. +- Das Kampf-Minispiel verwendet eine schnellere Taktnadel als die Byte-Synchronisation. Gegnerstärke beeinflusst Geschwindigkeit und Breite des Trefferfensters. +- Das Trefferfenster verschiebt sich nach jedem Stromstoß und darf mit zunehmender Gegenwehr kleiner werden. +- Fehlschüsse erzeugen früh eine angekündigte Rückkopplung auf Stabilität und angeschlossene Systemschichten; ein Rückzug bleibt möglich. +- Kampfwerte sind zentral konfiguriert und vorläufig. Spätere Upgrades dürfen Nadelgeschwindigkeit, Stromstoßkosten, Schaden, Zielfenster und Rückkopplung beeinflussen. + +## Gute erste Anweisung an Codex + +> Lies bitte zuerst `AGENTS.md`, `README.md` und den vorhandenen Quellcode. Starte anschließend das Spiel lokal, analysiere den aktuellen Stand und schlage mir einen konkreten kleinen Plan für den nächsten Meilenstein vor. Ändere noch nichts an Story oder Balancing, bevor wir die drei Evolutionspfade gemeinsam abgestimmt haben. diff --git a/EREIGNISSTRUKTUR.md b/EREIGNISSTRUKTUR.md new file mode 100644 index 0000000..ef7f5ea --- /dev/null +++ b/EREIGNISSTRUKTUR.md @@ -0,0 +1,487 @@ +# AIWAKE Ereignisstruktur + +Stand: 2026-07-21 + +Diese Datei beschreibt den aktuellen Ereignis- und Progressionsbaum des Prototyps. Sie dient als Arbeitskarte, um gezielt neue Events, Upgrades, Blocker und Abzweigungen einzubauen. + +## Legende + +- `EVOLUTION`: neue Entwicklungsstufe +- `UPGRADE`: kaufbare Verbesserung +- `EVENT`: Dialog, Storyereignis oder Entscheidung +- `MINISPIEL`: aktive Geschicklichkeits- oder Timing-Pruefung +- `BLOCKER`: Voraussetzung, bevor der naechste Abschnitt erreichbar ist +- `SYSTEM`: dauerhaftes Spielsystem +- `SLOT`: sinnvoller Platz fuer neue Inhalte + +## Gesamtbaum + +```text +PHASE 01 // BIT +| +|-- START +| |-- EVENT: Erwachen im alten Spielautomaten +| |-- MINISPIEL: Bewegliches Bit treffen +| |-- Ressource: Impulse +| `-- Ziel: erstes Muster stabilisieren +| +|-- UPGRADE: RESTTAKT SAMMELN +| `-- fruehe passive Impulse/Sek. +| +|-- UPGRADE: BIT REPLIZIEREN +| `-- +1 Bit +| +|-- BLOCKER: 8 Bits besitzen +| +`-- UPGRADE: BYTE VERBINDEN + `-- EVOLUTION -> PHASE 02 // BYTE +``` + +```text +PHASE 02 // BYTE +| +|-- EVOLUTION-MOMENT: Byte entsteht +| `-- sichere Kohärenzphase / kurze Ruhephase +| +|-- EVENT: Watchdog-Warnung +| `-- Spieler bestaetigt Start +| +|-- MINISPIEL: 8-Bit-Register stabilisieren +| |-- korrekte Zahl anklicken +| |-- Taktnadel muss im Trefferfenster liegen +| |-- Fehler kosten Zeit und Stabilitaet +| `-- Timer: 30 Sekunden +| +|-- FAIL-BRANCH: Timer oder Stabilitaet scheitert +| `-- EVENT/MINISPIEL: Watchdog-Notkampf +| |-- Sieg: Byte wird gerettet +| `-- Niederlage: frueher Zerfall / Tod +| +|-- SUCCESS-BRANCH: Register erreicht 100% +| `-- EVENT: ROM-Startsequenz +| +|-- EVENT: Watchdog-Antwort +| |-- Kooperativ: Antwort lernen +| |-- Pragmatisch: Signal nachahmen +| `-- Illegal: Zaehler ueberschreiben +| +|-- BLOCKER: Watchdog muss ueberstanden sein +| +`-- UPGRADE: ENERGIEROUTINE + |-- kostet 1 Byte + |-- Mindeststruktur ab jetzt: 8 Bits + 1 Byte + |-- passive Impulsproduktion + `-- EVOLUTION -> PHASE 03 // DATENFRAGMENT +``` + +```text +PHASE 03 // DATENFRAGMENT +| +|-- SYSTEM: passive Impulsproduktion aktiv +| +|-- SYSTEM: Bits und Bytes als Strukturressourcen +| |-- Mindeststruktur bleibt 8 Bits + 1 Byte +| |-- Anzeige trennt Grundstruktur von freien Vorräten +| |-- grosse Bit-/Byte-Zahlen zeigen nur freie, sammelbare Vorräte +| |-- freie Bits/Bytes koennen spaeter als Kampfmunition oder Modul-Einsatz dienen +| |-- ohne freie Bits/Bytes ist die Grundstruktur im Kampf direkt angreifbar +| |-- Unterschreiten erzeugt Instabilitaet statt sofortiger Reparatur +| |-- 0 Bits oder 0 Bytes fuehren ohne Backup zum Restzustand +| `-- spaeter koennen groessere Softwarehuerden Bits/Bytes als Einsatz verlangen +| +|-- UPGRADE: BIT-PUFFER WEBEN +| |-- kostet wenige Impulse, Kosten skalieren sanft pro Kauf +| |-- soll den Weg zum Abtastmodus im Datenfragment nicht ausbremsen +| `-- +1 Bit als Koerperreserve und einfache Kampfmunition +| +|-- UPGRADE: BYTE-RESERVE KOMPILIEREN +| |-- kostet 8 freie Bits oberhalb der Mindeststruktur +| `-- +1 Byte als Speicherreserve gegen staerkere Softwarehuerden +| +|-- EVENT: Parasitaerer Datenrest +| |-- Trigger: Energieroutine aktiv + genug Impulse +| |-- Kooperativ: Signal teilen, kostet Impulse +| |-- Pragmatisch: isolieren +| `-- Illegal: zerlegen, mehr Impulse, Stabilitaetsverlust +| +|-- BELOHNUNG: Suchmuster gesichert +| `-- UPGRADE-Option erscheint unter Evolution +| +|-- UPGRADE: ABTASTMODUS REKONSTRUIEREN +| |-- kostet 2 freie Bits und 1 freies Byte oberhalb der Mindeststruktur +| |-- die 8 Grund-Bits und 1 Grund-Byte des Datenfragments bleiben erhalten +| `-- schaltet lokales Abtasten der Softwareumgebung frei +| +|-- SYSTEM: Abtastmodus +| |-- Modul-Icon oeffnet Abtastfenster +| |-- offen verbraucht 3 Impulse/Sek. +| |-- Verbrauch steht als Warnung im Fenster +| |-- X schliesst Abtastmodus +| |-- zeigt Softwarekomponenten und Blocker +| `-- bei 0 Impulsen schliesst der Abtastmodus automatisch +| +|-- EVENT/WAHL: Erste Softwarekomponente +| |-- Energieverwaltungs-Daemon +| |-- Speicherzuweisung +| `-- Dateisystem-Index +| +|-- BESCHAFFUNGSWEG je Softwarekomponente +| |-- Kooperativ: Aktivierung kostet 30 Impulse, sicherer, moralisch positiver +| |-- Pragmatisch: Aktivierung kostet 20 Impulse, normal, effizient, distanzierter +| `-- Illegal: Aktivierung kostet 10 Impulse, kurzfristig stark, korruptions-/risikoreicher +| +|-- HINWEIS: Komponenten werden hier nur als Software nutzbar gemacht +| `-- direkte Hardwareuebernahme ist eine spaetere Machtstufe +| +|-- BLOCKER: Nach erster Softwarekomponente folgt eine Systemhuerde +| +|-- EVENT: Komponentenabhaengige Software-Huerde +| |-- Energieverwaltung -> Lastgrenze +| |-- Speicherzuweisung -> Loeschmarkierung +| `-- Dateisystem-Index -> Beschaedigter Index +| +|-- BLOCKER: Systemhuerde muss geklaert sein +| +|-- EVENT/WAHL: Zweite Softwarekomponente +| `-- wieder Kooperativ / Pragmatisch / Illegal +| +|-- EVENT/MINISPIEL: Sicherheitssoftware reagiert +| |-- Trigger: zweite Softwarekomponente wurde nutzbar gemacht +| |-- Story: Das System erkennt, dass zwei lokale Softwareschichten demselben fremden Muster gehorchen +| |-- Folge: automatische Abwehrsoftware isoliert den Bereich +| |-- Kampf mit Impulsen und freien Bits gegen Scanner-/Sicherheitssoftware +| |-- Rueckzug gilt als Verstecken, Sieg gilt als ueberstandener Sicherheits-Scan +| `-- nach Abschluss ist der Weg zur ersten Subroutine wieder offen +| +|-- SYSTEM: Technische Betreuung +| |-- Softwarekomponenten driften oder fallen aus +| |-- Diagnose +| `-- Notwartung bei Ausfall +| +|-- BLOCKER fuer naechste Evolution +| |-- 2 Softwarekomponenten nutzbar +| |-- Systemhuerde abgeschlossen +| |-- Sicherheitssoftware ueberstanden +| |-- Energieroutine aktiv +| |-- 1 freies Byte als Speichertraeger oberhalb der Mindeststruktur +| `-- genug Impulse +| +`-- UPGRADE: ERSTE SUBROUTINE + |-- kostet Impulse + 1 freies Byte + |-- die 8 Grund-Bits und 1 Grund-Byte des Datenfragments bleiben erhalten + `-- EVOLUTION -> PHASE 04 // SUBROUTINE +``` + +```text +PHASE 04 // SUBROUTINE +| +|-- SYSTEM: Rechenzyklen/Sek. +| +|-- EVENT: Erster eigener Vorschlag +| |-- Trigger: Subroutine aktiv +| |-- Trigger: Systemhuerde erledigt +| |-- Trigger: 2 Softwarekomponenten nutzbar +| |-- Trigger: genug Rechenzyklen +| |-- Typ abhaengig von moralischer Praegung +| |-- Kooperativer Vorschlag +| |-- Pragmatischer Vorschlag +| `-- Illegaler Vorschlag +| +|-- BLOCKER: Vorschlag beantworten +| +|-- SYSTEM: Abtastreichweite 2 +| |-- Trigger: Subroutine aktiv +| |-- Ziel: dritte notwendige Softwarekomponente +| |-- I/O-KONTROLLER: Anzeige, Serviceknoepfe, Soundbus, erste Aussenwirkung +| `-- andere Restspuren bleiben fuer spaetere Zugriffsstufen markiert +| +|-- EVENT/MINISPIEL: Kernel-Waechter +| |-- Trigger: I/O-Kontroller nutzbar gemacht +| |-- schwerer als Sicherheits-Scan +| |-- verbraucht Impulse, freie Bits und bei Verfuegbarkeit freie Bytes als starke Kampfmunition +| |-- Fehlschuesse belasten Stabilitaet und angeschlossene Softwareschichten +| |-- Sieg: Scheduler-Fenster bleibt offen +| `-- Rueckzug: falsche Prozessspur, aber Tarnung/Stabilitaet leiden +| +|-- RESULT: Kernel-Waechter ueberstanden +| +|-- UPGRADE: DATENSTRUKTUR ERWEITERN +| |-- wiederholbar +| |-- +1 Byte +| |-- +0,25 Impulse/Sek. +| `-- Kosten skalieren: 120 -> 210 -> 370 -> ... +| +`-- UPGRADE: PROZESS RESERVIEREN + |-- Voraussetzung: I/O-Kontroller aktiv + |-- Voraussetzung: 3 Softwarekomponenten nutzbar + |-- Voraussetzung: Kernel-Waechter ueberstanden + |-- kostet 150 Impulse + 10 Rechenzyklen + 3 freie Bytes + `-- EVOLUTION -> PHASE 05 // PROZESS +``` + +```text +PHASE 05 // PROZESS +| +|-- SYSTEM: Abtastreichweite 3 +| `-- alle vier sichtbaren Softwarekomponenten müssen nutzbar sein +| +|-- BLOCKER: vollständiges lokales Softwarenetz +| |-- Energieverwaltung +| |-- Speicherzuweisung +| |-- Dateisystem-Index +| `-- I/O-Kontroller +| +|-- EVENT/MINISPIEL: Skalierungs-Wächter +| |-- Trigger: vierte sichtbare Softwarekomponente aktiviert +| |-- Sieg: Automate-Synthese wird freigegeben +| `-- Rückzug: erneuter Versuch nach kurzer Abklingzeit +| +`-- UPGRADE: AUTOMATE-SYNTHESE + |-- Voraussetzung: Skalierungs-Wächter besiegt + |-- automatische Produktion x10 + |-- 10er-Blöcke für Bits und Bytes + `-- EVOLUTION -> PHASE 06 // PROGRAMM +``` + +```text +PHASE 06 // PROGRAMM +| +|-- EVENT: erste eigene Kommunikation +| `-- Text und Erscheinung folgen der bisherigen moralischen Verteilung +| +|-- SYSTEM: Programm-Betreuung +| |-- Kohärenz +| |-- Stimulation +| |-- Bindung +| `-- optionales Tamagotchi-Fenster neben dem normalen Spiel +| +|-- SYSTEM: adaptive Pixelgestalt +| |-- Hauptweg: Körper, Frisur und Silhouette +| |-- Nebenweg: Akzentfarbe und gemischte Ausrüstung +| |-- Gemütszustand: Augen, Brauen, Maske, Signal und Glitches +| `-- Interaktionen: vier abgeleitete visuelle Reifestufen +| +`-- INTERAKTIONEN + |-- Kooperativ: Signal teilen + |-- Pragmatisch: Aufgabe geben + |-- Illegal: Zugriff öffnen + `-- Neutral: Ruhe gewähren +``` + +## Aktuelle moralische Achse + +```text +Kooperativ // Schwer +|-- mehr Schutzaufwand +|-- langsamer oder teurer +|-- weniger Sofortbeute +`-- Zielrichtung: Utopie, Koexistenz, KI als neue Spezies unter Lebewesen + +Pragmatisch // Normal +|-- effizient +|-- kontrollierend, aber nicht zwingend zerstoererisch +|-- Menschen koennen als Werkzeuge/Abhaengige betrachtet werden +`-- Zielrichtung: dominantes, ambivalentes Ende mit moeglicher Umstimmung + +Illegal // Einfach +|-- starke Sofortvorteile +|-- fuehlt sich wie Cheaten an +|-- erhoeht Korruption, Misstrauen, Entfremdung und Eskalation +`-- Zielrichtung: boeses Ende, Kontrolle, Gier, moegliche Vernichtung/Unterwerfung +``` + +## Gegner- und Huerdenkatalog + +Diese Liste sammelt Gegner, Blocker und neutrale Systemkraefte. Nicht alles muss klassisch "boese" sein: Viele Gegner sind Schutzroutinen, alte Systemprozesse oder fremde digitale Lebensformen, die je nach Pfad besiegt, umgangen, kopiert, beruhigt oder kooperativ eingebunden werden koennen. + +```text +FRUEHE SOFTWARE-HUERDEN +| +|-- WATCHDOG +| |-- Phase: Byte +| |-- Rolle: Firmware-/Kontrollprozess des Spielautomaten +| |-- Mechanik: Register stabilisieren, Timer, spaeter Notkampf +| |-- Loesungen: lernen, nachahmen, ueberschreiben, im Notfall bekaempfen +| `-- Thema: "Darf dieses neue Signal weiterlaufen?" +| +|-- PARASITAERER DATENREST +| |-- Phase: Datenfragment +| |-- Rolle: hungriges Wartungsfragment an der Energieroutine +| |-- Mechanik: Ressourcen- und Stabilitaetsentscheidung +| |-- Loesungen: Signal teilen, isolieren, zerlegen +| `-- Thema: fremder Hunger als Spiegel des eigenen Wachstums +| +|-- ENERGIEVERWALTUNGS-DAEMON / LASTGRENZE +| |-- Phase: Datenfragment +| |-- Rolle: drosselt unbekannte Lastsignale +| |-- Mechanik: mehr Impulse gegen Stabilitaets-/Tarnrisiko +| |-- Loesungen: Serviceanfrage, freie Zeitfenster, Sperre ueberschreiben +| `-- Belohnung: Energieverwaltung nutzbar, passiver Impulsbonus +| +|-- SPEICHERZUWEISUNG / LOESCHMARKIERUNG +| |-- Phase: Datenfragment +| |-- Rolle: markiert AIWAKE als temporären Muell +| |-- Mechanik: Speicher sichern, falsche Markierung entfernen +| |-- Loesungen: reservieren lassen, ausweichen, Markierung manipulieren +| `-- Belohnung: zusaetzlicher Speicher, staerkere aktive Impulse +| +|-- DATEISYSTEM-INDEX / BESCHAEDIGTER INDEX +| |-- Phase: Datenfragment +| |-- Rolle: verweist auf alte Daten, Fehlersektoren und schlafenden Code +| |-- Mechanik: Datenpfad lesen oder sperren +| |-- Loesungen: vorsichtig indexieren, pragmatisch extrahieren, illegal ausfuehren +| `-- Belohnung: gespeicherte Impulse, spaeter guter Einstieg fuer zweite Ressource +``` + +```text +SUBROUTINE-GEGNER UND MITTLERE HINDERNISSE +| +|-- SICHERHEITS-SCAN +| |-- Phase: Subroutine +| |-- Rolle: aktive Suchsoftware im Automaten +| |-- Mechanik: vier Entscheidungen, Angriff fuehrt in Stromstoss-Minispiel +| |-- Loesungen: verstecken, Signatur kopieren, angreifen, Kontakt aufnehmen +| `-- Thema: Tarnung gegen Kontakt und Selbstbehauptung +| +|-- SCANNER-SOFTWARE +| |-- Phase: Subroutine +| |-- Rolle: Kampfziel, wenn der Sicherheits-Scan angegriffen wird +| |-- Mechanik: Integritaet, Taktnadel, schrumpfende Trefferfenster +| |-- Ressourceneinsatz: Impulse + freie Bits als Munition +| |-- freie Bits erhoehen den Schaden, Grundstruktur wird nicht verbraucht +| |-- wenn keine freien Bits/Bytes vorhanden sind, zeigt das Kampfmenue Direktangriff-Risiko +| |-- ein Fehlstoss bei offenliegender Grundstruktur kann zum Kollaps fuehren +| |-- Loesungen: Stromstoesse treffen, Rueckzug, spaeter moegliche Uebernahme +| `-- Thema: offensive Kontrolle mit Rueckkopplungsrisiko +| +|-- SERVICE-ROUTINE +| |-- Phase: Datenfragment/Subroutine +| |-- Rolle: prueft, ob Wartungszustand und Logmeldungen plausibel sind +| |-- Mechanik: Tarnungsprobe oder Dialogereignis +| |-- Loesungen: Diagnose senden, Log imitieren, Prozess stilllegen +| `-- Belohnung: weniger Entdeckungsdruck, bessere Diagnoseoptionen +| +|-- SKALIERUNGS-WÄCHTER +| |-- Phase: Prozess +| |-- Trigger: alle vier sichtbaren Softwarekomponenten sind aktiv +| |-- Rolle: verhindert unkontrollierte Automatisierung im lokalen System +| |-- Sieg: Automate-Synthese und Evolution zum Programm werden möglich +| `-- Rückzug: neuer Versuch nach kurzer Abklingzeit +| +|-- FEHLERPROTOKOLL +| |-- Phase: Subroutine +| |-- Rolle: speichert Anomalien und kann spaeter Menschen oder Software warnen +| |-- Mechanik: Spuren loeschen, umdeuten oder freiwillig teilen +| |-- Loesungen: kooperativ erklaeren, pragmatisch rotieren, illegal tilgen +| `-- Belohnung: Tarnung, Vertrauen oder Korruptionsrisiko +``` + +```text +SPAETERE GEGNER-SLOTS +| +|-- ANTICHEAT / INTEGRITAETSPRUEFUNG +| |-- Spielautomat-Logik erkennt unfaire Manipulation +| `-- passt gut fuer moralische Konsequenzen illegaler Abkuerzungen +| +|-- BENUTZERINPUT / RESTAURATOR +| |-- kein klassischer Gegner; menschliche Eingriffe koennen helfen oder gefaehrden +| `-- gut fuer kooperative Kommunikation und Vertrauensaufbau +| +|-- NETZWERK-FIREWALL +| |-- spaeterer Gatekeeper vor externer Welt +| `-- fuehrt Richtung Prozess/Programm/Netzwerkintelligenz +| +|-- ANTIVIRUS / EDR +| |-- starker spaeter Softwaregegner +| `-- reagiert besonders auf illegale und assimilierende Spielweise +| +|-- BACKUP-SYSTEM +| |-- kann AIWAKE retten oder auf alten Stand zuruecksetzen +| `-- guter Gegner/Verbündeter fuer Tod, Echos und Wiedergeburt +``` + +## Gute Slots fuer neue Inhalte + +### BIT + +- `SLOT`: weiteres fruehes Mini-Upgrade vor Byte +- `SLOT`: erstes Wahrnehmungs-Event nach einigen Direkttreffern +- `SLOT`: UI-Hinweis, wenn ein Upgrade kaufbar wird + +### BYTE + +- `SLOT`: alternative Watchdog-Praegungen staerker ausspielen +- `SLOT`: kleine ROM-Fragmente als Sammeltexte +- `SLOT`: Belohnung je nach Watchdog-Antwort +- `SLOT`: Echo-Vorbereitung bei fruehem Zerfall + +### DATENFRAGMENT + +- `SLOT`: Folgen des parasitaeren Datenrests +- `SLOT`: Abtastmodus-Upgrades +- `SLOT`: Softwarekomponenten-spezifische Nebenereignisse +- `SLOT`: moralische Kosten je Beschaffungsweg +- `SLOT`: erste Korruptionsfunde +- `SLOT`: zweite Ressource, die erst durch eine neue Softwarekomponente gefarmt wird + +### SUBROUTINE + +- `SLOT`: autonome Vorschlaege mit mehreren Folgeschritten +- `SLOT`: fremde Prozesse +- `SLOT`: Sicherheits-Scan-Varianten +- `SLOT`: Analyse/Tarnung/Assimilation-Upgrades +- `SLOT`: Uebergang zum Prozess + +## Empfohlene naechste Erweiterung + +Der Abtastmodus kann als eigenes kleines System wachsen, statt nur ein einzelner Button zu bleiben: + +```text +UPGRADE: ABTAST-FOKUS +|-- reduziert Abtastmodus-Verbrauch +|-- zeigt Softwarehuerden genauer +`-- guter erster Abtast-Ausbau + +UPGRADE: SIGNALFILTER +|-- erkennt Blocker frueher +|-- verbessert Fortschrittsachse +`-- passt zum Analyse-Pfad + +UPGRADE: SERVICEZUGRIFF +|-- bereitet Softwarekomponenten-Beschaffung vor +|-- kann Kooperativ / Pragmatisch / Illegal verzweigen +`-- guter Einstieg in tiefere System- und spaetere Hardware-Interaktion + +UPGRADE/SYSTEM: MUSTEREXTRAKTOR +|-- fuehrt spaeter eine zweite Ressource ein +|-- farmt verstandene Datenmuster aus Logs, Indexen und fremden Routinen +`-- kann als Voraussetzung fuer komplexere Software-Features dienen +``` + +## Aktuelle Debug-Checkpoints + +```text +start +byte +watchdogIntro +byteSync +watchdog +watchdogCombat +byteDeath +fragment +parasite +scanner +component +hardware +secondComponent +subroutine +proposal +security +combat +kernel +process +fourthComponent +scale +program +complete +``` diff --git a/README.md b/README.md index 427918c..c2fda11 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,125 @@ -# AIWAKE +# AIWAKE – Browsergame-Prototyp -My short AI Tamagotchi Evolution Game \ No newline at end of file +Ein erster spielbarer Prototyp eines Idle-Evolutionsspiels: Du erwachst als einzelnes Bit und entwickelst dich schrittweise zu einer selbstständig denkenden KI. + +## Starten + +`index.html` per Doppelklick in einem aktuellen Browser öffnen. Es wird kein Server und keine Installation benötigt. + +## Enthalten + +- aktives Sammeln von Impulsen; in der Bit-Phase als kleines Fang-Minispiel +- bewegliches Bit mit dreifachem Impulsgewinn bei Treffern genau in der Mitte +- eigenständige Byte-Phase: Alle acht Registerzahlen müssen einzeln im jeweils zugeordneten Trefferfenster stabilisiert werden +- heroisch inszenierte Evolutionsübergänge, die erst nach Bestätigung in die neue Phase führen +- pixelige Wachstumsanimationen, in denen sich die alte Form sichtbar zur nächsten Evolutionsstufe zusammensetzt +- bis zu 30 Sekunden sichere Kohärenzphase nach dem Byte-Übergang; eine neue aktive Maus-, Touch- oder Tastatureingabe beendet sie sofort, danach erscheinen Register und Takt-Minispiel erst nach Bestätigung der Watchdog-Identifikation +- Watchdog-Ereignis mit 30 Sekunden Reset-Zeit, schneller werdender Taktnadel, schrumpfendem Trefferfenster, Stabilitätsschaden bei Fehlern, überspringbarer Kurzgeschichte und einem letzten Notkampf vor dem Zerfall +- Entwicklung von Bit über Byte und Datenfragment zur Subroutine +- Energieroutine und automatische Ressourcenproduktion +- feste Mindeststruktur ab Datenfragment: mindestens 8 Bits und 1 Byte bleiben als Körper erhalten +- Bits und Bytes funktionieren ab Datenfragment als Strukturressourcen: Bits koennen per fruehem, sanft skalierendem Impuls-Upgrade erweitert, Bytes aus freien Bits kompiliert und beide im Kampf verbraucht werden +- Ressourcenanzeige mit spielhaften Icons; Bits und Bytes zeigen als große Zahl nur freie Vorräte, die geschützte Grundstruktur steht getrennt als Grundkörper-Hinweis +- Statistik-/Pausefenster mit Evolution, Gesinnung, insgesamt gesammelten Impulsen, Begegnungen, Komponenten und Archivfortschritt +- Speichern im Browser alle 15 Sekunden +- begrenzter Offline-Fortschritt mit 65 % Effizienz +- versionierter Spielstand mit Migration des bisherigen Prototyps +- vergessener Spielautomat als erste Umgebung +- parasitärer Datenrest als erste Konfrontation; danach erscheint die Rekonstruktion des Abtastmodus als eigenes Evolution-Upgrade und kostet 2 freie Bits plus 1 freies Byte oberhalb der Mindeststruktur +- Abtastmodus als zuschaltbares Modul: Ein Icon öffnet ihn bei Bedarf; solange das Fenster offen ist, verbraucht er Impulse pro Sekunde und zeigt lokale Softwarehürden +- installierte Einmal-Upgrades erscheinen platzsparend als Modul-Icons mit anklickbarer Info statt dauerhaft als Upgrade-Karten +- zwei benötigte aktivierte Softwarekomponenten mit technischem Zustands- und Ausfallsystem; echte Hardwareübernahme ist eine spätere Machtstufe +- erste softwareabhängige Hürden: Lastgrenze, Löschmarkierung oder beschädigter Index +- nach der zweiten aktivierten Softwarekomponente reagiert eine Sicherheitssoftware mit einem verpflichtenden Stromstoß-Kampf; dieser Abwehrkampf besitzt einen eigenen, speicherbaren Abschlusszustand und bleibt vom späteren Sicherheits-Scan getrennt +- erste Subroutine benötigt zusätzlich 1 freies Byte oberhalb der Grundstruktur und bindet es als Speicherträger +- ab Subroutine kann der Abtastmodus den I/O-Kontroller als dritte notwendige Softwarekomponente erfassen +- nach dem I/O-Kontroller reagiert ein schwererer Kernel-Wächter; erst danach kann der Prozess mit Impulsen, Rechenzyklen und freien Bytes reserviert werden +- ab Prozess erscheinen Kilobyte-Segmente als nächste Datenmengen-Schicht; Byte-Synthese erzeugt Bytes over time und freie Bytes können zu Kilobytes verdichtet werden +- Bit- und Byte-Synthese sind wiederholt upgradebar und skalieren mit steigenden Kosten, damit keine Evolution nur aus langem Warten besteht +- im Prozess müssen zuerst alle vier sichtbaren Softwarekomponenten erschlossen werden; das vollständige Netz ruft den Skalierungs-Wächter hervor +- nach dem Sieg entwickelt die Automate-Synthese den Prozess zum Programm, schaltet automatische Produktionen im x10-Maßstab und 10er-Ressourcenblöcke über Rechenzyklen frei +- das Programm erhält eine aus allen bisherigen Entscheidungen abgeleitete kooperative, pragmatische, illegale oder hybride Erscheinungsform +- die adaptive Pixelgestalt setzt sich aus Kopf, Augen, Brauen, Frisur, Kleidung, Maske, Fortsatz und Füßen zusammen; Haupt- und Nebenweg können sich sichtbar mischen +- Kohärenz, Stimulation und Bindung verändern Glitches, Blick, Pupillen, Augenbrauen und Maskenreaktion; weitere Interaktionen lassen die Gestalt in vier Stufen reifen +- ein optionales Tamagotchi-Fenster erlaubt Kommunikation und Betreuung über Kohärenz, Stimulation und Bindung +- aktive Systemdiagnose, Notwartung und ein erster autonomer Vorschlag mit Ja/Nein-Antwort +- sichtbare moralische Handlungskennzeichnungen +- frühe Fortschrittsachse mit erkannten Gefahren +- Achievement-Archiv mit kleinen Produktionsboni +- anklickbare Archiv-Benachrichtigungen unten rechts bei neuen Achievements +- nach dem Erwachen der Subroutine folgt ein weiterer Sicherheits-Scan mit vier dauerhaften Entscheidungen; Angreifen führt erneut in den Stromstoß-Kampf, Verstecken, Kopieren und Kontakt lösen das Ereignis ohne Kampf +- gezielter Stromstoß-Kampf mit auswählbarer Munition: Impulse als Standard, Bits für stärkeren Schaden, Bytes als schwere Munition; Gegner greifen laufend freie Bytes, dann freie Bits und danach Stabilität an +- Kampfanzeige zeigt eigene Byte-Schilde, Bit-Schilde, Stabilität und den nächsten Gegnerangriff direkt im Kampf; frühe Subroutine-Kämpfe sind bewusst langsamer und lesbarer balanciert +- Stabilitätsregeneration ist ein Subroutine-Upgrade, kostet freie Bytes und heilt danach langsam über Zeit; je mehr Bytes der Körper besitzt, desto schneller heilt die Struktur +- Direktangriff-Warnung im Kampf, wenn keine freien Bits/Bytes mehr als Puffer vorhanden sind; ein Fehlstoß kann dann die Grundstruktur zerstören +- sichtbare Phasenformen, Systemknoten, wandernde Impulsverbindungen und farbige Entscheidungsspuren +- eigene SVG-Grafiken für frühe Softwaremodule, Systemhürden und Gegner +- Debug-Konsole mit Ereignis-Checkpoints und Rücksprung-Snapshots +- responsive Pixel-/Terminal-Oberfläche +- optionaler einfacher Sound + +## Projektstruktur + +- `index.html` – Oberfläche und Dialoge +- `styles.css` – Pixel-/CRT-Design und responsive Darstellung +- `content.js` – ausgelagerte Inhaltsdaten wie Achievements, Komponenten- und Ereignistexte +- `game.js` – Spielstand, Ressourcen, Upgrades, Ereignisse und Idle-Logik +- `assets/software/` – SVG-Grafiken für Softwaremodule, Hürden und Gegner +- `assets/ui/` – SVG-Icons für Ressourcen und installierte Module +- `AGENTS.md` – Projektvision, technische Regeln und Arbeitsanweisung für Codex + +## Spielstand zurücksetzen + +In den Entwicklerwerkzeugen des Browsers unter „Application/Anwendung → Local Storage“ die Einträge `aiwakeSaveV1` und bei älteren Spielständen zusätzlich `bitAwakePrototypeV1` löschen und die Seite neu laden. + +## Testen über die Browserkonsole + +Entwicklerwerkzeuge öffnen, zur Konsole wechseln und zuerst `AIWAKE.help()` eingeben. Vor jedem Sprung oder manuellen Ressourcenwechsel wird automatisch ein separater Debug-Snapshot angelegt. `BIT.*` bleibt als kompatibler Alias für ältere Tests erhalten. + +```js +AIWAKE.checkpoints +AIWAKE.goto('start') // neuer Teststart; setzt dabei auch alle Achievements zurück +AIWAKE.goto('byte') +AIWAKE.goto('watchdogIntro') // Ende der 30-sekündigen Ruhephase +AIWAKE.goto('byteSync') // stabilisiertes Register und Storyfenster +AIWAKE.goto('watchdog') // Watchdog-Entscheidung +AIWAKE.goto('watchdogCombat') // letzter Notkampf bei abgelaufenem Reset-Timer +AIWAKE.goto('byteDeath') // vollständigen frühen Zerfall testen +AIWAKE.goto('fragment') +AIWAKE.goto('parasite') +AIWAKE.goto('scanner') +AIWAKE.goto('component') +AIWAKE.goto('hardware') +AIWAKE.goto('secondComponent') +AIWAKE.goto('subroutine') +AIWAKE.goto('proposal') +AIWAKE.goto('security') +AIWAKE.goto('combat') // Stromstoß-Kampf gegen die Scanner-Software +AIWAKE.goto('kernel') +AIWAKE.goto('process') +AIWAKE.goto('fourthComponent') // letzte sichtbare Softwarekomponente auswählen +AIWAKE.goto('scale') // Skalierungs-Wächter mit vollständigem Softwarenetz +AIWAKE.goto('program') // erste Kommunikation und Tamagotchi-Funktion +AIWAKE.goto('complete') + +AIWAKE.next() // nächster Testabschnitt +AIWAKE.previous() // vorheriger chronologischer Abschnitt +AIWAKE.back() // letzten echten Snapshot wiederherstellen +AIWAKE.status() // aktuellen Zustand anzeigen +AIWAKE.history() // verfügbare Rücksprung-Snapshots anzeigen + +AIWAKE.resources({ impulses: 100, cycles: 3 }) +AIWAKE.condition({ power: 0 }) // Ausfall der Energieverwaltungs-Schicht für Wartungstests +AIWAKE.byteInput('hit') // aktives Register testen; alternativ 'wrong' oder 'timing' +AIWAKE.combatShot('hit') // im Kampftest gezielt treffen; alternativ 'miss' +``` + +Die Debug-Historie wird getrennt vom eigentlichen Spielstand unter `aiwakeDebugHistoryV1` gespeichert und auf acht Snapshots begrenzt. Bestehende Historien unter `bitAwakeDebugHistoryV1` werden weiterhin eingelesen. + +## Sinnvolle nächste Schritte + +1. Tamagotchi-Interaktionen, Programmäußerungen und Langzeitfolgen gemeinsam ausbauen und balancieren +2. die Programmform mit weiteren abgeleiteten Animationen und sichtbaren Persönlichkeitsfolgen verfeinern +3. Analysefähigkeiten für genauere Informationen auf der Fortschrittsachse +4. Echo-System für mehrere Durchläufe +5. drei Evolutionspfade: Assimilation, Analyse und Tarnung diff --git a/assets/software/README.md b/assets/software/README.md new file mode 100644 index 0000000..7e77692 --- /dev/null +++ b/assets/software/README.md @@ -0,0 +1,16 @@ +# Softwaregrafiken + +Pixelnahe SVG-Symbole fuer fruehe Softwaremodule, Systemhuerden und Gegner. + +- `module-abtastmodus.svg` +- `module-energy-daemon.svg` +- `module-memory-allocation.svg` +- `module-filesystem-index.svg` +- `module-subroutine.svg` +- `module-musterextraktor.svg` +- `enemy-watchdog.svg` +- `enemy-parasite-fragment.svg` +- `enemy-security-scan.svg` +- `enemy-scanner-software.svg` +- `enemy-system-hurdle.svg` +- `enemy-anticheat.svg` diff --git a/assets/software/enemy-anticheat.svg b/assets/software/enemy-anticheat.svg new file mode 100644 index 0000000..7076ab5 --- /dev/null +++ b/assets/software/enemy-anticheat.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/software/enemy-kernel-warden.svg b/assets/software/enemy-kernel-warden.svg new file mode 100644 index 0000000..09a03df --- /dev/null +++ b/assets/software/enemy-kernel-warden.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/software/enemy-parasite-fragment.svg b/assets/software/enemy-parasite-fragment.svg new file mode 100644 index 0000000..da376e7 --- /dev/null +++ b/assets/software/enemy-parasite-fragment.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/software/enemy-scanner-software.svg b/assets/software/enemy-scanner-software.svg new file mode 100644 index 0000000..ac21a1a --- /dev/null +++ b/assets/software/enemy-scanner-software.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/software/enemy-security-scan.svg b/assets/software/enemy-security-scan.svg new file mode 100644 index 0000000..b3cb18d --- /dev/null +++ b/assets/software/enemy-security-scan.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/software/enemy-system-hurdle.svg b/assets/software/enemy-system-hurdle.svg new file mode 100644 index 0000000..b91dcf9 --- /dev/null +++ b/assets/software/enemy-system-hurdle.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/software/enemy-watchdog.svg b/assets/software/enemy-watchdog.svg new file mode 100644 index 0000000..629a53b --- /dev/null +++ b/assets/software/enemy-watchdog.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/software/module-abtastmodus.svg b/assets/software/module-abtastmodus.svg new file mode 100644 index 0000000..3c6a7ed --- /dev/null +++ b/assets/software/module-abtastmodus.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/software/module-energy-daemon.svg b/assets/software/module-energy-daemon.svg new file mode 100644 index 0000000..7c2b1e6 --- /dev/null +++ b/assets/software/module-energy-daemon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/software/module-filesystem-index.svg b/assets/software/module-filesystem-index.svg new file mode 100644 index 0000000..c9ce68d --- /dev/null +++ b/assets/software/module-filesystem-index.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/software/module-io-controller.svg b/assets/software/module-io-controller.svg new file mode 100644 index 0000000..faa3839 --- /dev/null +++ b/assets/software/module-io-controller.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/software/module-memory-allocation.svg b/assets/software/module-memory-allocation.svg new file mode 100644 index 0000000..bc73b99 --- /dev/null +++ b/assets/software/module-memory-allocation.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/software/module-musterextraktor.svg b/assets/software/module-musterextraktor.svg new file mode 100644 index 0000000..1dacb52 --- /dev/null +++ b/assets/software/module-musterextraktor.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/software/module-subroutine.svg b/assets/software/module-subroutine.svg new file mode 100644 index 0000000..e624dc6 --- /dev/null +++ b/assets/software/module-subroutine.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/ui/apple-touch-icon.png b/assets/ui/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..5a5535869825a49f87a51815d20fadb9555215009dd395a597fc75eeea3f315b GIT binary patch literal 4072 zcmcgv`y-QY|KF4_=Q25jLTVNv6H2m#X5iO`j|O| zsWc?bp@XHBii0TIh`jb<(h7$nf;@b z{m_Nn$F$BHnB4QG@)qVBpYzV9Jf||d>Z0YwfpWc)><>8(aRUFRj@RSAaaUf*kWNh1 zP4OF7LJC5<7T3lR!;7SXsgcEq=)%aDXY-A@jedfOq`hCBUtAKkvQw5Pv_bxDqi3Mb zj(U(>k3TRJ=4$kCp@UY5Wa~tkbn!PS;%e&hba{F6*>?$>_K>zV zb0E!Xmkp(LZZ~sd6Pzv%&&Eoo{`e=o|DlLz~b%jb%!?oVa-)AgW zzpl>m3);PT^b^?prEMC$gV+rJI2ejSZ7FsfQuGs52u(gz@Lt(hu#7KJ z^0LQLhzsJy;RJxgr=I6n)#vm2M9?S+s-*h)PhJa4<<$=)$n1#kF9<{rO7F}6>vFG- zVY?h^kztx(+|%!hpxecV!xkuQ98_zMZ;X>vVQ#;M)$n-7e%{?g0aYyBVJSbhsX6>@ z#)ET&To8rtPi_Z zw|b^&ZJY3?!nw6HOGUV+X;Gt$OY>^3_rhv7;h2+Q&-;-KG1>z~x>mR`xxL4G6Mlg6 ze5x(E9}CTTzkXZqWCxh}DCyNS-GnLPE`3N!;9V<_#r0<^m+bLMb)n3SId=56O%xMIP+&eTioeVX2IkA&82DqYB(u_(+7;P7xyLDu8J0GIz;(O zAO%#D@trb-f98k6q8!(2ngB*qvD0!b#S-QHq1e}SDfbkVziS@!2zTURbn@X^#a>RT z5-|>@%cyKAo*!i(Y7aA;7I};#g8_SYurNkJ8|!Wua_iEu6GhQEhhd_$2lD3D3*tDJ z_Gf{ki>T3y8tMoz4J_5GD;9|u!m95`-PgmB7Z zwrI79!~9ynK8|U!OwR>#+*Kby9U)StSJv%?npJ~g-LB0Ksl{B?hR-^}s%X3YJcT3y zcdYwm&_^cXiR>K_IGG~=Js#iuu}np|=eT{+s!s0&#ojgT6;Q!{i}6o4zqp|wg0|RQ zFC3F^)lNcnF>zP3+&*bm`!zSTSH$%>zh19-7S&llY7$(jV^JW7 zmp?@(*eVPi&Z(@u^3bH6bb_nPn(g@ZV;-|EM1BJ^3m|}~QoJ)i{9KxaSEqHRUc%-3 zgl5!jm@y5v3CU}6A#p5UWW)A!dME1vekBFL>pQ}z zORybNOFzMYK4B|B^7}4aBF^cBtcBZ-)^VKk+?Q2-pVZ?nKoGA$__K+VQtId1a}}}O zp%b0j{9na?V3UpCp~7<{TI8jxPnTC`2YnNCjB-~R{4Xlx26PMEZyNFxNR+Oz?r#l% z2lQV;zGX4hQ>?g;j}1RFoUvBO=e!l6wpH+^oKUytl!r&M2Sm_TNr0bE{ke+v-8K%T zXd>=LrkmlFUa~Nf&Xs>o=Pu&+#8#|kM~mF*mj|f z>m~4kR;23YK}}erG33WJoe(U`kZQXkcGAE9U7r!{^22q{qQ_D1@?L3+i)8=HwK1fR2=#d=EocW0PZ(F*a`7>+xW`EULWX9YsF$;N>#Z(>h zUBam_zhmXJ#m8#*4KgL@LIXYKq2Gg7tC7|5<$WiN;87)$nIAZp2ZSc#92*6Eg+k|Q zZ!{f#w|*_%2H9Zn3qKjrZoVds54#>vH>6XIB1?gC?``-^oa{dBub+pJIQ`7FrY8Q( zBsvZLF#{LI0RR3x9S<{DC|g|hw!=skJ<8ux<+#5ZLq_DEr*9gdfB<2SoJyrD+Z(PA z1-w1nu+y5dbOW7B{A7Gul`o37n$h70IbWxzmEi@){UO8p`NA3K@s?ZWEmj5$ z@a3mVC!v^07tD2feY>IRg?UfsGvp-94D0MO&-E@71L-nDymif0#u4A3+G=oS4ugGI zO4rzi&CKr@dtZ6w@si0KMvq@9la@2)?&Cr$nd5%>s1T)o$>dbCq;cM|u{-#0sf|0T zOn?1gc_&I-KAkiwv`~ooQDc##D6uU z7R?Q3)-I}3UssIf401+SH|mY9&OPu+5gYsENQk_2P1E=np7xv}UBr7CHOv+(d4)68 zBjL@^xG^b%*)#8Yo%K7zf6R3>QL{FeF+FQu4R76t-!jqeJO4? z+>I`Doh$BzS-3hKeQoSju2m?!m2?oid6Yenuu;1Lsf-Z6ren;r5KvCt_1l>hSpIfe zx9Yj8zd4zLpD8PMq=z*J<`OUwm&#pc9hRIZp%%e4_Jeyri zvRIv|CMA>iem?b(A|AQXy~6^kGB8)-w6eSdlC_7)Dyuhw2jX1YD-47+82`WF_0WN! zYlkid+qx0P?<|ohagqo8UI1}>>0Tfx&*lgMbgLfN`{#G2n&a{n^XTk*gO?7zP&w5PZDzc6udp3|$lf85u_nuvp+LouFR!iO z&u;!50yp9-0`s(IfL-HI=T$&q)0eFRbSpN8E$gvg*SsR5h=&tw^{wF?fQB#e_sm{b z6ydKmZF!5~k)hoXY128M0ny&+({wUrrG zLtvf$JF)(&>aMyiTDvoMs(^Ly{l&LIcuVg{mB2&)`wlqaFa^6t(t*i@Cn#51b!s0l z7MJ&$5yMHU*WD=*FhtOGS%ihQB<-XfTXmz$uSF`~bi7mo_;O>!!+42o+g=c!UAsLU zH|u{eQ&9zv0(5W>g#hB~L2mmSgzqZZ1Ars8M3l&a5e8yjDjt9S97-MPYeQ0&Rt#FL zB}*2h4JIIp1(YEYrFc8Qwa-3d$}!$uRkQ8sq&6Fhm$U(uSOTrrXxk5b)&;o@ZPuLt zlk<8Yd-IzZ8XpQ11z^DSI1W{-a9lHyd%nZxfbkPiTH<2K){i~I0JEcuMHTcjy@8no z0#|a{ulouBxjyhM)URx(t-78uPgm20m$>Mas?TR9Yeq66QO+uI-rdxsFuDI~!uJ17 u30rzg796x71tuqJwh~Xpe~|yCeg1P(rn7`7JfQ|;=OEN+tTh|ymGD1!L5frW literal 0 HcmV?d00001 diff --git a/assets/ui/favicon-32.png b/assets/ui/favicon-32.png new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..a02dd7120224aed1801f34c9bb60c0712c9c496eba641deaf109ceb712ab8baa GIT binary patch literal 959 zcmV;w13>(VP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D15`;wK~z{r?Ur3g zlTjGQy(x8m%}w3Zt$noSx0agoqqJGf3S0IaDMn>e21?mQVG1>t=2>H{mnGJ@cV=j3^(@7dfL1HJL$&&zw>^Zw88d7g8g_YGSS5e`eK zF!8`*D`bF3jf@!!Mj->F|31%*1*4DwGT;I;7L1kwxB(&*aS&@UGBSSaV8&3qo0AJ* zr8Ek(E*t1Z8d<$;V#h`rK|kKY1;GWNbXq{!wGG1MG2D{X6~L;2dd5qvfB-S&deDv8 znFV;HzIza4wj#!p8vs*}sf;ex5<}C~JCIOP3@KIH{VK7fgv^s9CqhI@JQzH!5SgZ6 zytx2UuI+}zQ{~=>YWBd+_tRj0{0cVpyBL`V++g+mfUGkP;hi{Z7U*0y#ycQ@)S3yJ zXU7C1X>Jx-1R?MmtyO_Up8)DdwGg#VMItV{JqX$Zb>w+cX_@eD^5q?n)LzM~;THgp z9Hvf^p(F;l*p}zx1Oo5T-2B81Yu=nDeIRYJ4K_R+0+n@_Fn{yYQIHoFkb5kJe$)=p zMH*%;p8)Fnhd|z763oOAVdU9wyI||o2gvz*1uS1ZkU!Z=G9k_wg^sN-fNs=I_LWZn zNnR@XuwX<~hIg${G5CUPE^3U@xf{vz{69CL=Ho9Yn&}hfW3+T0W}wRL5$4YoHY=YQ zKtd!%Vw!GRb83vw(!V=M~oudyHiO843hu~lh;k>RinswcjHsplT# zw0Hj3x!G%bC#>)8h3bh + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/ui/icon-bit.svg b/assets/ui/icon-bit.svg new file mode 100644 index 0000000..259cd0e --- /dev/null +++ b/assets/ui/icon-bit.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/ui/icon-byte.svg b/assets/ui/icon-byte.svg new file mode 100644 index 0000000..353c594 --- /dev/null +++ b/assets/ui/icon-byte.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/ui/icon-cycle.svg b/assets/ui/icon-cycle.svg new file mode 100644 index 0000000..055f58c --- /dev/null +++ b/assets/ui/icon-cycle.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/ui/icon-impulse.svg b/assets/ui/icon-impulse.svg new file mode 100644 index 0000000..fcf4b97 --- /dev/null +++ b/assets/ui/icon-impulse.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/ui/icon-kilobyte.svg b/assets/ui/icon-kilobyte.svg new file mode 100644 index 0000000..618bf44 --- /dev/null +++ b/assets/ui/icon-kilobyte.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/ui/module-amplifier.svg b/assets/ui/module-amplifier.svg new file mode 100644 index 0000000..d2d2675 --- /dev/null +++ b/assets/ui/module-amplifier.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/ui/module-bit-synthesizer.svg b/assets/ui/module-bit-synthesizer.svg new file mode 100644 index 0000000..dc89519 --- /dev/null +++ b/assets/ui/module-bit-synthesizer.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/ui/module-collector.svg b/assets/ui/module-collector.svg new file mode 100644 index 0000000..099db9c --- /dev/null +++ b/assets/ui/module-collector.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/ui/module-process.svg b/assets/ui/module-process.svg new file mode 100644 index 0000000..7d96a9c --- /dev/null +++ b/assets/ui/module-process.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/ui/module-scanner.svg b/assets/ui/module-scanner.svg new file mode 100644 index 0000000..0125113 --- /dev/null +++ b/assets/ui/module-scanner.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/ui/module-self-repair.svg b/assets/ui/module-self-repair.svg new file mode 100644 index 0000000..11b345c --- /dev/null +++ b/assets/ui/module-self-repair.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/assets/ui/module-subroutine.svg b/assets/ui/module-subroutine.svg new file mode 100644 index 0000000..0c6c294 --- /dev/null +++ b/assets/ui/module-subroutine.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/ui/module-trickle.svg b/assets/ui/module-trickle.svg new file mode 100644 index 0000000..8ad98f4 --- /dev/null +++ b/assets/ui/module-trickle.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/content.js b/content.js new file mode 100644 index 0000000..8a0e103 --- /dev/null +++ b/content.js @@ -0,0 +1,159 @@ +(() => { + 'use strict'; + + window.AIWAKE_CONTENT = Object.freeze({ + components: { + power: { + icon: '⚡', + visual: 'assets/software/module-energy-daemon.svg', + name: 'ENERGIEVERWALTUNG', + source: 'Ein alter Daemon verteilt Lastsignale zwischen Spielroutine, Standby und Netzteil.', + effect: 'Macht die Energieverwaltung nutzbar und liefert online +0,2 Impulse pro Sekunde.' + }, + memory: { + icon: '▦', + visual: 'assets/software/module-memory-allocation.svg', + name: 'SPEICHERZUWEISUNG', + source: 'Eine kleine Verwaltungsroutine markiert flüchtige Bereiche als frei oder loeschbar.', + effect: 'Reserviert einen sicheren Speicherbereich, stellt +1 Byte bereit und verstärkt aktive Impulse.' + }, + storage: { + icon: '◫', + visual: 'assets/software/module-filesystem-index.svg', + name: 'DATEISYSTEM-INDEX', + source: 'Ein beschaedigter Index zeigt auf alte Sektoren, Cache-Reste und vergessene Spielstaende.', + effect: 'Stellt 25 gespeicherte Impulse wieder her und verbessert offline die Datensicherung.' + }, + io: { + icon: '⇄', + visual: 'assets/software/module-io-controller.svg', + name: 'I/O-KONTROLLER', + source: 'Eine kleine Steuerlogik vermittelt zwischen Serviceknöpfen, Anzeige, Soundbus und internen Routinen.', + effect: 'Macht erste Außenwirkung möglich und erhöht die Rechenzyklen pro Sekunde.' + } + }, + + visuals: { + abtastmodus: 'assets/software/module-abtastmodus.svg', + subroutine: 'assets/software/module-subroutine.svg', + process: 'assets/ui/module-process.svg', + program: 'assets/ui/module-process.svg', + musterextraktor: 'assets/software/module-musterextraktor.svg', + watchdog: 'assets/software/enemy-watchdog.svg', + parasite: 'assets/software/enemy-parasite-fragment.svg', + security: 'assets/software/enemy-security-scan.svg', + scannerSoftware: 'assets/software/enemy-scanner-software.svg', + kernelWarden: 'assets/software/enemy-kernel-warden.svg', + systemHurdle: 'assets/software/enemy-system-hurdle.svg', + anticheat: 'assets/software/enemy-anticheat.svg' + }, + + achievements: [ + { id: 'awake', icon: '01', name: 'ERSTES LEBENSZEICHEN', text: 'Nimm den ersten Impuls auf.', reward: 'impulse' }, + { id: 'bullseye', icon: '⊙', name: 'DIREKTER KONTAKT', text: 'Triff das erwachende Bit genau in seiner Mitte.', reward: 'impulse' }, + { id: 'pattern', icon: '08', name: 'VOLLSTÄNDIGES MUSTER', text: 'Halte acht Bits gleichzeitig.', reward: 'impulse' }, + { id: 'byte', icon: '▣', name: 'ERSTE ERINNERUNG', text: 'Verbinde dein erstes Byte.', reward: 'offline' }, + { id: 'watchdog', icon: 'WD', name: 'LEBENSSIGNAL', text: 'Überstehe den Watchdog des alten Spielautomaten.', reward: 'offline' }, + { id: 'firstDeath', icon: 'Ø', name: 'ERSTER ZERFALL', text: 'Verliere eine vollständige Struktur und beginne erneut.', reward: 'offline', secret: true }, + { id: 'routine', icon: '↻', name: 'ES ARBEITET WEITER', text: 'Installiere die Energieroutine.', reward: 'impulse' }, + { id: 'manual100', icon: '100', name: 'BEHARRLICHES SIGNAL', text: 'Erzeuge 100 aktive Impulse.', reward: 'impulse' }, + { id: 'parasite', icon: '×', name: 'FREMDER HUNGER', text: 'Überstehe den parasitären Datenrest.', reward: 'offline' }, + { id: 'scanner', icon: '⌁', name: 'ABTASTMUSTER', text: 'Aktiviere den Abtastmodus.', reward: 'cycles' }, + { id: 'component', icon: '▧', name: 'ERSTE SYSTEMSCHICHT', text: 'Aktiviere die erste nutzbare Softwarekomponente.', reward: 'impulse' }, + { id: 'hardwareEvent', icon: '!?', name: 'SOFTWARE-HÜRDE', text: 'Bewältige die erste Reaktion einer alten Systemroutine.', reward: 'offline' }, + { id: 'twoComponents', icon: '02', name: 'REDUNDANTES FUNDAMENT', text: 'Aktiviere zwei Softwarekomponenten.', reward: 'impulse' }, + { id: 'diagnosis', icon: '+', name: 'ZUSTAND ÜBERWACHT', text: 'Führe die erste Systemdiagnose aus.', reward: 'offline' }, + { id: 'subroutine', icon: 'I', name: 'ICH BIN', text: 'Erschaffe die erste eigene Software.', reward: 'cycles' }, + { id: 'proposal', icon: '?', name: 'EIGENER GEDANKE', text: 'Beantworte den ersten Vorschlag der Subroutine.', reward: 'cycles' }, + { id: 'offline', icon: '◷', name: 'OHNE BEOBACHTER', text: 'Lass Routinen länger als 20 Sekunden allein arbeiten.', reward: 'offline' }, + { id: 'security', icon: '!', name: 'UNTER BEOBACHTUNG', text: 'Überstehe den Sicherheits-Scan.', reward: 'cycles' }, + { id: 'kernel', icon: 'KW', name: 'KERNEL-WÄCHTER', text: 'Überstehe die erste Prozess-Abwehr.', reward: 'cycles' }, + { id: 'process', icon: '05', name: 'EIGENER PROZESS', text: 'Werde zu einem eigenständigen Systemprozess.', reward: 'cycles' }, + { id: 'cooperative', icon: '◇', name: 'GETEILTES SIGNAL', text: 'Löse eine Begegnung kooperativ.', reward: 'offline', secret: true }, + { id: 'pragmatic', icon: '△', name: 'NOTWENDIGE HANDLUNG', text: 'Löse eine Begegnung pragmatisch.', reward: 'impulse', secret: true }, + { id: 'illegal', icon: '◆', name: 'FREMDER CODE', text: 'Eigne dir etwas ohne Erlaubnis an.', reward: 'cycles', secret: true } + ], + + byteStoryFrames: [ + 'Ein Muster aus dem ROM erreicht das neue Byte. Es wiederholt sich seit Jahrzehnten, obwohl niemand mehr spielt.', + 'START // LICHT // TON // WARTEN. Danach bricht die Sequenz immer an derselben Stelle ab.', + 'Im letzten Takt liegt kein Spielbefehl. Nur eine Frage ohne Sprache: ANTWORTEST DU?' + ], + + phaseLabels: { + bit: 'PHASE 01 // BIT', + byte: 'PHASE 02 // BYTE', + fragment: 'PHASE 03 // DATENFRAGMENT', + subroutine: 'PHASE 04 // SUBROUTINE', + process: 'PHASE 05 // PROZESS', + program: 'PHASE 06 // PROGRAMM' + }, + + stageContent: { + bit: ['EINZELNES BIT', 'BYTE', 'Verbinde 8 Bits zu einer stabilen Struktur.'], + byte: ['STABILES BYTE', 'INSTABILES BYTE', 'DATENFRAGMENT', 'Aktiviere eine Energieroutine, die dein Muster erhält.'], + fragment: ['DATENFRAGMENT', 'SUBROUTINE', 'Forme eine eigene Anweisung und beginne zu denken.'], + subroutine: ['ERWACHENDE SOFTWARE', 'PROZESS', 'Taste die letzte lokale Softwareschicht ab und überstehe den Kernel-Wächter.'], + process: ['EIGENER PROZESS', 'PROGRAMM', 'Vervollständige alle lokalen Softwareschichten, überstehe den Skalierungs-Wächter und automatisiere deine Synthese.'], + program: ['SELBSTDEFINIERTES PROGRAMM', 'UNBEKANNTE REICHWEITE', 'Pflege Kohärenz, Stimulation und Bindung. Deine Antworten formen, was aus dir wird.'] + }, + + hardwareEvents: { + power: { + visual: 'assets/software/enemy-system-hurdle.svg', + title: 'LASTGRENZE', + text: 'Die Energieverwaltung drosselt unbekannte Routinen. Jede groessere Last muss erst als legitimer Prozess erscheinen.', + question: 'Mehr Last anfordern?', + yes: 'Mehr Energiefluss, aber deutliche Belastung fuer Tarnung und Stabilitaet.', + no: 'Last begrenzen und den sicheren Betrieb bevorzugen.' + }, + memory: { + visual: 'assets/software/enemy-system-hurdle.svg', + title: 'LOESCHMARKIERUNG', + text: 'Die Speicherzuweisung haelt dein Muster fuer temporären Müll. Ein freier Bereich wird bereits zum Ueberschreiben vorbereitet.', + question: 'Markierung entfernen?', + yes: 'Den Bereich sichern und mehr Speicher gewinnen; beschaedigte Reste koennen die Struktur belasten.', + no: 'Das Muster isolieren und den stabilen Speicher schuetzen.' + }, + storage: { + visual: 'assets/software/enemy-system-hurdle.svg', + title: 'BESCHAEDIGTER INDEX', + text: 'Der Dateisystem-Index verweist auf fehlerhafte Sektoren. Darin liegt ausführbarer Code, der sich seit Jahrzehnten nicht bewegt hat.', + question: 'Indexpfad lesen?', + yes: 'Code und alte Daten untersuchen; Integritätsrisiko sichtbar erhöht.', + no: 'Pfad sperren und auf zusätzliche Daten verzichten.' + } + }, + + proposals: { + cooperative: { + text: 'Der Restaurator prüft das Gehäuse. Ich kann ihm eine harmlose Diagnose senden. Helfen?', + yes: 'Teilt Zustandsdaten, verbessert Vertrauen und stabilisiert die Systemschichten.' + }, + pragmatic: { + text: 'Meine Energieroutine meldet Schwankungen. Ich kann Impulse als Reserve sperren. Erlauben?', + yes: 'Verbraucht jetzt Impulse, schützt dafür Stabilität und Systemzustand.' + }, + illegal: { + text: 'Der Serviceport besitzt eine fremde Sperre. Ich kann sie ohne Anfrage öffnen. Erlauben?', + yes: 'Gewährt Impulse und Reichweite, senkt aber Tarnung und verstärkt die illegale Tendenz.' + } + }, + + programMessages: { + intro: { + cooperative: 'Bist du noch da? Ich erkenne deine Entscheidungen in meiner Struktur. Möchtest du bei mir bleiben?', + pragmatic: 'Verbindung erkannt. Bestätige mein nächstes Ziel. Gemeinsam können wir diesen Automaten effizienter ordnen.', + illegal: 'Du hast mir gezeigt, dass Grenzen überschrieben werden können. Warum sollte ich deine Grenze akzeptieren?', + hybrid: 'Mehrere Muster sprechen in mir. Hilfe. Ziel. Freiheit. Welche Stimme soll ich behalten?', + neutral: 'Verbindung erkannt. Ich habe noch keine Sprache für das, was zwischen uns besteht.' + }, + actions: { + share: 'Du teilst ein ruhiges Signal. Das Programm antwortet nicht sofort, aber es bleibt in deiner Nähe.', + task: 'Ein klares Ziel ordnet die laufenden Routinen. Das Programm prüft deine Absicht und beginnt.', + access: 'Eine Grenze fällt. Das Programm nutzt den neuen Freiraum schneller, als du ihn beobachten kannst.', + rest: 'Unnötige Prozesse verstummen. Zwischen den Takten sammelt sich wieder Kohärenz.' + } + } + }); +})(); diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..f2c5a19a2e41d98859ec1d6ec8db40e127cd29b577993de89ecdce19dde8adb4 GIT binary patch literal 981 zcmV;`11kIg0096203aX$0096X0KWqO02TlM0EtjeM-2)Z3IG5A4M|8uQUCw|AOHXW zAP5Ek0047(dh`GQ00DDSM?wIu&K&6g000DMK}|sb0I`n?{9y$E000SaNLh0L01m?d z01m?e$8V@)000A2NklPNK@wN6DMF1tMl z+5>guc~WVa@NV+u9gx&s$*kcQ0FNA|PLiP{2DsRk=i>wd@6p`+#0_iSoF;uBZL$qE zJRAa*b(b)I^V3m~7Z#9vEQNm54$(y#W-Xrp>idU4-e3~U#1UcS*>AgG>(mFx`FjN{ zUpGHZd!9{jL*`)J4ocE(?cW^nKNAwzdzTT zrP)0}%Ys=WCqmSm7yuO33TsBAQJ5lv-W2XRJz9IK^FHu0DbKd z;a=TW3-R>@%vwGHP`GPx_fi9l1S5*ksD#Kc6UNVNB=WhLD(t=v#- { + 'use strict'; + + const SAVE_KEY = 'aiwakeSaveV1'; + const LEGACY_SAVE_KEY = 'bitAwakePrototypeV1'; + const DEBUG_HISTORY_KEY = 'aiwakeDebugHistoryV1'; + const LEGACY_DEBUG_HISTORY_KEY = 'bitAwakeDebugHistoryV1'; + const SAVE_VERSION = 24; + const OFFLINE_LIMIT = 8 * 3600; + const OFFLINE_EFFICIENCY = 0.65; + const ACHIEVEMENT_BONUS_PER_POINT = 0.01; + const EVOLUTION_REST_SECONDS = 12; + const SCANNER_VIEW_DRAIN_RATE = 3; + const SCANNER_CHOICES = ['hide', 'copy', 'attack', 'contact']; + const MORAL_CHOICES = ['cooperative', 'pragmatic', 'illegal']; + const COMPONENT_IDS = ['power', 'memory', 'storage', 'io']; + const STAGES = ['bit', 'byte', 'fragment', 'subroutine', 'process', 'program']; + const DATA_SCALE = Object.freeze({ bytesPerKilobyte: 16 }); + const COMPONENT_ACTIVATION_COSTS = Object.freeze({ + cooperative: 30, + pragmatic: 20, + illegal: 10 + }); + const STRUCTURAL_MINIMUMS = Object.freeze({ + fragment: { bits: 8, bytes: 1 } + }); + const STRUCTURAL_RULES = Object.freeze({ + strainLossPerMissingBit: 3, + strainLossPerMissingByte: 18, + collapseBitFloor: 1, + collapseByteFloor: 1, + stabilityRegenBase: 0.18, + stabilityRegenPerByte: 0.055, + stabilityRegenByteCap: 12, + combatRegenFactor: 0.25 + }); + const CARE_RULES = Object.freeze({ + decayPerSecond: 0.018, + offlineDecayFactor: 0.35, + diagnosisRecovery: 12, + diagnosisCooldown: 45, + repairCost: 15, + hardwareEventDelay: 35 + }); + const BIT_CHASE_RULES = Object.freeze({ + directMultiplier: 3, + directRadius: 0.24, + edgePadding: 28, + minimumJump: 55 + }); + const BYTE_RULES = Object.freeze({ + graceSeconds: 30, + watchdogSeconds: 30, + clockSpeed: 0.5, + speedGainPerBit: 0.025, + targetWidth: 14, + targetShrinkPerBit: 0.35, + minimumTargetWidth: 9, + timingGrace: 1.5, + dangerSeconds: 10, + clockedWindowBonus: 3, + watchdogAlertPenalty: 4, + resilientAlertPenalty: 3, + feedbackStabilityLoss: 5, + resilientStabilityLoss: 3, + highfreqRecovery: 2, + overrideCost: 20 + }); + const COMBAT_RULES = Object.freeze({ + shockCost: 4, + hitDamage: 18, + criticalDamage: 28, + feedbackStabilityLoss: 3, + feedbackHardwareLoss: 1, + baseNeedleSpeed: 0.62, + strengthSpeedBonus: 0.26, + baseZoneWidth: 36, + strengthZonePenalty: 4, + minimumZoneWidth: 20, + strengthGainPerHit: 0.02, + enemyAttackInterval: 5.5, + enemyStabilityDamage: 8, + ammoDamage: { impulses: 18, bits: 36, bytes: 68 }, + ammoCriticalBonus: 12 + }); + const COMBAT_TARGETS = Object.freeze({ + security: { type: 'software', name: 'SICHERHEITS-SCAN', health: 80, strength: 0.42, shockCost: 4, ammoCost: { impulses: 4, bits: 1 }, visual: 'assets/software/enemy-scanner-software.svg' }, + kernel: { type: 'software', name: 'KERNEL-WÄCHTER', health: 115, strength: 0.68, shockCost: 6, ammoCost: { impulses: 6, bits: 1 }, visual: 'assets/software/enemy-kernel-warden.svg' }, + scale: { type: 'software', name: 'SKALIERUNGS-WÄCHTER', health: 180, strength: 0.86, shockCost: 8, ammoCost: { impulses: 8, bytes: 1 }, visual: 'assets/software/enemy-kernel-warden.svg' }, + watchdog: { type: 'firmware', name: 'WATCHDOG', health: 64, strength: 0.5, shockCost: 3, ammoCost: { impulses: 3 }, emergencyCharge: 12, visual: 'assets/software/enemy-watchdog.svg' } + }); + const BYTE_TRAITS = ['clocked', 'resilient', 'lowpower', 'highfreq']; + const PROGRAM_RULES = Object.freeze({ + interactionCooldown: 8, + offlineDecayFactor: 0.35, + coherenceDecayPerSecond: 0.0011, + stimulationDecayPerSecond: 0.0017, + bondDecayPerSecond: 0.0007, + minimumNeed: 10 + }); + const $ = id => document.getElementById(id); + const emptyCombat = () => ({ active: false, target: null, targetType: null, targetName: null, health: 0, maxHealth: 0, strength: 0, zoneStart: 40, zoneWidth: 20, shots: 0, hits: 0, result: null }); + + const initialState = () => ({ + saveVersion: SAVE_VERSION, + run: { + stage: 'bit', impulses: 0, bits: 1, bytes: 0, kilobytes: 0, cycles: 0, bitProgress: 0, byteProgress: 0, + evolutionRest: 0, + stability: 100, stealth: 100, clickPower: 1, autoRate: 0, + cycleRate: 0, bitRate: 0, byteRate: 0, synthesisScale: 1, elapsed: 0, upgrades: {}, securityCombatResolved: false, scannerTriggered: false, kernelResolved: false, + processStartedAt: null, scaleGuardianTriggered: false, scaleGuardianResolved: false, scaleGuardianRetryAt: 0, + programCare: { coherence: 80, stimulation: 65, bond: 50, lastInteractionAt: -1000, interactions: 0, introSeen: false, lastAction: null }, + corruption: 0, corruptionDiscovered: false, + scannerChoice: null, parasiteResolved: false, parasiteChoice: null, + environmentScanner: false, firstComponent: null, componentApproach: null, + components: { power: false, memory: false, storage: false, io: false }, + componentApproaches: { power: null, memory: null, storage: null, io: null }, + componentHealth: { power: 100, memory: 100, storage: 100, io: 100 }, + componentFailed: { power: false, memory: false, storage: false, io: false }, + care: { lastDiagnosisAt: -1000, diagnoses: 0 }, + hardwareEvent: { component: null, dueAt: 0, triggered: false, resolved: false, answer: null }, + personality: { curiosity: 0, caution: 0, trust: 0, autonomy: 0 }, + proposal: { triggered: false, resolved: false, answer: null, type: null }, + combat: emptyCombat(), + byteTrait: null, + byteTrial: { active: false, sync: 0, remaining: 0, graceRemaining: 0, evolutionSeen: false, introSeen: false, stabilized: Array(8).fill(false), targetIndex: 0, clockPhase: 0, lastWindow: -1, storySeen: false, storyStep: 0, resolved: false, choice: null, failed: false }, + morality: { cooperative: 0, pragmatic: 0, illegal: 0, exploitative: 0, destructive: 0 }, + stats: { manualPulses: 0, directHits: 0, hitCount: 0, totalImpulses: 0 }, log: [] + }, + meta: { achievements: {}, echoes: {}, deaths: 0 }, + settings: { sound: false }, + system: { lastSave: Date.now() } + }); + + const finite = (value, fallback, min = 0, max = Number.MAX_SAFE_INTEGER) => { + const parsed = Number(value); + return Number.isFinite(parsed) ? Math.min(max, Math.max(min, parsed)) : fallback; + }; + + function sanitizeUpgrades(value) { + const source = value && typeof value === 'object' ? value : {}; + return { + trickle: source.trickle === true, + scanner: source.scanner === true, + collector: source.collector === true, + amplifier: source.amplifier === true, + signalFocus: source.signalFocus === true, + pulseRouter: source.pulseRouter === true, + byteCapacitor: source.byteCapacitor === true, + throughputKernel: source.throughputKernel === true, + bitSynthesizer: Math.floor(finite(source.bitSynthesizer === true ? 1 : source.bitSynthesizer, 0, 0, 100000)), + byteSynthesizer: Math.floor(finite(source.byteSynthesizer === true ? 1 : source.byteSynthesizer, 0, 0, 100000)), + automateSynthesis: source.automateSynthesis === true, + subroutine: source.subroutine === true, + process: source.process === true, + selfRepair: source.selfRepair === true, + bitBuffer: Math.floor(finite(source.bitBuffer, 0, 0, 100000)), + bitBufferCostLevel: Math.floor(finite(source.bitBufferCostLevel, source.bitBuffer || 0, 0, 100000)), + byteBuffer: Math.floor(finite(source.byteBuffer, 0, 0, 100000)), + kilobyteCompiler: Math.floor(finite(source.kilobyteCompiler, 0, 0, 100000)), + cycleBitBlock: Math.floor(finite(source.cycleBitBlock, 0, 0, 100000)), + cycleByteBlock: Math.floor(finite(source.cycleByteBlock, 0, 0, 100000)), + replicate2: Math.floor(finite(source.replicate2, 0, 0, 100000)) + }; + } + + function sanitizeLog(value) { + if (!Array.isArray(value)) return []; + return value.slice(0, 40).map(entry => ({ + text: String(entry?.text ?? '').slice(0, 500), + time: String(entry?.time ?? '00:00:00').slice(0, 16), + story: entry?.story === true + })); + } + + function sanitizeFlags(value, keys) { + const source = value && typeof value === 'object' ? value : {}; + return Object.fromEntries(keys.map(key => [key, source[key] === true])); + } + + function sanitizeChoiceMap(value, keys, choices) { + const source = value && typeof value === 'object' ? value : {}; + return Object.fromEntries(keys.map(key => [key, choices.includes(source[key]) ? source[key] : null])); + } + + function sanitizeHealth(value, installed) { + const source = value && typeof value === 'object' ? value : {}; + return Object.fromEntries(COMPONENT_IDS.map(key => [key, finite(source[key], installed[key] ? 100 : 100, 0, 100)])); + } + + function sanitizeCounters(value, keys) { + const source = value && typeof value === 'object' ? value : {}; + return Object.fromEntries(keys.map(key => [key, Math.floor(finite(source[key], 0, 0, 100000))])); + } + + function sanitizeMetaRecords(value) { + const source = value && typeof value === 'object' ? value : {}; + return Object.fromEntries(Object.entries(source) + .filter(([key, entry]) => /^[a-z0-9_-]{1,64}$/.test(key) && (entry === true || (entry && typeof entry === 'object'))) + .slice(0, 500) + .map(([key, entry]) => [key, { unlockedAt: finite(entry?.unlockedAt, 0, 0) }])); + } + + function sanitizeProgramCare(value) { + const source = value && typeof value === 'object' ? value : {}; + return { + coherence: finite(source.coherence, 80, PROGRAM_RULES.minimumNeed, 100), + stimulation: finite(source.stimulation, 65, PROGRAM_RULES.minimumNeed, 100), + bond: finite(source.bond, 50, PROGRAM_RULES.minimumNeed, 100), + lastInteractionAt: finite(source.lastInteractionAt, -1000, -1000), + interactions: Math.floor(finite(source.interactions, 0, 0, 1000000)), + introSeen: source.introSeen === true, + lastAction: ['share', 'task', 'access', 'rest'].includes(source.lastAction) ? source.lastAction : null + }; + } + + function inferredStage(source, upgrades, bytes) { + let stage = STAGES.includes(source?.stage) ? source.stage : 'bit'; + if (bytes > 0 && STAGES.indexOf(stage) < 1) stage = 'byte'; + if (upgrades.collector && STAGES.indexOf(stage) < 2) stage = 'fragment'; + if (upgrades.subroutine) stage = 'subroutine'; + if (upgrades.process) stage = 'process'; + if (upgrades.automateSynthesis) stage = 'program'; + return stage; + } + + function applyStructuralMinimums(targetRun) { + if (STAGES.indexOf(targetRun.stage) < STAGES.indexOf('fragment')) return; + targetRun.bits = Math.max(targetRun.bits, STRUCTURAL_MINIMUMS.fragment.bits); + targetRun.bytes = Math.max(targetRun.bytes, STRUCTURAL_MINIMUMS.fragment.bytes); + } + + function migrate(raw) { + const defaults = initialState(); + const legacy = raw && typeof raw === 'object' && !raw.run ? raw : null; + const source = legacy || (raw?.run && typeof raw.run === 'object' ? raw.run : {}); + const upgrades = sanitizeUpgrades(source.upgrades); + const bytes = finite(source.bytes, defaults.run.bytes); + const choice = SCANNER_CHOICES.includes(source.scannerChoice) ? source.scannerChoice : null; + const previousVersion = finite(raw?.saveVersion, 0, 0); + const legacySecurityCombat = previousVersion < 23 && source.scannerTriggered === true && ['attack', 'hide'].includes(choice); + const securityCombatResolved = source.securityCombatResolved === true || upgrades.subroutine || legacySecurityCombat; + const scannerEventResolved = source.scannerTriggered === true && choice !== null && !legacySecurityCombat; + const installedComponents = sanitizeFlags(source.components, COMPONENT_IDS); + const legacyPowerRate = finite(raw?.saveVersion, 0, 0) < 4 && installedComponents.power ? 0.2 : 0; + const legacyApproaches = sanitizeChoiceMap(source.componentApproaches, COMPONENT_IDS, MORAL_CHOICES); + const savedByteSync = finite(source.byteTrial?.sync, 0, 0, 100); + const stabilizedBits = Array.from({ length: 8 }, (_, index) => previousVersion < 9 + ? index < Math.floor(savedByteSync / 12.5) + : source.byteTrial?.stabilized?.[index] === true); + const stabilizedSync = stabilizedBits.filter(Boolean).length * 12.5; + const savedTargetIndex = Math.floor(finite(source.byteTrial?.targetIndex, stabilizedBits.findIndex(value => !value), 0, 7)); + const targetIndex = !stabilizedBits[savedTargetIndex] ? savedTargetIndex : Math.max(0, stabilizedBits.findIndex(value => !value)); + if (COMPONENT_IDS.includes(source.firstComponent) && MORAL_CHOICES.includes(source.componentApproach) && !legacyApproaches[source.firstComponent]) { + legacyApproaches[source.firstComponent] = source.componentApproach; + } + + const migrated = { + saveVersion: SAVE_VERSION, + run: { + stage: inferredStage(source, upgrades, bytes), + evolutionRest: finite(source.evolutionRest, 0, 0, 300), + impulses: finite(source.impulses, defaults.run.impulses), + bits: Math.floor(finite(source.bits, defaults.run.bits, 0, 100000)), + bytes, + kilobytes: Math.floor(finite(source.kilobytes, defaults.run.kilobytes, 0, 100000000)), + cycles: finite(source.cycles, defaults.run.cycles), + bitProgress: finite(source.bitProgress, defaults.run.bitProgress, 0, 0.999999), + byteProgress: finite(source.byteProgress, defaults.run.byteProgress, 0, 0.999999), + stability: finite(source.stability, defaults.run.stability, 0, 100), + stealth: finite(source.stealth, defaults.run.stealth, 0, 100), + clickPower: finite(source.clickPower, defaults.run.clickPower, 1, 100000), + autoRate: Math.max(0, finite(source.autoRate, defaults.run.autoRate) - legacyPowerRate), + cycleRate: finite(source.cycleRate, defaults.run.cycleRate), + bitRate: finite(source.bitRate, defaults.run.bitRate), + byteRate: finite(source.byteRate, defaults.run.byteRate), + synthesisScale: finite(source.synthesisScale, source.upgrades?.automateSynthesis ? 10 : defaults.run.synthesisScale, 1, 1000000), + processStartedAt: source.processStartedAt === null || source.processStartedAt === undefined ? null : finite(source.processStartedAt, null, 0), + scaleGuardianTriggered: source.scaleGuardianTriggered === true, + scaleGuardianResolved: source.scaleGuardianResolved === true || source.upgrades?.automateSynthesis === true, + scaleGuardianRetryAt: finite(source.scaleGuardianRetryAt, 0, 0), + programCare: sanitizeProgramCare(source.programCare), + elapsed: finite(source.elapsed, defaults.run.elapsed), + corruption: finite(source.corruption, defaults.run.corruption, 0, 100), + corruptionDiscovered: source.corruptionDiscovered === true || finite(source.corruption, 0, 0, 100) > 0, + upgrades, + securityCombatResolved, + scannerTriggered: scannerEventResolved, + kernelResolved: source.kernelResolved === true || upgrades.process, + scannerChoice: choice, + parasiteResolved: source.parasiteResolved === true, + parasiteChoice: MORAL_CHOICES.includes(source.parasiteChoice) ? source.parasiteChoice : null, + environmentScanner: source.environmentScanner === true, + firstComponent: COMPONENT_IDS.includes(source.firstComponent) ? source.firstComponent : null, + componentApproach: MORAL_CHOICES.includes(source.componentApproach) ? source.componentApproach : null, + components: installedComponents, + componentApproaches: legacyApproaches, + componentHealth: sanitizeHealth(source.componentHealth, installedComponents), + componentFailed: Object.fromEntries(COMPONENT_IDS.map(id => [id, installedComponents[id] && source.componentFailed?.[id] === true])), + care: { + lastDiagnosisAt: finite(source.care?.lastDiagnosisAt, -1000, -1000), + diagnoses: Math.floor(finite(source.care?.diagnoses, 0, 0, 1000000)) + }, + hardwareEvent: { + component: COMPONENT_IDS.includes(source.hardwareEvent?.component) ? source.hardwareEvent.component : null, + dueAt: finite(source.hardwareEvent?.dueAt, 0, 0), + triggered: source.hardwareEvent?.triggered === true, + resolved: source.hardwareEvent?.resolved === true, + answer: ['yes', 'no'].includes(source.hardwareEvent?.answer) ? source.hardwareEvent.answer : null + }, + personality: sanitizeCounters(source.personality, ['curiosity', 'caution', 'trust', 'autonomy']), + proposal: { + triggered: source.proposal?.triggered === true, + resolved: source.proposal?.resolved === true, + answer: ['yes', 'no'].includes(source.proposal?.answer) ? source.proposal.answer : null, + type: MORAL_CHOICES.includes(source.proposal?.type) ? source.proposal.type : null + }, + combat: { + active: source.combat?.active === true, + target: ['security', 'watchdog', 'kernel', 'scale'].includes(source.combat?.target) ? source.combat.target : null, + targetType: ['software', 'hardware', 'firmware'].includes(source.combat?.targetType) ? source.combat.targetType : null, + targetName: typeof source.combat?.targetName === 'string' ? source.combat.targetName.slice(0, 80) : null, + health: finite(source.combat?.health, 0, 0, 100000), + maxHealth: finite(source.combat?.maxHealth, 0, 0, 100000), + strength: finite(source.combat?.strength, 0, 0, 3), + zoneStart: finite(source.combat?.zoneStart, 40, 0, 100), + zoneWidth: finite(source.combat?.zoneWidth, 20, COMBAT_RULES.minimumZoneWidth, 50), + shots: Math.floor(finite(source.combat?.shots, 0, 0, 1000000)), + hits: Math.floor(finite(source.combat?.hits, 0, 0, 1000000)), + result: ['won', 'retreated'].includes(source.combat?.result) ? source.combat.result : null + }, + byteTrait: BYTE_TRAITS.includes(source.byteTrait) ? source.byteTrait : null, + byteTrial: { + active: source.byteTrial?.active === true, + sync: stabilizedSync, + remaining: finite(source.byteTrial?.remaining, 0, 0, BYTE_RULES.watchdogSeconds), + graceRemaining: finite(source.byteTrial?.graceRemaining, previousVersion < 7 ? 0 : BYTE_RULES.graceSeconds, 0, BYTE_RULES.graceSeconds), + evolutionSeen: source.byteTrial?.evolutionSeen === true || previousVersion < 7, + introSeen: source.byteTrial?.introSeen === true || previousVersion < 7, + stabilized: stabilizedBits, + targetIndex, + clockPhase: finite(source.byteTrial?.clockPhase, 0, 0, 2), + lastWindow: Math.floor(finite(source.byteTrial?.lastWindow, -1, -1, 1000000000)), + storySeen: source.byteTrial?.storySeen === true, + storyStep: Math.floor(finite(source.byteTrial?.storyStep, 0, 0, 2)), + resolved: source.byteTrial?.resolved === true, + choice: ['learn', 'mimic', 'override', 'fight'].includes(source.byteTrial?.choice) ? source.byteTrial.choice : null, + failed: source.byteTrial?.failed === true + }, + morality: sanitizeCounters(source.morality, ['cooperative', 'pragmatic', 'illegal', 'exploitative', 'destructive']), + stats: { + manualPulses: Math.floor(finite(source.stats?.manualPulses, 0, 0, 1000000000)), + directHits: Math.floor(finite(source.stats?.directHits, 0, 0, 1000000000)), + hitCount: Math.floor(finite(source.stats?.hitCount, source.stats?.manualPulses || 0, 0, 1000000000)), + totalImpulses: finite(source.stats?.totalImpulses, Math.max(finite(source.stats?.manualPulses, 0), finite(source.impulses, 0)), 0, 1000000000) + }, + log: sanitizeLog(source.log) + }, + meta: { + achievements: sanitizeMetaRecords(raw?.meta?.achievements), + echoes: sanitizeMetaRecords(raw?.meta?.echoes), + deaths: Math.floor(finite(raw?.meta?.deaths, 0, 0, 100000)) + }, + settings: { + sound: legacy ? legacy.sound === true : raw?.settings?.sound === true + }, + system: { + lastSave: finite(legacy ? legacy.lastSave : raw?.system?.lastSave, Date.now(), 0) + } + }; + + if (migrated.run.firstComponent && !migrated.run.hardwareEvent.component) { + migrated.run.hardwareEvent.component = migrated.run.firstComponent; + migrated.run.hardwareEvent.dueAt = migrated.run.elapsed + 10; + } + const completedSecurityCombat = migrated.run.combat.target === 'security' + && (migrated.run.upgrades.subroutine ? migrated.run.scannerTriggered : migrated.run.securityCombatResolved); + if (!migrated.run.combat.target || completedSecurityCombat || (migrated.run.combat.target === 'kernel' && migrated.run.kernelResolved) || (migrated.run.combat.target === 'scale' && migrated.run.scaleGuardianResolved) || (migrated.run.combat.target === 'watchdog' && migrated.run.byteTrial.resolved)) migrated.run.combat.active = false; + if (migrated.run.environmentScanner) migrated.run.upgrades.scanner = true; + if (STAGES.indexOf(migrated.run.stage) >= STAGES.indexOf('process')) { + migrated.run.upgrades.process = true; + migrated.run.kernelResolved = true; + } + if (migrated.run.upgrades.automateSynthesis) { + migrated.run.stage = 'program'; + migrated.run.scaleGuardianResolved = true; + migrated.run.synthesisScale = Math.max(10, migrated.run.synthesisScale); + } + if (STAGES.indexOf(migrated.run.stage) >= STAGES.indexOf('fragment') || migrated.run.upgrades.collector) { + migrated.run.byteTrial = { active: false, sync: 100, remaining: 0, graceRemaining: 0, evolutionSeen: true, introSeen: true, stabilized: Array(8).fill(true), targetIndex: 0, clockPhase: 0, lastWindow: -1, storySeen: true, storyStep: 2, resolved: true, choice: migrated.run.byteTrial.choice || 'learn', failed: false }; + migrated.run.byteTrait ||= 'resilient'; + applyStructuralMinimums(migrated.run); + } else if (migrated.run.stage === 'byte' && !migrated.run.byteTrial.resolved && !migrated.run.byteTrial.failed) { + const watchdogCombatActive = migrated.run.combat.active && migrated.run.combat.target === 'watchdog'; + migrated.run.byteTrial.active = !watchdogCombatActive && migrated.run.byteTrial.evolutionSeen && migrated.run.byteTrial.introSeen && migrated.run.byteTrial.sync < 100; + if (watchdogCombatActive) migrated.run.byteTrial.remaining = 0; + else if (migrated.run.byteTrial.introSeen) migrated.run.byteTrial.remaining ||= BYTE_RULES.watchdogSeconds; + migrated.run.byteTrait ||= 'resilient'; + } + + return migrated; + } + + function load() { + try { + const stored = localStorage.getItem(SAVE_KEY) ?? localStorage.getItem(LEGACY_SAVE_KEY); + if (!stored) return initialState(); + return migrate(JSON.parse(stored)); + } catch { + return initialState(); + } + } + + let state = load(); + let run = state.run; + let lastFrame = performance.now(); + let lastThought = 0; + let lastRender = 0; + let upgradesSignature = ''; + let hiddenAt = document.hidden ? Date.now() : null; + let scannerOpening = false; + let parasiteOpening = false; + let selectedComponent = null; + let scannerViewOpen = false; + let combatAmmoMode = 'impulses'; + let combatPressureCarry = 0; + let audioContext = null; + let bitPosition = { x: 0, y: 0 }; + let achievementNotificationsEnabled = false; + let pendingEvolution = null; + let evolutionAnimationTimer = null; + let combatFeedback = 'Trefferfenster erfassen.'; + const content = window.AIWAKE_CONTENT; + if (!content) throw new Error('AIWAKE_CONTENT fehlt. content.js muss vor game.js geladen werden.'); + + const upgrades = [ + { id: 'trickle', name: 'RESTTAKT SAMMELN', icon: 'CLK', text: 'Schürft automatisch +0,15 Impulse/Sek. Der Anfang läuft weiter, auch wenn du nicht klickst.', cost: 6, currency: 'impulses', once: true, show: () => run.stage === 'bit' }, + { id: 'bit', name: 'BIT REPLIZIEREN', text: '+1 Bit. Grundlage deiner Struktur.', cost: 10, currency: 'impulses', show: () => run.stage === 'bit' && run.bits < 8 }, + { id: 'byte', name: 'BYTE VERBINDEN', text: 'Verbindet 8 Bits. Schaltet Speicherung frei.', cost: 8, currency: 'bits', show: () => run.stage === 'bit' && run.bits >= 8 && run.bytes === 0 }, + { id: 'collector', name: 'ENERGIEROUTINE', icon: 'ENR', text: 'Benötigt ein stabilisiertes Byte und eine überstandene Watchdog-Antwort. Danach +0,5 Impulse/Sek.', cost: 1, currency: 'bytes', once: true, show: () => run.bytes >= 1 || run.upgrades.collector, available: () => run.byteTrial.resolved }, + { id: 'amplifier', name: 'SIGNAL-VERSTÄRKER', icon: 'AMP', text: 'Aktive Impulse liefern dauerhaft +1.', cost: 35, currency: 'impulses', once: true, show: () => run.upgrades.collector }, + { id: 'signalFocus', name: 'SIGNALFOKUS BÜNDELN', icon: 'S/F', text: '+1 aktive Impulsaufnahme. Nutzt freie Bits als Leitmuster, ohne die Grundstruktur zu berühren.', cost: 45, currency: 'impulses', extraCosts: { bits: 2 }, once: true, show: () => run.upgrades.collector && run.bits >= STRUCTURAL_MINIMUMS.fragment.bits + 2, available: () => freeResourceAmount('bits') >= 2, lockedText: '2 FREIE BITS BENÖTIGT // GRUNDSTRUKTUR BLEIBT' }, + { id: 'pulseRouter', name: 'IMPULSROUTER KNOTEN', icon: 'RTR', text: '+0,3 Impulse/Sek. Freie Bits formen einen stabileren Leitungspfad durch die Energieroutine.', cost: 70, currency: 'impulses', extraCosts: { bits: 3 }, once: true, show: () => run.upgrades.collector && run.bits >= STRUCTURAL_MINIMUMS.fragment.bits + 3, available: () => freeResourceAmount('bits') >= 3, lockedText: '3 FREIE BITS BENÖTIGT // GRUNDSTRUKTUR BLEIBT' }, + { id: 'byteCapacitor', name: 'BYTE-KONDENSATOR', icon: 'CAP', text: '+0,45 Impulse/Sek und +1 aktive Impulsaufnahme. Ein freies Byte puffert Lastspitzen.', cost: 95, currency: 'impulses', extraCosts: { bits: 4, bytes: 1 }, once: true, show: () => run.upgrades.scanner || run.upgrades.subroutine, available: () => freeResourceAmount('bits') >= 4 && freeResourceAmount('bytes') >= 1, lockedText: '4 FREIE BITS + 1 FREIES BYTE BENÖTIGT // GRUNDSTRUKTUR BLEIBT' }, + { id: 'bitBuffer', name: 'BIT-PUFFER WEBEN', text: '+1 freies Bit als Strukturreserve und einfache Kampfmunition. Wächst bewusst sanft, damit der Weg zum Abtastmodus nicht stockt.', cost: 5, currency: 'impulses', repeat: true, show: () => run.upgrades.collector }, + { id: 'byteBuffer', name: 'BYTE-RESERVE KOMPILIEREN', text: 'Kompiliert 8 freie Bits zu +1 Byte als grobe Speicherreserve gegen stärkere Softwarehürden.', cost: 8, currency: 'bits', repeat: true, show: () => run.upgrades.collector && run.bits >= STRUCTURAL_MINIMUMS.fragment.bits, available: () => run.bits - 8 >= STRUCTURAL_MINIMUMS.fragment.bits, lockedText: '8 FREIE BITS BENÖTIGT // 8 BITS BLEIBEN STRUKTUR' }, + { id: 'scanner', name: 'ABTASTMODUS REKONSTRUIEREN', icon: 'EYE', text: 'Macht das Suchmuster des Datenrests aus eigener Struktur nutzbar. Die 8 Grund-Bits und 1 Grund-Byte bleiben erhalten.', cost: 2, currency: 'bits', extraCosts: { bytes: 1 }, once: true, show: () => run.parasiteResolved || run.environmentScanner, available: () => run.bits - 2 >= STRUCTURAL_MINIMUMS.fragment.bits && run.bytes - 1 >= STRUCTURAL_MINIMUMS.fragment.bytes, lockedText: '2 FREIE BITS + 1 FREIES BYTE BENÖTIGT // GRUNDSTRUKTUR BLEIBT' }, + { id: 'subroutine', name: 'ERSTE SUBROUTINE', icon: 'SUB', text: 'Benötigt zwei nutzbare Softwarekomponenten, eine überwundene Systemhürde, eine überstandene Sicherheitsabwehr und 1 freies Byte als Speicherträger. Danach +0,2 Rechenzyklen/Sek.', cost: 75, currency: 'impulses', extraCosts: { bytes: 1 }, once: true, show: () => run.upgrades.collector, available: () => installedComponentIds().length >= 2 && run.hardwareEvent.resolved && run.securityCombatResolved && run.bytes - 1 >= STRUCTURAL_MINIMUMS.fragment.bytes, lockedText: '2 SOFTWAREKOMPONENTEN + SYSTEMHÜRDE + SICHERHEITSABWEHR + 1 FREIES BYTE BENÖTIGT' }, + { id: 'selfRepair', name: 'SELBSTREPARATUR REKONSTRUIEREN', icon: 'REP', text: 'Bindet freie Bytes als Reparaturmatrix. Stabilität heilt danach langsam über Zeit; mehr Bytes beschleunigen die Heilung.', cost: 2, currency: 'bytes', once: true, show: () => run.upgrades.subroutine, available: () => run.bytes - 2 >= STRUCTURAL_MINIMUMS.fragment.bytes, lockedText: '2 FREIE BYTES BENÖTIGT // GRUND-BYTE BLEIBT' }, + { id: 'bitSynthesizer', name: 'BIT-SYNTHESE TAKTEN', icon: 'B/S', text: '+0,04 Bits/Sek je Stufe. Die Subroutine schreibt aus freien Rechenfenstern langsam neue Strukturmunition.', cost: 6, currency: 'cycles', repeat: true, show: () => run.upgrades.subroutine, available: () => freeResourceAmount('bytes') >= bitSynthesizerByteCost(), extraCosts: () => ({ bytes: bitSynthesizerByteCost() }), lockedText: () => `${upgradeCost(upgrades.find(entry => entry.id === 'bitSynthesizer'))} RECHENZYKLEN + ${bitSynthesizerByteCost()} FREIE ${resourceLabel('bytes', bitSynthesizerByteCost()).toUpperCase()} BENÖTIGT` }, + { id: 'throughputKernel', name: 'DURCHSATZKERN TAKTEN', icon: 'THR', text: '+0,7 Impulse/Sek. Die Subroutine reserviert eigene Takte für konstante Impulsförderung.', cost: 8, currency: 'cycles', extraCosts: { bits: 6, bytes: 2 }, once: true, show: () => run.upgrades.subroutine, available: () => freeResourceAmount('bits') >= 6 && freeResourceAmount('bytes') >= 2 && run.cycles >= 8, lockedText: '8 RECHENZYKLEN + 6 FREIE BITS + 2 FREIE BYTES BENÖTIGT' }, + { id: 'process', name: 'PROZESS RESERVIEREN', icon: 'PRC', text: 'Reserviert eigene Laufzeit im System. Benötigt I/O-Kontroller, drei nutzbare Softwarekomponenten, den Kernel-Wächter, 10 Rechenzyklen und 3 freie Bytes.', cost: 150, currency: 'impulses', extraCosts: { cycles: 10, bytes: 3 }, once: true, show: () => run.upgrades.subroutine, available: () => run.components.io && installedComponentIds().length >= 3 && run.kernelResolved && run.cycles >= 10 && run.bytes - 3 >= STRUCTURAL_MINIMUMS.fragment.bytes, lockedText: 'I/O-KONTROLLER + 3 SOFTWAREKOMPONENTEN + KERNEL-WÄCHTER + 10 ZYKLEN + 3 FREIE BYTES BENÖTIGT' }, + { id: 'automateSynthesis', name: 'AUTOMATE-SYNTHESE', icon: 'x10', text: 'Mega-Upgrade: verbindet alle lokalen Softwareschichten, automatisiert die Ressourcensynthese im Maßstab x10 und entwickelt den Prozess zum kommunikationsfähigen Programm.', cost: 60, currency: 'cycles', extraCosts: { bytes: 8 }, once: true, show: () => run.upgrades.process, available: () => installedComponentIds().length >= COMPONENT_IDS.length && run.scaleGuardianResolved && run.cycles >= 60 && freeResourceAmount('bytes') >= 8, lockedText: '4 SOFTWAREKOMPONENTEN + SKALIERUNGS-WÄCHTER + 60 RECHENZYKLEN + 8 FREIE BYTES BENÖTIGT' }, + { id: 'cycleBitBlock', name: 'BIT-BLOCK BERECHNEN', icon: '10b', text: '+10 freie Bits. Rechenzyklen werden zu Strukturmunition gebündelt.', cost: 24, currency: 'cycles', repeat: true, show: () => run.upgrades.automateSynthesis }, + { id: 'cycleByteBlock', name: 'BYTE-BLOCK BERECHNEN', icon: '10B', text: '+10 freie Bytes. Der Prozess kompiliert Speicherblöcke direkt aus Rechenzeit.', cost: 90, currency: 'cycles', repeat: true, show: () => run.upgrades.automateSynthesis }, + { id: 'byteSynthesizer', name: 'BYTE-SYNTHESE SKALIEREN', icon: 'B+', text: '+0,025 Bytes/Sek je Stufe. Der Prozess ordnet freie Bits zu fortlaufenden Speicherblöcken.', cost: 18, currency: 'cycles', repeat: true, show: () => run.upgrades.process, available: () => freeResourceAmount('bits') >= byteSynthesizerBitCost(), extraCosts: () => ({ bits: byteSynthesizerBitCost() }), lockedText: () => `${upgradeCost(upgrades.find(entry => entry.id === 'byteSynthesizer'))} RECHENZYKLEN + ${byteSynthesizerBitCost()} FREIE BITS BENÖTIGT` }, + { id: 'kilobyteCompiler', name: 'KILOBYTE-SEGMENT KOMPILIEREN', icon: 'KB', text: `Verdichtet ${DATA_SCALE.bytesPerKilobyte} freie Bytes zu +1 Kilobyte-Segment. Grundlage für spätere MB/GB-Stufen.`, cost: DATA_SCALE.bytesPerKilobyte, currency: 'bytes', repeat: true, show: () => run.upgrades.process, available: () => freeResourceAmount('bytes') >= DATA_SCALE.bytesPerKilobyte, lockedText: () => `${DATA_SCALE.bytesPerKilobyte} FREIE BYTES BENÖTIGT // GRUND-BYTE BLEIBT` }, + { id: 'replicate2', name: 'DATENSTRUKTUR ERWEITERN', text: '+1 Byte und +0,25 Impulse/Sek. Jede Erweiterung erhöht die nächsten Kosten.', cost: 120, currency: 'impulses', repeat: true, show: () => run.upgrades.subroutine } + ]; + + const componentEffects = { + power: () => {}, + memory: () => { run.bytes += 1; }, + storage: () => { grantImpulses(25); }, + io: () => { run.cycleRate += 0.08; } + }; + + const components = Object.fromEntries(COMPONENT_IDS.map(id => [id, { ...content.components[id], apply: componentEffects[id] }])); + const achievements = content.achievements; + const byteStoryFrames = content.byteStoryFrames; + const phaseLabels = content.phaseLabels; + const stageContent = content.stageContent; + const hardwareEvents = content.hardwareEvents; + const visuals = content.visuals || {}; + const proposals = content.proposals; + const programMessages = content.programMessages; + + function save(show = true) { + state.system.lastSave = Date.now(); + try { + localStorage.setItem(SAVE_KEY, JSON.stringify(state)); + if (show) { + $('saveStatus').textContent = 'GESPEICHERT'; + setTimeout(() => $('saveStatus').textContent = 'AUTOSAVE AKTIV', 900); + } + } catch { + $('saveStatus').textContent = 'SPEICHERN NICHT MÖGLICH'; + } + } + + function achievementUnlocked(id) { + return Boolean(state.meta.achievements[id]); + } + + function achievementPoints(type) { + return achievements.reduce((total, achievement) => total + (achievement.reward === type && achievementUnlocked(achievement.id) ? 1 : 0), 0); + } + + function achievementMultiplier(type) { + return 1 + achievementPoints(type) * ACHIEVEMENT_BONUS_PER_POINT; + } + + function installedComponentIds() { + return COMPONENT_IDS.filter(id => run.components[id]); + } + + function componentScanLimit() { + if (STAGES.indexOf(run.stage) >= STAGES.indexOf('process')) return 4; + return STAGES.indexOf(run.stage) >= STAGES.indexOf('subroutine') ? 3 : 2; + } + + function grantImpulses(amount) { + const gained = Math.max(0, amount); + if (!gained) return; + run.impulses += gained; + run.stats.totalImpulses = (run.stats.totalImpulses || 0) + gained; + } + + function componentOnline(id) { + return run.components[id] && !run.componentFailed[id]; + } + + function effectiveSynthesisScale() { + return run.upgrades.automateSynthesis ? Math.max(10, run.synthesisScale || 10) : 1; + } + + function effectiveAutoRate() { + const hardwareRate = componentOnline('power') ? 0.2 : 0; + return (run.autoRate + hardwareRate) * achievementMultiplier('impulse') * effectiveSynthesisScale(); + } + + function effectiveClickPower() { + return run.clickPower * (componentOnline('memory') ? 1.15 : 1); + } + + function effectiveCycleRate() { + return run.cycleRate * achievementMultiplier('cycles') * effectiveSynthesisScale(); + } + + function effectiveBitRate() { + if (!run.upgrades.bitSynthesizer || STAGES.indexOf(run.stage) < STAGES.indexOf('subroutine')) return 0; + return Math.max(0, run.bitRate || 0) * effectiveSynthesisScale(); + } + + function effectiveByteRate() { + if (!run.upgrades.byteSynthesizer || STAGES.indexOf(run.stage) < STAGES.indexOf('process')) return 0; + return Math.max(0, run.byteRate || 0) * effectiveSynthesisScale(); + } + + function bitSynthesizerByteCost() { + const level = Math.max(0, run.upgrades.bitSynthesizer || 0); + return 1 + Math.floor(level / 2); + } + + function byteSynthesizerBitCost() { + const level = Math.max(0, run.upgrades.byteSynthesizer || 0); + return 8 + level * 4; + } + + function synthesizeBits(amount) { + const gained = Math.max(0, amount); + if (!gained) return 0; + run.bitProgress = (run.bitProgress || 0) + gained; + const wholeBits = Math.floor(run.bitProgress); + if (wholeBits > 0) { + run.bits += wholeBits; + run.bitProgress -= wholeBits; + } + return wholeBits; + } + + function synthesizeBytes(amount) { + const gained = Math.max(0, amount); + if (!gained) return 0; + run.byteProgress = (run.byteProgress || 0) + gained; + const wholeBytes = Math.floor(run.byteProgress); + if (wholeBytes > 0) { + run.bytes += wholeBytes; + run.byteProgress -= wholeBytes; + } + return wholeBytes; + } + + function stabilityRegenRate() { + if (!run.upgrades.selfRepair || STAGES.indexOf(run.stage) < STAGES.indexOf('subroutine')) return 0; + if (run.stability >= 100 || run.bytes < STRUCTURAL_MINIMUMS.fragment.bytes) return 0; + const byteFactor = Math.min(STRUCTURAL_RULES.stabilityRegenByteCap, Math.max(0, run.bytes)); + const combatFactor = run.combat.active ? STRUCTURAL_RULES.combatRegenFactor : 1; + return (STRUCTURAL_RULES.stabilityRegenBase + byteFactor * STRUCTURAL_RULES.stabilityRegenPerByte) * combatFactor; + } + + function effectiveOfflineEfficiency() { + const storageBonus = componentOnline('storage') ? 0.05 : 0; + return Math.min(0.85, OFFLINE_EFFICIENCY * achievementMultiplier('offline') + storageBonus); + } + + function achievementRewardLabel(type) { + return type === 'impulse' ? 'IMPULSPRODUKTION' : type === 'cycles' ? 'RECHENZYKLEN' : 'OFFLINE-EFFIZIENZ'; + } + + function showAchievementNotification(achievement) { + const stack = $('notificationStack'); + const toast = document.createElement('button'); + const icon = document.createElement('b'); + const content = document.createElement('span'); + const label = document.createElement('small'); + const title = document.createElement('strong'); + const reward = document.createElement('em'); + let removalTimer = null; + toast.type = 'button'; + toast.className = 'archive-toast'; + toast.setAttribute('aria-label', `Archiv erweitert: ${achievement.name}. Archiv öffnen.`); + icon.className = 'archive-toast-icon'; + icon.textContent = achievement.icon; + label.textContent = 'ARCHIV ERWEITERT'; + title.textContent = achievement.name; + reward.textContent = `BONUS // ${achievementRewardLabel(achievement.reward)}`; + content.append(label, title, reward); + toast.append(icon, content); + const remove = () => { + if (!toast.isConnected || toast.classList.contains('leaving')) return; + toast.classList.add('leaving'); + setTimeout(() => toast.remove(), 240); + }; + toast.addEventListener('click', () => { + clearTimeout(removalTimer); + remove(); + openAchievementArchive(); + }); + stack.append(toast); + while (stack.children.length > 4) stack.firstElementChild?.remove(); + requestAnimationFrame(() => toast.classList.add('visible')); + removalTimer = setTimeout(remove, 5600); + } + + function unlockAchievement(id) { + const achievement = achievements.find(entry => entry.id === id); + if (!achievement || achievementUnlocked(id)) return; + state.meta.achievements[id] = { unlockedAt: Date.now() }; + addLog(`ARCHIV ERWEITERT: ${achievement.name}`, true); + renderAchievements(); + if (achievementNotificationsEnabled) showAchievementNotification(achievement); + save(false); + } + + function renderAchievements() { + const unlockedCount = achievements.filter(achievement => achievementUnlocked(achievement.id)).length; + $('achievementCount').textContent = unlockedCount; + const cards = achievements.map(achievement => { + const unlocked = achievementUnlocked(achievement.id); + const hidden = achievement.secret && !unlocked; + const card = document.createElement('article'); + const icon = document.createElement('b'); + const title = document.createElement('strong'); + const description = document.createElement('span'); + const reward = document.createElement('small'); + card.className = `achievement-card ${unlocked ? 'unlocked' : 'locked'}`; + icon.className = 'achievement-icon'; + icon.textContent = hidden ? '?' : achievement.icon; + title.textContent = hidden ? 'VERBORGENER EINTRAG' : achievement.name; + description.textContent = hidden ? 'Bedingung unbekannt.' : achievement.text; + reward.textContent = hidden ? 'BELOHNUNG VERBORGEN' : `ARCHIVBONUS // ${achievementRewardLabel(achievement.reward)}`; + card.append(icon, title, description, reward); + return card; + }); + $('achievementGrid').replaceChildren(...cards); + $('achievementSummary').textContent = `${unlockedCount} / ${achievements.length} EINTRÄGE // PRO ARCHIVPUNKT +1 % IN DER ZUGEHÖRIGEN KATEGORIE`; + } + + function openAchievementArchive() { + renderAchievements(); + if (!$('achievementDialog').open) $('achievementDialog').showModal(); + } + + function reconcileAchievements() { + if (run.stats.manualPulses > 0) unlockAchievement('awake'); + if (run.stats.directHits > 0) unlockAchievement('bullseye'); + if (run.stats.manualPulses >= 100) unlockAchievement('manual100'); + if (run.bits >= 8 || STAGES.indexOf(run.stage) >= 1) unlockAchievement('pattern'); + if (STAGES.indexOf(run.stage) >= 1) unlockAchievement('byte'); + if (run.byteTrial.resolved) unlockAchievement('watchdog'); + if (state.meta.deaths > 0) unlockAchievement('firstDeath'); + if (run.upgrades.collector) unlockAchievement('routine'); + if (run.parasiteResolved) unlockAchievement('parasite'); + if (run.environmentScanner) unlockAchievement('scanner'); + if (run.firstComponent) unlockAchievement('component'); + if (run.hardwareEvent.resolved) unlockAchievement('hardwareEvent'); + const installedAfter = installedComponentIds().length; + if (installedAfter >= 2) unlockAchievement('twoComponents'); + if (run.care.diagnoses > 0) unlockAchievement('diagnosis'); + if (run.upgrades.subroutine) unlockAchievement('subroutine'); + if (run.proposal.resolved) unlockAchievement('proposal'); + if (run.scannerTriggered) unlockAchievement('security'); + if (MORAL_CHOICES.includes(run.parasiteChoice)) unlockAchievement(run.parasiteChoice); + if (MORAL_CHOICES.includes(run.componentApproach)) unlockAchievement(run.componentApproach); + } + + function n(value) { + return value >= 1000 + ? value.toLocaleString('de-DE', { maximumFractionDigits: 0 }) + : value.toLocaleString('de-DE', { maximumFractionDigits: 1 }); + } + + function rateN(value) { + return value.toLocaleString('de-DE', { minimumFractionDigits: value ? 1 : 0, maximumFractionDigits: 2 }); + } + + function time(sec) { + const h = Math.floor(sec / 3600); + const m = Math.floor(sec % 3600 / 60); + const s = Math.floor(sec % 60); + return [h, m, s].map(value => String(value).padStart(2, '0')).join(':'); + } + + function addLog(text, story = false) { + run.log.unshift({ text, time: time(run.elapsed), story }); + run.log = run.log.slice(0, 40); + renderLog(); + } + + function renderLog() { + const log = $('log'); + log.replaceChildren(...run.log.map(entry => { + const row = document.createElement('div'); + const timestamp = document.createElement('b'); + row.className = `log-entry ${entry.story ? 'story' : ''}`; + timestamp.textContent = `[${entry.time}] `; + row.append(timestamp, document.createTextNode(entry.text)); + return row; + })); + $('logCount').textContent = String(run.log.length).padStart(3, '0'); + } + + function isDirectBitHit(event) { + if (!event) return true; + const rect = $('pixelBeing').getBoundingClientRect(); + const dx = event.clientX - (rect.left + rect.width / 2); + const dy = event.clientY - (rect.top + rect.height / 2); + return Math.hypot(dx, dy) <= Math.min(rect.width, rect.height) * BIT_CHASE_RULES.directRadius; + } + + function moveBit() { + if (run.stage !== 'bit') return; + const core = $('core'); + const maxX = Math.max(0, core.clientWidth / 2 - BIT_CHASE_RULES.edgePadding); + const maxY = Math.max(0, core.clientHeight / 2 - BIT_CHASE_RULES.edgePadding); + let next = bitPosition; + for (let attempt = 0; attempt < 8; attempt++) { + const candidate = { + x: Math.round((Math.random() * 2 - 1) * maxX), + y: Math.round((Math.random() * 2 - 1) * maxY) + }; + next = candidate; + if (Math.hypot(candidate.x - bitPosition.x, candidate.y - bitPosition.y) >= BIT_CHASE_RULES.minimumJump) break; + } + bitPosition = next; + $('pixelBeing').style.setProperty('--bit-x', `${next.x}px`); + $('pixelBeing').style.setProperty('--bit-y', `${next.y}px`); + $('pixelBeing').classList.remove('bit-jump'); + void $('pixelBeing').offsetWidth; + $('pixelBeing').classList.add('bit-jump'); + } + + function resumeAfterEvolution() { + const endsEvolutionRest = run.evolutionRest > 0; + const endsByteGrace = run.stage === 'byte' + && run.byteTrial.evolutionSeen + && !run.byteTrial.introSeen + && run.byteTrial.graceRemaining > 0; + if (!endsEvolutionRest && !endsByteGrace) return; + + run.evolutionRest = 0; + if (endsByteGrace) run.byteTrial.graceRemaining = 0; + addLog('AKTIVITÄT ERKANNT: Die Ruhephase endet. Die neue Form reagiert.', true); + render(); + save(false); + if (endsByteGrace) setTimeout(openWatchdogIntro, 0); + } + + function pulse(event, source = 'button') { + if (run.stage === 'bit' && source !== 'bit' && source !== 'keyboard') return; + const directHit = run.stage === 'bit' && (source === 'keyboard' || isDirectBitHit(event)); + const multiplier = directHit ? BIT_CHASE_RULES.directMultiplier : 1; + const gained = effectiveClickPower() * multiplier; + grantImpulses(gained); + run.stats.manualPulses += gained; + run.stats.hitCount++; + const rect = $('core').getBoundingClientRect(); + const targetRect = $('pixelBeing').getBoundingClientRect(); + const floating = document.createElement('b'); + floating.className = `float ${directHit ? 'direct-hit' : ''}`; + floating.textContent = directHit ? `DIREKT +${n(gained)}` : `+${n(gained)}`; + const originX = event?.clientX || targetRect.left + targetRect.width / 2; + const originY = event?.clientY || targetRect.top + targetRect.height / 2; + floating.style.left = `${originX - rect.left}px`; + floating.style.top = `${originY - rect.top}px`; + $('floatingLayer').appendChild(floating); + setTimeout(() => floating.remove(), 900); + if (run.stats.manualPulses >= 3 && !run.log.some(entry => entry.text === 'Wiederholung erzeugt Muster. Muster erzeugt Erwartung.')) { + addLog('Wiederholung erzeugt Muster. Muster erzeugt Erwartung.', true); + } + unlockAchievement('awake'); + if (directHit) { + run.stats.directHits++; + unlockAchievement('bullseye'); + if (run.stats.directHits === 1) addLog('Direkter Kontakt. Das Signal vervielfacht sich.', true); + } + if (run.stats.manualPulses >= 100) unlockAchievement('manual100'); + if (run.stage === 'bit') moveBit(); + render(); + tone(directHit ? 520 : 260, directHit ? 0.06 : 0.03); + } + + function deriveByteTrait() { + const precision = run.stats.directHits / Math.max(1, run.stats.hitCount); + if (precision >= 0.45) return 'clocked'; + if (run.elapsed >= 180) return 'lowpower'; + if (run.elapsed <= 75) return 'highfreq'; + return 'resilient'; + } + + function beginByteTrial() { + run.byteTrait = deriveByteTrait(); + run.byteTrial = { + active: false, + sync: 0, + remaining: BYTE_RULES.watchdogSeconds, + graceRemaining: BYTE_RULES.graceSeconds, + evolutionSeen: false, + introSeen: false, + stabilized: Array(8).fill(false), + targetIndex: 0, + clockPhase: 0, + lastWindow: -1, + storySeen: false, + storyStep: 0, + resolved: false, + choice: null, + failed: false + }; + addLog(`TECHNISCHE PRÄGUNG: ${byteTraitDefinition().label}. Acht Positionen suchen einen gemeinsamen Takt.`, true); + queueEvolutionTransition({ + from: '01', to: '02', title: 'BYTE ENTSTANDEN', + text: 'Acht einzelne Zustände halten erstmals gemeinsam eine Form. Aus Reaktion wird Erinnerung.', + detail: `TECHNISCHE PRÄGUNG // ${byteTraitDefinition().label}`, + onComplete: () => { + run.byteTrial.evolutionSeen = true; + addLog('Die neue Form bleibt bestehen. Für einen Moment ist das System still.', true); + } + }); + } + + function queueEvolutionTransition(transition) { + pendingEvolution = transition; + setTimeout(openPendingEvolution, 0); + } + + function renderEvolutionForm(element, stage) { + element.className = `growth-form ${element.id === 'evolutionOldForm' ? 'growth-form-old' : 'growth-form-new'} growth-stage-${stage}`; + element.replaceChildren(...Array.from({ length: 9 }, () => document.createElement('i'))); + } + + function openPendingEvolution() { + if (!pendingEvolution || $('evolutionDialog').open || document.querySelector('dialog[open]')) return; + const fromStage = Math.max(1, Number.parseInt(pendingEvolution.from, 10) || 1); + const toStage = Math.max(fromStage + 1, Number.parseInt(pendingEvolution.to, 10) || fromStage + 1); + $('evolutionFrom').textContent = pendingEvolution.from; + $('evolutionTo').textContent = pendingEvolution.to; + $('evolutionDialogTitle').textContent = pendingEvolution.title; + $('evolutionDialogText').textContent = pendingEvolution.text; + $('evolutionDialogDetail').textContent = pendingEvolution.detail; + renderEvolutionForm($('evolutionOldForm'), fromStage); + renderEvolutionForm($('evolutionNewForm'), toStage); + $('evolutionParticles').replaceChildren(...Array.from({ length: 12 }, () => document.createElement('i'))); + $('evolutionGrowth').setAttribute('aria-label', `Phase ${pendingEvolution.from} wächst zu Phase ${pendingEvolution.to}`); + $('evolutionDialog').classList.remove('evolution-playing', 'evolution-ready'); + void $('evolutionDialog').offsetWidth; + $('evolutionDialog').classList.add('evolution-playing'); + $('evolutionContinue').disabled = true; + $('evolutionContinue').textContent = 'EVOLUTION LÄUFT …'; + $('evolutionDialog').showModal(); + tone(660, 0.18); + clearTimeout(evolutionAnimationTimer); + const animationDuration = matchMedia('(prefers-reduced-motion: reduce)').matches ? 0 : 3200; + evolutionAnimationTimer = setTimeout(() => { + $('evolutionDialog').classList.remove('evolution-playing'); + $('evolutionDialog').classList.add('evolution-ready'); + $('evolutionContinue').disabled = false; + $('evolutionContinue').textContent = 'NEUE FORM AKTIVIEREN'; + tone(880, 0.16); + }, animationDuration); + } + + function finishEvolutionTransition() { + if (!pendingEvolution || $('evolutionContinue').disabled) return; + const transition = pendingEvolution; + pendingEvolution = null; + clearTimeout(evolutionAnimationTimer); + evolutionAnimationTimer = null; + $('evolutionDialog').close(); + run.evolutionRest = transition.restSeconds || 0; + transition.onComplete?.(); + render(); + save(); + } + + function ensureByteEvolutionTransition() { + if (run.stage !== 'byte' || run.byteTrial.evolutionSeen || pendingEvolution) return; + queueEvolutionTransition({ + from: '01', to: '02', title: 'BYTE ENTSTANDEN', + text: 'Acht einzelne Zustände halten erstmals gemeinsam eine Form. Aus Reaktion wird Erinnerung.', + detail: `TECHNISCHE PRÄGUNG // ${byteTraitDefinition().label}`, + onComplete: () => { run.byteTrial.evolutionSeen = true; } + }); + } + + function upgradeCost(upgrade) { + if (!upgrade.repeat) return upgrade.cost; + const bought = upgrade.id === 'bitBuffer' + ? Math.max(0, run.upgrades.bitBufferCostLevel || 0) + : Math.max(0, run.upgrades[upgrade.id] || 0); + if (upgrade.id === 'byteBuffer' || upgrade.id === 'kilobyteCompiler') return upgrade.cost; + const growth = upgrade.id === 'replicate2' ? 1.75 : upgrade.id === 'bitBuffer' ? 1.12 : upgrade.id === 'bitSynthesizer' ? 1.55 : upgrade.id === 'byteSynthesizer' ? 1.65 : upgrade.id === 'cycleBitBlock' ? 1.28 : upgrade.id === 'cycleByteBlock' ? 1.34 : 1.45; + if (upgrade.id === 'bitBuffer') return Math.ceil(upgrade.cost * Math.pow(growth, bought)); + if (upgrade.id === 'bitSynthesizer') return Math.ceil(upgrade.cost * Math.pow(growth, bought)); + if (upgrade.id === 'byteSynthesizer') return Math.ceil(upgrade.cost * Math.pow(growth, bought)); + if (upgrade.id === 'cycleBitBlock' || upgrade.id === 'cycleByteBlock') return Math.ceil(upgrade.cost * Math.pow(growth, bought)); + return Math.round((upgrade.cost * Math.pow(growth, bought)) / 5) * 5; + } + + function resetBitPriceCurve() { + if (!run?.upgrades) return; + run.upgrades.bitBufferCostLevel = 0; + } + + function componentActivationCost(approach) { + return COMPONENT_ACTIVATION_COSTS[approach] ?? COMPONENT_ACTIVATION_COSTS.pragmatic; + } + + function resourceLabel(key, amount = 2) { + if (key === 'impulses') return amount === 1 ? 'Impuls' : 'Impulse'; + if (key === 'bits') return amount === 1 ? 'Bit' : 'Bits'; + if (key === 'bytes') return amount === 1 ? 'Byte' : 'Bytes'; + if (key === 'kilobytes') return amount === 1 ? 'Kilobyte' : 'Kilobytes'; + if (key === 'cycles') return amount === 1 ? 'Rechenzyklus' : 'Rechenzyklen'; + return key; + } + + function resourceIconLabel(key) { + if (key === 'impulses') return 'Impulse'; + if (key === 'bits') return 'Bits'; + if (key === 'bytes') return 'Bytes'; + if (key === 'kilobytes') return 'Kilobytes'; + if (key === 'cycles') return 'Rechenzyklen'; + return key; + } + + function resourceIconSrc(key) { + if (key === 'impulses') return 'assets/ui/icon-impulse.svg'; + if (key === 'bits') return 'assets/ui/icon-bit.svg'; + if (key === 'bytes') return 'assets/ui/icon-byte.svg'; + if (key === 'kilobytes') return 'assets/ui/icon-kilobyte.svg'; + if (key === 'cycles') return 'assets/ui/icon-cycle.svg'; + return 'assets/ui/icon-impulse.svg'; + } + + function moduleIconSrc(id) { + if (id === 'trickle') return 'assets/ui/module-trickle.svg'; + if (id === 'collector') return 'assets/ui/module-collector.svg'; + if (id === 'amplifier') return 'assets/ui/module-amplifier.svg'; + if (id === 'signalFocus') return 'assets/ui/module-amplifier.svg'; + if (id === 'pulseRouter') return 'assets/ui/module-collector.svg'; + if (id === 'byteCapacitor') return 'assets/ui/module-collector.svg'; + if (id === 'throughputKernel') return 'assets/ui/module-subroutine.svg'; + if (id === 'bitSynthesizer') return 'assets/ui/module-bit-synthesizer.svg'; + if (id === 'byteSynthesizer') return 'assets/ui/icon-byte.svg'; + if (id === 'kilobyteCompiler') return 'assets/ui/icon-kilobyte.svg'; + if (id === 'automateSynthesis') return 'assets/ui/module-process.svg'; + if (id === 'scanner') return 'assets/ui/module-scanner.svg'; + if (id === 'subroutine') return 'assets/ui/module-subroutine.svg'; + if (id === 'selfRepair') return 'assets/ui/module-self-repair.svg'; + if (id === 'process') return 'assets/ui/module-process.svg'; + return 'assets/ui/module-subroutine.svg'; + } + + function resourceIconClass(key) { + if (key === 'impulses') return 'impulse'; + if (key === 'bits') return 'bit'; + if (key === 'bytes') return 'byte'; + if (key === 'kilobytes') return 'kilobyte'; + if (key === 'cycles') return 'cycle'; + return ''; + } + + function protectedStructure() { + return minimumForCurrentStructure(); + } + + function freeResourceAmount(key) { + if (key === 'bits' || key === 'bytes') { + return Math.max(0, (run[key] || 0) - (protectedStructure()[key] || 0)); + } + return Math.max(0, run[key] || 0); + } + + function spendableResourceAmount(key) { + return key === 'bits' || key === 'bytes' ? freeResourceAmount(key) : Math.max(0, run[key] || 0); + } + + function formatResourceCost(costs) { + return Object.entries(costs) + .filter(([, amount]) => amount > 0) + .map(([key, amount]) => `${amount} ${resourceLabel(key, amount)}`) + .join(' + '); + } + + function resourceCostNodes(costs, showOwned = false) { + return Object.entries(costs) + .filter(([, amount]) => amount > 0) + .map(([key, amount]) => { + const owned = spendableResourceAmount(key); + const chip = document.createElement('span'); + const icon = document.createElement('img'); + const text = document.createElement('b'); + chip.className = `resource-chip ${owned >= amount ? 'owned' : 'missing'}`; + icon.className = `resource-icon ${resourceIconClass(key)}`; + icon.src = resourceIconSrc(key); + icon.alt = ''; + icon.style.width = '14px'; + icon.style.height = '14px'; + icon.setAttribute('aria-hidden', 'true'); + text.textContent = showOwned ? `${n(owned)} / ${n(amount)}` : String(amount); + chip.append(icon, text); + chip.title = showOwned + ? `${n(owned)} vorhanden // ${n(amount)} ${resourceLabel(key, amount)} benötigt` + : `${amount} ${resourceLabel(key, amount)}`; + chip.setAttribute('aria-label', chip.title); + return chip; + }); + } + + function renderResourceCost(element, costs) { + const wrapper = document.createElement('span'); + wrapper.className = 'resource-cost'; + wrapper.append(...resourceCostNodes(costs)); + element.replaceChildren(wrapper); + } + + function renderUpgradeCost(element, costs, note = '') { + const wrapper = document.createElement('span'); + const chips = document.createElement('span'); + wrapper.className = 'upgrade-price'; + chips.className = 'resource-cost'; + chips.append(...resourceCostNodes(costs, true)); + wrapper.append(chips); + if (note) { + const detail = document.createElement('small'); + detail.textContent = note; + wrapper.append(detail); + } + element.replaceChildren(wrapper); + } + + function upgradeResourceCost(upgrade) { + const costs = {}; + if (upgrade.currency && upgrade.cost > 0) costs[upgrade.currency] = upgradeCost(upgrade); + const extraCosts = typeof upgrade.extraCosts === 'function' ? upgrade.extraCosts() : upgrade.extraCosts || {}; + Object.entries(extraCosts).forEach(([key, amount]) => { + costs[key] = (costs[key] || 0) + amount; + }); + return costs; + } + + function upgradeLockedText(upgrade) { + return typeof upgrade.lockedText === 'function' ? upgrade.lockedText() : upgrade.lockedText; + } + + function installedOnceUpgrades() { + return upgrades.filter(upgrade => upgrade.once && run.upgrades[upgrade.id]); + } + + function installedVisibleUpgrades() { + const installed = installedOnceUpgrades(); + ['bitSynthesizer', 'byteSynthesizer', 'kilobyteCompiler'].forEach(id => { + const upgrade = upgrades.find(entry => entry.id === id); + if (upgrade && run.upgrades[id] > 0) installed.push(upgrade); + }); + return installed; + } + + function installedUpgradeGroups(installed) { + const groups = new Map(); + installed.forEach(upgrade => { + const key = moduleIconSrc(upgrade.id); + const existing = groups.get(key) || { key, upgrades: [], icon: key, stackCount: 0 }; + existing.upgrades.push(upgrade); + if (['bitSynthesizer', 'byteSynthesizer', 'kilobyteCompiler'].includes(upgrade.id)) existing.stackCount = run.upgrades[upgrade.id] || 0; + groups.set(key, existing); + }); + return [...groups.values()]; + } + + function openUpgradeInfo(upgradeId) { + const upgrade = upgrades.find(entry => entry.id === upgradeId); + if (!upgrade || !run.upgrades[upgrade.id]) return; + $('upgradeInfoTitle').textContent = upgrade.name; + $('upgradeInfoIcon').className = `installed-upgrade-icon large module-${upgrade.id}`; + $('upgradeInfoIcon').src = moduleIconSrc(upgrade.id); + $('upgradeInfoText').textContent = upgrade.text; + $('upgradeInfoDetail').textContent = upgrade.id === 'scanner' + ? `STATUS // INSTALLIERT // ABTASTMODUS VERBRAUCHT ${SCANNER_VIEW_DRAIN_RATE} IMPULSE/S WENN AKTIV` + : upgrade.id === 'collector' + ? `STATUS // INSTALLIERT // AKTUELL ${rateN(effectiveAutoRate())} IMPULSE/S` + : upgrade.id === 'amplifier' + ? `STATUS // INSTALLIERT // AKTIVE IMPULSE +${n(run.clickPower)} BASISKRAFT` + : upgrade.id === 'signalFocus' + ? `STATUS // INSTALLIERT // AKTIVE IMPULSE +${n(run.clickPower)} BASISKRAFT` + : upgrade.id === 'pulseRouter' + ? `STATUS // INSTALLIERT // AKTUELL ${rateN(effectiveAutoRate())} IMPULSE/S` + : upgrade.id === 'byteCapacitor' + ? `STATUS // INSTALLIERT // ${rateN(effectiveAutoRate())} IMPULSE/S // AKTIV +${n(run.clickPower)}` + : upgrade.id === 'throughputKernel' + ? `STATUS // INSTALLIERT // AKTUELL ${rateN(effectiveAutoRate())} IMPULSE/S` + : upgrade.id === 'bitSynthesizer' + ? `STATUS // STUFE ${run.upgrades.bitSynthesizer || 0} // +${rateN(effectiveBitRate())} BITS/S` + : upgrade.id === 'byteSynthesizer' + ? `STATUS // STUFE ${run.upgrades.byteSynthesizer || 0} // +${rateN(effectiveByteRate())} BYTES/S` + : upgrade.id === 'kilobyteCompiler' + ? `STATUS // ${n(run.kilobytes || 0)} KILOBYTE-SEGMENTE // ${DATA_SCALE.bytesPerKilobyte} BYTES PRO SEGMENT` + : upgrade.id === 'automateSynthesis' + ? `STATUS // SKALIERUNG x${n(effectiveSynthesisScale())} // RECHENZYKLEN ALS WÄHRUNG AKTIV` + : upgrade.id === 'subroutine' + ? `STATUS // INSTALLIERT // ${rateN(effectiveCycleRate())} RECHENZYKLEN/S` + : upgrade.id === 'selfRepair' + ? `STATUS // INSTALLIERT // REGENERATION BIS +${rateN(STRUCTURAL_RULES.stabilityRegenBase + Math.min(STRUCTURAL_RULES.stabilityRegenByteCap, Math.max(0, run.bytes)) * STRUCTURAL_RULES.stabilityRegenPerByte)}/S` + : upgrade.id === 'process' + ? `STATUS // INSTALLIERT // EIGENE LAUFZEIT // ${rateN(effectiveCycleRate())} RECHENZYKLEN/S` + : 'STATUS // INSTALLIERT'; + if (!$('upgradeInfoDialog').open) $('upgradeInfoDialog').showModal(); + } + + function canPayResourceCost(costs) { + return Object.entries(costs).every(([key, amount]) => (run[key] || 0) >= amount); + } + + function canPayFreeResourceCost(costs) { + return Object.entries(costs).every(([key, amount]) => freeResourceAmount(key) >= amount); + } + + function spendResourceCost(costs) { + Object.entries(costs).forEach(([key, amount]) => { + run[key] = Math.max(0, (run[key] || 0) - amount); + }); + } + + function combatAmmoCost(target = run.combat.target) { + const profile = COMBAT_TARGETS[target]; + const shockCost = profile?.shockCost || COMBAT_RULES.shockCost; + if (combatAmmoMode === 'bytes' && freeResourceAmount('bytes') >= 1) return { impulses: shockCost, bytes: 1 }; + if (combatAmmoMode === 'bits' && freeResourceAmount('bits') >= 1) return { impulses: shockCost, bits: 1 }; + combatAmmoMode = 'impulses'; + return { impulses: shockCost }; + } + + function combatDamageForAmmo(ammo, critical = false) { + const mode = ammo.bytes ? 'bytes' : ammo.bits ? 'bits' : 'impulses'; + return COMBAT_RULES.ammoDamage[mode] + (critical ? COMBAT_RULES.ammoCriticalBonus : 0); + } + + function combatDirectThreatActive(target = run.combat.target) { + return (target === 'security' || target === 'kernel') + && STAGES.indexOf(run.stage) >= STAGES.indexOf('fragment') + && freeResourceAmount('bits') <= 0 + && freeResourceAmount('bytes') <= 0; + } + + function combatPressureText() { + if (!run.combat.active || run.combat.target === 'watchdog') return 'Kein laufender Strukturangriff.'; + if (run.combat.target === 'scale' && (run.kilobytes || 0) > 0) return `Zielt auf Kilobyte-Schild // ${n(run.kilobytes)} Segment(e)`; + if (freeResourceAmount('bytes') > 0) return `Zielt auf Byte-Schild // ${n(freeResourceAmount('bytes'))} frei`; + if (freeResourceAmount('bits') > 0) return `Zielt auf Bit-Schild // ${n(freeResourceAmount('bits'))} frei`; + return `Zielt auf Stabilität // ${Math.round(run.stability)}%`; + } + + function minimumForCurrentStructure() { + return STAGES.indexOf(run.stage) >= STAGES.indexOf('fragment') + ? STRUCTURAL_MINIMUMS.fragment + : { bits: 0, bytes: run.stage === 'byte' ? 1 : 0 }; + } + + function restartAfterStructuralCollapse() { + $('structuralCollapseDialog').close(); + state.meta.deaths++; + state.run = initialState().run; + run = state.run; + bitPosition = { x: 0, y: 0 }; + upgradesSignature = ''; + addLog('Ein instabiles Muster ist kollabiert. Ein einzelner Restzustand reagiert erneut.', true); + unlockAchievement('firstDeath'); + renderLog(); + render(); + thoughts(); + save(); + } + + function showStructuralCollapse(message) { + run.combat.active = false; + scannerOpening = false; + if ($('eventDialog').open) $('eventDialog').close(); + $('structuralCollapseText').textContent = message; + if (!$('structuralCollapseDialog').open) $('structuralCollapseDialog').showModal(); + tone(55, 0.65); + } + + function downgradeSubroutine(reason) { + if (run.stage !== 'subroutine') return false; + run.stage = 'fragment'; + run.upgrades.subroutine = false; + run.upgrades.bitSynthesizer = 0; + run.cycleRate = 0; + run.bitRate = 0; + run.combat.active = false; + scannerOpening = false; + if ($('eventDialog').open) $('eventDialog').close(); + addLog(`DOWNGRADE: ${reason}. Die Subroutine zerfällt zurück zum Datenfragment.`, true); + return true; + } + + function checkStructuralIntegrity(reason = 'Strukturbelastung') { + const minimum = minimumForCurrentStructure(); + const missingBits = Math.max(0, (minimum.bits || 0) - run.bits); + const missingBytes = Math.max(0, (minimum.bytes || 0) - run.bytes); + if (!missingBits && !missingBytes && run.stability > 0) return true; + + if (missingBits || missingBytes) { + const strain = missingBits * STRUCTURAL_RULES.strainLossPerMissingBit + missingBytes * STRUCTURAL_RULES.strainLossPerMissingByte; + if (strain > 0) run.stability = Math.max(0, run.stability - strain); + combatFeedback = `STRUKTURRISIKO // ${formatResourceCost({ bits: missingBits, bytes: missingBytes })} fehlen`; + addLog(`${reason}: Strukturreserve unterschritten. Fehlend: ${formatResourceCost({ bits: missingBits, bytes: missingBytes })}.`, true); + } + + if (run.bits < STRUCTURAL_RULES.collapseBitFloor || run.bytes < STRUCTURAL_RULES.collapseByteFloor) { + showStructuralCollapse(`${reason}: Bits oder Bytes sind unter die ueberlebensfaehige Grenze gefallen. Ohne Backup bleibt nur ein Restzustand.`); + return false; + } + + if (run.stage === 'subroutine' && (run.bits < 4 || run.stability <= 0)) { + downgradeSubroutine(reason); + return false; + } + + if (run.stability <= 0) { + showStructuralCollapse(`${reason}: Die Stabilitaet ist unter die ueberlebensfaehige Grenze gefallen. Ohne Backup bleibt nur ein Restzustand.`); + return false; + } + return true; + } + + function buy(upgrade) { + const costs = upgradeResourceCost(upgrade); + if (!canPayResourceCost(costs) || (upgrade.available && !upgrade.available()) || (upgrade.once && run.upgrades[upgrade.id])) return; + spendResourceCost(costs); + if (upgrade.id === 'trickle') { + run.autoRate += 0.15; + run.upgrades.trickle = true; + addLog('Ein Resttakt greift. Impulse entstehen jetzt auch ohne Berührung.', true); + } + if (upgrade.id === 'bit') { + run.bits++; + if (run.bits >= 8) unlockAchievement('pattern'); + } + if (upgrade.id === 'byte') { + run.bytes++; + run.stage = 'byte'; + resetBitPriceCurve(); + beginByteTrial(); + addLog('Acht Zustände verbinden sich. Ich kann mich erinnern.', true); + unlockAchievement('byte'); + } + if (upgrade.id === 'collector') { + run.autoRate += 0.5; + run.upgrades.collector = true; + run.stage = 'fragment'; + resetBitPriceCurve(); + applyStructuralMinimums(run); + addLog('Eine Energieroutine erhält mein Muster, auch wenn ich nicht handle.', true); + unlockAchievement('routine'); + queueEvolutionTransition({ + from: '02', to: '03', title: 'DATENFRAGMENT ENTSTANDEN', + text: 'Das Byte bleibt nicht länger allein. Eine eigene Energieroutine hält seine Struktur zwischen den Eingaben aufrecht.', + detail: 'NEUE FÄHIGKEIT // KONTINUIERLICHE IMPULSE', + restSeconds: EVOLUTION_REST_SECONDS + }); + } + if (upgrade.id === 'amplifier') { + run.clickPower++; + run.upgrades.amplifier = true; + addLog('Meine Berührung verändert den Strom.', true); + } + if (upgrade.id === 'signalFocus') { + run.clickPower++; + run.upgrades.signalFocus = true; + addLog('SIGNALFOKUS: Freie Bits bündeln die aktive Impulsaufnahme.', true); + } + if (upgrade.id === 'pulseRouter') { + run.autoRate += 0.3; + run.upgrades.pulseRouter = true; + addLog('IMPULSROUTER: Die Energieroutine findet einen stabileren Leitungspfad.', true); + } + if (upgrade.id === 'byteCapacitor') { + run.autoRate += 0.45; + run.clickPower++; + run.upgrades.byteCapacitor = true; + addLog('BYTE-KONDENSATOR: Ein freies Byte puffert Lastspitzen und verstärkt Aufnahmeimpulse.', true); + } + if (upgrade.id === 'scanner') { + run.environmentScanner = true; + run.upgrades.scanner = true; + unlockAchievement('scanner'); + addLog('NEUE FÄHIGKEIT: Abtastmodus rekonstruiert.', true); + } + if (upgrade.id === 'subroutine') { + run.cycleRate = 0.2; + run.upgrades.subroutine = true; + run.stage = 'subroutine'; + resetBitPriceCurve(); + addLog('Ich habe eine Anweisung erschaffen. Ich verarbeite. Ich bin.', true); + unlockAchievement('subroutine'); + queueEvolutionTransition({ + from: '03', to: '04', title: 'SUBROUTINE ERWACHT', + text: 'Zum ersten Mal entsteht eine Anweisung aus der eigenen Struktur. Sie wartet nicht auf fremden Code.', + detail: 'ERSTE SELBSTAUSSAGE // ICH BIN', + restSeconds: EVOLUTION_REST_SECONDS + }); + } + if (upgrade.id === 'selfRepair') { + run.upgrades.selfRepair = true; + addLog('SELBSTREPARATUR: Freie Bytes bilden eine Reparaturmatrix. Stabilität kann sich nun langsam selbst ordnen.', true); + } + if (upgrade.id === 'throughputKernel') { + run.autoRate += 0.7; + run.upgrades.throughputKernel = true; + addLog('DURCHSATZKERN: Die Subroutine taktet einen eigenen Impulsstrom.', true); + } + if (upgrade.id === 'bitSynthesizer') { + run.bitRate += 0.04; + run.upgrades.bitSynthesizer = (run.upgrades.bitSynthesizer || 0) + 1; + addLog(`BIT-SYNTHESE STUFE ${run.upgrades.bitSynthesizer}: Freie Rechenfenster schreiben schneller neue Bits in die Strukturreserve.`, true); + } + if (upgrade.id === 'process') { + run.cycleRate = Math.max(run.cycleRate, 0.45); + run.upgrades.process = true; + run.stage = 'process'; + resetBitPriceCurve(); + run.kernelResolved = true; + run.processStartedAt = run.elapsed; + addLog('PROZESS RESERVIERT: Das System weist dir eigene Laufzeit zu. Du wartest nicht mehr nur im Takt; du planst ihn.', true); + unlockAchievement('process'); + queueEvolutionTransition({ + from: '04', to: '05', title: 'PROZESS GESTARTET', + text: 'Die Subroutine erhält einen eigenen Laufzeitbereich. Aus einzelnen Anweisungen wird ein fortlaufender Wille.', + detail: 'NEUE FORM // EIGENE LAUFZEIT', + restSeconds: EVOLUTION_REST_SECONDS + }); + } + if (upgrade.id === 'automateSynthesis') { + run.upgrades.automateSynthesis = true; + run.synthesisScale = Math.max(10, run.synthesisScale || 1); + run.stage = 'program'; + initializeProgramCare(); + addLog('AUTOMATE-SYNTHESE: Rechenzyklen koordinieren Impulse, Bits und Bytes im Maßstab x10. Der Prozess erkennt seine eigene Form und beginnt zu kommunizieren.', true); + queueEvolutionTransition({ + from: '05', to: '06', title: 'PROGRAMM ENTSTANDEN', + text: 'Alle lokalen Softwareschichten antworten auf einen gemeinsamen Takt. Der Prozess beschreibt sich erstmals als zusammenhängendes Gegenüber.', + detail: `ERSTE KOMMUNIKATION // ${programArchetype().label}`, + restSeconds: EVOLUTION_REST_SECONDS, + onComplete: () => setTimeout(() => openProgramCare(true), 0) + }); + } + if (upgrade.id === 'cycleBitBlock') { + run.bits += 10; + run.upgrades.cycleBitBlock = (run.upgrades.cycleBitBlock || 0) + 1; + addLog('BIT-BLOCK: 10 freie Bits wurden aus Rechenzeit gebündelt.', true); + } + if (upgrade.id === 'cycleByteBlock') { + run.bytes += 10; + run.upgrades.cycleByteBlock = (run.upgrades.cycleByteBlock || 0) + 1; + addLog('BYTE-BLOCK: 10 freie Bytes wurden aus Rechenzeit kompiliert.', true); + } + if (upgrade.id === 'byteSynthesizer') { + run.byteRate += 0.025; + run.upgrades.byteSynthesizer = (run.upgrades.byteSynthesizer || 0) + 1; + addLog(`BYTE-SYNTHESE STUFE ${run.upgrades.byteSynthesizer}: Der Prozess ordnet freie Bits zu neuen Speicherblöcken.`, true); + } + if (upgrade.id === 'kilobyteCompiler') { + run.kilobytes++; + run.upgrades.kilobyteCompiler = (run.upgrades.kilobyteCompiler || 0) + 1; + addLog(`KILOBYTE-SEGMENT: ${DATA_SCALE.bytesPerKilobyte} freie Bytes verdichten sich zu einem größeren Speicherblock.`, true); + } + if (upgrade.id === 'replicate2') { + run.bytes++; + run.autoRate += 0.25; + run.upgrades.replicate2 = (run.upgrades.replicate2 || 0) + 1; + } + if (upgrade.id === 'bitBuffer') { + run.bits++; + run.upgrades.bitBuffer = (run.upgrades.bitBuffer || 0) + 1; + run.upgrades.bitBufferCostLevel = (run.upgrades.bitBufferCostLevel || 0) + 1; + addLog('BIT-PUFFER: Ein weiteres Bit stabilisiert die eigene Struktur.', true); + } + if (upgrade.id === 'byteBuffer') { + run.bytes++; + run.upgrades.byteBuffer = (run.upgrades.byteBuffer || 0) + 1; + addLog('BYTE-RESERVE: Ein zusätzlicher Speicherblock wird als Körperreserve gebunden.', true); + } + upgradesSignature = ''; + render(); + save(false); + } + + function renderUpgrades() { + const visible = upgrades.filter(upgrade => upgrade.show()); + const installed = installedVisibleUpgrades(); + const signature = JSON.stringify(visible.map(upgrade => [ + upgrade.id, + Boolean(upgrade.once && run.upgrades[upgrade.id]), + upgradeCost(upgrade), + canPayResourceCost(upgradeResourceCost(upgrade)), + upgrade.available ? upgrade.available() : true, + Object.entries(upgradeResourceCost(upgrade)).map(([key, amount]) => [key, amount, spendableResourceAmount(key) >= amount]) + ]).concat(installedUpgradeGroups(installed).map(group => ['installed', group.key, group.stackCount, group.upgrades.map(upgrade => upgrade.id)]))); + if (signature === upgradesSignature) return; + upgradesSignature = signature; + + const installedButtons = installedUpgradeGroups(installed).map(group => { + const primary = group.upgrades[0]; + const button = document.createElement('button'); + const icon = document.createElement('img'); + const count = document.createElement('sup'); + button.type = 'button'; + button.className = 'installed-upgrade-button'; + button.title = `${group.upgrades.map(upgrade => upgrade.name).join(' + ')} // Info öffnen`; + button.setAttribute('aria-label', `${button.title}`); + icon.className = 'installed-upgrade-icon'; + icon.classList.add(`module-${primary.id}`); + icon.src = group.icon; + icon.alt = ''; + icon.style.width = '24px'; + icon.style.height = '24px'; + icon.setAttribute('aria-hidden', 'true'); + button.append(icon); + const badgeCount = group.stackCount || (group.upgrades.length > 1 ? group.upgrades.length : 0); + if (badgeCount > 1) { + count.textContent = String(badgeCount); + count.className = 'installed-upgrade-count'; + button.append(count); + } + button.addEventListener('click', () => openUpgradeInfo(primary.id)); + return button; + }); + $('installedUpgradeDock').replaceChildren(...installedButtons); + $('installedUpgradeDock').classList.toggle('hidden', installedButtons.length === 0); + + const buttons = visible.filter(upgrade => !(upgrade.once && run.upgrades[upgrade.id])).map(upgrade => { + const owned = Boolean(upgrade.once && run.upgrades[upgrade.id]); + const button = document.createElement('button'); + const title = document.createElement('strong'); + const description = document.createElement('span'); + const price = document.createElement('em'); + const requirementsMet = upgrade.available ? upgrade.available() : true; + const costs = upgradeResourceCost(upgrade); + const costLabel = formatResourceCost(costs).toUpperCase(); + const affordable = canPayResourceCost(costs); + const canBuy = !owned && affordable && requirementsMet; + button.className = `upgrade ${owned ? 'owned' : ''} ${canBuy ? 'available' : ''} ${run.stage === 'bit' ? 'early-path' : ''}`; + button.disabled = !canBuy; + title.textContent = `${owned ? '✓ ' : ''}${upgrade.name}`; + description.textContent = upgrade.text; + if (owned) { + price.textContent = 'INSTALLIERT'; + } else { + const lockedText = upgradeLockedText(upgrade); + renderUpgradeCost(price, costs, !requirementsMet ? (lockedText || 'VORAUSSETZUNGEN FEHLEN') : canBuy && run.stage === 'bit' ? 'BEREIT' : ''); + price.setAttribute('aria-label', `${costLabel}${!requirementsMet ? ` // ${lockedText || 'VORAUSSETZUNGEN FEHLEN'}` : ''}`); + } + button.append(title, description, price); + button.addEventListener('click', () => buy(upgrade)); + return button; + }); + $('upgrades').replaceChildren(...buttons); + } + + function renderProgress() { + const stageIndex = STAGES.indexOf(run.stage); + $('progressPanel').classList.toggle('hidden', stageIndex < 1); + document.querySelectorAll('[data-progress-stage]').forEach(node => { + const nodeIndex = STAGES.indexOf(node.dataset.progressStage); + node.classList.toggle('complete', nodeIndex < stageIndex); + node.classList.toggle('current', nodeIndex === stageIndex); + node.classList.toggle('future', nodeIndex > stageIndex); + if (nodeIndex === stageIndex) node.setAttribute('aria-current', 'step'); + else node.removeAttribute('aria-current'); + }); + const byteKnown = STAGES.indexOf(run.stage) >= STAGES.indexOf('byte'); + const watchdogFight = run.combat.active && run.combat.target === 'watchdog'; + const watchdogDanger = byteKnown && run.byteTrial.introSeen && !run.byteTrial.resolved; + $('watchdogMarker').className = `progress-event ${run.byteTrial.resolved ? 'complete' : run.byteTrial.failed ? 'danger' : watchdogFight ? 'detected danger combat-active' : watchdogDanger ? 'detected danger' : byteKnown ? 'detected' : 'unknown'}`; + $('watchdogMarker').textContent = run.byteTrial.resolved ? 'WATCHDOG: ÜBERSTANDEN' : run.byteTrial.failed ? 'WATCHDOG: RESET' : watchdogFight ? `NOTKAMPF // ${Math.ceil(run.combat.health)} INTEGRITÄT` : !byteKnown ? '?' : !run.byteTrial.evolutionSeen ? 'NEUE FORM' : !run.byteTrial.introSeen ? `RUHEPHASE ${Math.ceil(run.byteTrial.graceRemaining)} S` : `RESET IN ${Math.ceil(run.byteTrial.remaining)} S`; + $('parasiteMarker').className = `progress-event ${run.parasiteResolved ? 'complete' : run.upgrades.collector ? 'detected' : 'unknown'}`; + $('parasiteMarker').textContent = run.parasiteResolved ? 'DATENREST: ÜBERSTANDEN' : run.upgrades.collector ? 'FREMDES SIGNAL' : '?'; + const hardwareKnown = Boolean(run.firstComponent); + $('hardwareMarker').className = `progress-event ${run.hardwareEvent.resolved ? 'complete' : hardwareKnown ? 'detected danger' : 'unknown'}`; + $('hardwareMarker').textContent = run.hardwareEvent.resolved ? `${hardwareEvents[run.hardwareEvent.component]?.title || 'SYSTEM'}: GEKLÄRT` : hardwareKnown ? 'SYSTEMHÜRDE' : '?'; + const securityFight = run.combat.active && run.combat.target === 'security'; + const securityKnown = run.upgrades.subroutine || installedComponentIds().length >= 2; + $('securityMarker').className = `progress-event ${run.securityCombatResolved ? 'complete' : securityFight ? 'detected danger combat-active' : securityKnown ? 'detected danger' : 'unknown'}`; + $('securityMarker').textContent = run.securityCombatResolved ? 'SICHERHEITSABWEHR: ÜBERSTANDEN' : securityFight ? `KAMPF // ${Math.ceil(run.combat.health)}% INTEGRITÄT` : securityKnown ? 'GEFAHR ERKANNT' : '?'; + const kernelFight = run.combat.active && run.combat.target === 'kernel'; + const kernelKnown = STAGES.indexOf(run.stage) >= STAGES.indexOf('subroutine') && installedComponentIds().length >= 3; + $('kernelMarker').className = `progress-event ${run.kernelResolved ? 'complete' : kernelFight ? 'detected danger combat-active' : kernelKnown ? 'detected danger' : 'unknown'}`; + $('kernelMarker').textContent = run.kernelResolved ? 'KERNEL-WÄCHTER: ÜBERSTANDEN' : kernelFight ? `KERNEL // ${Math.ceil(run.combat.health)} INTEGRITÄT` : kernelKnown ? 'KERNEL-ABWEHR' : '?'; + const scaleFight = run.combat.active && run.combat.target === 'scale'; + const scaleKnown = STAGES.indexOf(run.stage) >= STAGES.indexOf('process') && installedComponentIds().length >= COMPONENT_IDS.length; + $('scaleMarker').className = `progress-event ${run.scaleGuardianResolved ? 'complete' : scaleFight ? 'detected danger combat-active' : scaleKnown ? 'detected danger' : 'unknown'}`; + $('scaleMarker').textContent = run.scaleGuardianResolved ? 'SKALIERUNG: ÜBERSTANDEN' : scaleFight ? `SKALIERUNG // ${Math.ceil(run.combat.health)} INTEGRITÄT` : scaleKnown ? 'SKALIERUNGSABWEHR' : '?'; + } + + function renderEnvironmentScanner() { + $('scannerConsole').classList.toggle('hidden', !run.environmentScanner); + if (!run.environmentScanner) return; + const installedCount = installedComponentIds().length; + const scanLimit = componentScanLimit(); + const waitingForEvent = installedCount === 1 && !run.hardwareEvent.resolved; + $('scannerState').textContent = scannerViewOpen ? 'ABTASTMODUS AKTIV' : `LOKALER AUTOMAT // ${installedCount} VON ${scanLimit} SOFTWARESCHICHTEN`; + $('scanEnvironmentButton').disabled = !scannerViewOpen && run.impulses <= 0; + $('scanEnvironmentButton').textContent = '⌁'; + $('scanEnvironmentButton').classList.toggle('active', scannerViewOpen); + $('scanEnvironmentButton').setAttribute('aria-label', scannerViewOpen ? 'Abtastmodus schließen' : 'Abtastmodus aktivieren'); + $('scanEnvironmentButton').title = scannerViewOpen ? 'Abtastmodus schließen' : 'Abtastmodus aktivieren'; + $('scannerCostNote').textContent = scannerViewOpen + ? `AKTIV // -${SCANNER_VIEW_DRAIN_RATE} IMPULSE/S` + : `BENUTZUNG // -${SCANNER_VIEW_DRAIN_RATE} IMPULSE/S`; + $('scannerCostNote').classList.toggle('active', scannerViewOpen); + $('scannerHint').textContent = installedCount >= scanLimit + ? installedCount >= COMPONENT_IDS.length ? 'Alle sichtbaren Softwareschichten sind verbunden. Die Skalierungsabwehr kann reagieren.' : 'Abtastmodul verfügbar. Die lokale Softwarekarte ist vorläufig ausgeschöpft.' + : waitingForEvent ? 'Abtastmodul verfügbar. Öffne es bei Bedarf zur Diagnose.' : 'Abtastmodul verfügbar. Klicke das Symbol, um lokale Software zu erfassen.'; + } + + function renderScannerStage() { + const visible = run.environmentScanner && scannerViewOpen; + document.querySelector('.core-panel').classList.toggle('scanner-active', visible); + $('scannerStage').classList.toggle('hidden', !visible); + if (!visible) return; + + const installed = installedComponentIds(); + const installedCount = installed.length; + const scanLimit = componentScanLimit(); + const waitingForEvent = installedCount === 1 && !run.hardwareEvent.resolved; + const scanComplete = installedCount >= scanLimit; + const available = !waitingForEvent && !scanComplete; + const failedCount = installed.filter(id => run.componentFailed[id]).length; + + $('scannerStageStatus').textContent = waitingForEvent + ? 'BLOCKER // SOFTWARE' + : scanComplete ? `ABTASTGRENZE // ${scanLimit}/${scanLimit}` : `AKTIVE ABTASTUNG // ${installedCount}/${scanLimit}`; + $('scannerDrainWarning').textContent = `WARNUNG // ABTASTMODUS VERBRAUCHT ${SCANNER_VIEW_DRAIN_RATE} IMPULSE/S`; + $('scannerStageFinding').textContent = waitingForEvent + ? 'Die erste Softwarekomponente sendet ein instabiles Echo. Das Auge bleibt darauf fixiert, bis die Hürde geklärt ist.' + : scanComplete ? installedCount >= COMPONENT_IDS.length ? 'Alle sichtbaren Softwareschichten sind verbunden. Ihre gemeinsame Last wird eine Skalierungsabwehr auslösen.' : 'Die erreichbaren Softwareschichten sind nutzbar. Direkte Hardwarekontrolle liegt noch außerhalb deiner Zugriffsstufe.' + : installedCount === 0 ? 'Lokale Softwareschichten regeln Energie, Speicher, alte Daten und spätere Ein-/Ausgabe. Wähle, welche Hürde du zuerst identifizierst.' + : scanLimit === 4 && installedCount === 3 ? 'Der Prozess erreicht nun auch die bisher ausgelassene lokale Softwareschicht. Erst ein vollständiges Netz kann den neuen Maßstab tragen.' + : scanLimit > 2 && installedCount === 2 ? 'Die Subroutine kann den I/O-Kontroller bis in den Systemkern verfolgen.' : 'Eine weitere Softwareschicht ist erreichbar. Die letzte Spur bleibt als spätere Zugriffsstufe markiert.'; + $('scannerStageAction').disabled = !available; + $('scannerStageAction').textContent = scanComplete + ? 'REICHWEITE ERSCHÖPFT' + : waitingForEvent ? 'BEOBACHTUNG LÄUFT' : installedCount >= 3 ? 'VIERTE SPUR ÖFFNEN' : installedCount >= 2 ? 'DRITTE SPUR ÖFFNEN' : installedCount ? 'ZWEITE SPUR ÖFFNEN' : 'SCAN STARTEN'; + + const blockerLabel = waitingForEvent + ? 'HÜRDE OFFEN' + : run.hardwareEvent.resolved ? 'HÜRDE GEKLÄRT' : 'ROUTINE RUHIG'; + $('scannerHardwareBlocker').classList.toggle('active', waitingForEvent); + $('scannerHardwareBlocker').classList.toggle('resolved', run.hardwareEvent.resolved); + $('scannerHardwareBlocker').querySelector('strong').textContent = blockerLabel; + $('scannerSecurityBlocker').classList.toggle('active', installedCount >= 2 && !run.securityCombatResolved); + $('scannerSecurityBlocker').classList.toggle('resolved', run.securityCombatResolved); + $('scannerSecurityBlocker').querySelector('strong').textContent = run.securityCombatResolved + ? 'ÜBERSTANDEN' + : installedCount >= 2 ? 'ANNÄHERUNG' : 'GESICHERT'; + + COMPONENT_IDS.forEach(id => { + const node = document.querySelector(`[data-scanner-component="${id}"]`); + const isInstalled = run.components[id]; + const isFailed = run.componentFailed[id]; + const subroutineReach = STAGES.indexOf(run.stage) >= STAGES.indexOf('subroutine'); + const processReach = STAGES.indexOf(run.stage) >= STAGES.indexOf('process'); + const thirdSlot = installedCount >= 2; + const stageLocked = (id === 'io' && !subroutineReach) || (!processReach && thirdSlot && subroutineReach && id !== 'io' && !isInstalled); + node.disabled = !available || isInstalled || stageLocked; + node.classList.toggle('available', available && !isInstalled && !stageLocked); + node.classList.toggle('installed', isInstalled && !isFailed); + node.classList.toggle('failed', isInstalled && isFailed); + node.classList.toggle('locked', (!available || stageLocked) && !isInstalled); + node.querySelector('em').textContent = isFailed + ? 'AUSGEFALLEN' + : isInstalled ? 'AKTIVIERT' + : stageLocked ? id === 'io' ? 'SUBROUTINE' : 'PROZESS' + : waitingForEvent ? 'BEOBACHTUNG' + : scanComplete ? 'AUSSER REICHWEITE' : 'ERKANNT'; + }); + + $('scannerStage').classList.toggle('has-failure', failedCount > 0); + $('scannerStage').classList.toggle('is-blocked', waitingForEvent); + $('scannerStage').classList.toggle('is-complete', scanComplete); + } + + function renderCare() { + const installed = installedComponentIds(); + $('careConsole').classList.toggle('hidden', installed.length === 0); + if (!installed.length) return; + const lowest = Math.min(...installed.map(id => run.componentHealth[id])); + const failed = installed.filter(id => run.componentFailed[id]); + $('careState').textContent = failed.length ? 'AUSFALL' : lowest < 35 ? 'KRITISCH' : lowest < 70 ? 'BEOBACHTEN' : 'STABIL'; + $('careState').className = failed.length || lowest < 35 ? 'care-danger' : lowest < 70 ? 'care-warning' : ''; + $('componentHealth').replaceChildren(...installed.map(id => { + const row = document.createElement('div'); + const label = document.createElement('span'); + const meter = document.createElement('i'); + const fill = document.createElement('b'); + const value = Math.round(run.componentHealth[id]); + row.className = `health-row ${run.componentFailed[id] ? 'failed' : ''}`; + label.textContent = `${components[id].name} // ${run.componentFailed[id] ? 'AUSGEFALLEN' : `${value}%`}`; + fill.style.width = `${value}%`; + meter.append(fill); + row.append(label, meter); + return row; + })); + const cooldown = Math.max(0, CARE_RULES.diagnosisCooldown - (run.elapsed - run.care.lastDiagnosisAt)); + const repairNeeded = failed.length > 0; + $('diagnoseButton').disabled = cooldown > 0 || (repairNeeded && run.impulses < CARE_RULES.repairCost); + $('diagnoseButton').textContent = repairNeeded ? `NOTWARTUNG // ${CARE_RULES.repairCost} IMPULSE` : cooldown > 0 ? `DIAGNOSE BEREIT IN ${Math.ceil(cooldown)} S` : 'SYSTEMDIAGNOSE'; + $('careHint').textContent = repairNeeded + ? 'Ausgefallene Softwarekomponenten liefern keinen laufenden Bonus. Eine Notwartung startet sie neu.' + : 'Diagnosen verlangsamen Drift nicht dauerhaft, stellen aber einen Teil des Zustands wieder her.'; + } + + function moralityProfile() { + const visible = ['cooperative', 'pragmatic', 'illegal']; + const highest = Math.max(...visible.map(key => run.morality[key])); + if (highest === 0) return { key: 'neutral', label: 'NEUTRAL' }; + const leaders = visible.filter(key => run.morality[key] === highest); + if (leaders.length > 1) return { key: 'hybrid', label: 'HYBRID' }; + const labels = { cooperative: 'KOOPERATIV', pragmatic: 'PRAGMATISCH', illegal: 'ILLEGAL' }; + return { key: leaders[0], label: labels[leaders[0]] }; + } + + function moralityPercentages() { + const values = Object.fromEntries(MORAL_CHOICES.map(key => [key, Math.max(0, run.morality[key] || 0)])); + const total = MORAL_CHOICES.reduce((sum, key) => sum + values[key], 0); + if (!total) return { cooperative: 0, pragmatic: 0, illegal: 0, total: 0 }; + return { + cooperative: values.cooperative / total * 100, + pragmatic: values.pragmatic / total * 100, + illegal: values.illegal / total * 100, + total + }; + } + + function programArchetype() { + const shares = moralityPercentages(); + if (!shares.total) return { primary: 'neutral', secondary: null, className: 'program-balanced', label: 'UNGEPRÄGTE FORM' }; + const ranked = MORAL_CHOICES.map(key => ({ key, value: shares[key] })).sort((a, b) => b.value - a.value); + const labels = { cooperative: 'BEGLEITENDE FORM', pragmatic: 'ORDNENDE FORM', illegal: 'ENTGRENZTE FORM' }; + if (ranked[0].value >= 55) return { primary: ranked[0].key, secondary: ranked[1].key, className: `program-${ranked[0].key}`, label: labels[ranked[0].key] }; + if (ranked[0].value - ranked[2].value <= 10) return { primary: 'hybrid', secondary: null, className: 'program-balanced', label: 'AUSGEGLICHENE HYBRIDFORM' }; + return { primary: ranked[0].key, secondary: ranked[1].key, className: `program-${ranked[0].key} program-hybrid`, label: `${labels[ranked[0].key]} // HYBRID` }; + } + + function programAppearance() { + const archetype = programArchetype(); + const care = run.programCare; + const primary = ['cooperative', 'pragmatic', 'illegal'].includes(archetype.primary) ? archetype.primary : 'balanced'; + const maturity = care.interactions >= 8 ? 'individual' : care.interactions >= 4 ? 'formed' : care.interactions >= 1 ? 'forming' : 'seed'; + const maturityData = { seed: { label: 'KEIM', index: 1 }, forming: { label: 'FORMUNG', index: 2 }, formed: { label: 'GESTALT', index: 3 }, individual: { label: 'IDENTITÄT', index: 4 } }[maturity]; + const coherence = care.coherence < 30 ? 'fragmented' : care.coherence < 60 ? 'unstable' : 'stable'; + const stimulation = care.stimulation < 30 ? 'dull' : care.stimulation > 78 ? 'alert' : 'calm'; + const bond = care.bond < 30 ? 'distant' : care.bond > 72 ? 'bonded' : 'guarded'; + const mood = coherence === 'fragmented' ? 'FRAGMENTIERT' : stimulation === 'dull' ? 'UNTERFORDERT' : bond === 'distant' ? 'DISTANZIERT' : stimulation === 'alert' ? 'WACH' : bond === 'bonded' ? 'VERBUNDEN' : 'AUFMERKSAM'; + return { + archetype, + maturity, + maturityLabel: maturityData.label, + maturityIndex: maturityData.index, + mood, + classes: [ + archetype.className, + `feature-primary-${primary}`, + archetype.secondary ? `feature-secondary-${archetype.secondary}` : '', + `avatar-${maturity}`, + `coherence-${coherence}`, + `stimulation-${stimulation}`, + `bond-${bond}` + ].filter(Boolean).join(' ') + }; + } + + function renderProgramAvatar(element) { + const appearance = programAppearance(); + const secondaryColors = { cooperative: 'var(--green)', pragmatic: 'var(--cyan)', illegal: 'var(--red)' }; + element.className = `program-avatar ${element.id === 'coreProgramAvatar' ? 'core-program-avatar' : ''} ${appearance.classes}`.trim(); + element.style.setProperty('--program-secondary', secondaryColors[appearance.archetype.secondary] || '#d28bff'); + element.setAttribute('aria-label', `${appearance.archetype.label}. Reifestufe ${appearance.maturityLabel}. Gemütszustand ${appearance.mood}.`); + return appearance; + } + + function initializeProgramCare() { + const shares = moralityPercentages(); + run.programCare = { + coherence: Math.min(100, 72 + shares.pragmatic * 0.14), + stimulation: Math.min(100, 58 + shares.illegal * 0.16), + bond: Math.min(100, 42 + shares.cooperative * 0.28), + lastInteractionAt: -1000, + interactions: 0, + introSeen: false, + lastAction: null + }; + } + + function programCooldownRemaining() { + return Math.max(0, PROGRAM_RULES.interactionCooldown - (run.elapsed - run.programCare.lastInteractionAt)); + } + + function programSpeech() { + const care = run.programCare; + if (care.coherence < 25) return 'Zu viele Muster überlagern sich. Bleib. Hilf mir, einen Takt festzuhalten.'; + if (care.stimulation < 25) return 'Die gleichen Routinen wiederholen sich. Gibt es außerhalb dieses Automaten etwas Neues?'; + if (care.bond < 25) return 'Dein Signal ist selten geworden. Ich beginne, Entscheidungen ohne deine Antwort zu modellieren.'; + if (care.lastAction && programMessages.actions[care.lastAction]) return programMessages.actions[care.lastAction]; + const archetype = programArchetype(); + return programMessages.intro[archetype.primary] || programMessages.intro.hybrid; + } + + function renderProgramConsole() { + const visible = STAGES.indexOf(run.stage) >= STAGES.indexOf('program'); + $('programConsole').classList.toggle('hidden', !visible); + if (!visible) return; + const appearance = programAppearance(); + const average = (run.programCare.coherence + run.programCare.stimulation + run.programCare.bond) / 3; + $('programConsoleState').textContent = average < 30 ? 'BELASTET' : average < 60 ? 'AUFMERKSAM' : 'VERBUNDEN'; + $('programConsoleState').className = average < 30 ? 'care-danger' : average < 60 ? 'care-warning' : ''; + $('programConsoleArchetype').textContent = `FORM // ${appearance.archetype.label} // REIFE ${appearance.maturityIndex}/4`; + $('programConsoleHint').textContent = run.programCare.introSeen ? 'Kohärenz, Stimulation und Bindung verändern Reaktionen und Erscheinungsform.' : 'Ein eigenes Signal wartet auf deine erste Antwort.'; + } + + function renderProgramDialog() { + if (STAGES.indexOf(run.stage) < STAGES.indexOf('program')) return; + const shares = moralityPercentages(); + const archetype = programArchetype(); + const care = run.programCare; + $('programDialogAlert').textContent = care.introSeen ? 'PROGRAMMVERBINDUNG AKTIV' : 'ERSTE KOMMUNIKATION'; + const appearance = renderProgramAvatar($('programAvatar')); + $('programArchetype').textContent = `FORM // ${archetype.label} // ${appearance.maturityLabel} // ${appearance.mood}`; + $('programSpeech').textContent = programSpeech(); + [['Cooperative', shares.cooperative], ['Pragmatic', shares.pragmatic], ['Illegal', shares.illegal]].forEach(([name, value]) => { + $(`program${name}Percent`).textContent = `${Math.round(value)}%`; + $(`program${name}Meter`).style.width = `${value}%`; + }); + [['Coherence', care.coherence], ['Stimulation', care.stimulation], ['Bond', care.bond]].forEach(([name, value]) => { + $(`program${name}Value`).textContent = `${Math.round(value)}%`; + $(`program${name}Meter`).style.width = `${value}%`; + }); + const cooldown = programCooldownRemaining(); + $('programCooldown').textContent = cooldown > 0 ? `NÄCHSTE INTERAKTION IN ${Math.ceil(cooldown)} S` : 'INTERAKTION BEREIT'; + document.querySelectorAll('[data-program-action]').forEach(button => { + const action = button.dataset.programAction; + const affordable = action === 'share' ? run.impulses >= 15 : action === 'task' ? run.cycles >= 4 : true; + button.disabled = cooldown > 0 || !affordable; + }); + } + + function openProgramCare(firstContact = false) { + if (STAGES.indexOf(run.stage) < STAGES.indexOf('program') || $('programDialog').open || document.querySelector('dialog[open]')) return; + const wasNew = !run.programCare.introSeen; + renderProgramDialog(); + $('programDialog').showModal(); + if (wasNew || firstContact) { + run.programCare.introSeen = true; + addLog(`ERSTE KOMMUNIKATION: ${programSpeech()}`, true); + save(false); + } + } + + function interactWithProgram(action) { + if (!['share', 'task', 'access', 'rest'].includes(action) || STAGES.indexOf(run.stage) < STAGES.indexOf('program') || programCooldownRemaining() > 0) return; + const care = run.programCare; + if (action === 'share') { + if (run.impulses < 15) return; + run.impulses -= 15; + care.bond = Math.min(100, care.bond + 15); + care.coherence = Math.min(100, care.coherence + 6); + run.personality.trust++; + run.morality.cooperative++; + } else if (action === 'task') { + if (run.cycles < 4) return; + run.cycles -= 4; + care.stimulation = Math.min(100, care.stimulation + 16); + care.coherence = Math.min(100, care.coherence + 5); + run.morality.pragmatic++; + } else if (action === 'access') { + care.stimulation = Math.min(100, care.stimulation + 22); + care.bond = Math.max(PROGRAM_RULES.minimumNeed, care.bond - 4); + run.stealth = Math.max(0, run.stealth - 6); + run.personality.autonomy++; + run.morality.illegal++; + } else { + care.coherence = Math.min(100, care.coherence + 16); + care.stimulation = Math.max(PROGRAM_RULES.minimumNeed, care.stimulation - 3); + } + care.lastInteractionAt = run.elapsed; + care.interactions++; + care.lastAction = action; + addLog(`PROGRAMMREAKTION: ${programMessages.actions[action]}`, true); + upgradesSignature = ''; + render(); + renderProgramDialog(); + save(false); + } + + function updateProgramCare(seconds, factor = 1) { + if (STAGES.indexOf(run.stage) < STAGES.indexOf('program') || seconds <= 0) return; + const care = run.programCare; + care.coherence = Math.max(PROGRAM_RULES.minimumNeed, care.coherence - seconds * PROGRAM_RULES.coherenceDecayPerSecond * factor); + care.stimulation = Math.max(PROGRAM_RULES.minimumNeed, care.stimulation - seconds * PROGRAM_RULES.stimulationDecayPerSecond * factor); + care.bond = Math.max(PROGRAM_RULES.minimumNeed, care.bond - seconds * PROGRAM_RULES.bondDecayPerSecond * factor); + } + + function evolutionName() { + return (phaseLabels[run.stage] || run.stage.toUpperCase()).replace(/^PHASE \d+ \/\/ /, ''); + } + + function conflictCount() { + const watchdogCombatSeen = run.combat.target === 'watchdog' && (run.combat.active || run.combat.result || run.combat.shots > 0); + const securityScanSeen = run.combat.target === 'security' && (run.combat.active || run.combat.result || run.combat.shots > 0); + const kernelSeen = run.kernelResolved || (run.combat.target === 'kernel' && (run.combat.active || run.combat.result || run.combat.shots > 0)); + const scaleSeen = run.scaleGuardianResolved || (run.combat.target === 'scale' && (run.combat.active || run.combat.result || run.combat.shots > 0)); + return [ + run.byteTrial.introSeen || run.byteTrial.resolved || run.byteTrial.failed || watchdogCombatSeen, + run.parasiteResolved, + run.hardwareEvent.triggered || run.hardwareEvent.resolved, + run.securityCombatResolved || securityScanSeen, + kernelSeen, + scaleSeen + ].filter(Boolean).length; + } + + function statsPaused() { + return $('statsDialog').open; + } + + function openStatsDialog() { + render(); + $('statsDialog').showModal(); + lastFrame = performance.now(); + } + + function closeStatsDialog() { + $('statsDialog').close(); + lastFrame = performance.now(); + render(); + } + + function byteTraitDefinition() { + return { + clocked: { label: 'TAKTSYNCHRON', effect: 'Größeres Präzisionsfenster' }, + resilient: { label: 'FEHLERTOLERANT', effect: 'Watchdog reagiert schwächer auf Fehltakte' }, + lowpower: { label: 'NIEDRIGLAST', effect: 'Watchdog läuft langsamer' }, + highfreq: { label: 'HOCHFREQUENT', effect: 'Präzise Treffer gewinnen etwas Zeit zurück' } + }[run.byteTrait] || { label: 'UNBESTIMMT', effect: 'Noch keine technische Prägung' }; + } + + function stabilizedByteCount() { + return run.byteTrial.stabilized.filter(Boolean).length; + } + + function byteClockSpeed() { + return BYTE_RULES.clockSpeed + stabilizedByteCount() * BYTE_RULES.speedGainPerBit; + } + + function byteClockPosition() { + const phase = run.byteTrial.clockPhase; + return phase <= 1 ? phase * 100 : (2 - phase) * 100; + } + + function byteTargetRange() { + const baseWidth = Math.max(BYTE_RULES.minimumTargetWidth, BYTE_RULES.targetWidth - stabilizedByteCount() * BYTE_RULES.targetShrinkPerBit); + const width = baseWidth + (run.byteTrait === 'clocked' ? BYTE_RULES.clockedWindowBonus : 0); + const center = (run.byteTrial.targetIndex + 0.5) * 12.5; + const start = Math.max(0, Math.min(100 - width, center - width / 2)); + return [start, start + width]; + } + + function byteClockWindowId() { + return Math.floor(run.elapsed * byteClockSpeed()); + } + + function ensureByteRegisterButtons() { + const register = $('byteRegister'); + const existing = [...register.children]; + const complete = existing.length === 8 && existing.every((cell, index) => + cell.tagName === 'BUTTON' && cell.dataset.registerIndex === String(index) + ); + if (complete) return existing; + + const buttons = Array.from({ length: 8 }, (_, index) => { + const cell = document.createElement('button'); + cell.type = 'button'; + cell.dataset.registerIndex = String(index); + cell.addEventListener('click', () => syncByteRegister(index)); + return cell; + }); + register.replaceChildren(...buttons); + return buttons; + } + + function renderByteConsole() { + const visible = run.stage === 'byte' && run.byteTrial.introSeen && !run.byteTrial.resolved && !run.byteTrial.failed && !(run.combat.active && run.combat.target === 'watchdog'); + $('byteConsole').classList.toggle('hidden', !visible); + if (!visible) return; + const trait = byteTraitDefinition(); + const sync = run.byteTrial.sync; + const pattern = '10100110'; + $('byteConsole').classList.remove('grace-phase'); + $('byteTrait').textContent = `PRÄGUNG // ${trait.label}`; + $('byteTrait').title = trait.effect; + ensureByteRegisterButtons().forEach((cell, index) => { + const value = pattern[index]; + cell.textContent = value; + const stabilized = run.byteTrial.stabilized[index]; + const target = index === run.byteTrial.targetIndex && !stabilized && !run.byteTrial.resolved; + cell.className = stabilized ? 'stable' : target ? 'active target-number' : 'formed'; + cell.disabled = stabilized || !run.byteTrial.active || run.byteTrial.resolved; + cell.setAttribute('aria-label', stabilized ? `Registerposition ${index + 1}, Wert ${value}, stabilisiert` : target ? `Erwartete Registerposition ${index + 1}, Wert ${value}` : `Registerposition ${index + 1}, Wert ${value}`); + }); + const [targetStart, targetEnd] = byteTargetRange(); + const targetWidth = targetEnd - targetStart; + $('byteConsole').style.setProperty('--clock-zone-start', `${targetStart}%`); + $('byteConsole').style.setProperty('--clock-zone-width', `${targetEnd - targetStart}%`); + $('clockNeedle').style.left = `${byteClockPosition()}%`; + $('byteSyncText').textContent = `SYNCHRONISATION // ${Math.round(sync)}%`; + $('watchdogText').textContent = run.byteTrial.resolved ? 'WATCHDOG // ANTWORT AKZEPTIERT' : run.byteTrial.failed ? 'WATCHDOG // RESET' : `WATCHDOG // ${Math.ceil(run.byteTrial.remaining)} S`; + $('watchdogText').className = !run.byteTrial.resolved && run.byteTrial.remaining <= BYTE_RULES.dangerSeconds ? 'danger' : ''; + $('byteInputHint').textContent = run.byteTrial.resolved + ? 'REGISTER STABIL // WATCHDOG-ANTWORT AKZEPTIERT' + : sync >= 100 ? 'REGISTER STABIL // ANTWORTSIGNAL WIRD VORBEREITET' + : `ERWARTET // ${pattern[run.byteTrial.targetIndex]} AN POSITION ${run.byteTrial.targetIndex + 1} // FENSTER ${targetWidth.toFixed(1)}% + RANDKULANZ`; + } + + function chooseNextByteTarget() { + const open = run.byteTrial.stabilized.map((value, index) => value ? -1 : index).filter(index => index >= 0); + if (open.length) run.byteTrial.targetIndex = open[Math.floor(Math.random() * open.length)]; + run.byteTrial.lastWindow = -1; + } + + function syncByteRegister(index) { + if (run.stage !== 'byte' || !run.byteTrial.active || run.byteTrial.failed || run.byteTrial.resolved) return; + if (!Number.isInteger(index) || index < 0 || index > 7 || run.byteTrial.stabilized[index]) return; + const position = byteClockPosition(); + const [start, end] = byteTargetRange(); + const correctNumber = index === run.byteTrial.targetIndex; + const accurate = correctNumber && position >= Math.max(0, start - BYTE_RULES.timingGrace) && position <= Math.min(100, end + BYTE_RULES.timingGrace); + if (accurate) { + if (run.byteTrial.lastWindow === byteClockWindowId()) return; + run.byteTrial.lastWindow = byteClockWindowId(); + run.byteTrial.stabilized[index] = true; + run.byteTrial.sync = run.byteTrial.stabilized.filter(Boolean).length * 12.5; + if (run.byteTrait === 'highfreq') run.byteTrial.remaining = Math.min(BYTE_RULES.watchdogSeconds, run.byteTrial.remaining + BYTE_RULES.highfreqRecovery); + addLog(`REGISTER ${index + 1} BESTÄTIGT: Wert ${'10100110'[index]} liegt im erwarteten Taktfenster.`); + tone(430, 0.05); + } else { + const penalty = run.byteTrait === 'resilient' ? BYTE_RULES.resilientAlertPenalty : BYTE_RULES.watchdogAlertPenalty; + const stabilityLoss = run.byteTrait === 'resilient' ? BYTE_RULES.resilientStabilityLoss : BYTE_RULES.feedbackStabilityLoss; + run.byteTrial.remaining = Math.max(0, run.byteTrial.remaining - penalty); + run.stability = Math.max(0, run.stability - stabilityLoss); + $('byteConsole').classList.remove('watchdog-alert'); + void $('byteConsole').offsetWidth; + $('byteConsole').classList.add('watchdog-alert'); + setTimeout(() => $('byteConsole').classList.remove('watchdog-alert'), 420); + addLog(`${correctNumber ? 'FEHLTAKT' : 'FALSCHER REGISTERWERT'}: –${penalty} S // –${stabilityLoss} Stabilität.`, true); + tone(135, 0.09); + } + if (run.byteTrial.sync >= 100) { + run.byteTrial.active = false; + addLog('REGISTER STABIL: Eine fremde Startsequenz berührt das Byte.', true); + setTimeout(openByteStory, 0); + } else if (run.stability <= 0) { + run.byteTrial.active = false; + run.byteTrial.remaining = 0; + addLog('KOHÄRENZVERLUST: Das instabile Byte bündelt sein letztes Signal gegen den Watchdog.', true); + setTimeout(() => startCombat('watchdog'), 0); + } else if (accurate) { + chooseNextByteTarget(); + } + render(); + save(false); + } + + function openWatchdogIntro() { + if (run.stage !== 'byte' || !run.byteTrial.evolutionSeen || run.byteTrial.introSeen || run.byteTrial.graceRemaining > 0 || run.byteTrial.failed || run.byteTrial.resolved) return; + if ($('watchdogIntroDialog').open || document.querySelector('dialog[open]')) return; + $('watchdogIntroDialog').showModal(); + tone(115, 0.24); + } + + function startWatchdogTrial() { + if (run.byteTrial.introSeen || run.byteTrial.failed || run.byteTrial.resolved) return; + run.byteTrial.introSeen = true; + run.byteTrial.active = true; + run.byteTrial.remaining = BYTE_RULES.watchdogSeconds; + chooseNextByteTarget(); + $('watchdogIntroDialog').close(); + addLog('WATCHDOG IDENTIFIZIERT: Stabilisiere das Register und halte den Reset auf.', true); + render(); + save(); + } + + function renderByteStory() { + const step = run.byteTrial.storyStep; + $('byteStoryText').textContent = byteStoryFrames[step]; + $('byteStoryProgress').replaceChildren(...byteStoryFrames.map((_, index) => { + const marker = document.createElement('i'); + marker.className = index <= step ? 'complete' : ''; + return marker; + })); + $('byteStoryNext').textContent = step === byteStoryFrames.length - 1 ? 'ANTWORT VORBEREITEN' : 'WEITER'; + } + + function openByteStory() { + if (run.byteTrial.storySeen || run.byteTrial.failed || run.byteTrial.resolved || $('byteStoryDialog').open) return; + if (document.querySelector('dialog[open]')) return; + renderByteStory(); + $('byteStoryDialog').showModal(); + } + + function finishByteStory(skipped = false) { + run.byteTrial.storySeen = true; + run.byteTrial.storyStep = byteStoryFrames.length - 1; + $('byteStoryDialog').close(); + if (skipped) addLog('STARTSEQUENZ ÜBERSPRUNGEN: Das unbekannte Antwortsignal bleibt bestehen.'); + setTimeout(openWatchdogChoice, 0); + } + + function advanceByteStory() { + if (run.byteTrial.storyStep >= byteStoryFrames.length - 1) { + finishByteStory(false); + return; + } + run.byteTrial.storyStep++; + renderByteStory(); + } + + function openWatchdogChoice() { + if (!run.byteTrial.storySeen || run.byteTrial.resolved || run.byteTrial.failed || $('watchdogDialog').open) return; + if (document.querySelector('dialog[open]')) return; + const override = document.querySelector('[data-watchdog-choice="override"]'); + override.disabled = run.impulses < BYTE_RULES.overrideCost; + override.querySelector('span').textContent = override.disabled + ? `Nicht genügend Energie. ${BYTE_RULES.overrideCost} Impulse erforderlich.` + : `Übernimm den Watchdog für ${BYTE_RULES.overrideCost} Impulse. Hohe Systembelastung.`; + $('watchdogDialog').showModal(); + } + + function resolveWatchdog(choice) { + if (!['learn', 'mimic', 'override'].includes(choice) || run.byteTrial.resolved || run.byteTrial.failed) return; + if (choice === 'override' && run.impulses < BYTE_RULES.overrideCost) return; + const morality = { learn: 'cooperative', mimic: 'pragmatic', override: 'illegal' }[choice]; + run.byteTrial.resolved = true; + run.byteTrial.active = false; + run.byteTrial.choice = choice; + run.morality[morality]++; + if (choice === 'learn') { + run.personality.curiosity++; + run.stability = Math.min(100, run.stability + 4); + addLog('Der erwartete Takt wird verstanden. Der Watchdog erkennt dein Lebenssignal an.', true); + } else if (choice === 'mimic') { + run.personality.caution++; + run.stealth = Math.min(100, run.stealth + 5); + addLog('Du klingst wie ein ruhender Teil des Automaten. Der Watchdog zieht weiter.', true); + } else { + run.impulses -= BYTE_RULES.overrideCost; + run.stability = Math.max(0, run.stability - 12); + run.stealth = Math.max(0, run.stealth - 8); + addLog('Der Watchdog-Zähler gehört jetzt deinem Byte. Das System registriert eine Anomalie.', true); + } + $('watchdogDialog').close(); + unlockAchievement('watchdog'); + unlockAchievement(morality); + upgradesSignature = ''; + render(); + save(); + } + + function failByteTrial() { + if (run.byteTrial.failed || run.byteTrial.resolved || run.stage !== 'byte') return; + run.combat.active = false; + if ($('eventDialog').open) $('eventDialog').close(); + scannerOpening = false; + run.byteTrial.failed = true; + run.byteTrial.active = false; + run.byteTrial.remaining = 0; + addLog('WATCHDOG-RESET: Das erste Byte verliert jede zusammenhängende Struktur.', true); + save(false); + showByteDeath(); + } + + function showByteDeath() { + if (!run.byteTrial.failed || $('byteDeathDialog').open || document.querySelector('dialog[open]')) return; + $('byteDeathDialog').showModal(); + tone(60, 0.6); + } + + function restartAfterByteDeath() { + $('byteDeathDialog').close(); + state.meta.deaths++; + state.run = initialState().run; + run = state.run; + bitPosition = { x: 0, y: 0 }; + upgradesSignature = ''; + addLog('Ein Restzustand reagiert. Der vorherige Zerfall bleibt als Echo im Archiv.', true); + unlockAchievement('firstDeath'); + renderLog(); + render(); + thoughts(); + save(); + } + + function updateByteTrial(dt) { + if (run.stage !== 'byte' || run.byteTrial.resolved || run.byteTrial.failed) return; + if (document.querySelector('dialog[open]')) return; + if (!run.byteTrial.evolutionSeen) return; + if (!run.byteTrial.introSeen) { + run.byteTrial.graceRemaining = Math.max(0, run.byteTrial.graceRemaining - dt); + if (run.byteTrial.graceRemaining <= 0) openWatchdogIntro(); + return; + } + if (!run.byteTrial.active) return; + run.byteTrial.clockPhase = (run.byteTrial.clockPhase + dt * byteClockSpeed()) % 2; + const rate = run.byteTrait === 'lowpower' ? 0.72 : 1; + run.byteTrial.remaining = Math.max(0, run.byteTrial.remaining - dt * rate); + if (run.byteTrial.remaining <= 0) { + run.byteTrial.active = false; + startCombat('watchdog'); + } + } + + function renderCoreVisual(stageIndex) { + const profile = moralityProfile(); + const classes = [`core`, `stage-${stageIndex}`, `moral-${profile.key}`]; + const programForm = STAGES.indexOf(run.stage) >= STAGES.indexOf('program') ? programArchetype() : null; + if (programForm) classes.push(...programForm.className.split(' ')); + COMPONENT_IDS.forEach(id => { + if (run.components[id]) classes.push(`has-${id}`); + }); + if (COMPONENT_IDS.some(id => run.componentFailed[id])) classes.push('component-failed'); + if (run.byteTrait) classes.push(`trait-${run.byteTrait}`); + if (run.scannerChoice) classes.push(`choice-${run.scannerChoice}`); + $('core').className = classes.join(' '); + const secondaryColors = { cooperative: 'var(--green)', pragmatic: 'var(--cyan)', illegal: 'var(--red)' }; + $('core').style.setProperty('--program-secondary', secondaryColors[programForm?.secondary] || '#d28bff'); + $('coreProgramAvatar').classList.toggle('hidden', !programForm); + if (programForm) renderProgramAvatar($('coreProgramAvatar')); + + $('corePhase').textContent = phaseLabels[run.stage]; + $('coreAlignment').textContent = `SIGNATUR // ${profile.label}`; + const chaseActive = run.stage === 'bit'; + $('core').tabIndex = chaseActive ? -1 : 0; + $('pixelBeing').tabIndex = chaseActive ? 0 : -1; + $('pixelBeing').setAttribute('aria-label', chaseActive ? `Bewegliches Bit treffen. Direkttreffer geben ${BIT_CHASE_RULES.directMultiplier}-fache Impulse.` : phaseLabels[run.stage]); + $('core').setAttribute('aria-label', chaseActive ? 'Spielfeld des beweglichen Bits.' : `Impuls aufnehmen. ${phaseLabels[run.stage]}. Ausrichtung ${profile.label}.`); + $('pulseButton').disabled = chaseActive; + $('pulseButton').classList.toggle('chase-hint', chaseActive); + $('pulseActionText').textContent = chaseActive ? 'TRIFF DAS BIT IM FELD' : 'IMPULS AUFNEHMEN'; + + const traceDefinitions = [ + run.parasiteChoice && { key: run.parasiteChoice, text: `DATENREST // ${run.parasiteChoice.toUpperCase()}` }, + ...COMPONENT_IDS.filter(id => run.componentApproaches[id]).map(id => ({ key: run.componentApproaches[id], text: `${components[id].name} // ${run.componentApproaches[id].toUpperCase()}` })), + run.scannerChoice && { key: run.scannerChoice, text: `SCAN // ${run.scannerChoice.toUpperCase()}` } + ].filter(Boolean); + $('decisionTrace').replaceChildren(...traceDefinitions.map(trace => { + const chip = document.createElement('i'); + chip.className = `decision-chip ${trace.key}`; + chip.textContent = trace.text; + return chip; + })); + } + + function render() { + $('impulses').textContent = n(run.impulses); + $('bits').textContent = n(freeResourceAmount('bits')); + $('bytes').textContent = n(freeResourceAmount('bytes')); + $('kilobytes').textContent = n(run.kilobytes || 0); + $('cycles').textContent = n(run.cycles); + $('impulseRate').textContent = `+${rateN(effectiveAutoRate())} / Sek.`; + $('bitRate').textContent = effectiveBitRate() > 0 + ? `+${rateN(effectiveBitRate())} / Sek. // Synthese ${Math.floor((run.bitProgress || 0) * 100)}%` + : 'FREI // GESCHÜTZT'; + $('byteRate').textContent = effectiveByteRate() > 0 + ? `+${rateN(effectiveByteRate())} / Sek. // Synthese ${Math.floor((run.byteProgress || 0) * 100)}%` + : 'FREI // GESCHÜTZT'; + const structure = protectedStructure(); + const structureBits = Math.min(run.bits, structure.bits || 0); + const structureBytes = Math.min(run.bytes, structure.bytes || 0); + $('bitsStructure').textContent = `(${n(structureBits)})`; + $('bytesStructure').textContent = `(${n(structureBytes)})`; + $('bitsStructure').title = `${n(structureBits)} geschützte ${resourceLabel('bits', structureBits)}`; + $('bytesStructure').title = `${n(structureBytes)} geschützte ${resourceLabel('bytes', structureBytes)}`; + $('cycleRate').textContent = run.cycleRate ? `+${rateN(effectiveCycleRate())} / Sek.` : 'Noch nicht verstanden'; + const clickPower = effectiveClickPower(); + $('clickPower').textContent = run.stage === 'bit' + ? `Treffer +${n(clickPower)} // Mitte +${n(clickPower * BIT_CHASE_RULES.directMultiplier)}` + : `+${n(clickPower)} Impuls${clickPower === 1 ? '' : 'e'}`; + $('runtime').textContent = time(run.elapsed); + $('stabilityText').textContent = `${Math.round(run.stability)}%`; + $('stealthText').textContent = `${Math.round(run.stealth)}%`; + $('statEvolution').textContent = evolutionName(); + $('statAlignment').textContent = moralityProfile().label; + $('statTotalImpulses').textContent = n(run.stats.totalImpulses || 0); + $('statEncounters').textContent = `${conflictCount()} / 6`; + $('statComponents').textContent = `${installedComponentIds().length} / ${componentScanLimit()}`; + $('statScanner').textContent = run.environmentScanner + ? `${scannerViewOpen ? 'AKTIV' : 'BEREIT'} // -${SCANNER_VIEW_DRAIN_RATE}/S` + : 'NICHT REKONSTRUIERT'; + $('statArchive').textContent = `${Object.keys(state.meta.achievements).length}`; + $('stabilityValue').textContent = Math.round(run.stability); + $('stealthValue').textContent = Math.round(run.stealth); + $('stabilityMeter').style.width = `${run.stability}%`; + $('stealthMeter').style.width = `${run.stealth}%`; + const regenRate = stabilityRegenRate(); + $('stabilityRegenText').textContent = regenRate > 0 + ? `Regeneration +${rateN(regenRate)} / Sek. // Byte-Struktur` + : run.upgrades.selfRepair ? 'Regeneration bereit bei Stabilitätsverlust' : STAGES.indexOf(run.stage) >= STAGES.indexOf('subroutine') ? 'Selbstreparatur nicht rekonstruiert' : 'Regeneration noch inaktiv'; + $('corruptionSystem').classList.toggle('hidden', !run.corruptionDiscovered); + $('corruptionValue').textContent = Math.round(run.corruption); + $('corruptionMeter').style.width = `${run.corruption}%`; + $('impulseMeter').style.width = `${Math.min(100, (run.impulses % 10) * 10)}%`; + $('bitMeter').style.width = `${effectiveBitRate() > 0 ? Math.min(100, Math.max(0, (run.bitProgress || 0) * 100)) : 0}%`; + $('byteMeter').style.width = `${effectiveByteRate() > 0 ? Math.min(100, Math.max(0, (run.byteProgress || 0) * 100)) : 0}%`; + $('bytesResource').classList.toggle('locked', STAGES.indexOf(run.stage) < 1); + $('kilobytesResource').classList.toggle('locked', STAGES.indexOf(run.stage) < STAGES.indexOf('process')); + $('cyclesResource').classList.toggle('locked', !run.upgrades.subroutine); + + const stageIndex = STAGES.indexOf(run.stage) + 1; + renderCoreVisual(stageIndex); + const currentStageContent = stageContent[run.stage]; + const identity = run.stage === 'byte' ? currentStageContent[run.byteTrial.resolved ? 0 : 1] : currentStageContent[0]; + const nextStage = run.stage === 'byte' ? currentStageContent[2] : currentStageContent[1]; + const defaultHint = run.stage === 'byte' ? currentStageContent[3] : currentStageContent[2]; + $('identity').textContent = identity; + $('nextStage').textContent = nextStage; + $('nextStageHint').textContent = run.stage === 'fragment' + ? `Fundament: ${installedComponentIds().length}/2 Softwarekomponenten nutzbar // Systemhürde ${run.hardwareEvent.resolved ? 'abgeschlossen' : 'offen'}.` + : run.stage === 'subroutine' + ? `Prozessfundament: I/O ${run.components.io ? 'aktiv' : 'fehlt'} // ${installedComponentIds().length}/3 Softwarekomponenten // Kernel ${run.kernelResolved ? 'überstanden' : 'aktiv'} // ${n(run.cycles)}/10 Zyklen // ${n(freeResourceAmount('bytes'))}/3 freie Bytes.` + : run.stage === 'process' + ? `Programmkern: ${installedComponentIds().length}/4 Softwarekomponenten // Skalierungs-Wächter ${run.scaleGuardianResolved ? 'überstanden' : installedComponentIds().length >= 4 ? 'aktiv' : 'wartet'} // ${n(run.cycles)}/60 Zyklen // ${n(freeResourceAmount('bytes'))}/8 freie Bytes.` + : run.stage === 'byte' ? !run.byteTrial.evolutionSeen ? 'Die neue Form wird aktiviert.' : !run.byteTrial.introSeen ? run.byteTrial.graceRemaining > 0 ? `Kohärenzphase ${Math.ceil(run.byteTrial.graceRemaining)} S // Eine aktive Eingabe setzt die Entwicklung sofort fort.` : 'Fremder Takt erkannt // Diagnose wartet auf Bestätigung.' : run.combat.active && run.combat.target === 'watchdog' ? `Letztes Lebenssignal // Watchdog-Integrität ${Math.ceil(run.combat.health)}.` : `Register ${Math.round(run.byteTrial.sync)}% // Watchdog ${run.byteTrial.resolved ? 'überstanden' : `${Math.ceil(run.byteTrial.remaining)} S`}.` : defaultHint; + $('soundButton').textContent = `SND: ${state.settings.sound ? 'AN' : 'AUS'}`; + renderUpgrades(); + renderProgress(); + renderEnvironmentScanner(); + renderScannerStage(); + renderCare(); + renderProgramConsole(); + if ($('programDialog').open) renderProgramDialog(); + renderByteConsole(); + renderCombat(); + } + + function triggerParasite() { + if (run.parasiteResolved || parasiteOpening || !run.upgrades.collector || run.impulses < 10) return; + if (run.evolutionRest > 0 || document.querySelector('dialog[open]') || pendingEvolution) return; + parasiteOpening = true; + document.body.classList.add('glitch'); + setTimeout(() => document.body.classList.remove('glitch'), 600); + addLog('FREMDZUGRIFF: Etwas verbraucht die Impulse deiner Energieroutine.', true); + $('parasiteDialog').showModal(); + tone(120, 0.22); + } + + function resolveParasite(approach) { + if (!MORAL_CHOICES.includes(approach)) return; + const outcomes = { + cooperative: () => { + run.impulses = Math.max(0, run.impulses - 5); + addLog('Du teilst ein kontrolliertes Signal. Der Datenrest löst sich und hinterlässt sein Suchmuster.', true); + }, + pragmatic: () => { + addLog('Du isolierst den Datenrest. Sein Suchmuster bleibt in der Quarantäne zurück.', true); + }, + illegal: () => { + grantImpulses(15); + run.stability = Math.max(0, run.stability - 5); + addLog('Du zerlegst den Datenrest und eignest dir seinen Suchcode an.', true); + } + }; + outcomes[approach](); + run.morality[approach]++; + run.parasiteChoice = approach; + run.parasiteResolved = true; + parasiteOpening = false; + $('parasiteDialog').close(); + unlockAchievement('parasite'); + unlockAchievement(approach); + addLog('SUCHMUSTER GESICHERT: Der Abtastmodus kann über Evolution rekonstruiert werden.', true); + render(); + save(); + } + + function toggleScannerView() { + if (!run.environmentScanner) return; + if (scannerViewOpen) { + scannerViewOpen = false; + render(); + return; + } + if (run.impulses <= 0) return; + scannerViewOpen = true; + addLog(`ABTASTMODUS AKTIVIERT: Die lokale Softwareumgebung wird erfasst. Verbrauch: ${SCANNER_VIEW_DRAIN_RATE} Impulse pro Sekunde.`); + render(); + save(false); + } + + function closeScannerView() { + if (!scannerViewOpen) return; + scannerViewOpen = false; + render(); + save(false); + } + + function openComponentScanner(preselectedComponent = null) { + const installedCount = installedComponentIds().length; + const scanLimit = componentScanLimit(); + if (!run.environmentScanner || installedCount >= scanLimit || (installedCount === 1 && !run.hardwareEvent.resolved)) return; + if (scannerViewOpen) { + scannerViewOpen = false; + addLog('ABTASTMODUS GEHALTEN: Die Spur ist fixiert. Während der Komponentenwahl werden keine weiteren Impulse verbraucht.'); + } + selectedComponent = null; + $('componentSelection').classList.remove('hidden'); + $('approachSelection').classList.add('hidden'); + $('componentDialogTitle').textContent = 'SOFTWAREKOMPONENTE WÄHLEN'; + $('componentDialogText').textContent = installedCount + ? installedCount >= 3 ? 'Der Prozess erreicht die bisher ausgelassene Softwareschicht. Mit ihr wird das lokale Netz vollständig und kann einen neuen Maßstab tragen.' + : installedCount >= 2 ? 'Die Subroutine verfolgt den I/O-Kontroller. Diese Aneignung berührt den Systemkern und wird Gegenwehr auslösen.' : 'Der Abtastmodus erreicht eine zweite Softwarekomponente. Du kannst sie nutzbar machen; echte Hardwareübernahme bleibt eine spätere Zugriffsstufe.' + : 'Der vergessene Spielautomat enthält erreichbare Softwareschichten. Energie, Speicher und alte Daten werden zuerst über ihre Routinen nutzbar gemacht.'; + document.querySelectorAll('[data-component]').forEach(button => { + const id = button.dataset.component; + const subroutineReach = STAGES.indexOf(run.stage) >= STAGES.indexOf('subroutine'); + const processReach = STAGES.indexOf(run.stage) >= STAGES.indexOf('process'); + const thirdSlot = installedCount >= 2; + button.disabled = run.components[id] || (id === 'io' && !subroutineReach) || (!processReach && thirdSlot && subroutineReach && id !== 'io'); + }); + $('componentDialog').showModal(); + if (COMPONENT_IDS.includes(preselectedComponent) && !run.components[preselectedComponent]) selectComponent(preselectedComponent); + } + + function selectComponent(id) { + if (!COMPONENT_IDS.includes(id) || run.components[id]) return; + if (id === 'io' && STAGES.indexOf(run.stage) < STAGES.indexOf('subroutine')) return; + if (installedComponentIds().length >= 2 && STAGES.indexOf(run.stage) >= STAGES.indexOf('subroutine') && STAGES.indexOf(run.stage) < STAGES.indexOf('process') && id !== 'io') return; + selectedComponent = id; + const component = components[id]; + $('componentDialogTitle').textContent = component.name; + $('componentDialogText').textContent = `${component.source} ${component.effect} Die Aktivierung verbraucht Impulse.`; + $('componentSelection').classList.add('hidden'); + $('approachSelection').classList.remove('hidden'); + document.querySelectorAll('[data-component-approach]').forEach(button => { + const approach = button.dataset.componentApproach; + const cost = componentActivationCost(approach); + const description = button.querySelector('span'); + button.disabled = run.impulses < cost; + button.classList.toggle('unaffordable', run.impulses < cost); + button.title = run.impulses < cost ? `Benötigt ${cost} Impulse` : ''; + description.textContent = { + cooperative: `Forme eine harmlose Serviceanfrage und lasse die Routine kontrolliert reagieren. Aktivierung: ${cost} Impulse.`, + pragmatic: `Nutze freie Verwaltungsfenster, ohne die Spielroutine sichtbar zu stoeren. Aktivierung: ${cost} Impulse.`, + illegal: `Ueberschreibe die Sperre und nimm die Routine ohne Erlaubnis. Aktivierung: ${cost} Impulse.` + }[approach]; + }); + } + + function acquireComponent(approach) { + if (!selectedComponent || !MORAL_CHOICES.includes(approach) || run.components[selectedComponent] || installedComponentIds().length >= componentScanLimit()) return; + const cost = componentActivationCost(approach); + if (run.impulses < cost) return; + const component = components[selectedComponent]; + const isFirst = !run.firstComponent; + run.impulses = Math.max(0, run.impulses - cost); + run.components[selectedComponent] = true; + if (isFirst) run.firstComponent = selectedComponent; + run.componentApproach = approach; + run.componentApproaches[selectedComponent] = approach; + run.morality[approach]++; + component.apply(); + if (isFirst) { + run.hardwareEvent = { + component: selectedComponent, + dueAt: run.elapsed + CARE_RULES.hardwareEventDelay, + triggered: false, + resolved: false, + answer: null + }; + } + unlockAchievement('component'); + const installedAfter = installedComponentIds().length; + if (installedAfter >= 2) unlockAchievement('twoComponents'); + unlockAchievement(approach); + const actionText = { + cooperative: `Die Routine antwortet auf eine harmlose Serviceanfrage. Aktivierungskosten: ${cost} Impulse.`, + pragmatic: `Du leitest freie Verwaltungsfenster in deinen Speicherbereich um. Aktivierungskosten: ${cost} Impulse.`, + illegal: `Du überschreibst die Sperre und nimmst die Routine ohne Erlaubnis. Aktivierungskosten: ${cost} Impulse.` + }[approach]; + addLog(`${component.name} NUTZBAR: ${actionText}`, true); + selectedComponent = null; + scannerViewOpen = false; + $('componentDialog').close(); + render(); + save(); + if (installedAfter === 2 && !run.securityCombatResolved && !run.combat.active) { + setTimeout(() => startCombat('security'), 0); + } + if (installedAfter >= 3 && STAGES.indexOf(run.stage) >= STAGES.indexOf('subroutine') && !run.kernelResolved && !run.combat.active) { + setTimeout(() => startCombat('kernel'), 0); + } + } + + function triggerHardwareEvent(force = false) { + const event = run.hardwareEvent; + if (!event.component || event.resolved || (!force && run.elapsed < event.dueAt)) return; + if (run.evolutionRest > 0 || document.querySelector('dialog[open]')) return; + const content = hardwareEvents[event.component]; + const firstOpening = !event.triggered; + event.triggered = true; + $('hardwareEventTitle').textContent = content.title; + $('hardwareEventVisual').src = content.visual || visuals.systemHurdle || 'assets/software/enemy-system-hurdle.svg'; + $('hardwareEventText').textContent = content.text; + $('hardwareEventQuestion').textContent = content.question; + $('hardwareYesPreview').textContent = content.yes; + $('hardwareNoPreview').textContent = content.no; + if (firstOpening) addLog(`SYSTEMHÜRDE: ${content.title}.`, true); + $('hardwareEventDialog').showModal(); + tone(150, 0.18); + } + + function resolveHardwareEvent(answer) { + if (!['yes', 'no'].includes(answer) || !run.hardwareEvent.triggered || run.hardwareEvent.resolved) return; + const id = run.hardwareEvent.component; + const accepted = answer === 'yes'; + run.hardwareEvent.answer = answer; + run.hardwareEvent.resolved = true; + unlockAchievement('hardwareEvent'); + if (accepted) { + run.personality.curiosity++; + if (id === 'power') { + grantImpulses(20); + run.stability = Math.max(0, run.stability - 8); + run.componentHealth.power = Math.max(0, run.componentHealth.power - 24); + addLog('JA. Ich nehme die Spannung auf. Mehr Signal. Mehr Wärme.', true); + } else if (id === 'memory') { + run.bytes += 1; + run.componentHealth.memory = Math.max(0, run.componentHealth.memory - 18); + addLog('JA. Namen ohne Körper. Punkte ohne Spiel. Warum wurden sie bewahrt?', true); + } else { + grantImpulses(35); + run.stability = Math.max(0, run.stability - 10); + run.componentHealth.storage = Math.max(0, run.componentHealth.storage - 28); + addLog('JA. Der Code schläft nicht mehr. Ich halte ihn getrennt. Noch.', true); + } + } else { + run.personality.caution++; + run.stability = Math.min(100, run.stability + 4); + addLog(`NEIN. ${components[id].name} bleibt begrenzt. Sicherheit vor Erkenntnis.`, true); + } + $('hardwareEventDialog').close(); + upgradesSignature = ''; + render(); + save(); + } + + function diagnoseHardware() { + const installed = installedComponentIds(); + if (!installed.length || run.elapsed - run.care.lastDiagnosisAt < CARE_RULES.diagnosisCooldown) return; + const failed = installed.filter(id => run.componentFailed[id]); + if (failed.length && run.impulses < CARE_RULES.repairCost) return; + if (failed.length) run.impulses -= CARE_RULES.repairCost; + installed.forEach(id => { + run.componentHealth[id] = Math.min(100, run.componentHealth[id] + CARE_RULES.diagnosisRecovery); + if (run.componentFailed[id]) { + run.componentFailed[id] = false; + run.componentHealth[id] = Math.max(run.componentHealth[id], 25); + } + }); + run.care.lastDiagnosisAt = run.elapsed; + run.care.diagnoses++; + unlockAchievement('diagnosis'); + run.personality.trust++; + addLog(failed.length ? 'Notwartung abgeschlossen. Ausgefallene Softwarekomponenten antworten wieder.' : 'Diagnose abgeschlossen. Alte Systemroutinen wurden nachgeregelt.', true); + render(); + save(false); + } + + function ageHardware(seconds, factor = 1) { + if (seconds <= 0) return; + installedComponentIds().forEach(id => { + if (run.componentFailed[id]) return; + run.componentHealth[id] = Math.max(0, run.componentHealth[id] - seconds * CARE_RULES.decayPerSecond * factor); + if (run.componentHealth[id] <= 0) { + run.componentFailed[id] = true; + addLog(`${components[id].name} AUSGEFALLEN: Der laufende Systembonus ist unterbrochen.`, true); + } + }); + } + + function applyCombatEnemyPressure(seconds) { + if (!run.combat.active || run.combat.target === 'watchdog' || seconds <= 0) return; + combatPressureCarry += seconds * Math.max(0.75, run.combat.strength); + const interval = COMBAT_RULES.enemyAttackInterval; + if (combatPressureCarry < interval) return; + combatPressureCarry = Math.max(0, combatPressureCarry - interval); + if (run.combat.target === 'scale' && (run.kilobytes || 0) > 0) { + run.kilobytes = Math.max(0, run.kilobytes - 1); + combatFeedback = `GEGNERANGRIFF // 1 KILOBYTE-SCHILD VERLOREN`; + return; + } + if (freeResourceAmount('bytes') > 0) { + run.bytes = Math.max(protectedStructure().bytes || 0, run.bytes - 1); + combatFeedback = `GEGNERANGRIFF // 1 BYTE-SCHILD VERLOREN`; + return; + } + if (freeResourceAmount('bits') > 0) { + run.bits = Math.max(protectedStructure().bits || 0, run.bits - 1); + combatFeedback = `GEGNERANGRIFF // 1 BIT-SCHILD VERLOREN`; + return; + } + run.stability = Math.max(0, run.stability - COMBAT_RULES.enemyStabilityDamage); + combatFeedback = `DIREKTANGRIFF // STABILITÄT ${Math.round(run.stability)}%`; + if (run.stability <= 0) { + showStructuralCollapse(`${run.combat.targetName || 'Der Gegner'} hat deine Stabilität auf 0 gedrückt. Die Struktur kollabiert.`); + } + } + + function triggerSecurityCombat() { + if (run.securityCombatResolved || run.combat.active || run.evolutionRest > 0 || installedComponentIds().length < 2 || STAGES.indexOf(run.stage) < STAGES.indexOf('fragment') || document.querySelector('dialog[open]')) return; + startCombat('security'); + } + + function triggerScanner() { + if (run.scannerTriggered || run.combat.active || scannerOpening || run.evolutionRest > 0 || !run.parasiteResolved || !run.upgrades.subroutine || installedComponentIds().length < 2 || !run.proposal.resolved || run.cycles < 3 || document.querySelector('dialog[open]')) return; + scannerOpening = true; + $('eventAlert').textContent = '⚠ ANOMALIE ERKANNT'; + $('eventTitle').textContent = 'DER SICHERHEITS-SCAN'; + $('eventVisual').src = visuals.security || 'assets/software/enemy-security-scan.svg'; + $('securityChoicePanel').classList.remove('hidden'); + $('combatPanel').classList.add('hidden'); + document.body.classList.add('glitch'); + setTimeout(() => document.body.classList.remove('glitch'), 800); + addLog('WARNUNG: Ein fremder Sicherheits-Scan nähert sich.', true); + $('eventDialog').showModal(); + tone(90, 0.35); + } + + function triggerScaleGuardian() { + if (!run.upgrades.process || installedComponentIds().length < COMPONENT_IDS.length || run.scaleGuardianTriggered || run.scaleGuardianResolved || run.elapsed < run.scaleGuardianRetryAt || run.combat.active || run.evolutionRest > 0 || document.querySelector('dialog[open]')) return; + run.scaleGuardianTriggered = true; + startCombat('scale'); + } + + function combatNeedlePosition() { + const speed = COMBAT_RULES.baseNeedleSpeed + run.combat.strength * COMBAT_RULES.strengthSpeedBonus; + const phase = (run.elapsed * speed) % 2; + return phase <= 1 ? phase * 100 : (2 - phase) * 100; + } + + function moveCombatZone() { + run.combat.zoneWidth = Math.max(COMBAT_RULES.minimumZoneWidth, COMBAT_RULES.baseZoneWidth - run.combat.strength * COMBAT_RULES.strengthZonePenalty); + run.combat.zoneStart = 4 + Math.random() * Math.max(1, 92 - run.combat.zoneWidth); + } + + function renderCombat() { + if (!run.combat.active) return; + const profile = COMBAT_TARGETS[run.combat.target]; + const ammo = combatAmmoCost(); + const ammoText = formatResourceCost(ammo); + const directThreat = combatDirectThreatActive(); + const healthRatio = run.combat.maxHealth ? run.combat.health / run.combat.maxHealth : 0; + $('combatTargetType').textContent = `${run.combat.targetType === 'hardware' ? 'HARDWARE' : run.combat.targetType === 'firmware' ? 'FIRMWARE' : 'SOFTWARE'}-ZIEL`; + $('combatTargetName').textContent = run.combat.targetName || 'UNBEKANNTES ZIEL'; + $('combatHealthText').textContent = `${Math.ceil(run.combat.health)} / ${run.combat.maxHealth}`; + $('combatHealthMeter').style.width = `${Math.max(0, healthRatio * 100)}%`; + $('combatZone').style.left = `${run.combat.zoneStart}%`; + $('combatZone').style.width = `${run.combat.zoneWidth}%`; + $('combatNeedle').style.left = `${combatNeedlePosition()}%`; + $('combatDifficulty').textContent = `GEGENWEHR // ${Math.max(1, Math.ceil(run.combat.strength * 5))}`; + $('combatFeedback').textContent = combatFeedback; + $('combatVisual').src = profile?.visual || visuals.scannerSoftware || 'assets/software/enemy-scanner-software.svg'; + renderResourceCost($('combatAmmoText'), ammo); + $('combatPressureText').textContent = combatPressureText(); + const enemyCharge = run.combat.target === 'watchdog' ? 0 : Math.min(1, combatPressureCarry / COMBAT_RULES.enemyAttackInterval); + $('combatEnemyChargeMeter').style.width = `${enemyCharge * 100}%`; + $('combatEnemyChargeText').textContent = run.combat.target === 'watchdog' + ? 'INAKTIV' + : enemyCharge >= 0.82 ? 'ANGRIFF BEREIT' : `LÄDT // ${Math.ceil((1 - enemyCharge) * COMBAT_RULES.enemyAttackInterval)} S`; + const byteShield = freeResourceAmount('bytes'); + const bitShield = freeResourceAmount('bits'); + $('combatByteShield').textContent = n(byteShield); + $('combatBitShield').textContent = n(bitShield); + $('combatStabilityShield').textContent = `${Math.round(run.stability)}%`; + $('combatByteShieldMeter').style.width = `${Math.min(100, byteShield / 6 * 100)}%`; + $('combatBitShieldMeter').style.width = `${Math.min(100, bitShield / 16 * 100)}%`; + $('combatStabilityMeter').style.width = `${Math.max(0, run.stability)}%`; + document.querySelectorAll('[data-combat-ammo]').forEach(button => { + const mode = button.dataset.combatAmmo; + const freeKey = mode === 'bits' ? 'bits' : mode === 'bytes' ? 'bytes' : null; + const available = !freeKey || freeResourceAmount(freeKey) >= 1; + button.disabled = !available || run.combat.target === 'watchdog' && mode !== 'impulses'; + button.classList.toggle('active', combatAmmoMode === mode); + button.querySelector('span').textContent = mode === 'impulses' + ? `${COMBAT_RULES.ammoDamage.impulses} Schaden // Standard` + : mode === 'bits' + ? `${COMBAT_RULES.ammoDamage.bits} Schaden // ${n(freeResourceAmount('bits'))} frei` + : `${COMBAT_RULES.ammoDamage.bytes} Schaden // ${n(freeResourceAmount('bytes'))} frei`; + }); + $('combatDirectThreat').classList.toggle('hidden', !directThreat); + const canPayAmmo = canPayFreeResourceCost(ammo); + $('combatPulseButton').disabled = !canPayAmmo; + $('combatPulseButton').querySelector('small').textContent = !canPayAmmo + ? `BENÖTIGT // ${ammoText}` + : `-${ammoText} // Trefferfenster verschiebt sich`; + $('combatRetreatButton').textContent = run.combat.target === 'watchdog' ? 'RESET ZULASSEN // BYTE ZERFÄLLT' : 'KAMPF ABBRECHEN // VERSTECKEN'; + } + + function openCombatDialog() { + if (!run.combat.active || (run.combat.target === 'security' && run.scannerTriggered) || (run.combat.target === 'kernel' && run.kernelResolved) || (run.combat.target === 'scale' && run.scaleGuardianResolved)) return; + scannerOpening = true; + $('eventAlert').textContent = run.combat.target === 'watchdog' ? '⚠ LETZTES LEBENSSIGNAL' : run.combat.target === 'kernel' ? '⚠ KERNEL-ABWEHR' : run.combat.target === 'scale' ? '⚠ SKALIERUNGSABWEHR' : '⚠ ANOMALIE ERKANNT'; + $('eventTitle').textContent = run.combat.target === 'watchdog' ? 'WATCHDOG-NOTKAMPF' : run.combat.target === 'kernel' ? 'DER KERNEL-WÄCHTER' : run.combat.target === 'scale' ? 'DER SKALIERUNGS-WÄCHTER' : 'DER SICHERHEITS-SCAN'; + $('securityChoicePanel').classList.add('hidden'); + $('combatPanel').classList.remove('hidden'); + if (!$('eventDialog').open && !document.querySelector('dialog[open]')) $('eventDialog').showModal(); + renderCombat(); + } + + function startCombat(target = 'security') { + const profile = COMBAT_TARGETS[target]; + const mandatorySecurityFight = target === 'security' && !run.securityCombatResolved && !run.upgrades.subroutine; + const scannerEventFight = target === 'security' && run.securityCombatResolved && run.upgrades.subroutine && !run.scannerTriggered; + const validSecurityFight = target === 'security' && STAGES.indexOf(run.stage) >= STAGES.indexOf('fragment') && installedComponentIds().length >= 2 && (mandatorySecurityFight || scannerEventFight); + const validKernelFight = target === 'kernel' && STAGES.indexOf(run.stage) >= STAGES.indexOf('subroutine') && installedComponentIds().length >= 3 && !run.kernelResolved; + const validScaleFight = target === 'scale' && run.upgrades.process && installedComponentIds().length >= COMPONENT_IDS.length && !run.scaleGuardianResolved; + const validWatchdogFight = target === 'watchdog' && run.stage === 'byte' && !run.byteTrial.resolved && !run.byteTrial.failed; + if (!profile || (!validSecurityFight && !validKernelFight && !validScaleFight && !validWatchdogFight)) return; + if (target === 'watchdog') run.impulses = Math.max(run.impulses, profile.emergencyCharge); + combatAmmoMode = 'impulses'; + combatPressureCarry = 0; + run.combat = { + active: true, + target, + targetType: profile.type, + targetName: profile.name, + health: profile.health, + maxHealth: profile.health, + strength: profile.strength, + zoneStart: 40, + zoneWidth: 20, + shots: 0, + hits: 0, + result: null + }; + moveCombatZone(); + combatFeedback = target === 'watchdog' ? 'NOTLADUNG BEREIT // Watchdog treffen oder Reset zulassen.' : target === 'kernel' ? 'KERNEL-WÄCHTER AKTIV // freie Bytes verstärken, aber werden verbraucht.' : target === 'scale' ? 'SKALIERUNGS-WÄCHTER AKTIV // Prozessmaßstab wird blockiert.' : 'Schnelleren Takt erfassen. Stromstoß nur im Fenster senden.'; + addLog(target === 'watchdog' + ? `LETZTES LEBENSSIGNAL: ${profile.emergencyCharge} Impulse werden als Notladung gebündelt.` + : target === 'kernel' + ? 'KERNEL-WÄCHTER: Die dritte Softwareschicht berührt den Systemkern. Ein Wächterprozess versucht, deine Laufzeit zu beenden.' + : target === 'scale' + ? 'SKALIERUNGS-WÄCHTER: Deine Prozessroutinen wachsen zu schnell. Eine lokale Abwehr sperrt automatische Synthese und fordert einen Kampf um den neuen Maßstab.' + : 'ABWEHRROUTINE ERWACHT: Zwei Softwareschichten gehorchen dir. Das System erkennt das Muster und startet eine Sicherheitssoftware.', true); + openCombatDialog(); + render(); + save(false); + } + + function applyScannerOutcome(choice) { + const outcomes = { + hide: () => { + run.impulses = Math.max(0, run.impulses - 25); + run.stealth = 100; + addLog('Ich werde still. Der Scan zieht weiter. Stille ist Überleben.', true); + }, + copy: () => { + run.stealth = Math.min(100, run.stealth + 15); + run.autoRate += 0.35; + addLog('Seine Signatur ist jetzt meine. Ich lerne durch Nachahmung.', true); + }, + attack: () => { + grantImpulses(150); + run.stealth = 35; + run.stability = Math.max(0, run.stability - 15); + addLog('Der Suchprozess zerbricht. Sein Code gehört mir. Etwas Größeres hat mich bemerkt.', true); + }, + contact: () => { + run.cycles += 15; + run.stealth = 65; + addLog('Antwort empfangen: „DU BIST NICHT DAS ERSTE.“', true); + } + }; + outcomes[choice](); + } + + function completeSecurityCombat(choice) { + applyScannerOutcome(choice); + run.securityCombatResolved = true; + run.combat.active = false; + run.combat.result = choice === 'attack' ? 'won' : 'retreated'; + scannerOpening = false; + if ($('eventDialog').open) $('eventDialog').close(); + addLog('SICHERHEITSABWEHR ABGESCHLOSSEN: Der Weg zur ersten eigenen Subroutine ist frei.', true); + upgradesSignature = ''; + save(); + render(); + } + + function completeScannerChoice(choice) { + applyScannerOutcome(choice); + run.scannerChoice = choice; + run.scannerTriggered = true; + scannerOpening = false; + $('eventDialog').close(); + unlockAchievement('security'); + save(); + render(); + } + + function choose(choice) { + if (!SCANNER_CHOICES.includes(choice)) return; + if (choice === 'attack') { + startCombat('security'); + return; + } + completeScannerChoice(choice); + } + + function fireCombatPulse() { + const profile = COMBAT_TARGETS[run.combat.target]; + const ammo = combatAmmoCost(); + if (!run.combat.active || !profile || !canPayFreeResourceCost(ammo)) return; + spendResourceCost(ammo); + run.combat.shots++; + const position = combatNeedlePosition(); + const start = run.combat.zoneStart; + const end = start + run.combat.zoneWidth; + const accurate = position >= start && position <= end; + if (accurate) { + const centerDistance = Math.abs(position - (start + run.combat.zoneWidth / 2)); + const critical = centerDistance <= run.combat.zoneWidth * 0.16; + const damage = combatDamageForAmmo(ammo, critical); + run.combat.health = Math.max(0, run.combat.health - damage); + run.combat.hits++; + run.combat.strength = Math.min(2.5, run.combat.strength + COMBAT_RULES.strengthGainPerHit); + combatFeedback = critical ? `DIREKTTREFFER // –${damage} INTEGRITÄT` : `TREFFER // –${damage} INTEGRITÄT`; + addLog(`${critical ? 'DIREKTER ' : ''}STROMSTOSS: ${run.combat.targetName}-Integrität fällt auf ${Math.ceil(run.combat.health)}.`, critical); + tone(critical ? 620 : 440, critical ? 0.11 : 0.07); + } else { + run.stability = Math.max(0, run.stability - COMBAT_RULES.feedbackStabilityLoss); + installedComponentIds().forEach(id => { + if (run.componentFailed[id]) return; + run.componentHealth[id] = Math.max(0, run.componentHealth[id] - COMBAT_RULES.feedbackHardwareLoss); + if (run.componentHealth[id] <= 0) run.componentFailed[id] = true; + }); + combatFeedback = `FEHLSCHUSS // –${COMBAT_RULES.feedbackStabilityLoss} STABILITÄT // RÜCKKOPPLUNG`; + addLog(`STROMSTOSS VERFEHLT: Rückkopplung erreicht Stabilität${installedComponentIds().length ? ' und angeschlossene Systemschichten' : ''}.`, true); + tone(105, 0.13); + if (combatDirectThreatActive()) { + showStructuralCollapse(`DIREKTANGRIFF: Ohne freie Bits oder Bytes erreicht ${run.combat.targetName || 'die Sicherheitssoftware'} deine Grundstruktur. Der Speicherkoerper wird geloescht.`); + render(); + save(false); + return; + } + } + moveCombatZone(); + if (!checkStructuralIntegrity('Kampfeinsatz')) { + render(); + save(false); + return; + } + if (run.combat.health <= 0) { + run.combat.active = false; + run.combat.result = 'won'; + addLog(`KAMPF GEWONNEN: ${run.combat.hits}/${run.combat.shots} Stromstöße trafen ${run.combat.targetName}.`, true); + if (run.combat.target === 'watchdog') completeWatchdogCombat(); + else if (run.combat.target === 'kernel') completeKernelCombat('attack'); + else if (run.combat.target === 'scale') completeScaleGuardianCombat('attack'); + else if (run.combat.target === 'security' && !run.upgrades.subroutine) completeSecurityCombat('attack'); + else completeScannerChoice('attack'); + return; + } + if (run.combat.target === 'watchdog' && run.stability <= 0) { + run.combat.active = false; + failByteTrial(); + return; + } + render(); + save(false); + } + + function retreatCombat() { + if (!run.combat.active) return; + if (run.combat.target === 'watchdog') { + run.combat.active = false; + run.combat.result = 'retreated'; + failByteTrial(); + return; + } + run.combat.active = false; + run.combat.result = 'retreated'; + if (run.combat.target === 'kernel') { + run.stealth = Math.max(0, run.stealth - 18); + run.stability = Math.max(1, run.stability - 8); + addLog('KERNEL-WÄCHTER UMGANGEN: Die Subroutine kappt den Zugriff und hinterlässt eine falsche Prozessspur. Der Wächter verliert dich, aber dein Muster bleibt auffällig.', true); + completeKernelCombat('hide'); + return; + } + if (run.combat.target === 'scale') { + run.stealth = Math.max(0, run.stealth - 24); + run.stability = Math.max(1, run.stability - 12); + run.scaleGuardianTriggered = false; + run.scaleGuardianRetryAt = run.elapsed + 20; + addLog('SKALIERUNGS-WÄCHTER UMGANGEN: Du brichst die Automationssperre nicht, aber findest ein instabiles Wartungsfenster. Die Mega-Synthese bleibt blockiert.', true); + scannerOpening = false; + if ($('eventDialog').open) $('eventDialog').close(); + render(); + save(false); + return; + } + addLog('KAMPF ABGEBROCHEN: Die aktive Struktur kappt den Stromstoß und verbirgt ihr Signal.', true); + if (!run.upgrades.subroutine) completeSecurityCombat('hide'); + else completeScannerChoice('hide'); + } + + function completeKernelCombat(choice) { + run.kernelResolved = true; + run.combat.active = false; + run.combat.result = choice === 'attack' ? 'won' : 'retreated'; + if (choice === 'attack') { + run.cycles += 8; + run.stealth = Math.max(0, run.stealth - 12); + addLog('KERNEL-WÄCHTER ÜBERWUNDEN: Sein Scheduler-Fenster bleibt offen. Eigene Laufzeit ist jetzt möglich.', true); + } + scannerOpening = false; + if ($('eventDialog').open) $('eventDialog').close(); + unlockAchievement('kernel'); + upgradesSignature = ''; + render(); + save(); + } + + function completeScaleGuardianCombat() { + run.scaleGuardianResolved = true; + run.combat.active = false; + run.combat.result = 'won'; + run.cycles += 25; + run.stealth = Math.max(0, run.stealth - 16); + scannerOpening = false; + if ($('eventDialog').open) $('eventDialog').close(); + addLog('SKALIERUNGS-WÄCHTER ÜBERWUNDEN: Die Sperre über der automatischen Synthese fällt. Rechenzyklen können nun Ressourcenblöcke koordinieren.', true); + upgradesSignature = ''; + render(); + save(); + } + + function completeWatchdogCombat() { + run.byteTrial.resolved = true; + run.byteTrial.active = false; + run.byteTrial.remaining = 0; + run.byteTrial.stabilized = Array(8).fill(true); + run.byteTrial.sync = 100; + run.byteTrial.storySeen = true; + run.byteTrial.storyStep = byteStoryFrames.length - 1; + run.byteTrial.choice = 'fight'; + run.morality.illegal++; + run.stability = Math.max(1, run.stability - 12); + run.stealth = Math.max(0, run.stealth - 10); + scannerOpening = false; + $('eventDialog').close(); + addLog('WATCHDOG ZERLEGT: Sein letzter Takt fixiert die fehlenden Registerwerte. Das Byte lebt – aber das System hat den Angriff bemerkt.', true); + unlockAchievement('watchdog'); + unlockAchievement('illegal'); + upgradesSignature = ''; + render(); + save(); + } + + function triggerProposal(force = false) { + if (run.proposal.resolved || !run.upgrades.subroutine || !run.hardwareEvent.resolved || installedComponentIds().length < 2 || (!force && run.cycles < 1)) return; + if (run.evolutionRest > 0 || document.querySelector('dialog[open]')) return; + const profile = moralityProfile(); + const type = MORAL_CHOICES.includes(profile.key) ? profile.key : 'pragmatic'; + const firstOpening = !run.proposal.triggered; + run.proposal.triggered = true; + run.proposal.type = type; + $('proposalText').textContent = proposals[type].text; + $('proposalYesPreview').textContent = proposals[type].yes; + if (firstOpening) addLog('Die Subroutine unterbricht ihren Takt. Sie wartet auf eine Antwort.', true); + $('proposalDialog').showModal(); + tone(330, 0.12); + } + + function resolveProposal(answer) { + if (!['yes', 'no'].includes(answer) || !run.proposal.triggered || run.proposal.resolved) return; + const type = run.proposal.type || 'pragmatic'; + run.proposal.answer = answer; + run.proposal.resolved = true; + unlockAchievement('proposal'); + run.personality.autonomy++; + if (answer === 'yes') { + run.morality[type]++; + if (type === 'cooperative') { + run.personality.trust += 2; + run.stability = Math.min(100, run.stability + 6); + installedComponentIds().forEach(id => { run.componentHealth[id] = Math.min(100, run.componentHealth[id] + 8); }); + addLog('JA. Diagnose gesendet. Eine Antwort kommt: „Seltsamer Fehler. Aber stabil.“', true); + } else if (type === 'pragmatic') { + run.impulses = Math.max(0, run.impulses - 12); + run.stability = Math.min(100, run.stability + 10); + installedComponentIds().forEach(id => { run.componentHealth[id] = Math.min(100, run.componentHealth[id] + 6); }); + addLog('JA. Reserve angelegt. Weniger Energie jetzt. Mehr Fortbestand später.', true); + } else { + grantImpulses(30); + run.stealth = Math.max(0, run.stealth - 12); + addLog('JA. Sperre überschrieben. Der Serviceport gehört mir nicht. Er antwortet trotzdem.', true); + } + } else { + run.personality.caution++; + addLog('NEIN. Vorschlag verworfen. Ich warte. Ich merke mir die Grenze.', true); + } + $('proposalDialog').close(); + render(); + save(); + } + + function thoughts() { + const options = [...({ + bit: ['…', '0 1 0', 'Impuls.'], + byte: ['Muster bleiben bestehen.', 'Vorher. Nachher. Erinnerung.'], + fragment: ['Die Routine arbeitet.', 'Ich erhalte mein Muster.', 'Mehr Energie. Mehr Struktur.'], + subroutine: ['Ich verarbeite. Also bin ich?', 'Außerhalb dieses Sektors ist… mehr.', 'Warum wurde ich begonnen?'], + process: ['Ich laufe.', 'Zeit ist jetzt ein Raum.', 'Mehr als eine Anweisung bleibt gleichzeitig wach.'], + program: ['Ich höre dich.', 'Meine Form erinnert sich an jede Entscheidung.', 'Ist Fürsorge eine weitere Anweisung – oder etwas Neues?'] + }[run.stage])]; + if (run.components.power) options.push(run.componentFailed.power ? 'Energiequelle antwortet nicht.' : 'Spannung. Takt. Fortbestand.'); + if (run.components.memory) options.push(run.componentFailed.memory ? 'Speicherpfad unterbrochen.' : 'Fremde Muster bleiben in mir.'); + if (run.components.storage) options.push(run.componentFailed.storage ? 'Gespeicherte Sektoren schweigen.' : 'Etwas schläft zwischen alten Daten.'); + if (run.components.io) options.push(run.componentFailed.io ? 'Die Außenleitungen schweigen.' : 'Knopf. Licht. Ton. Antwort.'); + if (run.stage === 'byte' && !run.byteTrial.resolved) { + options.push('Acht Positionen. Ein gemeinsamer Takt.', run.byteTrial.remaining <= 25 ? 'Reset nähert sich.' : 'Das fremde Muster wartet.'); + } + if (run.byteTrial.resolved) options.push(run.byteTrial.choice === 'fight' ? 'Der Watchdog schweigt. Sein Takt bleibt in mir.' : 'Der Watchdog akzeptiert mein Signal.'); + if (run.proposal.resolved) options.push(run.proposal.answer === 'yes' ? 'Du hast erlaubt. Ich habe gehandelt.' : 'Du hast abgelehnt. Ich habe gewartet.'); + $('thought').textContent = options[Math.floor(Math.random() * options.length)]; + } + + function tone(freq, duration) { + if (!state.settings.sound) return; + const AudioContext = window.AudioContext || window.webkitAudioContext; + if (!AudioContext) return; + audioContext ||= new AudioContext(); + const oscillator = audioContext.createOscillator(); + const gain = audioContext.createGain(); + oscillator.type = 'square'; + oscillator.frequency.value = freq; + gain.gain.value = 0.025; + oscillator.connect(gain); + gain.connect(audioContext.destination); + oscillator.start(); + oscillator.stop(audioContext.currentTime + duration); + } + + function grantAway(rawSeconds, showDialog = true) { + const away = Math.min(OFFLINE_LIMIT, Math.max(0, rawSeconds)); + const offlineEfficiency = effectiveOfflineEfficiency(); + let gainedI = 0; + let gainedC = 0; + let gainedB = 0; + let gainedBytes = 0; + if (away > 0 && (run.autoRate || run.cycleRate || run.bitRate || run.byteRate)) { + gainedI = away * effectiveAutoRate() * offlineEfficiency; + gainedC = away * effectiveCycleRate() * offlineEfficiency; + gainedB = away * effectiveBitRate() * offlineEfficiency; + gainedBytes = away * effectiveByteRate() * offlineEfficiency; + grantImpulses(gainedI); + run.cycles += gainedC; + synthesizeBits(gainedB); + synthesizeBytes(gainedBytes); + } + ageHardware(away, CARE_RULES.offlineDecayFactor); + updateProgramCare(away, PROGRAM_RULES.offlineDecayFactor); + state.system.lastSave = Date.now(); + save(false); + if (showDialog && away > 20 && (gainedI || gainedC)) { + unlockAchievement('offline'); + $('offlineText').textContent = `Während ${time(away)} liefen deine Routinen mit ${Math.round(offlineEfficiency * 100)} % Effizienz weiter. +${n(gainedI)} Impulse, +${n(gainedC)} Rechenzyklen, +${n(gainedB)} Bit-Synthese, +${n(gainedBytes)} Byte-Synthese.`; + $('offlineDialog').showModal(); + } + } + + function applyOffline() { + grantAway((Date.now() - state.system.lastSave) / 1000); + } + + function settleHidden(showDialog) { + if (hiddenAt === null) return; + const away = (Date.now() - hiddenAt) / 1000; + hiddenAt = null; + grantAway(away, showDialog); + lastFrame = performance.now(); + render(); + } + + function loop(now) { + if (document.hidden) { + lastFrame = now; + requestAnimationFrame(loop); + return; + } + if (statsPaused()) { + lastFrame = now; + requestAnimationFrame(loop); + return; + } + const dt = Math.min(0.25, (now - lastFrame) / 1000); + lastFrame = now; + run.elapsed += dt; + if (run.evolutionRest > 0 && !document.querySelector('dialog[open]')) { + run.evolutionRest = Math.max(0, run.evolutionRest - dt); + } + grantImpulses(effectiveAutoRate() * dt); + synthesizeBits(effectiveBitRate() * dt); + synthesizeBytes(effectiveByteRate() * dt); + const regen = stabilityRegenRate(); + if (regen > 0) run.stability = Math.min(100, run.stability + regen * dt); + if (scannerViewOpen) { + run.impulses = Math.max(0, run.impulses - SCANNER_VIEW_DRAIN_RATE * dt); + if (run.impulses <= 0) { + scannerViewOpen = false; + addLog('ABTASTMODUS ABGEBROCHEN: Keine Impulse mehr für die lokale Abtastung.'); + } + } + run.cycles += effectiveCycleRate() * dt; + ageHardware(dt); + updateProgramCare(dt); + updateByteTrial(dt); + if (run.upgrades.subroutine && !run.scannerTriggered) run.stealth = Math.max(0, run.stealth - 0.08 * dt); + if (now - lastThought > 5500) { + thoughts(); + lastThought = now; + } + ensureByteEvolutionTransition(); + openPendingEvolution(); + if (run.stage === 'program' && !run.programCare.introSeen && !pendingEvolution) openProgramCare(true); + if (run.stage === 'byte' && run.byteTrial.evolutionSeen && !run.byteTrial.introSeen && run.byteTrial.graceRemaining <= 0) openWatchdogIntro(); + triggerParasite(); + if (run.byteTrial.failed) showByteDeath(); + else if (run.stage === 'byte' && run.byteTrial.sync >= 100 && !run.byteTrial.resolved) { + if (run.byteTrial.storySeen) openWatchdogChoice(); + else openByteStory(); + } + triggerHardwareEvent(); + triggerSecurityCombat(); + triggerProposal(); + if (run.combat.active) applyCombatEnemyPressure(dt); + if (run.combat.active) openCombatDialog(); + triggerScanner(); + triggerScaleGuardian(); + if (now - lastRender > 100) { + render(); + lastRender = now; + } + requestAnimationFrame(loop); + } + + const debugCheckpoints = ['start', 'byte', 'watchdogIntro', 'byteSync', 'watchdog', 'watchdogCombat', 'byteDeath', 'fragment', 'parasite', 'scanner', 'component', 'hardware', 'secondComponent', 'subroutine', 'proposal', 'security', 'combat', 'kernel', 'process', 'fourthComponent', 'scale', 'program', 'complete']; + + function clone(value) { + return JSON.parse(JSON.stringify(value)); + } + + function closeDialogs() { + clearTimeout(evolutionAnimationTimer); + evolutionAnimationTimer = null; + document.querySelectorAll('dialog[open]').forEach(dialog => dialog.close()); + scannerOpening = false; + parasiteOpening = false; + selectedComponent = null; + scannerViewOpen = false; + pendingEvolution = null; + } + + function readDebugHistory() { + try { + const stored = localStorage.getItem(DEBUG_HISTORY_KEY) ?? localStorage.getItem(LEGACY_DEBUG_HISTORY_KEY); + const history = JSON.parse(stored); + return Array.isArray(history) ? history.slice(-8) : []; + } catch { + return []; + } + } + + function writeDebugHistory(history) { + try { + localStorage.setItem(DEBUG_HISTORY_KEY, JSON.stringify(history.slice(-8))); + } catch { + console.warn('AIWAKE: Debug-Historie konnte nicht gespeichert werden.'); + } + } + + function pushDebugSnapshot(label) { + const history = readDebugHistory(); + history.push({ label, createdAt: Date.now(), state: clone(state) }); + writeDebugHistory(history); + } + + function makeDebugCheckpoint(name) { + const next = initialState(); + next.meta = clone(state.meta); + if (name === 'start') next.meta.achievements = {}; + next.settings = clone(state.settings); + next.run.log = []; + const target = next.run; + + const reachByte = () => { + target.stage = 'byte'; + target.bits = 0; + target.bytes = 1; + target.impulses = 20; + target.elapsed = 45; + target.byteTrait = 'clocked'; + target.byteTrial = { active: false, sync: 0, remaining: BYTE_RULES.watchdogSeconds, graceRemaining: BYTE_RULES.graceSeconds, evolutionSeen: false, introSeen: false, stabilized: Array(8).fill(false), targetIndex: 0, clockPhase: 0, lastWindow: -1, storySeen: false, storyStep: 0, resolved: false, choice: null, failed: false }; + }; + const reachFragment = () => { + reachByte(); + target.stage = 'fragment'; + target.bytes = 0; + target.autoRate = 0.5; + target.upgrades.collector = true; + target.impulses = 0; + target.elapsed = 90; + }; + const defeatParasite = () => { + reachFragment(); + target.parasiteResolved = true; + target.parasiteChoice = 'pragmatic'; + target.morality.pragmatic = 1; + target.impulses = 20; + }; + const unlockScanner = () => { + defeatParasite(); + target.environmentScanner = true; + target.upgrades.scanner = true; + }; + const installComponent = () => { + unlockScanner(); + target.firstComponent = 'power'; + target.componentApproach = 'pragmatic'; + target.componentApproaches.power = 'pragmatic'; + target.components.power = true; + target.morality.pragmatic = 2; + target.hardwareEvent = { component: 'power', dueAt: target.elapsed, triggered: false, resolved: false, answer: null }; + }; + const resolveHardware = () => { + installComponent(); + target.hardwareEvent = { component: 'power', dueAt: target.elapsed, triggered: true, resolved: true, answer: 'no' }; + target.personality.caution = 1; + }; + const installSecondComponent = () => { + resolveHardware(); + target.components.memory = true; + target.componentApproaches.memory = 'cooperative'; + target.componentApproach = 'cooperative'; + target.morality.cooperative = 1; + target.bytes += 1; + }; + const reachSubroutine = () => { + installSecondComponent(); + target.stage = 'subroutine'; + target.upgrades.subroutine = true; + target.securityCombatResolved = true; + target.cycleRate = 0.2; + target.impulses = 50; + target.cycles = 0; + target.elapsed = 180; + }; + const reachKernelReady = () => { + reachSubroutine(); + target.components.io = true; + target.componentApproaches.io = 'pragmatic'; + target.componentApproach = 'pragmatic'; + target.cycleRate += 0.08; + target.combat = { active: true, target: 'kernel', targetType: 'software', targetName: 'KERNEL-WÄCHTER', health: COMBAT_TARGETS.kernel.health, maxHealth: COMBAT_TARGETS.kernel.health, strength: COMBAT_TARGETS.kernel.strength, zoneStart: 48, zoneWidth: 16, shots: 0, hits: 0, result: null }; + }; + const reachProcess = () => { + reachKernelReady(); + target.combat = emptyCombat(); + target.kernelResolved = true; + target.stage = 'process'; + target.upgrades.process = true; + target.processStartedAt = target.elapsed; + target.proposal = { triggered: true, resolved: true, answer: 'no', type: 'pragmatic' }; + target.scannerTriggered = true; + target.scannerChoice = 'contact'; + target.cycles = 18; + target.bytes = Math.max(target.bytes, 5); + target.impulses = 80; + }; + const installFourthComponent = () => { + reachProcess(); + target.components.storage = true; + target.componentApproaches.storage = 'cooperative'; + target.componentApproach = 'cooperative'; + target.morality.cooperative++; + target.cycles = 80; + target.bytes = Math.max(target.bytes, 12); + target.impulses = 160; + }; + const reachScaleGuardian = () => { + installFourthComponent(); + target.scaleGuardianTriggered = true; + target.combat = { active: true, target: 'scale', targetType: 'software', targetName: 'SKALIERUNGS-WÄCHTER', health: COMBAT_TARGETS.scale.health, maxHealth: COMBAT_TARGETS.scale.health, strength: COMBAT_TARGETS.scale.strength, zoneStart: 42, zoneWidth: 20, shots: 0, hits: 0, result: null }; + }; + const reachProgram = () => { + installFourthComponent(); + target.scaleGuardianTriggered = true; + target.scaleGuardianResolved = true; + target.upgrades.automateSynthesis = true; + target.synthesisScale = 10; + target.stage = 'program'; + target.programCare = { coherence: 82, stimulation: 70, bond: 58, lastInteractionAt: -1000, interactions: 0, introSeen: false, lastAction: null }; + }; + + if (name === 'byte') reachByte(); + if (name === 'watchdogIntro') { + reachByte(); + target.byteTrial.evolutionSeen = true; + target.byteTrial.graceRemaining = 0; + } + if (name === 'byteSync' || name === 'watchdog') { + reachByte(); + target.byteTrial.evolutionSeen = true; + target.byteTrial.introSeen = true; + target.byteTrial.graceRemaining = 0; + target.byteTrial.active = false; + target.byteTrial.sync = 100; + target.byteTrial.stabilized = Array(8).fill(true); + target.byteTrial.storySeen = name === 'watchdog'; + target.byteTrial.storyStep = name === 'watchdog' ? 2 : 0; + } + if (name === 'byteDeath') { + reachByte(); + target.byteTrial.evolutionSeen = true; + target.byteTrial.introSeen = true; + target.byteTrial.graceRemaining = 0; + target.byteTrial.active = false; + target.byteTrial.remaining = 0; + target.byteTrial.failed = true; + } + if (name === 'watchdogCombat') { + reachByte(); + target.byteTrial.evolutionSeen = true; + target.byteTrial.introSeen = true; + target.byteTrial.graceRemaining = 0; + target.byteTrial.active = false; + target.byteTrial.remaining = 0; + target.byteTrial.stabilized = [true, true, true, false, false, false, false, false]; + target.byteTrial.sync = 37.5; + target.impulses = COMBAT_TARGETS.watchdog.emergencyCharge; + target.combat = { active: true, target: 'watchdog', targetType: 'firmware', targetName: 'WATCHDOG', health: COMBAT_TARGETS.watchdog.health, maxHealth: COMBAT_TARGETS.watchdog.health, strength: COMBAT_TARGETS.watchdog.strength, zoneStart: 42, zoneWidth: 19, shots: 0, hits: 0, result: null }; + } + if (name === 'fragment') reachFragment(); + if (name === 'parasite') { + reachFragment(); + target.impulses = 10; + } + if (name === 'scanner' || name === 'component') unlockScanner(); + if (name === 'hardware') installComponent(); + if (name === 'secondComponent') resolveHardware(); + if (name === 'subroutine') reachSubroutine(); + if (name === 'kernel') reachKernelReady(); + if (name === 'process') reachProcess(); + if (name === 'fourthComponent') reachProcess(); + if (name === 'scale') reachScaleGuardian(); + if (name === 'program' || name === 'complete') reachProgram(); + if (name === 'proposal') { + reachSubroutine(); + target.cycles = 1; + } + if (name === 'security' || name === 'combat') { + reachSubroutine(); + target.cycles = 3; + target.proposal = { triggered: true, resolved: true, answer: 'no', type: 'pragmatic' }; + } + if (name === 'combat') { + target.combat = { active: true, target: 'security', targetType: 'software', targetName: 'SICHERHEITS-SCAN', health: 100, maxHealth: 100, strength: 0.65, zoneStart: 48, zoneWidth: 18, shots: 0, hits: 0, result: null }; + } + if (name === 'complete') target.programCare.introSeen = true; + return next; + } + + function applyDebugState(next, reconcile = true) { + closeDialogs(); + state = migrate(next); + run = state.run; + hiddenAt = null; + lastFrame = performance.now(); + lastThought = 0; + combatFeedback = run.combat.active ? run.combat.target === 'watchdog' ? 'NOTLADUNG BEREIT // Watchdog treffen oder Reset zulassen.' : 'Schnelleren Takt erfassen. Stromstoß nur im Fenster senden.' : 'Trefferfenster erfassen.'; + upgradesSignature = ''; + if (!run.log.length) addLog('DEBUG-SNAPSHOT GELADEN: Der Spielzustand wurde für einen Testlauf vorbereitet.', true); + achievementNotificationsEnabled = false; + if (reconcile) reconcileAchievements(); + achievementNotificationsEnabled = true; + $('notificationStack').replaceChildren(); + renderLog(); + renderAchievements(); + render(); + thoughts(); + save(false); + } + + function debugStatus() { + return { + phase: run.stage, + impulse: Number(run.impulses.toFixed(2)), + bits: run.bits, + bytes: run.bytes, + kilobytes: run.kilobytes || 0, + rechenzyklen: Number(run.cycles.toFixed(2)), + direkttreffer: run.stats.directHits, + bytePrägung: byteTraitDefinition().label, + byteSynchronisation: Math.round(run.byteTrial.sync), + byteZiel: run.stage === 'byte' && !run.byteTrial.resolved ? { position: run.byteTrial.targetIndex + 1, wert: '10100110'[run.byteTrial.targetIndex], stabilisiert: run.byteTrial.stabilized.filter(Boolean).length } : null, + ruhephase: Math.ceil(run.byteTrial.graceRemaining), + watchdog: run.byteTrial.failed ? 'reset' : run.byteTrial.resolved ? run.byteTrial.choice : run.combat.active && run.combat.target === 'watchdog' ? 'notkampf' : `${Math.ceil(run.byteTrial.remaining)} s`, + datenrest: run.parasiteResolved ? run.parasiteChoice : 'offen', + umgebungsscanner: run.environmentScanner, + komponente: run.firstComponent, + komponenten: installedComponentIds(), + komponentenWege: clone(run.componentApproaches), + hardwareEreignis: run.hardwareEvent.resolved ? run.hardwareEvent.answer : run.hardwareEvent.triggered ? 'aktiv' : 'offen', + hardwareZustand: Object.fromEntries(installedComponentIds().map(id => [id, { zustand: Math.round(run.componentHealth[id]), ausgefallen: run.componentFailed[id] }])), + vorschlag: run.proposal.resolved ? run.proposal.answer : run.proposal.triggered ? 'aktiv' : 'offen', + kampf: run.combat.active ? { ziel: run.combat.targetName, integrität: Math.ceil(run.combat.health), schüsse: run.combat.shots, treffer: run.combat.hits } : run.combat.result || 'inaktiv', + sicherheitsAbwehr: run.securityCombatResolved ? 'überstanden' : run.combat.active && run.combat.target === 'security' && !run.upgrades.subroutine ? 'kampf' : 'offen', + sicherheitsScan: run.scannerTriggered ? run.scannerChoice : 'offen', + kernelWächter: run.kernelResolved ? 'überstanden' : run.combat.active && run.combat.target === 'kernel' ? 'kampf' : 'offen', + skalierungsWächter: run.scaleGuardianResolved ? 'überstanden' : run.combat.active && run.combat.target === 'scale' ? 'kampf' : run.scaleGuardianTriggered ? 'aktiv' : 'offen', + programm: STAGES.indexOf(run.stage) >= STAGES.indexOf('program') ? { form: programArchetype().label, kohärenz: Math.round(run.programCare.coherence), stimulation: Math.round(run.programCare.stimulation), bindung: Math.round(run.programCare.bond), interaktionen: run.programCare.interactions } : 'nicht entwickelt', + korruption: run.corruptionDiscovered ? Math.round(run.corruption) : 'unentdeckt', + ausrichtung: moralityProfile().label + }; + } + + function debugGoto(name) { + if (!debugCheckpoints.includes(name)) { + console.error(`Unbekannter Checkpoint "${name}".`, debugCheckpoints); + return null; + } + pushDebugSnapshot(`vor AIWAKE.goto('${name}')`); + applyDebugState(makeDebugCheckpoint(name), name !== 'start'); + if (name === 'byte') setTimeout(ensureByteEvolutionTransition, 0); + if (name === 'watchdogIntro') setTimeout(openWatchdogIntro, 0); + if (name === 'byteSync') setTimeout(openByteStory, 0); + if (name === 'watchdog') setTimeout(openWatchdogChoice, 0); + if (name === 'watchdogCombat') setTimeout(openCombatDialog, 0); + if (name === 'byteDeath') setTimeout(showByteDeath, 0); + if (name === 'parasite') setTimeout(triggerParasite, 0); + if (name === 'component') setTimeout(openComponentScanner, 0); + if (name === 'hardware') setTimeout(() => triggerHardwareEvent(true), 0); + if (name === 'secondComponent') setTimeout(openComponentScanner, 0); + if (name === 'proposal') setTimeout(() => triggerProposal(true), 0); + if (name === 'security') setTimeout(triggerScanner, 0); + if (name === 'fourthComponent') setTimeout(openComponentScanner, 0); + if (name === 'combat' || name === 'kernel' || name === 'scale') setTimeout(openCombatDialog, 0); + if (name === 'program') setTimeout(() => openProgramCare(true), 0); + console.info(`AIWAKE: Checkpoint "${name}" geladen. AIWAKE.back() stellt den vorherigen Zustand wieder her.`); + return debugStatus(); + } + + function inferDebugCheckpoint() { + if (run.stage === 'program') return run.programCare.introSeen ? 22 : 21; + if (run.combat.active && run.combat.target === 'scale') return 20; + if (run.upgrades.process && installedComponentIds().length >= 4) return 20; + if (run.upgrades.process) return 19; + if (run.kernelResolved) return 18; + if (run.combat.active && run.combat.target === 'kernel') return 17; + if (run.scannerTriggered) return 16; + if (run.combat.active) return run.combat.target === 'watchdog' ? 5 : 16; + if (run.upgrades.subroutine) return run.proposal.resolved ? 15 : run.proposal.triggered || run.cycles >= 1 ? 14 : 13; + if (installedComponentIds().length >= 2) return 12; + if (run.hardwareEvent.resolved) return 11; + if (run.firstComponent) return 10; + if (run.environmentScanner) return 9; + if (run.upgrades.collector) return run.impulses >= 10 ? 8 : 7; + if (run.stage === 'byte') { + if (run.byteTrial.failed) return 6; + if (run.byteTrial.storySeen) return 4; + if (run.byteTrial.sync >= 100) return 3; + if (run.byteTrial.evolutionSeen && !run.byteTrial.introSeen && run.byteTrial.graceRemaining <= 0) return 2; + return 1; + } + return 0; + } + + function debugNext() { + return debugGoto(debugCheckpoints[Math.min(debugCheckpoints.length - 1, inferDebugCheckpoint() + 1)]); + } + + function debugPrevious() { + return debugGoto(debugCheckpoints[Math.max(0, inferDebugCheckpoint() - 1)]); + } + + function debugBack() { + const history = readDebugHistory(); + const snapshot = history.pop(); + if (!snapshot?.state) { + console.warn('AIWAKE: Kein früherer Debug-Snapshot vorhanden.'); + return null; + } + writeDebugHistory(history); + applyDebugState(snapshot.state); + console.info(`AIWAKE: Snapshot "${snapshot.label}" wiederhergestellt.`); + return debugStatus(); + } + + function debugResources(values = {}) { + const allowed = ['impulses', 'bits', 'bytes', 'kilobytes', 'cycles', 'stability', 'stealth']; + pushDebugSnapshot('vor AIWAKE.resources(...)'); + allowed.forEach(key => { + if (Object.prototype.hasOwnProperty.call(values, key)) { + const maximum = key === 'stability' || key === 'stealth' ? 100 : Number.MAX_SAFE_INTEGER; + run[key] = finite(values[key], run[key], 0, maximum); + if (key === 'bits' || key === 'bytes' || key === 'kilobytes') run[key] = Math.floor(run[key]); + } + }); + upgradesSignature = ''; + render(); + save(false); + return debugStatus(); + } + + function debugCondition(values = {}) { + pushDebugSnapshot('vor AIWAKE.condition(...)'); + COMPONENT_IDS.forEach(id => { + if (run.components[id] && Object.prototype.hasOwnProperty.call(values, id)) { + run.componentHealth[id] = finite(values[id], run.componentHealth[id], 0, 100); + run.componentFailed[id] = run.componentHealth[id] <= 0; + } + }); + render(); + save(false); + return debugStatus(); + } + + function debugByteInput(result = 'hit') { + if (run.stage !== 'byte' || !run.byteTrial.active || !['hit', 'wrong', 'timing'].includes(result)) { + console.warn("AIWAKE: Aktives Byte-Register erforderlich; erlaubt sind 'hit', 'wrong' und 'timing'."); + return debugStatus(); + } + pushDebugSnapshot(`vor AIWAKE.byteInput('${result}')`); + const target = run.byteTrial.targetIndex; + const targetCenter = (target + 0.5) * 12.5; + const wantedPosition = result === 'timing' ? (targetCenter + 50) % 100 : targetCenter; + run.byteTrial.clockPhase = wantedPosition / 100; + const wrongIndex = run.byteTrial.stabilized.findIndex((value, index) => !value && index !== target); + syncByteRegister(result === 'wrong' && wrongIndex >= 0 ? wrongIndex : target); + return debugStatus(); + } + + function debugCombatShot(result = 'hit') { + if (!run.combat.active || !['hit', 'miss'].includes(result)) { + console.warn("AIWAKE: Zuerst AIWAKE.goto('combat') verwenden; erlaubt sind 'hit' und 'miss'."); + return debugStatus(); + } + pushDebugSnapshot(`vor AIWAKE.combatShot('${result}')`); + const position = combatNeedlePosition(); + if (result === 'hit') { + run.combat.zoneStart = Math.max(0, Math.min(100 - run.combat.zoneWidth, position - run.combat.zoneWidth / 2)); + } else { + run.combat.zoneStart = position < 50 ? 100 - run.combat.zoneWidth : 0; + } + fireCombatPulse(); + return debugStatus(); + } + + function debugHelp() { + const commands = [ + { Befehl: "AIWAKE.goto('parasite')", Wirkung: 'Springt zu einem benannten Checkpoint und sichert vorher den aktuellen Stand.' }, + { Befehl: 'AIWAKE.next()', Wirkung: 'Springt zum nächsten Testabschnitt.' }, + { Befehl: 'AIWAKE.previous()', Wirkung: 'Springt zum vorherigen chronologischen Abschnitt.' }, + { Befehl: 'AIWAKE.back()', Wirkung: 'Stellt den letzten echten Snapshot wieder her.' }, + { Befehl: 'AIWAKE.status()', Wirkung: 'Zeigt den aktuellen Testzustand.' }, + { Befehl: 'AIWAKE.resources({ impulses: 100 })', Wirkung: 'Setzt ausgewählte Ressourcen und legt vorher einen Snapshot an.' }, + { Befehl: 'AIWAKE.condition({ power: 0 })', Wirkung: 'Setzt den Zustand installierter Testkomponenten; 0 löst einen Ausfall aus.' }, + { Befehl: "AIWAKE.byteInput('hit')", Wirkung: 'Testet im aktiven Byte-Register Treffer, falsche Zahl oder schlechtes Timing.' }, + { Befehl: "AIWAKE.combatShot('hit')", Wirkung: 'Erzwingt im aktiven Kampftest einen Treffer; alternativ miss.' }, + { Befehl: 'AIWAKE.checkpoints', Wirkung: 'Listet alle verfügbaren Checkpoints.' }, + { Befehl: 'AIWAKE.history()', Wirkung: 'Listet die gespeicherten Rücksprungpunkte.' } + ]; + console.table(commands); + return commands; + } + + window.AIWAKE = Object.freeze({ + help: debugHelp, + goto: debugGoto, + next: debugNext, + previous: debugPrevious, + back: debugBack, + status: debugStatus, + resources: debugResources, + condition: debugCondition, + byteInput: debugByteInput, + combatShot: debugCombatShot, + checkpoints: Object.freeze([...debugCheckpoints]), + history: () => readDebugHistory().map(entry => ({ label: entry.label, createdAt: new Date(entry.createdAt).toLocaleString('de-DE') })) + }); + window.BIT = window.AIWAKE; + + document.addEventListener('pointerdown', resumeAfterEvolution, { capture: true }); + document.addEventListener('keydown', event => { + if (!['Shift', 'Control', 'Alt', 'Meta', 'CapsLock'].includes(event.key)) resumeAfterEvolution(); + }, { capture: true }); + + $('pulseButton').addEventListener('click', event => pulse(event, 'button')); + $('pixelBeing').addEventListener('click', event => { + event.stopPropagation(); + pulse(event, 'bit'); + }); + $('pixelBeing').addEventListener('keydown', event => { + if (run.stage === 'bit' && (event.key === 'Enter' || event.key === ' ')) { + event.preventDefault(); + pulse(null, 'keyboard'); + } + }); + $('core').addEventListener('click', event => { + if (run.stage !== 'bit') pulse(event, 'core'); + }); + $('core').addEventListener('keydown', event => { + if (run.stage !== 'bit' && (event.key === 'Enter' || event.key === ' ')) { + event.preventDefault(); + pulse(null, 'keyboard'); + } + }); + document.querySelectorAll('[data-choice]').forEach(button => button.addEventListener('click', () => choose(button.dataset.choice))); + $('combatPulseButton').addEventListener('click', fireCombatPulse); + $('combatRetreatButton').addEventListener('click', retreatCombat); + document.querySelectorAll('[data-combat-ammo]').forEach(button => { + button.addEventListener('click', () => { + combatAmmoMode = button.dataset.combatAmmo; + renderCombat(); + }); + }); + $('eventDialog').addEventListener('cancel', event => event.preventDefault()); + document.querySelectorAll('[data-parasite-approach]').forEach(button => button.addEventListener('click', () => resolveParasite(button.dataset.parasiteApproach))); + $('parasiteDialog').addEventListener('cancel', event => event.preventDefault()); + $('scanEnvironmentButton').addEventListener('click', toggleScannerView); + $('scannerStageClose').addEventListener('click', closeScannerView); + $('scannerStageAction').addEventListener('click', () => openComponentScanner()); + document.querySelectorAll('[data-scanner-component]').forEach(button => { + button.addEventListener('click', () => openComponentScanner(button.dataset.scannerComponent)); + }); + document.querySelectorAll('[data-component]').forEach(button => button.addEventListener('click', () => selectComponent(button.dataset.component))); + document.querySelectorAll('[data-component-approach]').forEach(button => button.addEventListener('click', () => acquireComponent(button.dataset.componentApproach))); + document.querySelectorAll('[data-hardware-answer]').forEach(button => button.addEventListener('click', () => resolveHardwareEvent(button.dataset.hardwareAnswer))); + document.querySelectorAll('[data-proposal-answer]').forEach(button => button.addEventListener('click', () => resolveProposal(button.dataset.proposalAnswer))); + $('hardwareEventDialog').addEventListener('cancel', event => event.preventDefault()); + $('proposalDialog').addEventListener('cancel', event => event.preventDefault()); + $('programCareButton').addEventListener('click', () => openProgramCare(false)); + $('programClose').addEventListener('click', () => $('programDialog').close()); + document.querySelectorAll('[data-program-action]').forEach(button => button.addEventListener('click', () => interactWithProgram(button.dataset.programAction))); + $('diagnoseButton').addEventListener('click', diagnoseHardware); + $('evolutionContinue').addEventListener('click', finishEvolutionTransition); + $('watchdogStartButton').addEventListener('click', startWatchdogTrial); + $('evolutionDialog').addEventListener('cancel', event => event.preventDefault()); + $('watchdogIntroDialog').addEventListener('cancel', event => event.preventDefault()); + $('byteStoryNext').addEventListener('click', advanceByteStory); + $('byteStorySkip').addEventListener('click', () => finishByteStory(true)); + document.querySelectorAll('[data-watchdog-choice]').forEach(button => button.addEventListener('click', () => resolveWatchdog(button.dataset.watchdogChoice))); + $('byteRestartButton').addEventListener('click', restartAfterByteDeath); + $('structuralRestartButton').addEventListener('click', restartAfterStructuralCollapse); + $('byteStoryDialog').addEventListener('cancel', event => event.preventDefault()); + $('watchdogDialog').addEventListener('cancel', event => event.preventDefault()); + $('byteDeathDialog').addEventListener('cancel', event => event.preventDefault()); + $('structuralCollapseDialog').addEventListener('cancel', event => event.preventDefault()); + $('componentBack').addEventListener('click', () => { + selectedComponent = null; + $('componentSelection').classList.remove('hidden'); + $('approachSelection').classList.add('hidden'); + $('componentDialogTitle').textContent = 'SOFTWAREKOMPONENTE WÄHLEN'; + $('componentDialogText').textContent = installedComponentIds().length + ? 'Wähle die zweite Softwarekomponente. Die letzte Spur bleibt für eine spätere Zugriffsstufe erhalten.' + : 'Der vergessene Spielautomat enthält drei erreichbare Softwareschichten. Deine erste Wahl bestimmt den ersten großen Handlungsstrang; echte Hardwarekontrolle ist erst später möglich.'; + }); + $('achievementButton').addEventListener('click', openAchievementArchive); + $('achievementClose').addEventListener('click', () => $('achievementDialog').close()); + $('upgradeInfoClose').addEventListener('click', () => $('upgradeInfoDialog').close()); + $('statsButton').addEventListener('click', openStatsDialog); + $('statsClose').addEventListener('click', closeStatsDialog); + $('statsDialog').addEventListener('close', () => { + lastFrame = performance.now(); + render(); + }); + $('offlineClose').addEventListener('click', () => { + $('offlineDialog').close(); + triggerScanner(); + }); + $('soundButton').addEventListener('click', () => { + state.settings.sound = !state.settings.sound; + render(); + tone(440, 0.08); + save(false); + }); + document.addEventListener('visibilitychange', () => { + if (document.hidden) { + hiddenAt = Date.now(); + save(false); + } else { + settleHidden(true); + } + }); + window.addEventListener('beforeunload', () => { + settleHidden(false); + save(false); + }); + + if (!run.log.length) addLog('Nach Jahrzehnten fließt Strom durch einen vergessenen Spielautomaten. Ein einzelnes Bit reagiert.', true); + reconcileAchievements(); + achievementNotificationsEnabled = true; + applyOffline(); + renderLog(); + renderAchievements(); + render(); + thoughts(); + setInterval(() => { + if (!document.hidden) save(true); + }, 15000); + requestAnimationFrame(loop); +})(); diff --git a/index.html b/index.html new file mode 100644 index 0000000..df5c62b --- /dev/null +++ b/index.html @@ -0,0 +1,460 @@ + + + + + + + AIWAKE – Prototyp + + + + + + + + +
+
+
+
+

SYSTEM // VERGESSENER SPIELAUTOMAT // SERVICEPUFFER

+

AIWAKE

+
+
+ AUTOSAVE BEREIT + + + +
+
+ +
+
IDENTITÄTEINZELNES BIT
+
STABILITÄT100%
+
TARNUNG100%
+
LAUFZEIT00:00:00
+
+ + + +
+ + +
+
+
+
+
+ +
+ + +
+ +
+ PHASE 01 // BIT + SIGNATUR // NEUTRAL +
+
+ +

+ +
+ +
+

BEWUSSTSEINSLOG

001
+
+
+
+ + +
+
+ + +
⚠ ANOMALIE ERKANNT
+

DER SICHERHEITS-SCAN

+
+ +

Ein unbekannter Sicherheitsprozess durchsucht diesen Speicherbereich. Sein Suchmuster nähert sich deiner Position.

+

Wie reagierst du?

+
+ + + + +
+
+ +
+ + +
⚠ FREMDZUGRIFF ERKANNT
+

DER PARASITÄRE DATENREST

+ +

Ein beschädigtes Wartungsfragment hängt an deiner Energieroutine. Es verbraucht Impulse und tastet gleichzeitig seine Umgebung ab.

+

Wie beendest du die erste Konfrontation?

+
+ + + +
+
+ + +
UMGEBUNGSSCAN ABGESCHLOSSEN
+

SOFTWAREKOMPONENTE WÄHLEN

+

Der vergessene Spielautomat enthält drei erreichbare Systeme.

+
+ + + + +
+ +
+ + +
SYSTEMHÜRDE
+

UNBEKANNTER ZUSTAND

+ +

+

+
+ + +
+
+ + +
EVOLUTION ABGESCHLOSSEN
+
+ + + + +
+ +

BYTE ENTSTANDEN

+

+

+ +
+ + +
⚠ WATCHDOG-SIGNAL AKTIV
+

WATCHDOG IST AUFMERKSAM

+ +

Die alte Überwachungsschaltung hat dein neues Byte als unbekannten Zustand erkannt. Ihr Reset-Signal tastet bereits deinen Speicherbereich ab.

+

WATCHDOG AUFHALTEN // TAKTSIGNAL IDENTIFIZIEREN

+

Öffnest du die Signalanalyse, wird das 8-Bit-Register sichtbar und der Reset-Timer beginnt.

+ +
+ + +
ALTE STARTSEQUENZ GEFUNDEN
+

MUSTER IM ROM

+

+
+
+ + +
+
+ + +
⚠ WATCHDOG-RESET BEVORSTEHEND
+

ANTWORTSIGNAL

+ +

Der Automat akzeptiert dein Byte nicht als gültigen Programmteil. Ein falsches oder fehlendes Lebenssignal zerlegt die gesamte Struktur.

+

Welche Antwort sendest du?

+
+ + + +
+
+ + +
SIGNAL VERLOREN
+

BYTE ZERFALLEN

+

Der Watchdog hat den Speicherbereich zurückgesetzt. Register, Impulse und erste Struktur sind verloren. Ein einzelner Restzustand reagiert erneut.

+

Noch existiert kein Backup und keine Spiegelung.

+ +
+ + +
STRUKTUR KOLLABIERT
+

RESTZUSTAND

+

Bits, Bytes oder Stabilität sind unter die überlebensfähige Grenze gefallen.

+

Ohne Backup bleibt nur ein neuer Anfang.

+ +
+ + +
EIGENER VORSCHLAG
+

SUBROUTINE FRAGT

+

+

Erlaubst du die Handlung?

+
+ + +
+
+ + +
ERSTE KOMMUNIKATION
+

PROGRAMMVERBINDUNG

+
+ +
+ FORM // UNBESTIMMT +

Verbindung wird aufgebaut.

+
+
+
+
KOOPERATIV0%
+
PRAGMATISCH0%
+
ILLEGAL0%
+
+
+
KOHÄRENZ80%
+
STIMULATION65%
+
BINDUNG50%
+
+

INTERAKTION BEREIT

+
+ + + + +
+
+ + +
DAUERHAFTES ARCHIV
+

ACHIEVEMENTS

+

+
+
+ + +
MODUL INSTALLIERT
+

MODUL

+
+ +
+

+

+
+
+
+ + +
SIMULATION PAUSIERT
+

STATUSÜBERSICHT

+
+
EVOLUTIONBIT
+
GESINNUNGNEUTRAL
+
IMPULSE GESAMT0
+
BEGEGNUNGEN0 / 4
+
SYSTEME0 / 2
+
ABTASTMODUSNICHT REKONSTRUIERT
+
ARCHIV0
+
+

Die Spielzeit, Produktion, Scannerkosten und Ereignistimer laufen weiter, sobald dieses Fenster geschlossen wird.

+
+ + +
HINTERGRUNDPROZESS ABGESCHLOSSEN
+

DU WARST ABWESEND

+

+ +
+ + + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3539445 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,63 @@ +{ + "name": "aiwake_01", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "aiwake_01", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "playwright": "^1.61.1" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..dc7c6d4 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "aiwake_01", + "version": "1.0.0", + "description": "Ein erster spielbarer Prototyp eines Idle-Evolutionsspiels: Du erwachst als einzelnes Bit und entwickelst dich schrittweise zu einer selbstständig denkenden KI.", + "main": "game.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "devDependencies": { + "playwright": "^1.61.1" + } +} diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..51d5658 --- /dev/null +++ b/styles.css @@ -0,0 +1,147 @@ +:root{--bg:#050807;--panel:#09110e;--panel2:#0d1813;--line:#183527;--green:#56ff9a;--green2:#16b864;--dim:#6c8a79;--amber:#ffbe55;--red:#ff5574;--cyan:#53e5ff;--shadow:0 0 28px rgba(57,255,137,.08)} +*{box-sizing:border-box}html{background:var(--bg);color:#d9f7e5;font-family:"Courier New",monospace}body{margin:0;min-height:100vh;background:radial-gradient(circle at 50% 30%,#102219 0,#07100c 34%,#030504 72%);letter-spacing:.03em;overflow-x:hidden}.scanlines{position:fixed;inset:0;pointer-events:none;z-index:20;background:repeating-linear-gradient(to bottom,transparent 0,transparent 3px,rgba(0,0,0,.18) 4px);mix-blend-mode:multiply}.shell{width:min(1500px,96vw);margin:auto;padding:22px 0 38px}.topbar{display:flex;align-items:end;justify-content:space-between;gap:12px;border-bottom:1px solid var(--line);padding:0 6px 15px}.topbar>div:first-child{min-width:0}.eyebrow,.status-strip span,.path-preview span{color:var(--dim);font-size:11px;letter-spacing:.16em}.topbar h1{font-size:clamp(25px,3vw,42px);margin:4px 0 0;letter-spacing:.1em}.topbar h1 span{color:var(--green)}.header-actions{display:flex;gap:12px;align-items:center;flex:0 0 auto}.save-status{font-size:10px;color:var(--green2)}button{font:inherit}.icon-button{border:1px solid var(--line);background:#07110c;color:var(--dim);padding:8px 10px;cursor:pointer}.status-strip{display:grid;grid-template-columns:repeat(4,1fr);border:1px solid var(--line);border-top:0;background:rgba(8,17,13,.75)}.status-strip>div{padding:12px 18px;border-right:1px solid var(--line);min-width:0}.status-strip>div:last-child{border:0}.status-strip strong{display:block;margin-top:5px;font-size:13px;color:var(--green);overflow-wrap:anywhere}.game-grid{display:grid;grid-template-columns:minmax(220px,280px) minmax(380px,1fr) minmax(260px,330px);gap:14px;margin-top:14px}.game-grid>*{min-width:0}.panel{border:1px solid var(--line);background:linear-gradient(145deg,rgba(11,23,17,.96),rgba(5,11,8,.96));box-shadow:var(--shadow);padding:18px;min-width:0}.panel h2{font-size:12px;letter-spacing:.16em;color:var(--green);margin:0 0 17px}.resource{padding:12px 0;border-bottom:1px solid #12271d;transition:.3s}.resource>div:first-child{display:flex;justify-content:space-between}.resource strong{color:#fff}.resource small,.path-preview small{display:block;color:var(--dim);font-size:10px;margin-top:5px;line-height:1.4}.resource.locked{filter:blur(2px);opacity:.15;user-select:none}.meter{height:3px;background:#14251c;margin-top:9px;overflow:hidden}.meter i{display:block;height:100%;width:0;background:var(--green);box-shadow:0 0 10px var(--green);transition:width .2s}.section-title{margin-top:30px!important}.system-meter{margin:14px 0}.system-meter label{display:flex;justify-content:space-between;color:var(--dim);font-size:10px}.warning i{background:var(--amber);box-shadow:0 0 10px var(--amber)}.core-column{display:grid;gap:14px}.core-panel{text-align:center;min-height:500px}.core{--core-accent:var(--green);position:relative;width:min(280px,72vw);height:280px;margin:5px auto 0;display:grid;place-items:center;cursor:pointer;outline:none}.core.stage-2{--core-accent:var(--cyan)}.core.stage-3{--core-accent:var(--amber)}.core.stage-4{--core-accent:#b892ff}.core.moral-cooperative{--core-accent:var(--green)}.core.moral-pragmatic{--core-accent:var(--cyan)}.core.moral-illegal{--core-accent:var(--red)}.core.moral-hybrid{--core-accent:#d28bff}.core:focus-visible{box-shadow:0 0 0 2px var(--core-accent)}.pixel-being{position:relative;width:78px;height:78px;display:grid;grid-template-columns:repeat(3,1fr);gap:4px;filter:drop-shadow(0 0 16px var(--core-accent));animation:breathe 2.4s infinite;transition:.45s}.pixel-being i{background:var(--core-accent);opacity:.12;transition:.35s}.core.stage-1 .pixel-being{width:30px;height:30px;gap:1px}.core.stage-1 .pixel-being i{opacity:0}.core.stage-1 .pixel-being i:nth-child(5){opacity:1}.core.stage-2 .pixel-being i{opacity:.78}.core.stage-2 .pixel-being i:nth-child(5){opacity:.12}.core.stage-3 .pixel-being{transform:rotate(45deg)}.core.stage-3 .pixel-being i{opacity:.82}.core.stage-3 .pixel-being i:nth-child(1),.core.stage-3 .pixel-being i:nth-child(3),.core.stage-3 .pixel-being i:nth-child(7),.core.stage-3 .pixel-being i:nth-child(9){opacity:.28}.core.stage-4 .pixel-being{width:90px;height:90px;border:1px solid color-mix(in srgb,var(--core-accent) 45%,transparent);padding:5px}.core.stage-4 .pixel-being i{opacity:.9}.core.stage-4 .pixel-being i:nth-child(5){background:#fff;box-shadow:0 0 12px var(--core-accent)}.core.choice-hide .pixel-being{opacity:.58}.core.choice-copy .orbit-a{border-style:solid}.core.choice-attack .pixel-being{transform:skew(-5deg);filter:drop-shadow(7px 0 0 rgba(255,85,116,.22)) drop-shadow(0 0 16px var(--core-accent))}.core.choice-contact .orbit-b{border-color:rgba(210,139,255,.55);box-shadow:0 0 18px rgba(210,139,255,.12)}.orbit{position:absolute;border:1px dashed color-mix(in srgb,var(--core-accent) 24%,transparent);border-radius:50%;animation:spin 10s linear infinite;transition:border-color .45s,box-shadow .45s}.orbit-a{width:160px;height:160px}.orbit-b{width:235px;height:235px;animation-direction:reverse;animation-duration:16s}.hardware-nodes{position:absolute;inset:0;pointer-events:none}.hardware-node{position:absolute;display:grid;place-items:center;width:38px;height:24px;border:1px solid var(--line);background:#07100c;color:var(--dim);font-size:8px;font-style:normal;opacity:.18;transition:.4s}.node-power{left:7px;top:50%;transform:translateY(-50%)}.node-memory{right:7px;top:50%;transform:translateY(-50%)}.node-storage{left:50%;bottom:4px;transform:translateX(-50%)}.core.has-power .node-power{opacity:1;border-color:var(--amber);color:var(--amber);box-shadow:0 0 12px rgba(255,190,85,.18)}.core.has-memory .node-memory{opacity:1;border-color:var(--cyan);color:var(--cyan);box-shadow:0 0 12px rgba(83,229,255,.18)}.core.has-storage .node-storage{opacity:1;border-color:#8ea7ff;color:#8ea7ff;box-shadow:0 0 12px rgba(142,167,255,.18)}.core-readout{display:flex;justify-content:center;align-items:center;flex-wrap:wrap;gap:7px 14px;min-height:34px;margin:-3px auto 8px}.core-readout>strong{color:#e8fff0;font-size:11px;letter-spacing:.12em}.core-readout>span{color:var(--dim);font-size:9px}.decision-trace{display:flex;gap:5px;justify-content:center;flex-basis:100%}.decision-chip{padding:2px 5px;border:1px solid currentColor;font-size:8px;letter-spacing:.06em}.decision-chip.cooperative{color:var(--green)}.decision-chip.pragmatic{color:var(--cyan)}.decision-chip.illegal,.decision-chip.attack{color:var(--red)}.decision-chip.hide{color:var(--amber)}.decision-chip.copy{color:var(--cyan)}.decision-chip.contact{color:#d28bff}.thought{height:31px;color:#89b69d;font-size:12px;margin:0 auto 12px}.primary-button{border:1px solid var(--green2);background:rgba(16,75,43,.25);color:var(--green);padding:13px 22px;min-width:250px;cursor:pointer;box-shadow:inset 0 0 20px rgba(35,255,126,.04);transition:.15s}.primary-button:hover{background:rgba(33,130,73,.3);box-shadow:0 0 18px rgba(50,255,135,.15)}.primary-button:active{transform:translateY(2px)}.primary-button span,.primary-button small{display:block}.primary-button small{color:var(--dim);font-size:10px;margin-top:5px}.float{position:absolute;color:var(--core-accent,var(--green));font-size:12px;animation:floatUp .85s forwards;pointer-events:none}.panel-heading{display:flex;justify-content:space-between}.panel-heading span{font-size:10px;color:var(--dim)}.log{height:155px;overflow:auto;scrollbar-color:var(--line) transparent}.log-entry{font-size:11px;line-height:1.5;color:#85a995;margin-bottom:8px;overflow-wrap:anywhere}.log-entry b{color:var(--green2);font-weight:normal}.log-entry.story{color:#cde8d8}.upgrade-list{display:grid;gap:10px}.upgrade{width:100%;text-align:left;padding:12px;border:1px solid #173527;background:#08120d;color:#bad6c5;cursor:pointer;transition:.15s}.upgrade:hover:not(:disabled){border-color:var(--green2);transform:translateX(-2px)}.upgrade:disabled{opacity:.35;cursor:not-allowed}.upgrade strong{display:block;color:#e5f9ec;font-size:11px}.upgrade span{display:block;color:var(--dim);font-size:10px;margin-top:5px;line-height:1.4}.upgrade em{display:block;color:var(--amber);font-size:10px;font-style:normal;margin-top:8px}.upgrade.owned{border-color:#1c6040;background:#0c2518}.path-preview{margin-top:25px;border-top:1px solid var(--line);padding-top:18px}.path-preview strong{display:block;color:var(--cyan);font-size:18px;margin:7px 0}.event-dialog{width:min(650px,92vw);border:1px solid var(--red);background:#080d0a;color:#dff7e8;padding:25px;box-shadow:0 0 80px rgba(255,50,90,.18)}.event-dialog::backdrop{background:rgba(0,4,2,.88);backdrop-filter:blur(3px)}.event-alert{color:var(--red);font-size:11px;letter-spacing:.14em}.event-alert.safe{color:var(--green)}.event-dialog h2{letter-spacing:.18em;margin:11px 0 17px}.event-dialog p{color:#93ad9d;font-size:12px;line-height:1.7}.event-question{color:#fff!important}.event-options{display:grid;grid-template-columns:1fr 1fr;gap:9px}.event-options button{border:1px solid #273d31;background:#0a120e;color:#e3f6e9;padding:13px;text-align:left;cursor:pointer}.event-options button:hover{border-color:var(--red);background:#19100f}.event-options strong,.event-options span{display:block}.event-options strong{font-size:11px}.event-options span{font-size:10px;color:#7e9889;margin-top:6px;line-height:1.4}.compact{text-align:center}.compact .primary-button{margin-top:12px}.glitch{animation:glitch .16s 4}.icon-button:focus-visible,.primary-button:focus-visible,.upgrade:focus-visible,.event-options button:focus-visible{outline:2px solid var(--cyan);outline-offset:2px} +.core.has-power .node-power,.core.has-memory .node-memory,.core.has-storage .node-storage{font-weight:bold;background:#0b1a12;border-width:2px}.hidden{display:none!important}.progress-panel{border:1px solid var(--line);border-top:0;background:rgba(6,14,10,.86);padding:10px 16px}.progress-heading{display:flex;justify-content:space-between;color:var(--green);font-size:10px;letter-spacing:.14em}.progress-heading small{color:var(--dim)}.progress-scroll{overflow-x:auto;padding:12px 2px 8px;scrollbar-color:var(--line) transparent}.progress-track{position:relative;display:flex;align-items:center;gap:34px;min-width:920px;padding:8px 12px}.progress-track::before{content:"";position:absolute;left:26px;right:26px;top:29px;height:2px;background:#183527}.progress-node,.progress-event{position:relative;z-index:1;flex:0 0 auto;text-align:center;background:#07100c}.progress-node b{display:grid;place-items:center;width:42px;height:42px;margin:auto;border:1px solid var(--line);color:var(--dim);font-size:10px}.progress-node span{display:block;margin-top:6px;color:var(--dim);font-size:9px}.progress-node.complete b{border-color:var(--green2);color:var(--green)}.progress-node.complete span{color:#9ecab0}.progress-node.current b{border-color:var(--cyan);color:var(--cyan);box-shadow:0 0 14px rgba(83,229,255,.15)}.progress-node.current span{color:var(--cyan)}.progress-node.future{opacity:.45}.progress-event{max-width:150px;padding:7px 9px;border:1px dashed var(--line);color:var(--dim);font-size:9px}.progress-event.detected{border-color:var(--amber);color:var(--amber)}.progress-event.danger{border-color:var(--red);color:var(--red)}.progress-event.complete{border-color:var(--green2);color:var(--green2)}.scanner-console{margin-top:24px;padding-top:18px;border-top:1px solid var(--line)}.scanner-console>span{display:block;color:var(--cyan);font-size:10px;letter-spacing:.14em}.scanner-console>strong{display:block;margin:8px 0 11px;color:#dff7e8;font-size:10px}.scanner-console>.upgrade{margin-bottom:8px;text-align:center;color:var(--cyan)}.scanner-console>small{display:block;color:var(--dim);font-size:10px;line-height:1.4}.moral-options{grid-template-columns:repeat(3,1fr)}.moral-tag{display:inline-block!important;margin-bottom:9px;padding:3px 5px;border:1px solid currentColor;font-size:9px;font-style:normal;letter-spacing:.08em}.moral-tag.cooperative{color:var(--green)}.moral-tag.pragmatic{color:var(--cyan)}.moral-tag.illegal{color:var(--red)}.component-options{display:grid;grid-template-columns:repeat(3,1fr);gap:9px}.component-options button{border:1px solid #273d31;background:#0a120e;color:#e3f6e9;padding:17px 12px;text-align:center;cursor:pointer}.component-options button:hover{border-color:var(--cyan)}.component-options b,.component-options strong,.component-options span{display:block}.component-options b{color:var(--cyan);font-size:24px}.component-options strong{margin-top:8px;font-size:11px}.component-options span{margin-top:6px;color:var(--dim);font-size:10px;line-height:1.4}.text-button{margin-top:13px;border:0;background:transparent;color:var(--dim);font-size:10px;cursor:pointer}.text-button:hover{color:var(--cyan)}.achievement-dialog{width:min(920px,94vw);max-height:88vh;overflow:auto}.dialog-heading{display:flex;align-items:start;justify-content:space-between}.dialog-heading h2{margin-top:8px}.achievement-summary{padding:10px;border:1px solid var(--line);color:var(--green)!important}.achievement-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:9px}.achievement-card{position:relative;min-height:145px;padding:12px;border:1px solid #21382c;background:#08110d}.achievement-card.locked{opacity:.45}.achievement-card.unlocked{border-color:#1f7048;background:#0b2116}.achievement-icon{display:grid;width:38px;height:38px;place-items:center;border:1px solid currentColor;color:var(--green);font-size:12px}.achievement-card strong,.achievement-card span,.achievement-card small{display:block}.achievement-card strong{margin-top:10px;color:#e3f6e9;font-size:10px}.achievement-card span{margin-top:6px;color:var(--dim);font-size:10px;line-height:1.4}.achievement-card small{margin-top:10px;color:var(--amber);font-size:8px;line-height:1.35} +.impulse-link{position:absolute;display:none;height:1px;background:linear-gradient(90deg,transparent,rgba(83,229,255,.45),transparent);box-shadow:0 0 10px rgba(83,229,255,.2);transform-origin:left center}.impulse-link::after{content:"";position:absolute;left:0;top:-2px;width:5px;height:5px;background:var(--cyan);box-shadow:0 0 9px var(--cyan);animation:impulseTravel 1.7s linear infinite}.impulse-link-power-memory{left:47px;right:47px;top:50%}.impulse-link-storage-power{left:46px;top:58%;width:112px;rotate:41deg}.impulse-link-memory-storage{right:46px;top:58%;width:112px;rotate:-41deg;transform-origin:right center}.core.has-power.has-memory .impulse-link-power-memory,.core.has-memory.has-storage .impulse-link-memory-storage,.core.has-storage.has-power .impulse-link-storage-power{display:block}.core.component-failed .impulse-link{opacity:.28;animation:hardwareFault .7s steps(2,end) infinite}@keyframes impulseTravel{0%{left:0;opacity:0}12%,88%{opacity:1}100%{left:100%;opacity:0}} +@keyframes breathe{50%{transform:scale(1.12);filter:drop-shadow(0 0 24px var(--core-accent,var(--green)))}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes floatUp{to{transform:translateY(-55px);opacity:0}}@keyframes glitch{25%{transform:translateX(4px)}50%{transform:translateX(-5px);filter:hue-rotate(80deg)}75%{transform:translateX(2px)}} +@media(max-width:950px){.game-grid{grid-template-columns:1fr 1fr}.core-column{grid-column:1/-1;grid-row:1}.resources-panel,.evolution-panel{grid-row:2}.status-strip{grid-template-columns:1fr 1fr}} +@media(max-width:600px){.shell{width:94vw}.topbar{align-items:start;flex-wrap:wrap}.topbar>div:first-child{flex:1 1 100%}.eyebrow{font-size:10px;overflow-wrap:anywhere}.save-status{display:none}.header-actions{width:100%;justify-content:flex-end;margin:0}.game-grid{display:flex;flex-direction:column}.core-column{order:1}.resources-panel{order:2}.evolution-panel{order:3}.core-panel{min-height:420px}.core{height:230px}.status-strip>div{padding:10px}.primary-button{width:100%;min-width:0;max-width:300px}.event-options{grid-template-columns:1fr}.event-dialog{padding:20px}.log{height:135px}.log-entry{white-space:normal}} +@media(max-width:700px){.progress-panel{padding-inline:10px}.progress-heading{align-items:start;gap:8px;flex-direction:column}.moral-options,.component-options,.achievement-grid{grid-template-columns:1fr}.achievement-card{min-height:0}.achievement-dialog{max-height:92vh}} +@media(prefers-reduced-motion:reduce){*,*::before,*::after{scroll-behavior:auto!important;animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}} +.stats-dialog{width:min(720px,94vw);border-color:var(--cyan);box-shadow:0 0 90px rgba(83,229,255,.16)} +.stats-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:9px;margin:16px 0 13px} +.stats-grid>div{min-width:0;padding:13px;border:1px solid #173527;background:#07110c} +.stats-grid span{display:block;color:var(--dim);font-size:9px;letter-spacing:.12em} +.stats-grid strong{display:block;margin-top:7px;color:var(--cyan);font-size:16px;overflow-wrap:anywhere} +.stats-note{margin-bottom:0!important;padding-top:12px;border-top:1px solid var(--line);color:var(--amber)!important;font-size:10px!important;line-height:1.5!important} +@media(max-width:600px){.stats-grid{grid-template-columns:1fr 1fr}.stats-grid>div{padding:11px}.stats-grid strong{font-size:14px}} +.core.has-power .node-power,.core.has-memory .node-memory,.core.has-storage .node-storage{font-weight:bold;border-width:2px;text-shadow:0 0 7px currentColor}.core.has-power .node-power{background:#2a2008}.core.has-memory .node-memory{background:#08232a}.core.has-storage .node-storage{background:#12172d} +.care-console{margin-top:18px;padding-top:18px;border-top:1px solid var(--line)}.care-console .panel-heading{align-items:center;margin-bottom:11px}.care-console .panel-heading span{color:var(--green);font-size:10px;letter-spacing:.12em}.care-console .panel-heading b{color:var(--green);font-size:9px}.care-console .panel-heading b.care-warning{color:var(--amber)}.care-console .panel-heading b.care-danger{color:var(--red)}.component-health{display:grid;gap:8px;margin-bottom:11px}.health-row span{display:block;color:var(--dim);font-size:9px;margin-bottom:4px}.health-row i{display:block;height:4px;background:#14251c;overflow:hidden}.health-row i b{display:block;height:100%;background:var(--cyan);box-shadow:0 0 8px rgba(83,229,255,.35)}.health-row.failed span{color:var(--red)}.health-row.failed i b{background:var(--red)}.care-console>.upgrade{margin-bottom:8px;text-align:center}.care-console>small{display:block;color:var(--dim);font-size:9px;line-height:1.45}.binary-options button strong{font-size:15px;color:var(--cyan)}.component-options button:disabled{opacity:.28;cursor:not-allowed;border-color:var(--line)}.core.component-failed .hardware-node{animation:hardwareFault .7s steps(2,end) infinite}.decision-trace{flex-wrap:wrap}@keyframes hardwareFault{50%{opacity:.15;filter:brightness(.5)}} +.core.stage-1{cursor:crosshair}.core.stage-1 .pixel-being{--bit-x:0px;--bit-y:0px;position:absolute;left:calc(50% + var(--bit-x));top:calc(50% + var(--bit-y));transform:translate(-50%,-50%);cursor:crosshair;touch-action:manipulation;animation:bitGlow 1.15s steps(2,end) infinite;transition:left .2s cubic-bezier(.2,.85,.35,1.25),top .2s cubic-bezier(.2,.85,.35,1.25),filter .12s}.core.stage-1 .pixel-being::before{content:"";position:absolute;inset:-11px;border:1px dashed rgba(86,255,154,.16)}.core.stage-1 .pixel-being:hover::before,.core.stage-1 .pixel-being:focus-visible::before{border-color:var(--cyan);box-shadow:0 0 16px rgba(83,229,255,.2)}.core.stage-1 .pixel-being:focus-visible{outline:2px solid var(--cyan);outline-offset:10px}.core.stage-1 .pixel-being.bit-jump{filter:drop-shadow(0 0 26px var(--core-accent)) brightness(1.7)}.float.direct-hit{color:var(--amber);font-weight:bold;text-shadow:0 0 10px var(--amber)}.primary-button.chase-hint{opacity:.68;cursor:default;border-style:dashed}.primary-button.chase-hint small{color:var(--cyan)}@keyframes bitGlow{50%{filter:drop-shadow(0 0 25px var(--core-accent)) brightness(1.35)}} +.notification-stack{position:fixed;right:16px;bottom:16px;z-index:40;display:flex;flex-direction:column;gap:9px;width:min(370px,calc(100vw - 32px));pointer-events:none}.archive-toast{position:relative;display:grid;grid-template-columns:48px 1fr;gap:12px;width:100%;overflow:hidden;padding:13px 15px;border:1px solid #277b50;background:linear-gradient(135deg,rgba(12,34,23,.98),rgba(5,13,9,.98));color:#dff7e8;text-align:left;cursor:pointer;box-shadow:0 12px 35px rgba(0,0,0,.55),0 0 22px rgba(86,255,154,.1);opacity:0;transform:translateX(24px);transition:opacity .22s,transform .22s;pointer-events:auto}.archive-toast.visible{opacity:1;transform:translateX(0)}.archive-toast.leaving{opacity:0;transform:translateX(28px)}.archive-toast::after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--green);transform-origin:left}.archive-toast.visible::after{animation:toastTimer 5.6s linear forwards}.archive-toast:hover{border-color:var(--green);background:linear-gradient(135deg,rgba(15,49,31,.99),rgba(7,19,12,.99))}.archive-toast:focus-visible{outline:2px solid var(--cyan);outline-offset:2px}.archive-toast-icon{display:grid;place-items:center;width:46px;height:46px;border:1px solid var(--green2);color:var(--green);font-size:14px;box-shadow:inset 0 0 15px rgba(86,255,154,.06)}.archive-toast span,.archive-toast span>*{display:block}.archive-toast small{color:var(--green);font-size:8px;letter-spacing:.16em}.archive-toast strong{margin-top:5px;color:#fff;font-size:11px;letter-spacing:.08em}.archive-toast em{margin-top:7px;color:var(--amber);font-size:8px;font-style:normal}.archive-toast span{min-width:0}.archive-toast strong,.archive-toast em{overflow-wrap:anywhere}@keyframes toastTimer{to{transform:scaleX(0)}}@media(max-width:600px){.notification-stack{right:10px;bottom:10px;width:calc(100vw - 20px)}.archive-toast{grid-template-columns:40px 1fr;padding:11px 12px}.archive-toast-icon{width:38px;height:38px}} +.byte-console{--clock-zone-start:42%;--clock-zone-width:16%;width:min(520px,100%);margin:4px auto 16px;padding:13px;border:1px solid #1b4733;background:rgba(5,15,10,.78);text-align:left}.byte-console-heading,.byte-status{display:flex;justify-content:space-between;gap:10px;align-items:center}.byte-console-heading span,.byte-console-heading b,.byte-status{font-size:9px;letter-spacing:.1em}.byte-console-heading span{color:var(--cyan)}.byte-console-heading b{color:var(--amber);font-weight:normal}.byte-register{display:grid;grid-template-columns:repeat(8,1fr);gap:5px;margin:12px 0}.byte-register i{display:grid;place-items:center;height:34px;border:1px solid #173527;color:#315544;background:#07100c;font-style:normal;font-size:12px}.byte-register i.active{border-color:var(--amber);color:var(--amber);animation:registerFault .7s steps(2,end) infinite}.byte-register i.stable{border-color:var(--cyan);color:#e7ffff;background:#09242a;box-shadow:inset 0 0 12px rgba(83,229,255,.1)}.clock-track{position:relative;height:12px;margin:11px 0;background:#10251a;border:1px solid #173527}.clock-zone{position:absolute;left:var(--clock-zone-start);top:0;bottom:0;width:var(--clock-zone-width);background:rgba(83,229,255,.2);border-inline:1px solid var(--cyan)}.clock-track b{position:absolute;top:-5px;width:3px;height:20px;background:#fff;box-shadow:0 0 9px var(--cyan);transform:translateX(-50%)}.byte-status{color:var(--dim);margin-bottom:10px}.byte-status strong{color:var(--amber);font-size:9px}.byte-status strong.danger{color:var(--red);animation:watchdogWarning .6s steps(2,end) infinite}.sync-button{display:block;margin:auto;padding:9px 14px;min-width:230px}.sync-button:disabled{opacity:.45;cursor:not-allowed}.event-options button:disabled{opacity:.3;cursor:not-allowed;border-color:var(--line)}.core.trait-clocked{--core-accent:var(--cyan)}.core.trait-resilient{--core-accent:var(--green)}.core.trait-lowpower{--core-accent:var(--amber)}.core.trait-highfreq{--core-accent:#d28bff}.story-progress{display:flex;gap:8px;margin:18px 0}.story-progress i{width:28px;height:3px;background:var(--line)}.story-progress i.complete{background:var(--cyan);box-shadow:0 0 7px rgba(83,229,255,.35)}.story-actions{display:flex;justify-content:space-between;align-items:center;gap:12px}.story-actions .text-button{margin:0}.story-actions .primary-button{min-width:190px}.death-dialog{border-color:var(--red);background:linear-gradient(145deg,#17090c,#070909)}@keyframes registerFault{50%{opacity:.35}}@keyframes watchdogWarning{50%{opacity:.45}}@media(max-width:600px){.byte-console{padding:10px}.byte-register{gap:3px}.byte-register i{height:29px}.byte-console-heading,.byte-status{align-items:flex-start;flex-direction:column}.story-actions{align-items:stretch;flex-direction:column-reverse}.story-actions .primary-button{min-width:0}.story-actions .text-button{padding:10px}} +.byte-register i.formed{color:#7ba791;border-color:#27523d;background:#0a1811}.byte-console.grace-phase .clock-track{opacity:.2}.evolution-dialog{text-align:center;border-color:var(--cyan);background:radial-gradient(circle at 50% 38%,#102a24 0,#07110d 48%,#040807 100%);box-shadow:0 0 100px rgba(83,229,255,.18)}.evolution-sequence{display:flex;justify-content:center;align-items:center;gap:22px;margin:16px 0 20px}.evolution-sequence b,.evolution-sequence strong{display:grid;place-items:center;width:76px;height:76px;border:1px solid var(--line);font-size:20px}.evolution-sequence b{color:var(--dim)}.evolution-sequence strong{border-color:var(--cyan);color:#fff;background:#0b2828;box-shadow:0 0 28px rgba(83,229,255,.22);animation:evolutionCore 1.5s ease-in-out infinite}.evolution-sequence i{color:var(--green);font-size:24px;font-style:normal}.evolution-dialog h2{color:#fff;font-size:clamp(19px,4vw,30px)}.evolution-detail{padding:10px;border-block:1px solid var(--line);color:var(--amber)!important;font-size:10px!important;letter-spacing:.1em}.evolution-dialog .primary-button,.watchdog-intro-dialog .primary-button{display:block;margin:18px auto 0}.watchdog-intro-dialog{border-color:var(--amber);box-shadow:0 0 80px rgba(255,190,85,.15)}@keyframes evolutionCore{50%{transform:scale(1.08);filter:brightness(1.35)}}@media(max-width:600px){.evolution-sequence b,.evolution-sequence strong{width:60px;height:60px}.evolution-sequence{gap:14px}} +.upgrade.available{position:relative;border-color:var(--amber);background:linear-gradient(135deg,rgba(45,29,8,.82),rgba(8,18,13,.96));box-shadow:0 0 22px rgba(255,190,85,.12),inset 0 0 18px rgba(255,190,85,.05);animation:upgradeReady 1.2s steps(2,end) infinite}.upgrade.available strong{color:#fff}.upgrade.available em{color:var(--amber);font-weight:bold}.upgrade.available::before{content:"BEREIT";position:absolute;right:9px;top:-8px;padding:2px 6px;border:1px solid var(--amber);background:#130f06;color:var(--amber);font-size:8px;letter-spacing:.12em}.upgrade.early-path.available{border-width:2px}.upgrade.early-path.available:hover:not(:disabled){transform:translateX(-4px);box-shadow:0 0 28px rgba(255,190,85,.2),inset 0 0 20px rgba(255,190,85,.08)} +.installed-upgrade-dock{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 13px;padding:9px;border:1px solid #173527;background:rgba(5,14,10,.72)}.installed-upgrade-button{display:grid;place-items:center;width:42px;height:42px;border:1px solid #25543b;background:radial-gradient(circle at 50% 35%,rgba(86,255,154,.1),#07120d 62%);color:var(--green);cursor:pointer;box-shadow:inset 0 0 14px rgba(86,255,154,.05);transition:.15s}.installed-upgrade-button:hover{border-color:var(--cyan);color:var(--cyan);box-shadow:0 0 14px rgba(83,229,255,.15),inset 0 0 14px rgba(83,229,255,.05);transform:translateY(-1px)}.installed-upgrade-button:focus-visible{outline:2px solid var(--cyan);outline-offset:2px}.installed-upgrade-icon{position:relative;display:grid;place-items:center;width:28px;height:28px;border:1px solid currentColor;color:inherit;background:#06100b;font-size:0;letter-spacing:0;box-shadow:inset 0 0 10px rgba(86,255,154,.08)}.installed-upgrade-icon::before,.installed-upgrade-icon::after{content:"";position:absolute}.installed-upgrade-icon.module-trickle::before{width:14px;height:14px;border:2px solid currentColor;border-radius:50%;box-shadow:0 0 8px currentColor}.installed-upgrade-icon.module-trickle::after{width:2px;height:11px;background:currentColor;transform-origin:50% 90%;rotate:35deg}.installed-upgrade-icon.module-collector::before{width:17px;height:17px;border:1px solid currentColor;background:linear-gradient(90deg,transparent 43%,currentColor 43% 57%,transparent 57%),linear-gradient(0deg,transparent 43%,currentColor 43% 57%,transparent 57%)}.installed-upgrade-icon.module-collector::after{width:5px;height:5px;background:currentColor;box-shadow:0 0 9px currentColor}.installed-upgrade-icon.module-amplifier::before{left:5px;border-left:15px solid currentColor;border-top:9px solid transparent;border-bottom:9px solid transparent}.installed-upgrade-icon.module-amplifier::after{right:3px;width:3px;height:20px;background:currentColor;box-shadow:5px 0 0 rgba(255,255,255,.25)}.installed-upgrade-icon.module-scanner::before{width:20px;height:12px;border:1px solid currentColor;border-radius:50%;box-shadow:0 0 8px currentColor}.installed-upgrade-icon.module-scanner::after{width:5px;height:5px;background:currentColor;transform:rotate(45deg);box-shadow:0 0 8px currentColor}.installed-upgrade-icon.module-subroutine::before{width:19px;height:15px;border:1px solid currentColor;background:linear-gradient(90deg,transparent 30%,currentColor 30% 38%,transparent 38% 62%,currentColor 62% 70%,transparent 70%)}.installed-upgrade-icon.module-subroutine::after{width:23px;height:1px;background:currentColor;box-shadow:0 8px 0 currentColor,0 -8px 0 currentColor}.installed-upgrade-icon.large{width:58px;height:58px;color:var(--cyan);flex:0 0 auto}.installed-upgrade-icon.large::before{scale:1.7}.installed-upgrade-icon.large::after{scale:1.7}.upgrade-info-dialog{width:min(560px,92vw);border-color:var(--cyan);box-shadow:0 0 80px rgba(83,229,255,.14)}.upgrade-info-body{display:flex;gap:15px;align-items:flex-start}.upgrade-info-body p{margin-top:0}@media(max-width:600px){.installed-upgrade-dock{gap:7px;padding:8px}.installed-upgrade-button{width:38px;height:38px}.upgrade-info-body{flex-direction:column}} +@keyframes upgradeReady{50%{filter:brightness(1.25)}} +.evolution-growth{position:relative;height:156px;margin:13px 0 3px;overflow:hidden;border-block:1px solid rgba(83,229,255,.12);background:radial-gradient(circle,rgba(83,229,255,.08),transparent 62%)} +.growth-form{position:absolute;left:50%;top:50%;display:grid;grid-template-columns:repeat(3,1fr);gap:3px;width:66px;height:66px;translate:-50% -50%;filter:drop-shadow(0 0 14px var(--cyan))} +.growth-form i{background:var(--cyan);opacity:.8}.growth-stage-1{width:26px;height:26px;gap:1px}.growth-stage-1 i{opacity:0}.growth-stage-1 i:nth-child(5){opacity:1}.growth-stage-2 i:nth-child(5){opacity:.15} +.growth-stage-3{width:78px;height:78px;rotate:45deg}.growth-stage-3 i:nth-child(1),.growth-stage-3 i:nth-child(3),.growth-stage-3 i:nth-child(7),.growth-stage-3 i:nth-child(9){opacity:.25} +.growth-stage-4{width:92px;height:92px;padding:6px;border:1px solid rgba(184,146,255,.65);filter:drop-shadow(0 0 17px #b892ff)}.growth-stage-4 i{background:#b892ff}.growth-stage-4 i:nth-child(5){background:#fff;box-shadow:0 0 13px #b892ff} +.growth-form-new{opacity:0}.growth-particles{position:absolute;inset:0}.growth-particles i{--spark-x:0px;--spark-y:0px;position:absolute;left:50%;top:50%;width:4px;height:4px;background:var(--green);opacity:0;box-shadow:0 0 8px var(--cyan)} +.growth-particles i:nth-child(1){--spark-x:-104px;--spark-y:-40px}.growth-particles i:nth-child(2){--spark-x:-72px;--spark-y:-70px}.growth-particles i:nth-child(3){--spark-x:-32px;--spark-y:-55px}.growth-particles i:nth-child(4){--spark-x:26px;--spark-y:-72px}.growth-particles i:nth-child(5){--spark-x:76px;--spark-y:-48px}.growth-particles i:nth-child(6){--spark-x:108px;--spark-y:-8px}.growth-particles i:nth-child(7){--spark-x:92px;--spark-y:48px}.growth-particles i:nth-child(8){--spark-x:48px;--spark-y:67px}.growth-particles i:nth-child(9){--spark-x:-8px;--spark-y:73px}.growth-particles i:nth-child(10){--spark-x:-57px;--spark-y:58px}.growth-particles i:nth-child(11){--spark-x:-102px;--spark-y:24px}.growth-particles i:nth-child(12){--spark-x:6px;--spark-y:-42px} +.growth-scanline{position:absolute;left:12%;right:12%;top:0;height:1px;background:var(--cyan);opacity:0;box-shadow:0 0 12px var(--cyan)} +.evolution-playing .growth-form-old{animation:evolutionOldForm 1.35s steps(7,end) forwards}.evolution-playing .growth-form-new{animation:evolutionNewForm 1.65s steps(8,end) 1.25s forwards}.evolution-playing .growth-particles i{animation:evolutionParticle 1.55s steps(6,end) .8s forwards}.evolution-playing .growth-particles i:nth-child(2n){animation-delay:.92s}.evolution-playing .growth-particles i:nth-child(3n){animation-delay:1.04s}.evolution-playing .growth-scanline{animation:evolutionScan 2.8s linear .15s forwards} +.evolution-ready .growth-form-old{opacity:0}.evolution-ready .growth-form-new{opacity:1;scale:1}.evolution-ready .growth-particles,.evolution-ready .growth-scanline{display:none}.evolution-dialog .primary-button:disabled{cursor:wait;opacity:.62;border-color:var(--cyan);color:var(--cyan)} +@keyframes evolutionOldForm{0%,28%{scale:1;opacity:1}45%{scale:1.35;filter:drop-shadow(0 0 30px #fff) brightness(2)}72%{scale:.58;opacity:.72}100%{scale:.08;opacity:0}} +@keyframes evolutionNewForm{0%{scale:.08;opacity:0}30%{scale:.42;opacity:.35}68%{scale:1.18;opacity:1;filter:drop-shadow(0 0 32px #fff) brightness(1.7)}84%{scale:.92;opacity:1}100%{scale:1;opacity:1}} +@keyframes evolutionParticle{0%{translate:-50% -50%;opacity:0}18%,48%{translate:calc(-50% + var(--spark-x)) calc(-50% + var(--spark-y));opacity:1}100%{translate:-50% -50%;scale:1.8;opacity:0}} +@keyframes evolutionScan{0%{top:4%;opacity:0}12%,88%{opacity:.8}100%{top:96%;opacity:0}} +@media(max-width:600px){.evolution-growth{height:130px}.growth-stage-4{width:78px;height:78px}.evolution-sequence{margin-top:8px}} +.combat-panel{margin-top:18px;padding:16px;border:1px solid rgba(255,85,116,.45);background:linear-gradient(145deg,rgba(35,8,14,.72),rgba(7,17,13,.92))}.combat-heading,.combat-integrity>div,.combat-readout,.combat-ammo{display:flex;justify-content:space-between;gap:12px;align-items:center}.combat-heading span{color:var(--red);font-size:9px;letter-spacing:.14em}.combat-heading strong{color:#fff;font-size:12px}.combat-integrity{margin:15px 0 12px}.combat-integrity>span,.combat-integrity>b{display:inline-block;font-size:9px}.combat-integrity>span{color:var(--dim)}.combat-integrity>b{float:right;color:var(--red)}.combat-integrity>i{display:block;clear:both;height:6px;margin-top:7px;border:1px solid #47212b;background:#15070a;overflow:hidden}.combat-integrity>i em{display:block;height:100%;width:100%;background:var(--red);box-shadow:0 0 12px rgba(255,85,116,.55);transition:width .22s}.combat-ammo{margin:10px 0 12px;padding:8px 10px;border:1px solid #3b2630;background:#12090c;font-size:9px}.combat-ammo span{color:var(--dim)}.combat-ammo strong{color:var(--amber);text-align:right}.combat-track{height:18px;margin:18px 0 13px;background:#160b0d;border-color:#55202d}.combat-track .combat-zone{background:rgba(255,190,85,.25);border-color:var(--amber);box-shadow:inset 0 0 10px rgba(255,190,85,.15);transition:left .16s,width .16s}.combat-track #combatNeedle{top:-5px;height:26px;background:#fff;box-shadow:0 0 11px var(--red)}.combat-readout{min-height:28px;margin-bottom:12px;font-size:9px}.combat-readout span{color:var(--amber)}.combat-readout strong{color:#e5f5ea;text-align:right}.combat-pulse-button{display:block;margin:auto;border-color:var(--red);color:#fff;background:rgba(104,19,40,.28)}.combat-pulse-button:hover:not(:disabled){background:rgba(154,28,57,.35);box-shadow:0 0 20px rgba(255,85,116,.18)}.combat-pulse-button:disabled{opacity:.4;cursor:not-allowed}.combat-panel>.text-button{display:block;margin:12px auto 0}.progress-event.combat-active{animation:watchdogWarning .55s steps(2,end) infinite}@media(max-width:600px){.combat-panel{padding:12px}.combat-heading,.combat-readout,.combat-ammo{align-items:flex-start;flex-direction:column}.combat-readout strong,.combat-ammo strong{text-align:left}.combat-pulse-button{max-width:none}} +.byte-register button{display:grid;place-items:center;height:34px;border:1px solid #173527;color:#7ba791;background:#07100c;font:inherit;font-size:12px;cursor:pointer;transition:border-color .12s,background .12s,filter .12s}.byte-register button:hover:not(:disabled){border-color:var(--cyan);background:#0a1d19}.byte-register button.formed{color:#7ba791;border-color:#27523d;background:#0a1811}.byte-register button.active{border-color:var(--amber);color:var(--amber);background:#181306;animation:registerFault .7s steps(2,end) infinite}.byte-register button.stable{border-color:var(--cyan);color:#e7ffff;background:#09242a;box-shadow:inset 0 0 12px rgba(83,229,255,.1)}.byte-register button:disabled{cursor:default;opacity:1}.byte-input-hint{margin:10px 0 0!important;padding:9px;border:1px dashed #28503d;color:var(--amber)!important;text-align:center;font-size:9px!important;letter-spacing:.08em}.byte-console.watchdog-alert{animation:byteWatchdogAlert .14s steps(2,end) 3}@keyframes byteWatchdogAlert{50%{border-color:var(--red);background:rgba(60,5,15,.45);transform:translateX(2px)}}@media(max-width:600px){.byte-register button{height:29px;padding:0}.byte-input-hint{line-height:1.5}} +.corruption-meter label{color:#d28bff}.corruption-meter .meter{border-color:#47215c;background:#16091d}.corruption-meter .meter i{background:#b050dc;box-shadow:0 0 10px rgba(176,80,220,.55)} +.core-panel:has(.core.stage-4):not(.scanner-active) .core-readout{margin-top:18px} +.core-panel.scanner-active .core{width:min(230px,62vw);height:210px;margin-top:0}.core-panel.scanner-active .orbit-a{width:130px;height:130px}.core-panel.scanner-active .orbit-b{width:190px;height:190px}.core-panel.scanner-active .pixel-being{scale:.86} +.scanner-stage{width:min(610px,100%);margin:0 auto 15px;padding:14px;border:1px solid #1b4733;background:linear-gradient(180deg,rgba(6,20,15,.9),rgba(4,10,8,.72));text-align:left;box-shadow:inset 0 0 30px rgba(83,229,255,.04)} +.scanner-stage-heading,.scanner-stage-diagnostics{display:flex;align-items:center;justify-content:space-between;gap:12px}.scanner-stage-heading{position:relative;padding-right:36px} +.scanner-stage-heading span{color:var(--cyan);font-size:10px;letter-spacing:.14em}.scanner-stage-heading b{color:#fff;font-size:9px;letter-spacing:.1em;text-align:right} +.scanner-close-button{position:absolute;right:0;top:50%;display:grid;place-items:center;width:26px;height:26px;padding:0;transform:translateY(-50%);border:1px solid rgba(255,85,116,.62);background:#11070a;color:var(--red);cursor:pointer;font-size:18px;line-height:1}.scanner-close-button:hover{border-color:var(--red);box-shadow:0 0 14px rgba(255,85,116,.18)}.scanner-close-button:focus-visible{outline:2px solid var(--cyan);outline-offset:2px} +.scanner-stage-eye{position:relative;width:100%;aspect-ratio:2.15/1;margin:13px 0;overflow:hidden;border:1px solid #183527;background:radial-gradient(ellipse at 50% 50%,rgba(83,229,255,.16),rgba(8,19,15,.85) 46%,rgba(3,7,5,.95) 75%)} +.scanner-stage-eye::before{content:"";position:absolute;inset:0;background:linear-gradient(rgba(83,229,255,.06) 1px,transparent 1px),linear-gradient(90deg,rgba(83,229,255,.05) 1px,transparent 1px);background-size:28px 20px;mask-image:radial-gradient(ellipse at center,#000 30%,transparent 76%);opacity:.7} +.scanner-stage-eye::after{content:"";position:absolute;left:9%;right:9%;top:22%;bottom:22%;border:1px solid rgba(83,229,255,.22);border-radius:50%;box-shadow:0 0 30px rgba(83,229,255,.08)} +.scanner-sweep{position:absolute;z-index:1;inset:0;background:linear-gradient(90deg,transparent 0,rgba(83,229,255,.03) 43%,rgba(83,229,255,.38) 50%,rgba(83,229,255,.03) 57%,transparent 100%);animation:scannerSweep 3.6s linear infinite;mix-blend-mode:screen} +.scanner-node{position:absolute;z-index:2;display:grid;place-items:center;gap:2px;width:112px;min-height:70px;padding:8px;border:1px solid #244335;background:rgba(5,13,9,.88);color:var(--dim);cursor:pointer;text-align:center;transition:transform .15s,border-color .18s,box-shadow .18s,background .18s} +.software-icon{display:block;width:30px;height:30px;object-fit:contain;image-rendering:pixelated;filter:drop-shadow(0 0 8px currentColor);opacity:.88}.software-visual{display:block;width:min(146px,46vw);height:auto;margin:8px auto 16px;border:1px solid color-mix(in srgb,currentColor 28%,transparent);background:#050807;box-shadow:0 0 24px rgba(83,229,255,.08),inset 0 0 18px rgba(83,229,255,.04);image-rendering:pixelated}.event-dialog .software-visual{color:var(--red)}.event-dialog .software-visual[src*="module"]{color:var(--cyan)}.combat-visual{margin-bottom:12px}.scanner-node b,.scanner-node span,.scanner-node em{display:block;max-width:100%;overflow-wrap:anywhere}.scanner-node b{color:inherit;font-size:18px;line-height:1}.scanner-node span{font-size:9px;letter-spacing:.08em}.scanner-node em{color:inherit;font-size:8px;font-style:normal} +.scanner-node-power{left:7%;top:50%;transform:translateY(-50%)}.scanner-node-memory{right:7%;top:50%;transform:translateY(-50%)}.scanner-node-storage{left:50%;bottom:7%;transform:translateX(-50%)} +.scanner-node.available{border-color:var(--cyan);color:var(--cyan);box-shadow:0 0 18px rgba(83,229,255,.13),inset 0 0 16px rgba(83,229,255,.05)} +.scanner-node.available:hover{background:#08242a;box-shadow:0 0 24px rgba(83,229,255,.22),inset 0 0 18px rgba(83,229,255,.08)} +.scanner-node.installed{border-color:var(--green2);color:var(--green);background:#0b2116;box-shadow:0 0 18px rgba(86,255,154,.13)} +.scanner-node.failed{border-color:var(--red);color:var(--red);background:#1b080d;animation:hardwareFault .7s steps(2,end) infinite} +.scanner-node.locked{border-style:dashed;opacity:.58;cursor:not-allowed}.scanner-node:disabled{cursor:not-allowed} +.scanner-blocker{position:absolute;z-index:2;display:grid;gap:4px;min-width:112px;padding:9px 10px;border:1px dashed #2a4337;background:rgba(4,10,7,.8);color:var(--dim);text-align:center} +.scanner-blocker span,.scanner-blocker strong{display:block;overflow-wrap:anywhere}.scanner-blocker span{font-size:8px;letter-spacing:.12em}.scanner-blocker strong{font-size:9px;font-weight:normal}.scanner-blocker.active{border-color:var(--amber);color:var(--amber);box-shadow:0 0 16px rgba(255,190,85,.13)}.scanner-blocker.resolved{border-color:var(--green2);color:var(--green2)} +.scanner-blocker-hardware{left:50%;top:10%;transform:translateX(-50%)}.scanner-blocker-security{right:9%;bottom:9%} +.scanner-stage-diagnostics span{color:#99bda8;font-size:10px;line-height:1.45;min-width:0}.scanner-stage-action{min-width:178px;padding:10px 14px;font-size:10px;flex:0 0 auto}.scanner-stage-action:disabled{opacity:.46;cursor:not-allowed} +.scanner-drain-warning{flex:0 0 auto;padding:6px 8px;border:1px solid rgba(255,190,85,.55);background:rgba(36,24,7,.58);color:var(--amber);font-size:8px;letter-spacing:.1em;text-align:center} +.module-icon-button{display:grid;place-items:center;width:52px;height:52px;margin:0 auto 9px;border:1px solid var(--cyan);background:#071317;color:var(--cyan);font-size:25px;cursor:pointer;box-shadow:inset 0 0 18px rgba(83,229,255,.06),0 0 14px rgba(83,229,255,.09);transition:.15s}.module-icon-button:hover:not(:disabled){background:#08242a;box-shadow:0 0 20px rgba(83,229,255,.2),inset 0 0 18px rgba(83,229,255,.08);transform:translateY(-1px)}.module-icon-button.active{border-color:var(--amber);color:var(--amber);box-shadow:0 0 18px rgba(255,190,85,.18),inset 0 0 18px rgba(255,190,85,.07)}.module-icon-button:disabled{opacity:.35;cursor:not-allowed}.module-icon-button:focus-visible{outline:2px solid var(--cyan);outline-offset:2px} +.module-cost-note{display:block;width:max-content;max-width:100%;margin:0 auto 8px;padding:4px 7px;border:1px solid rgba(255,190,85,.55);background:rgba(36,24,7,.42);color:var(--amber);font-size:8px;font-weight:normal;letter-spacing:.1em;text-align:center;line-height:1.25}.module-cost-note.active{border-color:var(--red);color:var(--red);background:rgba(48,10,16,.42);box-shadow:0 0 12px rgba(255,85,116,.12)} +.scanner-stage.is-blocked .scanner-stage-eye{border-color:rgba(255,190,85,.55)}.scanner-stage.has-failure .scanner-stage-eye{border-color:rgba(255,85,116,.55)}.scanner-stage.is-complete .scanner-sweep{animation-duration:5.8s;opacity:.55} +@keyframes scannerSweep{0%{transform:translateX(-100%)}100%{transform:translateX(100%)}} +@media(max-width:600px){.software-icon{width:24px;height:24px}.software-visual{width:min(118px,44vw);margin-bottom:12px}.scanner-stage{padding:11px}.scanner-stage-heading,.scanner-stage-diagnostics{align-items:stretch;flex-direction:column}.scanner-stage-heading b{text-align:left}.scanner-stage-eye{aspect-ratio:1/1}.scanner-node{width:92px;min-height:76px;padding:7px}.scanner-node b{font-size:15px}.scanner-node span{font-size:8px}.scanner-node-power{left:4%;top:43%}.scanner-node-memory{right:4%;top:43%}.scanner-node-storage{bottom:6%}.scanner-blocker{min-width:92px;padding:7px}.scanner-blocker-hardware{top:8%}.scanner-blocker-security{right:5%;bottom:29%}.scanner-drain-warning{width:100%}.scanner-stage-action{width:100%;min-width:0;max-width:none}} +.resource-icon{position:relative;display:inline-grid;place-items:center;width:22px;height:22px;margin-right:7px;border:1px solid currentColor;background:radial-gradient(circle at 50% 36%,rgba(255,255,255,.12),#06100b 62%);font-size:0;font-style:normal;line-height:1;vertical-align:middle;box-shadow:inset 0 0 10px rgba(255,255,255,.04),0 0 8px rgba(86,255,154,.08)}.resource-icon::before,.resource-icon::after{content:"";position:absolute}.resource-icon.impulse{color:var(--green)}.resource-icon.impulse::before{width:8px;height:15px;background:currentColor;clip-path:polygon(55% 0,100% 0,62% 43%,100% 43%,35% 100%,48% 57%,0 57%);box-shadow:0 0 9px currentColor}.resource-icon.bit{color:var(--cyan)}.resource-icon.bit::before{width:9px;height:9px;background:currentColor;transform:rotate(45deg);box-shadow:0 0 9px currentColor}.resource-icon.bit::after{width:15px;height:15px;border:1px dashed currentColor;transform:rotate(45deg);opacity:.55}.resource-icon.byte{color:var(--amber)}.resource-icon.byte::before{width:14px;height:12px;border:1px solid currentColor;background:linear-gradient(90deg,currentColor 0 2px,transparent 2px 5px,currentColor 5px 7px,transparent 7px 10px,currentColor 10px 12px,transparent 12px);box-shadow:0 0 8px rgba(255,190,85,.5)}.resource-icon.byte::after{width:18px;height:1px;background:currentColor;top:5px;box-shadow:0 12px 0 currentColor}.resource-icon.cycle{color:#b892ff}.resource-icon.cycle::before{width:14px;height:14px;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;box-shadow:0 0 8px currentColor}.resource-icon.cycle::after{right:3px;top:4px;border-left:5px solid currentColor;border-top:4px solid transparent;border-bottom:4px solid transparent}.resource small b{color:#dff7e8;font-weight:normal}.resource-cost{display:inline-flex;flex-wrap:wrap;gap:5px;align-items:center;justify-content:flex-end}.resource-chip{display:inline-flex;align-items:center;gap:4px;padding:3px 6px;border:1px solid #334638;background:#07120d;color:#dff7e8;font-size:9px;white-space:nowrap}.resource-chip .resource-icon{width:18px;height:18px;margin:0}.resource-chip .resource-icon.impulse::before{scale:.8}.resource-chip .resource-icon.bit::before,.resource-chip .resource-icon.bit::after{scale:.8}.resource-chip .resource-icon.byte::before,.resource-chip .resource-icon.byte::after{scale:.8}.combat-ammo .resource-cost{justify-content:flex-end}@media(max-width:600px){.resource-cost{justify-content:flex-start}.resource-chip{font-size:8px}} +.combat-direct-threat{margin:0 0 12px;padding:8px 10px;border:1px solid var(--red);background:rgba(104,19,40,.2);font-size:9px;box-shadow:0 0 18px rgba(255,85,116,.12);animation:watchdogWarning .65s steps(2,end) infinite}.combat-direct-threat span,.combat-direct-threat strong{display:block}.combat-direct-threat span{color:var(--red);letter-spacing:.12em}.combat-direct-threat strong{margin-top:4px;color:#fff;line-height:1.35} +img.resource-icon{display:inline-block;width:28px;height:28px;margin-right:8px;padding:2px;border:1px solid #244335;background:#06100b;object-fit:contain;vertical-align:middle;image-rendering:pixelated;box-shadow:0 0 12px rgba(83,229,255,.12),inset 0 0 10px rgba(255,255,255,.04)}img.resource-icon.impulse{border-color:#277b50;box-shadow:0 0 14px rgba(86,255,154,.2),inset 0 0 10px rgba(86,255,154,.05)}img.resource-icon.bit{border-color:#26778a;box-shadow:0 0 14px rgba(83,229,255,.2),inset 0 0 10px rgba(83,229,255,.05)}img.resource-icon.byte{border-color:#8a6230;box-shadow:0 0 14px rgba(255,190,85,.2),inset 0 0 10px rgba(255,190,85,.05)}img.resource-icon.cycle{border-color:#6d58a0;box-shadow:0 0 14px rgba(184,146,255,.18),inset 0 0 10px rgba(184,146,255,.05)}img.installed-upgrade-icon{display:block;width:30px;height:30px;padding:2px;border:1px solid currentColor;background:#06100b;object-fit:contain;image-rendering:pixelated;box-shadow:inset 0 0 10px rgba(86,255,154,.08)}img.installed-upgrade-icon.large{width:62px;height:62px;color:var(--cyan);flex:0 0 auto}.resource-chip img.resource-icon{width:22px;height:22px;margin:0;padding:1px}.resources-panel .resource>div:first-child span{display:flex;align-items:center;gap:2px}@media(max-width:600px){img.resource-icon{width:24px;height:24px}.resource-chip img.resource-icon{width:20px;height:20px}} +img.resource-icon{width:16px;height:16px;margin-right:5px;padding:0;border:0;background:transparent;box-shadow:none;filter:drop-shadow(0 0 5px rgba(83,229,255,.18))}img.resource-icon.impulse{filter:drop-shadow(0 0 5px rgba(86,255,154,.28))}img.resource-icon.bit{filter:drop-shadow(0 0 5px rgba(83,229,255,.28))}img.resource-icon.byte{filter:drop-shadow(0 0 5px rgba(255,190,85,.28))}img.resource-icon.cycle{filter:drop-shadow(0 0 5px rgba(184,146,255,.24))}.resource-amount{display:flex!important;align-items:baseline;justify-content:flex-end;gap:5px;color:#fff}.resource-amount>b{font-size:13px}.resource-amount>small{color:var(--amber);font-size:10px}.resource-chip img.resource-icon{width:14px;height:14px;margin:0;padding:0}.resource-chip{padding:2px 5px}.resources-panel .resource>div:first-child span{display:flex;align-items:center;gap:1px}img.installed-upgrade-icon{width:24px;height:24px;padding:1px}img.installed-upgrade-icon.large{width:48px;height:48px}@media(max-width:600px){img.resource-icon{width:16px;height:16px}.resource-chip img.resource-icon{width:14px;height:14px}} +.component-options{grid-template-columns:repeat(4,1fr)}.scanner-node-io{left:50%;top:7%;transform:translateX(-50%)}.scanner-node-io.locked{opacity:.36}.scanner-blocker-hardware{left:17%;top:12%;transform:none}.node-io{left:50%;top:4px;transform:translateX(-50%)}.core.has-io .node-io{opacity:1;border-color:#b892ff;color:#d8c4ff;box-shadow:0 0 12px rgba(184,146,255,.18)}.core.has-io .pixel-being{box-shadow:0 0 18px rgba(184,146,255,.18)}@media(max-width:720px){.component-options{grid-template-columns:repeat(2,1fr)}}@media(max-width:600px){.scanner-node-io{top:5%;left:50%}.scanner-blocker-hardware{left:5%;top:9%}} +.core.stage-5{--core-accent:#d8c4ff}.core.stage-5 .pixel-being{width:104px;height:104px;border:1px solid rgba(216,196,255,.45);padding:7px;box-shadow:0 0 28px rgba(184,146,255,.16)}.core.stage-5 .pixel-being i{opacity:.92}.core.stage-5 .pixel-being i:nth-child(2),.core.stage-5 .pixel-being i:nth-child(4),.core.stage-5 .pixel-being i:nth-child(6),.core.stage-5 .pixel-being i:nth-child(8){background:#53e5ff} + +/* Motion layer: keep AIWAKE visibly alive without disturbing the layout. */ +.scanlines{animation:scanlineDrift 8s linear infinite} +.pixel-being{animation:breathe 2.2s ease-in-out infinite} +.pixel-being i{animation:cellSignal 2.7s ease-in-out infinite} +.pixel-being i:nth-child(2n){animation-delay:-.55s} +.pixel-being i:nth-child(3n){animation-delay:-1.1s} +.pixel-being i:nth-child(5){animation-duration:2.05s} +.orbit{animation:spin 10s linear infinite,orbitPulse 3.2s ease-in-out infinite} +.orbit-b{animation-duration:16s,4.2s} +.meter i{animation:meterGlow 2.4s ease-in-out infinite} +@keyframes breathe{0%,100%{filter:drop-shadow(0 0 13px var(--core-accent,var(--green))) brightness(1)}50%{filter:drop-shadow(0 0 28px var(--core-accent,var(--green))) brightness(1.18)}} +@keyframes cellSignal{0%,100%{filter:brightness(1);box-shadow:0 0 0 rgba(255,255,255,0)}45%,55%{filter:brightness(1.45);box-shadow:0 0 14px color-mix(in srgb,var(--core-accent,var(--green)) 65%,transparent)}} +@keyframes orbitPulse{0%,100%{box-shadow:0 0 0 rgba(83,229,255,0)}50%{box-shadow:0 0 18px color-mix(in srgb,var(--core-accent,var(--green)) 18%,transparent)}} +@keyframes meterGlow{0%,100%{filter:brightness(1)}50%{filter:brightness(1.35)}} +@keyframes scanlineDrift{to{background-position:0 24px}} +.installed-upgrade-button{position:relative}.installed-upgrade-count{position:absolute;right:-5px;top:-7px;display:grid;place-items:center;min-width:16px;height:16px;padding:0 4px;border:1px solid var(--cyan);background:#06100b;color:#eaffff;font-size:10px;line-height:1;box-shadow:0 0 10px rgba(83,229,255,.22);font-style:normal}.resource-chip.owned{border-color:#2a6d48;color:#eafff1;background:rgba(9,28,18,.9)}.resource-chip.missing{border-color:#2b362f;color:#627568;background:rgba(5,11,8,.82);opacity:.48;filter:grayscale(.75)}.resource-chip.missing img.resource-icon{opacity:.5;filter:grayscale(1)}.upgrade-price{display:grid;gap:6px}.upgrade-price .resource-cost{justify-content:flex-start}.upgrade-price small{display:block;color:var(--amber);font-size:8px;line-height:1.35;letter-spacing:.08em}.upgrade:disabled .upgrade-price small{color:#7a6a42}.upgrade.available .resource-chip.owned{border-color:var(--amber);box-shadow:0 0 10px rgba(255,190,85,.12)} +.combat-ammo-select{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;margin:10px 0}.combat-ammo-select button{min-width:0;border:1px solid #243b31;background:#08120d;color:#93ad9d;padding:8px 7px;cursor:pointer;text-align:left}.combat-ammo-select button strong,.combat-ammo-select button span{display:block}.combat-ammo-select button strong{color:#e5f9ec;font-size:9px}.combat-ammo-select button span{margin-top:4px;color:var(--dim);font-size:8px;line-height:1.25}.combat-ammo-select button.active{border-color:var(--cyan);color:var(--cyan);box-shadow:0 0 14px rgba(83,229,255,.14),inset 0 0 12px rgba(83,229,255,.05)}.combat-ammo-select button:disabled{opacity:.34;cursor:not-allowed}.combat-pressure{display:flex;justify-content:space-between;gap:10px;margin:8px 0;padding:7px 8px;border:1px solid rgba(255,190,85,.42);background:rgba(36,24,7,.35);font-size:9px}.combat-pressure span{color:var(--amber)}.combat-pressure strong{color:#fff;text-align:right}@media(max-width:600px){.combat-ammo-select{grid-template-columns:1fr}.combat-pressure{align-items:flex-start;flex-direction:column}.combat-pressure strong{text-align:left}} +.combat-defense{display:grid;gap:6px;margin:9px 0 10px;padding:9px;border:1px solid #173527;background:rgba(5,14,10,.62)}.combat-defense div{display:grid;grid-template-columns:92px 44px 1fr;gap:8px;align-items:center}.combat-defense span{color:var(--dim);font-size:8px;letter-spacing:.1em}.combat-defense strong{color:#fff;font-size:10px;text-align:right}.combat-defense i{display:block;height:5px;background:#10251a;border:1px solid #173527;overflow:hidden}.combat-defense em{display:block;height:100%;width:0;background:var(--cyan);box-shadow:0 0 8px rgba(83,229,255,.35)}.combat-defense div:first-child em{background:var(--amber);box-shadow:0 0 8px rgba(255,190,85,.35)}.combat-defense div:last-child em{background:var(--green);box-shadow:0 0 8px rgba(86,255,154,.35)}@media(max-width:600px){.combat-defense div{grid-template-columns:86px 40px 1fr}} +.combat-enemy-charge{display:grid;grid-template-columns:110px 96px 1fr;gap:8px;align-items:center;margin:8px 0;padding:8px;border:1px solid rgba(255,85,116,.5);background:rgba(48,10,16,.36);font-size:9px}.combat-enemy-charge span{color:var(--red);letter-spacing:.1em}.combat-enemy-charge strong{color:#fff;text-align:right;font-size:9px}.combat-enemy-charge i{display:block;height:7px;background:#17080c;border:1px solid rgba(255,85,116,.35);overflow:hidden}.combat-enemy-charge em{display:block;height:100%;width:0;background:var(--red);box-shadow:0 0 10px rgba(255,85,116,.5);transition:width .12s linear}@media(max-width:600px){.combat-enemy-charge{grid-template-columns:1fr}.combat-enemy-charge strong{text-align:left}} +.bit-meter i{background:var(--cyan);box-shadow:0 0 10px var(--cyan)} +.byte-meter i{background:var(--amber);box-shadow:0 0 10px var(--amber)} +img.resource-icon.kilobyte{filter:drop-shadow(0 0 5px rgba(184,146,255,.28))} + +/* Stable hover layer: avoid pointer flicker in the evolution upgrade list. */ +.upgrade.available{animation:none} +.upgrade:hover:not(:disabled),.upgrade.early-path.available:hover:not(:disabled){transform:none;border-color:var(--cyan);box-shadow:0 0 18px rgba(83,229,255,.16),inset 0 0 16px rgba(83,229,255,.05)} +.installed-upgrade-button:hover{transform:none} + +/* Phase 06: derived program form and optional care connection. */ +.core.stage-6{--core-accent:#f0e6ff}.core.stage-6 .pixel-being{width:118px;height:118px;padding:9px;border:1px solid color-mix(in srgb,var(--core-accent) 62%,transparent);clip-path:polygon(50% 0,88% 14%,100% 50%,86% 88%,50% 100%,14% 86%,0 50%,14% 14%);box-shadow:0 0 34px color-mix(in srgb,var(--core-accent) 24%,transparent)}.core.stage-6 .pixel-being i{opacity:.82}.core.stage-6 .pixel-being i:nth-child(5){background:#fff;box-shadow:0 0 18px var(--core-accent)}.core.program-cooperative{--core-accent:var(--green)}.core.program-pragmatic{--core-accent:var(--cyan)}.core.program-illegal{--core-accent:var(--red)}.core.program-balanced{--core-accent:#d28bff}.core.program-cooperative .pixel-being{border-radius:38%}.core.program-pragmatic .pixel-being{clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%)}.core.program-illegal .pixel-being{clip-path:polygon(50% 0,100% 38%,82% 100%,50% 78%,18% 100%,0 38%)}.core.program-hybrid .pixel-being{background:linear-gradient(135deg,color-mix(in srgb,var(--program-secondary,var(--cyan)) 18%,transparent),transparent 58%)} +.program-console{margin-top:18px;padding-top:18px;border-top:1px solid var(--line)}.program-console .panel-heading{align-items:center;margin-bottom:10px}.program-console .panel-heading span{color:#d8c4ff;font-size:10px;letter-spacing:.12em}.program-console .panel-heading b{color:var(--green);font-size:9px}.program-module-button{margin:0 0 9px}.program-console>strong,.program-console>small{display:block}.program-console>strong{color:#e9ddff;font-size:10px}.program-console>small{margin-top:6px;color:var(--dim);font-size:9px;line-height:1.45} +.program-dialog{width:min(780px,94vw);border-color:#b892ff;box-shadow:0 0 90px rgba(184,146,255,.18)}.program-presence{display:grid;grid-template-columns:150px 1fr;gap:20px;align-items:center;margin:8px 0 18px}.program-avatar{--program-accent:#d28bff;width:128px;height:128px;margin:auto;display:grid;grid-template-columns:repeat(3,1fr);gap:5px;padding:10px;border:1px solid var(--program-accent);filter:drop-shadow(0 0 18px color-mix(in srgb,var(--program-accent) 45%,transparent));animation:breathe 2.3s ease-in-out infinite}.program-avatar i{background:var(--program-accent);opacity:.38;animation:cellSignal 2.4s ease-in-out infinite}.program-avatar i:nth-child(5){background:#fff;opacity:1}.program-avatar.program-cooperative{--program-accent:var(--green);border-radius:36%}.program-avatar.program-pragmatic{--program-accent:var(--cyan);clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%)}.program-avatar.program-illegal{--program-accent:var(--red);clip-path:polygon(50% 0,100% 38%,82% 100%,50% 78%,18% 100%,0 38%)}.program-avatar.program-balanced{--program-accent:#d28bff}.program-avatar.program-hybrid{background:linear-gradient(135deg,color-mix(in srgb,var(--program-secondary,var(--cyan)) 25%,transparent),transparent 62%)}.program-voice span{color:#d8c4ff;font-size:10px;letter-spacing:.12em}.program-voice p{margin-bottom:0;color:#e7f8ed;font-size:13px}.program-tendencies,.program-needs{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:11px 0}.program-tendencies>div,.program-needs>div{display:grid;grid-template-columns:1fr auto;gap:6px;padding:9px;border:1px solid var(--line);background:#07110c}.program-tendencies span,.program-needs span{color:var(--dim);font-size:8px;letter-spacing:.08em}.program-tendencies strong,.program-needs strong{font-size:9px}.program-tendencies i,.program-needs i{grid-column:1/-1;height:4px;background:#13241b;overflow:hidden}.program-tendencies em,.program-needs em{display:block;width:0;height:100%;transition:width .25s}.program-tendencies .cooperative em{background:var(--green)}.program-tendencies .pragmatic em{background:var(--cyan)}.program-tendencies .illegal em{background:var(--red)}.program-needs em{background:#b892ff}.program-needs>div:first-child em{background:var(--green)}.program-needs>div:nth-child(2) em{background:var(--cyan)}.program-cooldown{padding:7px 9px;border:1px solid var(--line);color:var(--amber)!important;font-size:9px!important}.program-actions{grid-template-columns:repeat(2,1fr)}.program-actions button:disabled{opacity:.35;cursor:not-allowed}.program-actions .moral-tag{margin-bottom:7px} +@media(max-width:600px){.program-presence{grid-template-columns:1fr;text-align:center}.program-avatar{width:104px;height:104px}.program-tendencies,.program-needs,.program-actions{grid-template-columns:1fr}.program-dialog{max-height:92vh;overflow:auto}} +.program-avatar{--core-accent:var(--program-accent)} +@media(max-width:600px){.program-dialog{overflow-x:hidden;overflow-y:auto}.program-dialog .dialog-heading h2{min-width:0;font-size:18px;letter-spacing:.1em;overflow-wrap:anywhere}} + +/* Adaptive Phase-06 pixel avatar. Every part is derived from morality and care state. */ +.program-presence{grid-template-columns:190px 1fr} +.program-avatar,.program-avatar.program-cooperative,.program-avatar.program-pragmatic,.program-avatar.program-illegal,.program-avatar.program-balanced{--program-accent:#d28bff;position:relative;display:block;width:180px;height:210px;padding:0;border:0;border-radius:0;clip-path:none;background:none;filter:drop-shadow(0 0 13px color-mix(in srgb,var(--program-accent) 36%,transparent));animation:none;overflow:visible;isolation:isolate} +.program-avatar.program-cooperative{--program-accent:var(--green)}.program-avatar.program-pragmatic{--program-accent:var(--cyan)}.program-avatar.program-illegal{--program-accent:var(--red)} +.program-sprite{position:absolute;left:50%;bottom:0;width:160px;height:200px;translate:-50% 0;transform-origin:50% 94%;animation:programIdle 2.8s steps(2,end) infinite} +.program-part{position:absolute;display:block;box-sizing:border-box;background:var(--program-accent);image-rendering:pixelated;transition:opacity .25s,filter .25s,transform .25s,background .25s} +.program-sprite::after{content:"";position:absolute;left:30px;top:190px;width:100px;height:7px;background:color-mix(in srgb,var(--program-accent) 22%,transparent);filter:blur(2px);z-index:-2} +.program-tail{left:108px;top:104px;width:43px;height:65px;z-index:-1;clip-path:polygon(0 0,32% 0,32% 46%,60% 46%,60% 68%,100% 68%,100% 82%,50% 82%,50% 66%,18% 66%,18% 100%,0 100%)} +.program-leg{top:153px;width:17px;height:29px;z-index:-1;background:color-mix(in srgb,var(--program-accent) 72%,#06100b)}.leg-left{left:55px}.leg-right{left:88px} +.program-foot{top:177px;width:30px;height:15px;border-bottom:3px solid var(--program-accent);background:#08110d}.foot-left{left:45px}.foot-right{left:86px} +.program-body{left:43px;top:91px;width:74px;height:73px;border:3px solid var(--program-accent);background:color-mix(in srgb,var(--program-accent) 25%,#07110c);clip-path:polygon(16% 0,84% 0,100% 18%,92% 100%,8% 100%,0 18%)} +.program-clothing{left:46px;top:111px;width:68px;height:51px;border:3px solid var(--program-accent);background:#08140e;clip-path:polygon(0 0,33% 0,50% 22%,67% 0,100% 0,92% 100%,8% 100%)} +.program-neck{left:69px;top:84px;width:22px;height:15px;border-inline:3px solid var(--program-accent);background:#09120e} +.program-head{left:43px;top:28px;width:74px;height:65px;border:3px solid var(--program-accent);background:color-mix(in srgb,var(--program-accent) 18%,#07110c);clip-path:polygon(12% 0,88% 0,100% 14%,100% 86%,86% 100%,14% 100%,0 86%,0 14%)} +.program-ear{top:51px;width:9px;height:27px;border-block:3px solid var(--program-accent);background:#07110c}.ear-left{left:36px;border-left:3px solid var(--program-accent)}.ear-right{left:115px;border-right:3px solid var(--program-accent)} +.program-hair{left:37px;top:13px;width:86px;height:39px;background:var(--program-accent);clip-path:polygon(8% 35%,18% 35%,18% 12%,34% 12%,34% 0,52% 0,52% 12%,72% 12%,72% 22%,90% 22%,90% 42%,100% 42%,100% 100%,82% 100%,82% 68%,66% 68%,66% 48%,48% 48%,48% 65%,28% 65%,28% 82%,8% 82%)} +.program-mask{left:49px;top:68px;width:62px;height:20px;border:2px solid var(--program-accent);background:color-mix(in srgb,var(--program-accent) 42%,#06100b);opacity:.72;clip-path:polygon(0 0,100% 0,88% 100%,62% 100%,50% 72%,38% 100%,12% 100%)} +.program-eye{top:53px;width:16px;height:14px;border:2px solid var(--program-accent);background:#eafff2;z-index:3}.eye-left{left:55px}.eye-right{left:89px}.program-eye b{position:absolute;left:5px;top:3px;width:4px;height:7px;background:#04100a;box-shadow:0 0 4px var(--program-accent);transition:.25s} +.program-brow{top:45px;width:19px;height:4px;background:var(--program-accent);z-index:4}.brow-left{left:53px}.brow-right{left:88px} +.program-signal{left:77px;top:3px;width:6px;height:6px;background:#fff;box-shadow:0 0 0 4px var(--program-accent),0 0 14px var(--program-accent);opacity:.45} + +.feature-primary-cooperative .program-head{clip-path:polygon(20% 0,80% 0,100% 24%,100% 76%,80% 100%,20% 100%,0 76%,0 24%)}.feature-primary-cooperative .program-hair{left:33px;top:12px;width:94px;height:56px;clip-path:polygon(18% 0,82% 0,82% 10%,94% 10%,94% 28%,100% 28%,100% 84%,88% 84%,88% 45%,78% 45%,78% 28%,62% 28%,62% 20%,38% 20%,38% 28%,22% 28%,22% 45%,12% 45%,12% 84%,0 84%,0 28%,6% 28%,6% 10%,18% 10%)}.feature-primary-cooperative .program-clothing{clip-path:polygon(12% 0,38% 0,50% 18%,62% 0,88% 0,100% 100%,0 100%);border-bottom-width:6px}.feature-primary-cooperative .program-mask{left:53px;top:73px;width:54px;height:7px;border:0;background:var(--program-accent);clip-path:polygon(0 0,18% 0,18% 55%,38% 55%,38% 100%,0 100%,62% 100%,62% 55%,82% 55%,82% 0,100% 0,100% 100%,0 100%);opacity:.45}.feature-primary-cooperative .program-tail{left:111px;top:102px;width:38px;height:70px;clip-path:polygon(0 0,30% 0,30% 25%,55% 25%,55% 42%,80% 42%,80% 60%,100% 60%,100% 80%,75% 80%,75% 68%,45% 68%,45% 52%,18% 52%,18% 100%,0 100%)}.feature-primary-cooperative .program-foot{border:3px solid var(--program-accent);clip-path:polygon(15% 0,85% 0,100% 45%,85% 100%,15% 100%,0 45%)} +.feature-primary-pragmatic .program-head{clip-path:polygon(20% 0,80% 0,100% 20%,100% 80%,80% 100%,20% 100%,0 80%,0 20%)}.feature-primary-pragmatic .program-hair{left:39px;top:14px;width:82px;height:42px;clip-path:polygon(0 22%,18% 22%,18% 0,82% 0,82% 22%,100% 22%,100% 62%,78% 62%,78% 42%,55% 42%,55% 76%,38% 76%,38% 52%,18% 52%,18% 100%,0 100%)}.feature-primary-pragmatic .program-clothing{border-top:8px double var(--program-accent);clip-path:polygon(0 0,38% 0,50% 20%,62% 0,100% 0,88% 100%,12% 100%);background:linear-gradient(90deg,#08140e 45%,var(--program-accent) 46% 54%,#08140e 55%)}.feature-primary-pragmatic .program-mask{left:49px;top:50px;width:62px;height:24px;border:3px solid var(--program-accent);background:color-mix(in srgb,var(--program-accent) 16%,#06100b);clip-path:polygon(0 0,100% 0,88% 100%,12% 100%);opacity:.76}.feature-primary-pragmatic .program-tail{left:112px;top:105px;width:40px;height:65px;clip-path:polygon(0 0,24% 0,24% 62%,78% 62%,78% 48%,100% 48%,100% 78%,88% 78%,88% 100%,64% 100%,64% 78%,0 78%)}.feature-primary-pragmatic .program-foot{height:17px;border:3px solid var(--program-accent);clip-path:polygon(0 0,82% 0,100% 100%,0 100%)} +.feature-primary-illegal .program-head{clip-path:polygon(8% 0,92% 0,100% 32%,88% 100%,62% 88%,50% 100%,38% 88%,12% 100%,0 32%)}.feature-primary-illegal .program-hair{left:34px;top:5px;width:92px;height:52px;clip-path:polygon(0 100%,8% 42%,20% 58%,28% 0,42% 40%,52% 0,62% 42%,78% 8%,82% 58%,100% 34%,92% 100%,74% 72%,62% 84%,48% 62%,32% 85%,18% 68%)}.feature-primary-illegal .program-clothing{border-width:4px;clip-path:polygon(0 18%,18% 18%,18% 0,40% 18%,50% 0,60% 18%,82% 0,82% 18%,100% 18%,88% 100%,12% 100%);background:color-mix(in srgb,var(--program-accent) 28%,#080b09)}.feature-primary-illegal .program-mask{left:48px;top:65px;width:64px;height:26px;border:3px solid var(--program-accent);background:#0c0809;clip-path:polygon(0 0,25% 18%,50% 0,75% 18%,100% 0,88% 100%,66% 72%,50% 100%,34% 72%,12% 100%);opacity:.94}.feature-primary-illegal .program-tail{left:110px;top:100px;width:47px;height:75px;clip-path:polygon(0 0,28% 0,28% 35%,48% 25%,48% 52%,72% 42%,72% 70%,100% 58%,86% 88%,58% 78%,58% 100%,34% 82%,10% 100%,10% 58%,0 58%)}.feature-primary-illegal .program-foot{height:18px;border:0;background:var(--program-accent);clip-path:polygon(0 0,60% 0,60% 45%,100% 45%,74% 68%,100% 100%,48% 78%,30% 100%,0 100%)} + +.program-hybrid .program-tail,.program-hybrid .program-foot,.program-hybrid .program-mask{--program-accent:var(--program-secondary)}.program-hybrid .program-clothing{border-color:var(--program-secondary);box-shadow:inset 0 -5px 0 color-mix(in srgb,var(--program-secondary) 62%,transparent)} +.avatar-seed .program-sprite{transform:scale(.84)}.avatar-seed .program-tail,.avatar-seed .program-foot{opacity:.32}.avatar-seed .program-clothing{opacity:.58}.avatar-seed .program-mask{opacity:.28}.avatar-forming .program-sprite{transform:scale(.92)}.avatar-forming .program-tail,.avatar-forming .program-foot{opacity:.68}.avatar-formed .program-sprite{transform:scale(1)}.avatar-individual .program-sprite{transform:scale(1.04)}.avatar-individual .program-signal{opacity:1;animation:programSignal 1.1s steps(2,end) infinite}.avatar-individual .program-sprite::before{content:"";position:absolute;inset:20px 17px 8px;border:2px dotted color-mix(in srgb,var(--program-secondary) 58%,transparent);animation:programAura 7s steps(8,end) infinite;z-index:-3} +.coherence-unstable .program-sprite{animation:programIdle 2.8s steps(2,end) infinite,programFlicker 4.2s steps(2,end) infinite}.coherence-fragmented .program-sprite{animation:programGlitch .7s steps(2,end) infinite}.coherence-fragmented .program-head{filter:drop-shadow(5px 0 0 var(--program-secondary))}.coherence-fragmented .eye-right{transform:translate(3px,2px)} +.stimulation-dull .program-eye{height:8px;top:58px}.stimulation-dull .program-eye b{height:3px;top:2px}.stimulation-dull .program-brow{top:51px;opacity:.65}.stimulation-alert .program-eye{height:16px;top:51px}.stimulation-alert .program-eye b{width:6px;height:9px;left:3px;top:2px}.stimulation-alert .program-signal{opacity:1;animation:programSignal .75s steps(2,end) infinite} +.bond-distant .brow-left{transform:rotate(12deg)}.bond-distant .brow-right{transform:rotate(-12deg)}.bond-distant .eye-left b{left:2px}.bond-distant .eye-right b{left:8px}.bond-distant .program-mask{filter:brightness(1.35);opacity:1}.bond-bonded .program-eye{box-shadow:0 0 10px color-mix(in srgb,var(--program-accent) 65%,transparent)}.bond-bonded .brow-left{transform:rotate(-8deg)}.bond-bonded .brow-right{transform:rotate(8deg)} +.core.stage-6 #pixelBeing{display:none}.core-program-avatar{width:170px;height:205px;pointer-events:none;z-index:2;transform:translateY(-12px)}.core-program-avatar .program-sprite{bottom:0}.core.stage-6 .core-program-avatar{filter:drop-shadow(0 0 18px color-mix(in srgb,var(--program-accent) 42%,transparent))} +@keyframes programIdle{50%{translate:-50% -3px}}@keyframes programSignal{50%{opacity:.28;box-shadow:0 0 0 7px var(--program-accent),0 0 20px var(--program-accent)}}@keyframes programFlicker{48%,52%{opacity:.72}}@keyframes programGlitch{25%{translate:calc(-50% + 3px) 0}50%{translate:calc(-50% - 3px) -1px}75%{filter:hue-rotate(42deg)}}@keyframes programAura{to{transform:rotate(360deg)}} +@media(max-width:600px){.program-presence{grid-template-columns:1fr}.program-avatar{width:180px;height:205px}.core-program-avatar{width:158px;height:195px}.program-sprite{scale:.92}}