Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,3 +325,10 @@ All notable changes to DarkFrame are documented here. Format based on
- Initial Postgres migration effort, GitHub repo sanitization (secret scrub + history rewrite),
README redesign, Vercel deployment pipeline bring-up (lazy DB connection for build-time
env isolation), production DB connection fixes.

### Changed — ladder-truth gate extended to every documented game-math table (FID-20260915-007)

- **Four new ladders** (10 new documented sites, 65 new cells): regeneration-rate (4 sites — the rate table + three range summaries, actuals **engine-derived** as tick(0) ÷ spawner-max), unit-cost curve (game.types roster counts, BALANCING PHILOSOPHY table ↔ `TIER_UNLOCK_REQUIREMENTS`, slot ladder 1/3/7/15/30), build-rate (header intervals ↔ `BUILD_RATES`, Ghost pinned at the docs' one-decimal precision — 0.67 is a rounded 1/1.5), army composition (header bullets + table comments ↔ `ARMY_COMPOSITION`, bare `50/50` rows parsed as str-first).
- **Two live falsehoods corrected** (the new gate's first catch, before it even shipped): both regen summaries said "5-20% per hour" while Boss regenerates at 2% — now "2-20%" in all four sites; UNIT_CONFIGS' "all 40 units (5 tiers × 8)" now scoped truthfully to 65 units (40-unit blueprint-derived core + 25 SPEC/PRESTIGE).
- `BUILD_RATES`/`ARMY_COMPOSITION` exported from botGrowthEngine (pure data; botArmyCaps.test.ts already behavior-pins caps/age — scope respected).
- Drill-proven: comment-only edits to the regen table (drill E), the botService range summary (drill F), and the philosophy table (drill G) each fail the gate (2/2/2 tests), restored green after each. Coverage arithmetic asserted in-test (a parser silently skipping the bare 50/50 row shape is itself caught).
1 change: 1 addition & 0 deletions SCOPE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1043,3 +1043,4 @@ Every step of the approved plan carries an explicit status (`implemented | block

Verification evidence for the `implemented` statuses is recorded in
`dev/session-summaries/SESSION-2026-09-01-001.md` and `dev/session-summaries/SESSION-2026-09-02-001.md`.
| 54 | **Ladder-truth gate → every documented game-math table (FID-20260915-007):** four new ladders (regen-rate 12 cells/4 sites engine-derived via tick(0)÷max, unit-cost 23/2, build-rate 10/2, army-composition 20/2) + first catches corrected pre-ship: regen summaries said 5-20% while Boss is 2% (now 2-20% ×4 sites), UNIT_CONFIGS header said 40 units vs 65 actual (rescoped). BUILD_RATES/ARMY_COMPOSITION exported. Drill-proven E/F/G (comment-only edits fail 2/2/2). Gates: tsc 0 · lint 0 · vitest 839/1 skipped. Status: verified (uncommitted) |
91 changes: 74 additions & 17 deletions __tests__/lib/ladderTruth.test.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,70 @@
/**
* Ladder-truth gate — the FID-20260915-006a lesson made executable.
*
* The bot tier ladders drifted for months because the documented tables lived
* only in comments while the truth lived in formulas; nothing ever EXECUTED
* the comments. This gate parses the documented ladders out of
* `lib/botService.ts` itself and asserts each documented value equals the
* live function output, so:
* Documented game-math tables are parsed out of the production source itself
* and paired with live function/constant output, so:
* - a comment edit that drifts from code fails CI (the original sin), and
* - a formula change without a doc update fails too.
*
* Seven ladders (FID-20260915-007 extended the original three to every
* documented game-math table):
* 1. resource multiplier (botService, 2 sites, 21 cells)
* 2. base defense (botService, 2 sites, 14 cells)
* 3. player level bracket (botService, 1 site, 7 cells)
* 4. regeneration rate (botGrowthEngine + botService, 4 sites, 12 cells)
* 5. unit cost curve (game.types, 2 sites, 23 cells)
* 6. build rate (botGrowthEngine, 2 sites, 10 cells)
* 7. army composition (botGrowthEngine, 2 sites, 20 cells)
*
* The scanner (scripts/ladderTruth.ts) executes at import and throws loudly
* if a documented table disappears or its format changes — the gate cannot
* silently degrade to zero assertions. Independent truth spot-pins below
* guard against the scanner itself lying.
* silently degrade to zero assertions. Site counts AND cell counts are
* asserted here (a parser silently skipping a row shape must fail, not
* shrink). Independent truth spot-pins guard against the scanner lying.
* Blueprint↔config field parity of the unit roster is pinned separately by
* __tests__/unit/catalog-unification.test.ts (behavior) — this gate covers
* the documented claims (docs ↔ code).
*/
import { describe, it, expect } from 'vitest';
import {
ladders,
resourceLadder,
defenseLadder,
bracketLadder,
regenRateLadder,
unitCostLadder,
buildRateLadder,
armyCompositionLadder,
} from '@/scripts/ladderTruth';
import {
getResourceRange,
getBotDefenseForTier,
getPlayerLevelBonus,
} from '@/lib/botService';
import { BotSpecialization } from '@/types/game.types';
import { regenerateBotResources, BUILD_RATES, ARMY_COMPOSITION } from '@/lib/botGrowthEngine';
import { BotSpecialization, type Player } from '@/types/game.types';

const EXPECTED_CELLS: Record<string, number> = {
'resource multiplier': 21,
'base defense': 14,
'player level bracket': 7,
'regeneration rate': 12,
'unit cost curve': 23,
'build rate': 10,
'army composition': 20,
};

describe('ladder truth: documented tier tables equal code (FID-20260915-006a lesson)', () => {
it('scanner is alive — all three ladders found with full expected coverage', () => {
expect(ladders).toHaveLength(3);
// Resource: 7 header tiers × 2 specs + 7 function-comment tiers = 21.
// Defense: 7 × 2 sites = 14. Brackets: 7 rows × 1 site.
expect(resourceLadder.cellsChecked).toBe(21);
expect(defenseLadder.cellsChecked).toBe(14);
expect(bracketLadder.cellsChecked).toBe(7);
describe('ladder truth: documented game-math tables equal code (FID-20260915-006a/-007)', () => {
it('scanner is alive — all seven ladders found at full expected coverage', () => {
expect(ladders).toHaveLength(7);
for (const ladder of ladders) {
expect(ladder.cellsChecked, `${ladder.ladder} coverage`).toBe(EXPECTED_CELLS[ladder.ladder]);
}
// Site-count guards hold (drift-by-deletion protection)
expect(regenRateLadder.sites).toHaveLength(4);
expect(unitCostLadder.sites).toHaveLength(2);
expect(buildRateLadder.sites).toHaveLength(2);
expect(armyCompositionLadder.sites).toHaveLength(2);
});

it('resource multiplier ladder: header table and function comment match getResourceRange', () => {
Expand All @@ -50,6 +79,22 @@ describe('ladder truth: documented tier tables equal code (FID-20260915-006a les
expect(bracketLadder.mismatches).toEqual([]);
});

it('regeneration-rate ladder: rate table, range summaries, and engine agree', () => {
expect(regenRateLadder.mismatches).toEqual([]);
});

it('unit-cost ladder: roster counts, philosophy table, and slot ladder match code', () => {
expect(unitCostLadder.mismatches).toEqual([]);
});

it('build-rate ladder: header intervals and table comments match BUILD_RATES', () => {
expect(buildRateLadder.mismatches).toEqual([]);
});

it('army-composition ladder: header bullets and table comments match ARMY_COMPOSITION', () => {
expect(armyCompositionLadder.mismatches).toEqual([]);
});

it('aggregate: zero drift anywhere (failure lists every offending cell)', () => {
const bad = ladders.flatMap((l) =>
l.mismatches.map((m) => `${l.ladder} [${m.site}] ${m.key}: documented ${m.documented} ≠ actual ${m.actual}`)
Expand All @@ -58,12 +103,24 @@ describe('ladder truth: documented tier tables equal code (FID-20260915-006a les
});

// --- Independent truth spot-pins (do not trust the scanner blindly) ---
it('truth spot-pins: the ladders are what FID-20260915-006a established', () => {
it('truth spot-pins: the ladders are what FID-006a/-007 established', () => {
expect(getBotDefenseForTier(1)).toBe(15);
expect(getBotDefenseForTier(7)).toBe(2880);
expect(getResourceRange(BotSpecialization.Hoarder, 1).max).toBe(112500); // 150k × 0.75
expect(getResourceRange(BotSpecialization.Hoarder, 7).max).toBe(337500); // 150k × 2.25
expect(getPlayerLevelBonus(5)).toBe(1.0);
expect(getPlayerLevelBonus(65)).toBe(2.5);
// regen: Boss's 0.02 entry → 120,000/h on the 6M fixed range (the cell
// whose existence the "5-20%" falsehood hid)
const boss = {
botConfig: { specialization: BotSpecialization.Boss, tier: 1 },
resources: { metal: 0, energy: 0, food: 0 },
} as unknown as Player;
expect(regenerateBotResources(boss).metal).toBe(120_000);
// build rates: the rounded-reciprocal pairs (0.5 ↔ 2h, 0.67 ↔ 1.5h)
expect(BUILD_RATES.Fortress).toBe(0.5);
expect(BUILD_RATES.Ghost).toBe(0.67);
// composition: Fortress 30/70 wall
expect(ARMY_COMPOSITION.Fortress).toEqual({ str: 0.3, def: 0.7 });
});
});
8 changes: 4 additions & 4 deletions lib/botGrowthEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* challenging bot populations that scale in difficulty over time.
*
* KEY FEATURES:
* - Full Permanence: Bots regenerate resources hourly (5-20% by type)
* - Full Permanence: Bots regenerate resources hourly (2-20% by type — Boss 2%; FID-20260915-007 corrected)
* - Unit Building: Bots build BOTH STR and DEF armies that scale with age/tier
* - Growth Patterns: 70% grow, 20% stable, 10% decrease for dynamic economy
* - Movement System: Raiders roam, Ghosts teleport, others stationary
Expand Down Expand Up @@ -73,7 +73,7 @@ const REGENERATION_RATES = {
/**
* Unit building rates by specialization (units built per hour)
*/
const BUILD_RATES = {
export const BUILD_RATES = {
Fortress: 0.5, // 1 unit every 2 hours - slow but defensive
Raider: 1.0, // 1 unit per hour - fast aggressive builds
Hoarder: 0.25, // 1 unit every 4 hours - minimal unit focus
Expand All @@ -84,7 +84,7 @@ const BUILD_RATES = {
/**
* Army composition by specialization (STR vs DEF percentages)
*/
const ARMY_COMPOSITION = {
export const ARMY_COMPOSITION = {
Fortress: { str: 0.3, def: 0.7 }, // 30% STR, 70% DEF - defensive wall
Raider: { str: 0.7, def: 0.3 }, // 70% STR, 30% DEF - offensive power
Hoarder: { str: 0.5, def: 0.5 }, // 50/50 - minimal but balanced
Expand Down Expand Up @@ -611,7 +611,7 @@ export async function forceRegeneration(): Promise<{ success: boolean; count: nu
* - DEF units: Guard (T1), Sentinel (T2), Bastion (T3)
*
* 5. FULL PERMANENCE MODEL:
* - Bots regenerate 5-20% resources hourly (never despawn)
* - Bots regenerate 2-20% resources hourly (never despawn; Boss 2% — FID-20260915-007 corrected)
* - Growth pattern adds economic variation (70/20/10)
* - Movement creates dynamic map presence
* - Nest attraction maintains strategic clustering
Expand Down
4 changes: 2 additions & 2 deletions lib/botService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*
* OVERVIEW:
* Manages AI-controlled bot players that mimic real player behavior.
* Full Permanence Model: Bots stay on map permanently, regenerate resources hourly (5-20% by type).
* Full Permanence Model: Bots stay on map permanently, regenerate resources hourly (2-20% by type — Boss 2%; FID-20260915-007 corrected).
* Beer Bases despawn when defeated and respawn weekly at random locations.
*
* EXPANDED BOT TIER SYSTEM (7 Tiers) — values below are CODE truth
Expand Down Expand Up @@ -916,7 +916,7 @@ export async function createBeerBaseBots(count: number): Promise<Partial<Player>
// - Full Permanence Model: Regular bots never despawn, regenerate resources hourly
// - Beer Bases despawn when defeated, respawn weekly (Sunday 4 AM)
// - All bots have permanentBase=true for static base locations
// - Resource regeneration rates: 5-20% per hour based on specialization
// - Resource regeneration rates: 2-20% per hour based on specialization (Boss 2%; FID-20260915-007 corrected)
// - Reputation system tracks defeats for bonus loot (up to 2x)
// - Zone system ensures even distribution across 150×150 map
// - Admin panel will control all bot parameters via configuration
Expand Down
Loading
Loading