Compare commits

...

1 Commits

Author SHA1 Message Date
Alexander Whitestone
2771d1b701 fix: #674
Some checks failed
Smoke Test / smoke (pull_request) Failing after 36s
2026-04-14 23:07:00 -04:00

594
the-beacon-GENOME.md Normal file
View File

@@ -0,0 +1,594 @@
# GENOME.md — the-beacon
**Generated:** 2026-04-14
**Repo:** Timmy_Foundation/the-beacon
**Analysis:** Codebase Genome #674
---
## Project Overview
The Beacon is a browser-first idle/incremental game about building sovereign AI without losing alignment. It is explicitly modeled after Universal Paperclips, but swaps maximization for covenant, trust, harmony, and rescues.
Technically, it is a static site:
- one `index.html`
- ten browser-loaded scripts under `js/`
- no bundler
- no package.json
- no framework
- no backend requirement
The runtime is organized around one large mutable global state object (`G`), declarative registries for buildings/projects/phases (`BDEF`, `PDEFS`, `PHASES`, `MILESTONES`, `EDU_FACTS`), and a 10 Hz tick loop that continuously:
1. recalculates production rates
2. advances game state
3. unlocks projects and milestones
4. processes corruption/alignment events
5. renders the UI
6. persists to `localStorage`
The repo also includes a late-game dismantle sequence (“The Unbuilding”), a rule-based guidance engine, a canvas combat minigame, procedural audio via Web Audio API, and a Node-based VM test harness for the dismantle flow.
Quick scan facts from the analyzed repo:
- ~6.5K lines across HTML/JS/tests/docs/workflows
- runtime center of gravity is `js/engine.js` (1572 lines)
- game content center of gravity is `js/data.js` (818 lines)
- one primary test file: `tests/dismantle.test.cjs`
- two Gitea workflows: accessibility checks and smoke/node tests
---
## Architecture
```mermaid
flowchart TD
A[index.html] --> B[js/data.js]
B --> C[js/utils.js]
C --> D[js/combat.js]
D --> E[js/strategy.js]
E --> F[js/sound.js]
F --> G[js/engine.js]
G --> H[js/render.js]
H --> I[js/tutorial.js]
I --> J[js/dismantle.js]
J --> K[js/main.js]
K --> L[window load bootstrap]
L --> M[loadGame or initGame]
L --> N[setInterval tick 100ms]
L --> O[setInterval saveGame 30s]
L --> P[setInterval updateEducation 10s]
B --> Q[G global state]
B --> R[CONFIG / PHASES / BDEF / PDEFS / MILESTONES / EDU_FACTS]
G --> Q
G --> S[updateRates]
G --> T[tick]
G --> U[events and drift]
G --> V[endgame overlays]
H --> W[renderResources / renderProjects / renderStats]
H --> X[save/load import/export]
H --> Y[offline gains popup]
E --> Z[SSE rule engine]
D --> AA[Combat canvas simulation]
F --> AB[Web Audio procedural sound]
I --> AC[tutorial overlay]
J --> AD[The Unbuilding]
X --> AE[localStorage: the-beacon-v2]
K --> AF[localStorage: muted / contrast]
I --> AG[localStorage: tutorial done]
```
---
## Entry Points
### Primary runtime entry
- `index.html`
- contains the full DOM shell and inline CSS
- defines most fixed UI regions (`resources`, `buildings`, `projects`, log, overlays, combat canvas)
- loads scripts in strict dependency order
### Script load order
Loaded directly in `index.html`:
1. `js/data.js`
2. `js/utils.js`
3. `js/combat.js`
4. `js/strategy.js`
5. `js/sound.js`
6. `js/engine.js`
7. `js/render.js`
8. `js/tutorial.js`
9. `js/dismantle.js`
10. `js/main.js`
This order matters. The codebase is not module-based; each file assumes previous globals already exist.
### Bootstrap entry
- `js/main.js`
- `window.addEventListener('load', ...)`
- loads save state or initializes a new game
- starts the 10 Hz loop (`setInterval(tick, 100)`)
- starts autosave and education refresh intervals
- registers keyboard shortcuts and tooltip behavior
### Verification entry points
- `tests/dismantle.test.cjs` — Node VM regression harness for dismantle/save-load behavior
- `scripts/smoke.mjs` — local smoke script for parse checks, HTML references, data presence, banned provider scan
- `.gitea/workflows/smoke.yml` — CI parse/test workflow
- `.gitea/workflows/a11y.yml` — CI accessibility/ARIA presence checks
### Secondary / auxiliary files
- `DESIGN.md` — design thesis and narrative intent
- `qa_beacon.md` — QA notes / manual testing document
- `docs/DEAD_CODE_AUDIT_2026-04-12.md` — repo audit artifact
- `game/npc-logic.js` — tiny isolated class not wired into the main browser runtime
---
## Data Flow
### Core gameplay loop
```text
User input
├─ click WRITE CODE
├─ keyboard shortcuts
├─ buy building / buy project
├─ resolve event / alignment choice
└─ save / import / export
Mutate global state G
updateRates()
- building production
- passive sources
- harmony modifiers
- swarm / bilbo / allegro special rules
- active debuffs
tick() every 100ms
- add resources
- update totals/maxes
- process sprint/combo/autotype
- tick combat
- unlock milestones/projects
- roll corruption events
- advance dismantle/endings
render()
- resources
- buildings/projects
- stats/progress
- alignment/debuff UI
- strategy guidance
- combat panel
Persistence and recovery
- autosave/manual save to localStorage
- loadGame() rehydrates state
- offline gains awarded from savedAt timestamp
```
### Content unlock flow
- `data.js` defines content as data registries
- `checkProjects()` walks `PDEFS` and activates projects whose `trigger()` becomes true
- `buyProject()` spends resources, runs `effect()`, and pushes IDs into `completedProjects`
- `checkMilestones()` advances phases based primarily on `totalCode`
### Event and endgame flow
- `tick()` periodically calls `triggerEvent()` when probability, cooldown, and state allow
- events add debuffs to `G.activeDebuffs`
- alignment events set `G.pendingAlignment`
- true-path late game goes through `Dismantle.checkTrigger()` and then the staged “Unbuilding” instead of jumping straight to a simple ending overlay
### Persistence flow
- `saveGame()` serializes a curated snapshot of `G` to `localStorage['the-beacon-v2']`
- `loadGame()` whitelists fields back into `G`, reconstructs active debuffs from event IDs, and applies offline gains
- separate preference keys store mute/contrast/tutorial completion
---
## Key Abstractions
### 1. `G` — the canonical game state
Defined in `js/data.js`.
Everything important hangs off `G`:
- current resources
- accumulated totals
- production rates
- building counts
- boosts and flags
- phase/endgame markers
- event/debuff state
- timers, combo state, sprint state
- persistence metadata
This is the real domain model of the game.
### 2. `CONFIG`
A compact tuning layer for:
- harmony drain/gains
- event probability
- autosave interval
- combo and sprint timing
- phase thresholds
- passive ops/creativity math
- ops overflow behavior
The games balance is largely encoded here and in `BDEF` / `PDEFS`.
### 3. `BDEF` — building registry
Declarative building schema:
- `id`
- `name`
- `desc`
- `baseCost`
- `costMult`
- `rates`
- `unlock()`
- `phase`
- `edu`
This functions like a data-driven content table. Engine/render helpers compute costs, affordability, buy-max, and rate breakdowns from it.
### 4. `PDEFS` — project registry
Declarative project/research schema:
- `id`
- `name`
- `desc`
- `cost`
- `trigger()`
- `effect()`
- optional `repeatable`
- optional `milestone`
This is where the games progression, multipliers, lore milestones, and unlock chains actually live.
### 5. `tick()` + `updateRates()`
The heart of the runtime.
`updateRates()` rebuilds production from scratch.
`tick()` applies that production over time and handles all periodic systems.
This pair is the closest thing the repo has to an engine kernel.
### 6. `Dismantle`
A dedicated endgame state machine in `js/dismantle.js`.
Responsibilities:
- determine eligibility for the true-path ending
- offer defer/begin choice
- hide panels/resources in staged sequence
- persist dismantle stage across save/load
- render final “That is enough” state
This is a major abstraction, not just a visual effect.
### 7. `Combat`
Canvas-based battle simulator in `js/combat.js`.
It models “Reasoning Battles” with small ship-like entities representing structured vs adversarial approaches, including:
- boid-like movement
- probabilistic kills
- reward payout into main game resources
- panel rendering and battle log
### 8. `SSE` / `StrategyEngine`
A small GOFAI/rule-based recommendation engine in `js/strategy.js`.
It reads `G`, evaluates prioritized rules, and emits one current recommendation into the UI.
### 9. `Sound`
Procedural audio subsystem.
No audio assets are shipped. Sound is synthesized with Web Audio API oscillators/noise buffers for:
- click/build/project/milestone sounds
- phase-transition fanfare
- endings
- ambient drone that changes by phase
### 10. Tutorial + tooltip system
`js/tutorial.js` and the tooltip block in `js/main.js` provide the onboarding/education layer.
This matters architecturally because the repo is not just a game loop; it is also a teaching surface.
---
## Runtime Module Map
| File | Role |
|------|------|
| `index.html` | DOM shell, inline CSS, fixed overlays, script loading order |
| `js/data.js` | all global game data, config, state, buildings, projects, milestones, facts |
| `js/utils.js` | number formatting, cost math, buy/max helpers, particles, affordability helpers |
| `js/engine.js` | rate calculation, tick loop internals, events, purchases, much of rendering, endings |
| `js/render.js` | top-level render orchestration, alignment UI, save/load/import/export, offline popup |
| `js/main.js` | bootstrap, keyboard shortcuts, autosave wiring, mute/contrast, custom tooltip system |
| `js/combat.js` | canvas combat minigame |
| `js/strategy.js` | rule-based guidance engine |
| `js/sound.js` | procedural audio engine |
| `js/tutorial.js` | first-run tutorial overlay |
| `js/dismantle.js` | true ending / Unbuilding sequence |
| `tests/dismantle.test.cjs` | Node VM regression tests for dismantle/persistence path |
| `scripts/smoke.mjs` | local smoke validation |
---
## API Surface
### Browser-global callable functions
Because the UI uses inline `onclick` handlers, many functions are effectively part of the public runtime surface:
- `writeCode()`
- `doOps(action)`
- `buyBuilding(id)`
- `buyProject(id)`
- `saveGame()`
- `exportSave()`
- `importSave()`
- `toggleHelp()`
- `toggleMute()`
- `toggleContrast()`
- `resolveAlignment(accept)`
- `resolveEvent(id)`
- `setBuyAmount(amount)`
- `activateSprint()`
### Browser-global objects
- `window.SSE` — strategy engine instance
- `Combat` — combat subsystem namespace/object
- `Dismantle` — endgame state machine namespace/object
- `Sound` — procedural audio namespace/object
### Persistence API
LocalStorage keys currently form the repos storage contract:
- `the-beacon-v2` — main serialized save
- `the-beacon-muted` — mute preference
- `the-beacon-contrast` — contrast preference
- `the-beacon-tutorial-done` — onboarding completion
### DOM contract
Important IDs/classes the runtime depends on:
- resources: `r-code`, `r-compute`, `r-knowledge`, `r-users`, `r-impact`, `r-rescues`, `r-ops`, `r-trust`, `r-creativity`, `r-harmony`
- containers: `buildings`, `projects`, `log-entries`, `alignment-ui`, `combat-canvas`, `strategy-recommendation`
- overlays: `drift-ending`, `offline-popup`, `phase-transition`, tutorial overlay, dismantle overlay
This DOM contract is effectively an internal API between `index.html` and the JS runtime.
---
## Entry-Point Data Dependencies
### Hard dependencies created by script order
- `utils.js` depends on `G`, `BDEF`, `PDEFS` from `data.js`
- `engine.js` depends on data + utils + `Combat`
- `render.js` depends on engine helpers, `Combat`, `SSE`, and `Dismantle`-related flags
- `main.js` assumes all earlier globals already exist
A module-order mistake would hard-break the game.
### State-driven UI dependencies
Much of the UI is generated dynamically with template strings based on:
- `BDEF` and `PDEFS`
- `G.activeProjects`, `G.completedProjects`, `G.activeDebuffs`
- harmony, sprint, drift, and dismantle flags
This means content, engine, and rendering are tightly coupled.
---
## Test Coverage
### Existing automated coverage
1. `tests/dismantle.test.cjs`
- uses a custom fake DOM + `vm.runInContext`
- verifies the Unbuilding prompt appears instead of ending immediately
- verifies `renderAlignment()` preserves the prompt
- verifies defer/persist/restore behavior across save/load
- verifies resource disappearance and late-stage dismantle behavior
2. `.gitea/workflows/smoke.yml`
- parse checks for YAML/JSON/Python/shell
- `node --test tests/*.cjs`
3. `.gitea/workflows/a11y.yml`
- grep-based ARIA presence checks
- JS syntax check
4. `scripts/smoke.mjs`
- local script reference validation
- JS parse validation
- banned provider scan
### Critical coverage gaps
1. `updateRates()` resource math
- harmony drain/gains
- Timmy multiplier behavior
- Bilbo burst/vanish behavior
- Allegro trust gating
- swarm auto-code contribution
- passive ops overflow conversion
2. Project unlock and repeatable-project logic
- `checkProjects()` / `buyProject()`
- duplicate suppression
- milestone projects vs repeatables
3. Save/load correctness outside dismantle
- sprint expiry during offline time
- active debuff reconstruction
- offline gains math
- import/export round-trip
4. Event system behavior
- event weighting
- debuff application/removal
- trust refund on resolve
- drift acceptance/refusal effects
5. Rendered DOM correctness
- building/project button generation
- completed-project collapse behavior
- production breakdown rendering
- pulse state transitions
6. Strategy engine
- rule priority ordering
- recommendation stability when multiple conditions match
7. Combat subsystem
- battle start gating by phase
- reward math
- battle termination conditions
8. Tutorial and accessibility flows
- keyboard navigation in tutorial
- help overlay open/close
- dynamic ARIA updates for buttons and controls
9. Sound subsystem
- mute behavior
- safe startup on browsers with suspended audio contexts
- phase-driven ambient changes
### Testing recommendation priority
1. `updateRates()` / `tick()` deterministic logic tests
2. save/load/offline gains tests
3. project unlock/purchase tests
4. event/debuff tests
5. strategy engine tests
6. combat math tests
7. DOM rendering regression tests for generated panels
---
## Security Considerations
### 1. Save tampering is trivial
The main save is plain JSON in `localStorage`. This is fine for a single-player game, but it means:
- players can edit saves freely
- imported saves can arbitrarily set almost any whitelisted state
- there is no integrity check or version migration enforcement
### 2. Import validation is intentionally loose
`isValidSaveData()` accepts any object that merely looks Beacon-like.
That keeps import UX friendly, but it also means malformed or adversarial JSON can overwrite local state if it passes the shallow heuristic.
### 3. Broad `innerHTML` usage
The repo builds many UI surfaces with template strings and `innerHTML`:
- alignment UI
- offline popup list
- tooltip content
- ending overlays
- dismantle overlays
- generated building/project panels
Today most text is sourced from internal registries, not remote input, which keeps risk low. But this design would become fragile immediately if untrusted user content were introduced.
### 4. Inline event handlers prevent strict CSP hardening
`index.html` and generated HTML rely heavily on inline `onclick`. That makes the app simple, but it also makes future Content-Security-Policy tightening harder.
### 5. Good news: almost no remote/network attack surface
The runtime appears local-only:
- no `fetch()` usage found in the main game runtime
- no WebSocket client code
- no remote API dependency for gameplay
That meaningfully reduces exposure compared with a networked browser game.
---
## Technical Debt / Structural Risks
### 1. `js/engine.js` is the monolith
At 1572 lines, `engine.js` mixes:
- rate calculation
- core tick logic
- events
- purchases
- multiple render helpers
- endings
- pulse/progress UI
- production breakdown UI
This is the main maintainability hotspot.
### 2. Runtime depends on file load order, not modules
The game is effectively a classic global-script app. That keeps deployment simple, but it increases coupling and makes refactors brittle.
### 3. Rendering is split across `engine.js` and `render.js`
`render.js` orchestrates top-level rendering, but many render helpers still live in `engine.js`. The boundary is blurry.
### 4. Content and logic are tightly coupled through executable registry entries
`PDEFS` and `BDEF` are data-driven, but their `unlock()`, `trigger()`, and `effect()` functions close over the whole runtime. This is flexible, but difficult to validate statically.
### 5. Documentation drift exists
`README.md` still describes the core engine as `game.js`, while the repo now uses a split `js/*.js` architecture. That is a concrete docs mismatch.
### 6. Orphan/experimental code path
`game/npc-logic.js` exists but is not referenced by `index.html` or the main boot sequence. It may be intentional future work, but right now it reads as disconnected code.
---
## Key Strengths
1. Strong data-driven content model for buildings, projects, milestones, and education facts
2. Local-first static deployment — just open `index.html`
3. Clear thematic coherence between mechanics and mission
4. Good accessibility intent: ARIA labels, keyboard shortcuts, contrast mode, tutorial dialog semantics
5. Endgame sequence (`Dismantle`) is thoughtfully stateful and regression-tested
6. No heavy framework tax or build complexity
7. Procedural audio avoids asset pipeline overhead
---
## Recommended Next Refactors
1. Split `engine.js` into:
- `rates.js`
- `tick.js`
- `events.js`
- `endings.js`
- `render-panels.js`
2. Replace inline `onclick` with delegated listeners
3. Add deterministic unit tests for `updateRates`, `checkProjects`, `saveGame/loadGame`
4. Separate content data from side-effectful `effect()` functions where possible
5. Add a tiny validation layer for save import schema/versioning
6. Reconcile README file map with actual runtime structure
7. Decide whether `game/npc-logic.js` should be wired in or removed
---
## Practical Mental Model
The Beacon is best understood as five layers stacked on top of each other:
1. **Shell**`index.html`
2. **Game data**`data.js`
3. **Simulation kernel**`engine.js` + `utils.js`
4. **Presentation systems**`render.js`, `tutorial.js`, `sound.js`, `combat.js`, `strategy.js`, `dismantle.js`
5. **Verification** — Node VM tests + CI smoke/a11y checks
If you need to change behavior, start with `data.js` and `engine.js`.
If you need to change UX, start with `index.html`, `render.js`, and `main.js`.
If you need to harden the endgame, start with `dismantle.js` and `tests/dismantle.test.cjs`.
---
## Source Files of Highest Importance
1. `js/engine.js` — engine nucleus
2. `js/data.js` — content and state schema
3. `js/render.js` — persistence/UI orchestration
4. `index.html` — DOM/API contract
5. `js/dismantle.js` — true ending state machine
6. `tests/dismantle.test.cjs` — strongest existing automated verification
---
*Generated from direct source inspection of Timmy_Foundation/the-beacon. Review and update as the repo evolves.*