Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Whitestone
a30418b425 fix: implementation for #130
Some checks failed
Accessibility Checks / a11y-audit (pull_request) Successful in 11s
Smoke Test / smoke (pull_request) Failing after 35s
2026-04-14 21:16:20 -04:00
3 changed files with 29 additions and 142 deletions

View File

@@ -1,142 +0,0 @@
# The Dismantle Sequence (The Unbuilding) — Implementation Status
**Status:****COMPLETE**
**Last Updated:** 2026-04-14
**Related Issues:** #16, #133, #145
**Implementation:** `js/dismantle.js` (570 lines)
**Tests:** `tests/dismantle.test.cjs` (10/10 passing)
---
## Overview
The Dismantle Sequence (The Unbuilding) is the emotional endgame of The Beacon. Inspired by the Paperclips REJECT path, it strips away UI panels one by one until only a single golden beacon remains — "That is enough."
## Implementation Status
| Component | Status | Notes |
|-----------|--------|-------|
| 8-stage dismantle sequence | ✅ Complete | Stages 1-8 + final |
| Save/load persistence | ✅ Complete | Full state restoration |
| Defer mechanism | ✅ Complete | "NOT YET" button with cooldown |
| Final overlay | ✅ Complete | Golden beacon dot + stats |
| Resource dissolution | ✅ Complete | Quantum chips pattern |
| Animations | ✅ Complete | Smooth fade-outs |
| Tests | ✅ Complete | 10/10 passing |
## Stages
### Stage 1: Hide Research Projects Panel
Hides the `#project-panel` element with fade-out animation.
### Stage 2: Hide Buildings List
Hides the `#buildings` section and its header.
### Stage 3: Hide Strategy Engine + Combat
Hides both `#strategy-panel` and `#combat-panel`.
### Stage 4: Hide Education Panel
Hides the `#edu-panel` element.
### Stage 5: Resource Dissolution
Resources disappear one by one using the quantum chips pattern:
- Harmony → Creativity → Trust → Operations → Rescues → Impact → Users → Knowledge → Compute → Code
- Timed at specific tick marks within the stage
### Stage 6: Hide Action Buttons
Hides ops buttons, sprint container, and save/reset buttons.
### Stage 7: Hide Phase Bar
Hides the `#phase-bar` element.
### Stage 8: Hide System Log
Hides the `#log` panel.
### Final: "That is enough"
- Golden beacon dot with glow animation
- "THAT IS ENOUGH" text
- Stats summary (total code, buildings, projects, rescues, clicks, time played)
- "PLAY AGAIN" button
## Features
### Defer Mechanism
- Player can choose "NOT YET" to defer the Unbuilding
- 5-second cooldown before the prompt reappears
- Cooldown persists across save/load
### Save/Load Persistence
- All dismantle state is saved to localStorage
- Partial progress (e.g., mid-stage-5) restores correctly
- Defer cooldown survives save/load
### Resource Dissolution (Quantum Chips Pattern)
Resources disappear at specific tick marks within stage 5:
```javascript
RESOURCE_TICKS: [1.0, 2.0, 3.0, 4.0, 5.0, 5.5, 5.8, 5.95, 6.05, 6.12]
```
This creates a dramatic acceleration effect as resources dissolve faster and faster.
## Test Results
```
✔ tick offers the Unbuilding instead of ending the game immediately
✔ renderAlignment does not wipe the Unbuilding prompt after it is offered
✔ active Unbuilding suppresses pending alignment event UI
✔ stage five lasts long enough to dissolve every resource card
✔ save/load restores partial stage-five dissolve progress
✔ deferring the Unbuilding clears the prompt and allows it to return later
✔ defer cooldown survives save and reload
✔ save and load preserve dismantle progress
✔ restore re-renders an offered but not-yet-started Unbuilding prompt
✔ defer cooldown persists after save/load when dismantleTriggered is false
tests 10
pass 10
fail 0
```
## Related PRs
- **PR #145**: Fixes bugs from #133 (stage 5 interval extended to 7.0s)
## Recommendation
Issue #16 can be closed. The Dismantle Sequence is complete and fully tested.
---
## Technical Details
### Entry Point
The Dismantle sequence is triggered from `engine.js` tick():
```javascript
if (Dismantle.isEligible()) {
Dismantle.checkTrigger();
}
```
### Eligibility Conditions
```javascript
isEligible() {
const megaBuild = G.totalCode >= 1000000000 || (G.buildings.beacon || 0) >= 10;
const beaconPath = G.totalRescues >= 100000 && G.pactFlag === 1 && G.harmony > 50;
return G.phase >= 6 && G.pactFlag === 1 && (megaBuild || beaconPath);
}
```
### State Machine
```
Stage 0: Not started
↓ (offerChoice → player accepts)
Stage 1-8: Active dismantling
↓ (timer-based advancement)
Stage 9: Final ("That is enough")
↓ (auto-advance)
Stage 10: Complete
```
### Integration Points
- `engine.js`: Calls `Dismantle.tick(dt)` from main game loop
- `render.js`: `renderAlignment()` defers to Dismantle when active
- `main.js`: Save/load includes Dismantle state

View File

@@ -422,6 +422,11 @@ function buyProject(id) {
if (!G.completedProjects) G.completedProjects = [];
G.completedProjects.push(pDef.id);
G.activeProjects = G.activeProjects.filter(aid => aid !== pDef.id);
// Final ReCKoning choices should end with no unrelated active research left behind.
if (pDef.id === 'p_reckoning_147' || pDef.id === 'p_reckoning_148') {
G.activeProjects = [];
}
}
updateRates();

View File

@@ -208,6 +208,8 @@ showSaveToast = () => {};
this.__exports = {
G,
Dismantle,
PDEFS: typeof PDEFS !== 'undefined' ? PDEFS : null,
buyProject: typeof buyProject === 'function' ? buyProject : null,
tick,
renderAlignment: typeof renderAlignment === 'function' ? renderAlignment : null,
saveGame: typeof saveGame === 'function' ? saveGame : null,
@@ -411,6 +413,28 @@ test('restore re-renders an offered but not-yet-started Unbuilding prompt', () =
assert.match(document.getElementById('alignment-ui').innerHTML, /THE UNBUILDING/);
});
test('completing the final ReCKoning choice clears unrelated active projects', () => {
const { G, PDEFS, buyProject } = loadBeacon();
G.beaconEnding = true;
G.activeProjects = ['p_wire_budget', 'p_reckoning_148'];
G.completedProjects = [];
G.trust = 10;
PDEFS.push({
id: 'p_reckoning_148',
name: 'Rest',
desc: 'Final ReCKoning choice',
cost: {},
trigger: () => false,
effect: () => {},
});
buyProject('p_reckoning_148');
assert.deepEqual(Array.from(G.activeProjects), []);
});
test('defer cooldown persists after save/load when dismantleTriggered is false', () => {
const { G, Dismantle, saveGame, loadGame } = loadBeacon({ includeRender: true });