A single-player, stat-driven crime saga for iPad and Steam. You play a lifetime, not a history. The Ceremony sets your dice. The Contemporary collects the debt. Every minigame is life or death. There is no respawn.
A baptism and a massacre happen in the same church. The sacred and the profane share the same altar. You play a lifetime, not a history. The Ceremony sets your dice. The Contemporary collects the debt.
Bittersweet tragedy. Every victory has a cost. Finality is non-negotiable. The sacred and the profane on the same altar.
Minigames are 1990s coin-op absurdity. The story is prestige drama. The contrast is intentional. Jackpot vs. Scar.
You are playing a lifetime. The Ceremony sets. The Contemporary collects. Everything in between is yours to ruin.
The blood oath in Nonna's kitchen. The dice fall for the first time. The Arch Enemy is assigned.
Establishing power. Every street corner is a negotiation. The Family Boss watches.
Aggressive growth. Territory wars. The empire stretches across locations worldwide.
Maximum influence. Maximum exposure. The most dangerous moment — when everything is working.
Internal rot. External heat. Betrayal from within. The Arch Enemy circles closer.
Digital laundering. Legacy and reckoning. The Contemporary collects the debt.
D&D-inspired rolling blended with arcade action. Dice determine the quality of success and the severity of failure — never a binary outcome. Skull Multipliers can exponentially compound both.
Starting Capital is rolled at Ceremony: (3d6 × 100) + 700 WADD — range 1,000 to 2,500. Respect grows at 0.3× multiplier. Legendary status sets a permanent Heat floor of floor(respect / 5). You cannot go cold once you are feared.
| Rank | Respect | Gate |
|---|---|---|
| Picciotto | 0–9 | None |
| Associate | 10–24 | None |
| Soldier | 25–50 | None |
| Made Man | 51–67 | Ceremony required |
| Capo | 68–84 | Ceremony required |
| Don | 85+ | Ceremony required |
Respect alone does not promote you. Each tier requires the story ceremony event to fire.
Every run triggers a sequence of escalating events. The Arch Enemy who appears never disappears. The Sitdown might be your only way out of a war. Events are queued, paced, and permanent.








Jackpot on success. Permanent Scar on failure. These are not breaks from the story — they ARE the story's highest-stakes resolution mechanics.
Every run ends exactly once. No good endings — only earned ones. Pick from what you've unlocked, or gamble everything on a d20.
The moral gravity of The Godfather. The electric chaos of Goodfellas. Devastating consequences.
Brutal economy loop married to dice ceremony and stat progression. Pure cause-and-effect.
Every choice matters and compounds. CYOA's branching consequence at scale. No undo.
Sierra's world-building ambition. Carmen's global scope. Cabinet-era design — zero friction.
| Primary | iPad (iPadOS) |
| Secondary | macOS (Steam) |
| Future | Windows (Steam) |
| Engine | Flutter / Dart (SDK 3.0+) |
| State | Provider 6.1.0 (ChangeNotifier) |
| Audio | Audioplayers 6.5.1 |
| Steam | Steamworks 0.4.7 |
| Crash | Sentry Flutter 8.14.0 |
| Save | File-backed JSON, 3 slots |
| Minigames | All complete |
| Characters | Full named cast |
| Items | Common · Rare · Legendary |
| Territories | Global locations |
| Story chapters | Six life phases |
| Final Fates | 50 unique endings, 11 fate types |
| Tests | Full unit/widget suite |
| Architecture | Single-player, no backend |
Arcade Mandate enforced: zero scrolling. Every core screen fits a single cabinet frame.
Architecture, atmosphere, forbidden patterns, and everything the engine runs on. This is the bible — for the team and for the machines.
Blood. Bones. WADD.
These are not suggestions. Every screen, every label, every line of dialogue is evaluated against them. If a feature fails any pillar, it fails the Nonna Standard.
Bittersweet tragedy. Every victory costs. A baptism and a massacre in the same church. The sacred and profane on the same altar.
1990s coin-op absurdity meets prestige drama. The contrast is intentional. Jackpot vs. permanent Scar. Play is the hook; consequence is the weight.
You play a lifetime. The Ceremony sets. The Contemporary collects. Everything in between is the accounting.
The Lead (atmospheric fade-in). The Slam (screenshake + haptics + SFX on consequence). The Countdown (Insert Coin finality).
Visceral, present-tense, superstition-soaked. Not corporate. Not web-app. Not documentation. If it sounds like a settings page, cut it.
The ultimate quality gate. Would a street-level Sicilian grandmother find this insulting? If it sounds like corporate onboarding, it fails.
These failures have been hardened against. Do not reintroduce them.
Image.asset() directly — use ImageUtils.safeAsset().Direct calls crash silently on missing assets. No silent failures.gameProvider.addEventToQueue().Inline triggers bypass pacing, deduplication, and the 3-event queue cap.EconomicBalanceConfig.Hardcoded values break balance tuning globally.CharacterDatabase.getCharacterById(id).Static data in screens will desync from the database.Color communicates category — not decoration. Apply these consistently across all screens. Color is faction language.
All JSON-serializable via build_runner. Never deserialize manually. Run flutter pub run build_runner build after any model change.
selectedCharacter Character? customPlayerName String? // Overrides archetype name currentFamilyBoss Character? // Nonna at start stats PlayerStats // All numeric state currentStoryId String // Active story node completedStories List<String> inventory List<GameItem> equippedItems List<GameItem> crewMembers List<CrewMember> flags Map<String,dyn> // Named state archEnemy String? // Character ID archEnemyState ArchEnemyState? // Full rivalry characterAge double // 14–80+ lifePhase String // young|prime|veteran|elderly generation int // 1=original, 2+=successors currentLocation String? // e.g. 'new_york' locationReputations Map // Per-city memory
money int // WADD — use addMoney() / subtractMoney()
guts int // 1–18 — improveGuts() / setGuts()
heart int // 1–18 — improveHeart() / setHeart()
brains int // 1–18 — improveBrains() / setBrains()
fists int // 1–18 — improveFists() / setFists()
respect int // 0–100 — changeRespect()
heat int // 0–100 — changeHeat()
// floor = respect/5 (HEAT FLOOR RULE)
// You cannot cool off once you are feared.
madeManCeremonyDone bool // Story gate, not auto-set
capoCeremonyDone bool // Story gate
donCeremonyDone bool // Story gate
Every WADD constant lives here. Change numbers here — nowhere else. Hardcoded values break global balance.
| Constant | Value | Context |
|---|---|---|
| Starting Capital | (3d6 × 100) + 700 | Rolled at Ceremony. Range: 1,000–2,500 WADD |
| lifestyleCost Picciotto | 100 WADD | Respect 0–9, every 30 stories |
| lifestyleCost Associate | 200 WADD | Respect 10–24 |
| lifestyleCost Soldier | 400 WADD | Respect 25–50 |
| lifestyleCost Made Man | 800 WADD | Respect 51–74 |
| lifestyleCost Capo | 1,500 WADD | Respect 75–89 |
| lifestyleCost Don | 3,000 WADD | Respect 90+ |
| crewUpkeepPerStory | 10 WADD | Per crew member, per story |
| heatFine (Heat > 75) | 50–200 WADD | Applied each story cycle when hot |
| arrestFine | 500–2,000 WADD | + 5–15 respect loss, 30–50 heat reduction |
| nonnaLoan | 500 WADD | One-time emergency bailout. Prevents softlock. |
| respectGainMultiplier | 0.3× | All respect gains. Earned over years, not days. |
Persistent, serializable, gated by tutorial. Must never fire before the player has entered the main hub. All flags survive game state serialization.
Not a one-time boss. A persistent shadow that scales with the player. Assigned at first blood. Removed only through Sitdown or Street War.
| Field | Behavior |
|---|---|
| lastVendettaLevel | Carries over. Floor 30. Ceiling 90. Never zero. |
| scaledFists | baseFists + (confrontations × 2). Enemy grows harder. |
| rivalryLabel | FIRST ENCOUNTER → LIFELONG ENEMIES |
| hasCalledCops | Permanent flag. Ratting has consequences. |
| hospitalVisits | Narrative weight. Feeds Final Fate context. |
// Via story choice effects: ChoiceEffects(setArchEnemy: 'RANDOM') // Or specific character: ChoiceEffects(setArchEnemy: 'tano_garofalo') // Always resolves through CharacterDatabase. // Never store character data on GameState directly.
Events are queued, paced, and deduplicated. Hard cap: 3 events. Priority ≥ 85 auto-launches. Priority ≥ 95 bypasses pacing.
// CORRECT gameProvider.addEventToQueue(event); gameProviderQueueDynamicEvent( provider, event, allowAutoLaunch: true); // NEVER inline — bypasses everything navigator.push(MaterialPageRoute( builder:(_) => MyEventScreen()));
95+ Mandatory. Bypasses anti-spam pacing.
Ceremonies, Final Fate.
85–94 Auto-launch candidate. High urgency.
Arch Enemy confrontations.
<85 Queued normally. Subject to 3-story
cooldown between random events.
// Anti-spam: if pacing_counter < 3, // skip random events (priority < 95) // Queue cap: hard limit of 3 events // Dedup: event.id must be unique in queue // Asset guard: empty backgroundImagePath // or empty phases → event skipped // Mutiny: checked separately via // gameProviderCheckForMutiny()
The pacing_counter lives in gameState.flags and resets to 0 when an event is successfully queued.
All character data lives in CharacterDatabase. Never hardcode names, portraits, or dialogue in screens. ID must match image filename exactly.
final c = CharacterDatabase.getCharacterById('nonna');
final portrait = ImageUtils.safeAsset(c.imagePath);
// Path: assets/images/characters/{id}.png
// Victory: victory_defeat/{Name}_Victory.png
// Defeat: victory_defeat/{Name}_Defeat.png
// For atmospheric reveals — always:
ImageUtils.arcadePortraitStage(character)
// bleed bg + foreground + accent glow
id String // snake_case, stable across saves name String nickname String // 'The Bear', 'The Volcano' gender String // 'male' | 'female' startingLocation String backstory String // Multi-paragraph bonuses CharacterBonuses voicePattern String // 'enforcer_professional' generation int // 1=original, 2+=successors dialogue CharacterDialogue greeting, success, failure, threat, family storyModifiers StoryModifiers businessDeals, violence, family, law
| Aspect | Detail |
|---|---|
| Slots | 3 named slots with metadata previews |
| Format | JSON — GameState.toJson() / fromJson() |
| Code gen | json_serializable — all .g.dart files |
| Migration | Legacy shared-prefs → file on first load |
| Regression | save_service_regression_test.dart |
| Crash DSN | Wired. Not yet configured. |
// Always serialize through the model final json = gameState.toJson(); await saveService.saveSlot(idx, json); // After any model field change: flutter pub run build_runner build \ --delete-conflicting-outputs // The [STORY SELECT] AppLogger line in // story_screen.dart is intentional. // It logs which story loaded and why. // Do NOT remove it.
| Package | Version | Role |
|---|---|---|
| Flutter | SDK 3.0+ | Framework |
| Provider | 6.1.0 | State — ChangeNotifier |
| Audioplayers | 6.5.1 | Location-keyed audio |
| Steamworks | 0.4.7 | macOS dylib + Win DLL |
| Sentry Flutter | 8.14.0 | Crash reporting |
| json_serializable | — | Model .g.dart files |
| flutter_animate | — | 60fps The Lead / Slam |
GameProvider (root ChangeNotifier) ├── game_provider_economy WADD, market, lay low ├── game_provider_crew Crew, mutiny check ├── game_provider_event_queue Events, pacing, auto-launch ├── game_provider_story_prog Story selection by phase ├── game_provider_persistence Save/load, migration ├── game_provider_legendary Legendary moments ├── game_provider_territory Territory, location travel └── game_provider_empire Empire pressure, bosses // STATE IS THE ENEMY // Logic lives in services. UI only renders state. // No business logic in build() methods.
| Pattern | Example |
|---|---|
| Event screens | arch_enemy_confrontation_screen.dart |
| Minigame screens | pizza_slinger_screen_arcade.dart |
| Services | dice_service.dart |
| Provider mixins | game_provider_economy.dart |
| Story databases | story_database_origins.dart |
| Generated files | game_state.g.dart — do not edit |
| Test files | nonna_hub_visibility_test.dart |
| Character IDs | tano_garofalo — must match image filename |
| Story IDs | nonna_first_kidnapping — stable, save key |
| Flag keys | nonna_kidnapped — snake_case, stable |
// ~3,470 lines. Navigate with ═══ dividers. // Cmd+F on section number to jump. Section 1 State variables Section 2 Immersion hooks (music, typewriter) Section 3 Input handling (keyboard, save/load) Section 4 Build + leave dialog Section 5 RPG dialog popup Section 6 Location label resolution Section 7 Choice dispatch (5 typed handlers) Section 8 Dice engine + ceremony maps Section 9 Consequence + arcade moments Section 10 _buildContestedRoll Section 11 Vendetta reveal // Wrappers at bottom: // _QuickEncounterWrapper // _BossTakeoverWrapper // _ArchEnemyRevealDialog
flutter analyze — must return zero errors before any commit.flutter test — minigame_screen_smoke_test.dart covers UI stability.