Files
the-beacon/qa_beacon.md
QA Agent 74575929af
Some checks failed
Accessibility Checks / a11y-audit (pull_request) Failing after 2s
Smoke Test / smoke (pull_request) Failing after 5s
QA: Add comprehensive playtest bug report (19 issues found)
Critical: duplicate const declarations, malformed BDEF array, drone balance
Functional: resource naming, Bilbo tick randomness, memory leak toast
Accessibility: missing mute/contrast buttons, tutorial focus trap
Balance: drone rates (26M/s!), community trust cost (25K)
Save/Load: debuff restoration logging
2026-04-12 22:34:20 -04:00

13 KiB
Raw Blame History

The Beacon — QA Playtest Report

Date: 2026-04-12 Tester: Hermes Agent (automated code analysis + simulated play) Version: HEAD (main branch)


CRITICAL BUGS

BUG-01: Duplicate const declarations across data.js and engine.js

Severity: CRITICAL (game-breaking) Files: js/data.js, js/engine.js Description: Both files declare the same global constants:

  • const CONFIG (both files)
  • const G (both files)
  • const PHASES (both files)
  • const BDEF (both files)
  • const PDEFS (both files)

Script load order in index.html: data.js loads first, then engine.js. Since both use const, the browser will throw SyntaxError: redeclaration of const CONFIG (Firefox) or Identifier 'CONFIG' has already been declared (Chrome) when engine.js loads. The game cannot start.

Fix: Remove these declarations from one of the two files. Recommend keeping definitions in data.js and having engine.js only contain logic functions (tick, updateRates, etc.).

BUG-02: BDEF array has extra }, creating invalid array element

Severity: CRITICAL File: js/engine.js lines 357-358 Description:

    },  // closes memPalace object
    },  // <-- EXTRA: this becomes `undefined` or invalid array element
    {
        id: 'harvesterDrone', ...

The }, on line 358 is a stray empty element. After memPalace's closing }, there is another }, which creates an invalid or empty slot in the BDEF array. This breaks the drone buildings that follow (harvesterDrone, wireDrone, droneFactory).

Fix: Remove the extra }, on line 358.

BUG-03: Duplicate project definitions — p_the_pact and p_the_pact_early

Severity: HIGH Files: js/data.js lines 612-619, lines 756-770 Description: Two Pact projects exist:

  1. p_the_pact — costs 100 trust, triggers at totalImpact >= 10000, trust >= 75. Grants impactBoost *= 3.
  2. p_the_pact_early — costs 10 trust, triggers at deployFlag === 1, trust >= 5. Grants codeBoost *= 0.8, computeBoost *= 0.8, userBoost *= 0.9, impactBoost *= 1.5.

Both set G.pactFlag = 1. If the player buys the early version, the late-game version's trigger (G.pactFlag !== 1) is never met, so it never appears. This is likely intentional design (choose your path), BUT:

  • The early Pact REDUCES boosts (0.8x code/compute) as a tradeoff. A new player may not understand this penalty.
  • If the player somehow buys BOTH (race condition or save manipulation), pactFlag is set twice and impactBoost is multiplied by 3 AND the early penalties apply.

Fix: Add a guard in p_the_pact trigger: && G.pactFlag !== 1.


FUNCTIONAL BUGS

BUG-04: Resource key mismatch — user vs users

Severity: MEDIUM File: js/engine.js lines 15, js/data.js building definitions Description: The game state uses G.users as the resource name, but building rate definitions use user as the key:

rates: { user: 10 }  // api building
rates: { user: 50, impact: 2 }  // fineTuner

In updateRates(), the code checks resource === 'user' and adds to G.userRate. This works for rate calculation, but the naming mismatch is confusing and could cause bugs if someone tries to reference G.user (which is undefined).

Fix: Standardize on one key name. Either rename the resource to user everywhere or change building rate keys to users.

BUG-05: Bilbo randomness recalculated every tick (10Hz)

Severity: MEDIUM File: js/engine.js lines 81-87 Description:

if (G.buildings.bilbo > 0 && Math.random() < CONFIG.BILBO_BURST_CHANCE) {
    G.creativityRate += 50 * G.buildings.bilbo;
}
if (G.buildings.bilbo > 0 && Math.random() < CONFIG.BILBO_VANISH_CHANCE) {
    G.creativityRate = 0;
}

updateRates() is called every tick via the render loop. Each tick, Bilbo has independent 10% burst and 5% vanish chances. Over 10 seconds (100 ticks):

  • Probability of at least one burst: 1 - 0.9^100 = 99.997%
  • Probability of at least one vanish: 1 - 0.95^100 = 99.4%

The vanish check runs AFTER the burst check, so a burst can be immediately overwritten by vanish on the same tick. Effectively, Bilbo's "wildcard" behavior is almost guaranteed every second, making it predictable rather than surprising.

Fix: Move Bilbo's random effects to a separate timer-based system (e.g., roll once per 10-30 seconds) or use a tick counter.

BUG-06: Memory leak toast says "trust draining" but resolves with code

Severity: LOW (cosmetic/misleading) File: js/engine.js line 660 Description:

showToast('Memory Leak — trust draining', 'event');

But the actual resolve cost is:

resolveCost: { resource: 'ops', amount: 100 }

The toast says "trust draining" but the event drains compute (70% reduction) and ops, and costs 100 ops to resolve. The toast message is misleading.

Fix: Change toast to 'Memory Leak — compute draining'.

BUG-07: phase-transition overlay element missing from HTML

Severity: LOW (visual feature broken) File: js/engine.js line 237, index.html Description: showPhaseTransition() looks for document.getElementById('phase-transition') but this element does not exist in index.html. Phase transitions will silently fail — no celebratory overlay appears.

Fix: Add the phase-transition overlay div to index.html or create it dynamically in showPhaseTransition().

BUG-08: renderResources null reference risk on rescues

Severity: LOW File: js/engine.js line 954 Description:

const rescuesRes = document.getElementById('r-rescues');
if (rescuesRes) {
    rescuesRes.closest('.res').style.display = ...

If the rescues .res container is missing or the DOM structure is different, closest('.res') could return null and throw. In practice the HTML structure supports this, but no null check on closest().

Fix: Add null check: const container = rescuesRes.closest('.res'); if (container) container.style.display = ...


ACCESSIBILITY ISSUES

BUG-09: Mute and Contrast buttons referenced but not in HTML

Severity: MEDIUM (accessibility) Files: js/main.js lines 76-93, index.html Description: toggleMute() looks for #mute-btn and toggleContrast() looks for #contrast-btn. Neither button exists in index.html. The keyboard shortcuts M (mute) and C (contrast) will silently do nothing.

Fix: Add mute and contrast buttons to the HTML header or action panel.

BUG-10: Missing role="status" on resource display updates

Severity: LOW (screen readers) File: index.html line 117 Description: The resources div has aria-live="polite" which is good, but rapid updates (10Hz) will flood screen readers with announcements. Consider throttling aria-live updates to once per second.

BUG-11: Tutorial overlay traps focus

Severity: MEDIUM (keyboard accessibility) File: js/tutorial.js Description: The tutorial overlay doesn't implement focus trapping. Screen reader users can tab behind the overlay. Also, the overlay doesn't set role="dialog" or aria-modal="true".

Fix: Add role="dialog", aria-modal="true" to the tutorial overlay, and implement focus trapping.


UI/UX ISSUES

BUG-12: Harmony tooltip shows rate ×10

Severity: LOW (confusing) File: js/engine.js line 972 **Description:

lines.push(`${b.label}: ${b.value >= 0 ? '+' : ''}${(b.value * 10).toFixed(1)}/s`);

The harmony breakdown tooltip multiplies by 10, presumably to convert from per-tick (0.1s) to per-second. But b.value is already the per-second rate (set from CONFIG.HARMONY_DRAIN_PER_WIZARD etc. which are per-second values used in G.harmonyRate). The ×10 multiplication makes the tooltip display 10× the actual rate.

Fix: Remove the * 10 multiplier: b.value.toFixed(1) instead of (b.value * 10).toFixed(1).

BUG-13: Auto-type increments totalClicks

Severity: LOW (statistics inflation) File: js/engine.js line 783 Description:

function autoType() {
    G.code += amount;
    G.totalCode += amount;
    G.totalClicks++;  // <-- inflates click count
}

Auto-type fires automatically from buildings but increments the "Clicks" stat, making it meaningless as a manual-click counter.

Fix: Remove G.totalClicks++ from autoType().

BUG-14: Spelling: "AutoCod" missing 'e'

Severity: TRIVIAL (typo) File: js/data.js line 775 Description:

{ flag: 1, msg: "AutoCod available" },

Should be "AutoCoder".

Fix: Change to "AutoCoder available".

BUG-15: No negative resource protection

Severity: LOW Files: js/engine.js, js/utils.js Description: Resources can go negative in several scenarios:

  • ops can go negative from Fenrir buildings (ops: -1 rate)
  • Spending resources doesn't check for negative results (only checks affordability before spending)
  • Negative resources display with a minus sign via fmt() but can trigger weird behavior in threshold checks

Fix: Add G.ops = Math.max(0, G.ops) in the tick function, or clamp all resources after production.


BALANCE ISSUES

BAL-01: Drone buildings have absurdly high rates

Severity: MEDIUM File: js/engine.js lines 362-382 Description: The three drone buildings have rates in the millions/billions:

  • harvesterDrone: code: 26,180,339 (≈26M per drone per second)
  • wireDrone: compute: 16,180,339 (≈16M per drone per second)
  • droneFactory: code: 161,803,398, compute: 100,000,000

These appear to use golden ratio values as literal rates. One harvester drone produces more code per second than all other buildings combined by several orders of magnitude. This completely breaks game balance once unlocked.

Fix: Scale down by ~10000x or redesign to use golden ratio as a multiplier rather than absolute rate.

BAL-02: Community building costs 25,000 trust

Severity: MEDIUM File: js/data.js line 243 Description:

baseCost: { trust: 25000 }, costMult: 1.15,

Trust generation is slow (typically 0.5-10/sec). Accumulating 25,000 trust would take 40+ minutes of dedicated trust-building. Meanwhile the building produces code (100/s) and users (30/s), which is modest compared to the trust investment.

Fix: Reduce trust cost to 2,500 or increase the building's output significantly.

BAL-03: "Request More Compute" repeatable project can drain trust

Severity: LOW File: js/data.js lines 376-387 Description: p_wire_budget costs 1 trust and also subtracts 1 trust in its effect:

cost: { trust: 1 },
effect: () => { G.trust -= 1; G.compute += 100 + ...; }

This means each use costs 2 trust total. The trigger (G.compute < 1) fires whenever compute is depleted. If a player has no compute generation and clicks this repeatedly, they can drain trust to 0 or negative.

Fix: Change the effect to not double-count trust. Either remove from cost or from effect.


SAVE/LOAD ISSUES

SAV-01: Debuffs re-apply effects on load, then updateRates applies again

Severity: MEDIUM File: js/render.js (loadGame function) lines 281-292 Description: When loading a save with active debuffs, the code re-fires evDef.effect() which pushes a debuff with an applyFn. Then updateRates() is called, which runs each debuff's applyFn. Some debuffs apply permanent rate modifications in their applyFn (e.g., G.codeRate *= 0.5). If updateRates() was already called before debuff restoration, the rates are correct. But the order matters and could lead to double-application.

Looking at the actual load sequence: updateRates() is NOT called before debuff restoration. Debuffs are restored, THEN updateRates() is called. The applyFns run inside updateRates(), so the sequence is actually correct. However, the debuff effect() function also logs messages and shows toasts during load, which may confuse the player.

Fix: Suppress logging/toasts during debuff restoration by checking G.isLoading (which is set to true during load).


SUMMARY

Category Count
Critical Bugs 3
Functional Bugs 5
Accessibility Issues 3
UI/UX Issues 4
Balance Issues 3
Save/Load Issues 1
Total 19

Top Priority Fixes:

  1. BUG-01: Remove duplicate const declarations (game cannot start)
  2. BUG-02: Remove stray }, in BDEF array (drone buildings broken)
  3. BUG-03: Guard against double-Pact purchase
  4. BAL-01: Fix drone building rates (absurd numbers)
  5. BUG-07: Add phase-transition overlay element

Positive Observations:

  • Excellent ARIA labeling on most interactive elements
  • Robust save/load system with validation and offline progress
  • Good keyboard shortcut coverage (Space, 1-4, B, S, E, I, Ctrl+S, ?)
  • Educational content is well-written and relevant
  • Combo system creates engaging click gameplay
  • Sound design uses procedural audio (no external files needed)
  • Tutorial is well-structured with skip option
  • Toast notification system is polished
  • Strategy engine provides useful guidance
  • Production breakdown helps players understand mechanics