Phase 5 — Tuning, Bruteforce & Content Generation
Goal: Close the loop — search fixes and propose content, accepting only simulation-validated candidates in the project's real data shape.
Bruteforce (bruteforce, mode-bruteforce)
pub struct LevelBruteforceOptions<'a> { /* level id, which fields to vary, ranges/steps, styles, input_models, runs, seed */ }
pub fn bruteforce_level(data: &GameData, options: LevelBruteforceOptions)Design rules:
- Vary 1–3 parameters at a time; coarse grid then refine.
- Score vs all
style × input_modelbands (distance from band centers) + penalties: grade degeneracy, downtime dominance, single-kind failure concentration, economy drift (clear-time → currency/minute), and platform gap penalty (max(0, mouse_win_rate - touch_win_rate - 0.12)). A candidate that fixes desktop but widens the mouse-vs-touch gap fails. - Simulate all styles and shipped input models for every candidate — fixing
average@mousewhile droppingaverage@touchto 30% fails. - Search at 100–300 runs; re-validate winner at ≥1000 before recommend.
- Print ranked shortlist as exact source edits in the project's truth format (Resource field /
.tres/ GDScript assignment).
tune_mode_progression(...) follows the same pattern for mode curves — search so each level's mode win rates stay in band across all shipped input models.
Content generation
pub struct GeneratedLevel { /* candidate + LevelValidation */ }
pub fn generate_level(data, /* id, target profile, constraints */) -> GeneratedLevel
pub fn generate_weapon(...) -> GeneratedWeapon
pub fn generate_trap(data, id, name, unlock) -> (Trap, String)Generation procedure
- Fit curves to existing catalog (regress balance fields vs progression index). New content starts on-curve, then identity via deliberate trade-offs.
- Respect influence graph & platform limits: unlock-gated pools, one new element per session when designing levels. Generated mechanics must respect the touch tap-rate cap (
taps_per_second_cape.g. 7.0/sec) and supportsimultaneous_actions = 1for one-hand play. - Validate with full
style × input_modelmatrix; bounded retries; report aggregates with the candidate. - Emit the project's data shape (see table below).
Emit the project's data shape
| Project truth | Generator output |
|---|---|
.tres / Resources |
Write .tres (or JSON dump the editor imports) |
CSV→.tres pipeline |
Emit CSV row matching designer sheet |
| GDScript factories only | Emit factory block only if Phase 0 confirmed no Resource layer |
NEVER paste .gd factories into a Resource-first project. Prefer godot-resource-data-patterns shapes.
Recalibration (content changed)
inspect— extractor sees the change; nothing else moved unexpectedly.simulate --runs 1000full matrix across all shipped input models + diff snapshot (compare_balance_snapshots.py).- If the change touches any input-sensitive mechanic (tap minigame, drag placement, target sizes), re-run touch cells at 1000+ runs even if desktop cells look unchanged.
- Triage intended vs collateral; classify: intended vs collateral (shared constants, curve interactions, platform gap widening).
bruteforcecollateral cells without reverting the new content or widening the mouse-vs-touch platform gap.- Modes +
careerfor casual & average — verify economy, session length caps, and grind targets still hold. - Phase 7 spot-check if physics/AI-heavy systems moved.
- Save new snapshot; summarize before/after win rates per cell across input models.
Level-from-scratch
Define role on interest curve + target bands → generate → review texture metrics (leaks by kind, downtime, time-to-first-fault, touch platform gap) — not just band OK → write source → inspect → full matrix including neighbors → career to confirm progression pacing and session caps.