Commit Graph

76 Commits

Author SHA1 Message Date
airkjw 1ec59ba80d fix(ads): reset ad round in startStage so nextStage advance counts (review fix)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 14:09:02 +09:00
airkjw 297449ccce feat(ads): stage-end interstitial gated by policy; restart resets round 2026-06-13 14:03:52 +09:00
airkjw 3943653a23 feat(ads): rewarded ad gates the continue/extra-moves rescue 2026-06-13 14:02:31 +09:00
airkjw 6c4304cfd8 style(test): drop unused import in ads_notifier_test 2026-06-13 14:00:03 +09:00
airkjw 662ee55e1d feat(ads): run consent flow (UMP->ATT->init) after the first frame
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 13:59:22 +09:00
airkjw 539afd1dad feat(ads): ref-constructed ad/consent/iap providers, single ownership source 2026-06-13 13:57:30 +09:00
airkjw 6d2ffebb92 feat(iap): remove_ads purchase/restore service + adsRemoved notifier 2026-06-13 13:55:44 +09:00
airkjw e43fda8551 feat(ads): ConsentService enforcing UMP -> ATT -> SDK init order 2026-06-13 13:53:10 +09:00
airkjw 4744aa167a feat(ads): AdService for interstitial/rewarded/banner with policy gating 2026-06-13 13:49:18 +09:00
airkjw eb258c7324 feat(ads): ad id config with test/real switch via dart-define 2026-06-13 13:46:44 +09:00
airkjw 947d5566a2 feat(iap): persist adsRemoved flag (additive, saveVersion 1)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 13:45:33 +09:00
airkjw f560b9d4c8 feat(ads): pure-Dart interstitial frequency policy with tests 2026-06-13 13:42:55 +09:00
airkjw 2422a94b9a build(ios): commit pod artifacts for google_mobile_ads (Podfile.lock, pbxproj)
pod install for google_mobile_ads added Google-Mobile-Ads-SDK 12.14.0 and a
[CP] Copy Pods Resources build phase. Tracking both keeps the build
reproducible (follow-up to 245a065).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 13:41:29 +09:00
airkjw 245a065ac7 build: add AdMob/IAP/ATT plugins and native ad config (test ids)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 13:40:23 +09:00
airkjw 0781e817d0 docs: Phase 5 monetization (AdMob + IAP) implementation plan
14 tasks: pure-Dart frequency policy (TDD), adsRemoved persistence, ad config
(test ids), AdService/ConsentService/IapService, ref-constructed providers
(single ownership source), rewarded-gated rescue, stage-end interstitial,
home/map banner, settings (remove ads + restore), simulator verification.
Runs on Google test ids today; owner real ids slot in by config later.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 12:50:59 +09:00
airkjw 3a83c0a2b1 docs: add Firebase-integrated build runtime screenshot (evidence)
Firebase Analytics SDK initialized on the iOS simulator; debug build renders
gameplay and (per system log) disables GA4 collection. Evidence for the
analytics wiring milestone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 12:20:38 +09:00
airkjw 74fe1858d4 feat(analytics): disable GA4 collection in debug builds
setAnalyticsCollectionEnabled(kReleaseMode) after Firebase init so the native
SDK's automatic events (session_start, screen_view) stay out of production
analytics in debug too — not only our custom events. Verified on simulator:
release builds collect, debug builds do not.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 12:18:05 +09:00
airkjw 41b0180b44 feat(analytics): wire Firebase Analytics into app startup
flutterfire configure registered the iOS/Android apps under project
block-seasons and generated firebase_options.dart + native config. main()
now initializes Firebase and routes analytics through FirebaseAnalyticsBackend
in release builds (console logger in debug, so dev traffic never pollutes
GA4). Firebase init is guarded — failure falls back to the debug logger
rather than blocking startup.

firebase.json keeps the existing Hosting config and gains the FlutterFire
platform section. Client config files are committed (they ship in the binary;
Firebase security is enforced by rules, not config secrecy).

flutter analyze clean, all 161 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 12:10:03 +09:00
airkjw e3fb5959c5 build(ios): raise deployment target to iOS 15.0 for Firebase SDK
firebase_core 4.x bundles the Firebase iOS SDK which requires iOS 15.0+.
Sets the Podfile platform and the three Xcode build configs accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 12:01:43 +09:00
airkjw 5cd9d0ab10 feat(analytics): add FirebaseAnalyticsBackend (firebase wiring pt.1)
Adds firebase_core + firebase_analytics and a FirebaseAnalyticsBackend that
adapts the existing AnalyticsBackend interface to GA4. Kept in its own file
so the typed AnalyticsService and DebugAnalyticsBackend stay free of the
firebase dependency (unit tests never pull in platform channels).

Not yet wired into main() — that needs lib/firebase_options.dart from
flutterfire configure (owner step). All 161 tests still green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 12:00:45 +09:00
airkjw 3e25e3b9ca chore: add Firebase Hosting config for season content delivery
firebase init hosting wired to project block-seasons, public dir = deploy.
Generated season payload (deploy/content/) and CLI cache (.firebase/) are
gitignored — content/ is the source of truth, regenerated by the deploy
script in docs/firebase-hosting-guide.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 09:18:53 +09:00
airkjw af7bb83fb9 Merge Phase 4: remote seasons + analytics
Manifest-driven season delivery (SHA256 + atomic cache, bundled
fallback), Season 2 'Summer Tide' remote-only content, analytics
abstraction with debug backend, rescue crash fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 22:49:42 +09:00
airkjw b5134ef86d fix: season list re-reads via provider dependency; ignore pid files
seasonsProvider now watches seasonRefreshProvider instead of relying on
a HomeScreen ref.listen, making the refresh ordering-independent. Removes
the redundant listener from HomeScreen. Appends *.pid to .gitignore.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 22:49:24 +09:00
airkjw 8555397c43 feat: phase 4 remote seasons verified end-to-end; owner hosting guide
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 22:43:53 +09:00
airkjw ba70db3e60 feat: season 2 'Summer Tide' content and manifest generator
Adds Summer Tide (season_002) — 30 stages with deep-teal gradient and
cyan accent — generated remote-only (no copyToAssets). Introduces
tool/make_manifest.dart to rebuild content/manifest.json from all
season packs with SHA-256 verification support.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:45:22 +09:00
airkjw 9763968db9 fix: hide spent rescue to prevent StateError crash; log per-attempt starts
Expose engine.rescueUsed getter and surface it through GameViewState so
the result overlay can omit the watch-ad FilledButton after a rescue has
been consumed, preventing a second tap from calling useContinue /
addExtraMoves and hitting their StateError guard. Give-up is promoted to
FilledButton when rescue is unavailable for clear affordance.

Also emit stageStart / endlessStart analytics in restart() so every
attempt (not just the first) is bracketed by a matching start event.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:41:59 +09:00
airkjw 074a21ea2b feat: analytics abstraction with debug backend and game event wiring
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:35:31 +09:00
airkjw 6d2d97bfcc fix: guard journey map against an empty season list
Prevents StateError when data builder is called with empty list
by displaying loading indicator instead of passing empty list to
activeSeason(), matching title screen behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:31:22 +09:00
airkjw 4fa5564975 feat: session content sync trigger and newest-season selection
Add seasonRefreshProvider (once-per-session FutureProvider) and activeSeason()
helper; HomeScreen listens and invalidates seasonsProvider when new packs arrive;
season_map_screen and season_title_screen switch from list.first to activeSeason.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:28:39 +09:00
airkjw 73a56aeeb1 perf: async cache reads in content repository 2026-06-12 13:24:14 +09:00
airkjw e722fe2ce1 feat: content repository merges cached seasons with bundled fallback
Extends ContentRepository with optional cacheDir: cached packs in
<cacheDir>/seasons/*/pack.json merge with bundled ones (cached wins
for same id), corrupt/future-schema packs silently ignored, refresh()
fires ContentDownloader.sync() once per session. main() wires the real
cache+downloader instance; default constructor stays bundled-only for tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:15:35 +09:00
airkjw a820e97237 fix: harden downloader against path traversal, URL escape, oversized bodies
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:11:18 +09:00
airkjw bfa9c09b28 feat: content downloader with sha256 verify and atomic cache
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:05:56 +09:00
airkjw c7bdb9b9c9 feat: remote manifest model and content dependencies
Add http, crypto, path_provider deps; introduce RemoteManifest /
ManifestSeason immutable models with fromJson/toJson, schema-version
guard (FormatException on unsupported schema), and fallback for missing
current field. 3/3 TDD tests pass, flutter analyze clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:02:17 +09:00
airkjw 63ac8c6b9e docs: add Phase 4 remote seasons implementation plan
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 12:44:41 +09:00
airkjw 41b9a14c0c Merge Phase 3.5: commercial polish round
Glossy tiles + season theme system, juice kit, intro flow
(splash/season card/tutorial), serpentine journey map,
endless classic mode, redesigned home.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 07:39:27 +09:00
airkjw f9eaa3ae59 docs: polish round simulator screenshots (splash, season card, home)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 07:39:19 +09:00
airkjw c59454aa5f fix: center splash logo and wordmark (stack was sized to children)
Wrap AnimatedBuilder in SizedBox.expand so the Stack fills the full
Scaffold body; alignment: Alignment.center now centers within the whole
screen instead of within the wordmark-sized intrinsic box.

Adds a regression widget test (test/ui/splash_screen_test.dart) that
asserts the wordmark dx is within 1px of screen-center at 1500ms into
the animation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 07:37:54 +09:00
airkjw bf7720ebd3 fix: clear season flow when starting Classic so tutorial/theme can't leak
- Add SeasonFlowNotifier.clear() to null out the flow state
- Call clear() in HomeScreen's Classic button before startStage()
- Broaden tutorial-end guard to next.phase != GamePhase.playing (covers stuck)
- Add regression test: clear() resets flow to null

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 07:33:41 +09:00
airkjw 26adf98d73 fix: guard home buttons against double-tap double-push 2026-06-12 07:22:18 +09:00
airkjw 94e62d3e41 feat: redesigned home with adventure/classic entries and endless best
Add adventure/classic l10n keys; rewrite HomeScreen with SeasonBackground,
2×2 glossy logo mark, Adventure→SeasonMap + Classic→endless GameScreen buttons,
and conditional best-score caption. Update widget_test assertions accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 07:18:42 +09:00
airkjw 5a84a47cd4 fix: keep HUD balance in endless via invisible moves chip
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 07:13:47 +09:00
airkjw fea8336391 feat: endless mode UI - game over card, best score, HUD 2026-06-12 07:06:30 +09:00
airkjw c5e9029cad test: relax endless survival bound to >30 (proves no cap, seed-robust) 2026-06-12 07:01:22 +09:00
airkjw 6c76837ab6 feat: endless score-attack mode in the engine
Add StageConfig.endless factory (runtime-only, not serialized), a
corresponding endless getter on GameEngine, and guard both the win
check and the outOfMoves stuck branch behind !_stage.endless so
endless runs can never be won or move-limited. Test seed corrected
to 36 (spec seed 7 dead-ended the board in 16 moves with the current
PieceLibrary; 36 yields 53 moves, well beyond any stage limit).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 06:53:02 +09:00
airkjw 2b44dcd812 fix: journey map season-complete styling, scroll callback guard, palette constant
- Guard addPostFrameCallback with !_autoScrolled so it only fires once per
  widget lifetime instead of on every LayoutBuilder rebuild.
- Derive seasonComplete flag; pass it into _node so the last stage uses
  gold "done" styling (not "current/next") when the season is fully 3-starred.
- Extract const Color(0xFF232B4A) to GamePalette.lockedNode.
- Remove warnIfMissed: false from tap call in season_map_screen_test; ensureVisible
  already guarantees hit-testing succeeds (confirmed: test still passes cleanly).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 23:30:22 +09:00
airkjw 78eb5c0639 feat: serpentine journey map with auto-scroll and glowing current node
Replaces the plain GridView with a Candy-Crush-style journey map:
dotted serpentine path, circular nodes (gold=done, glowing=current,
dark+lock=locked), glass header, auto-scroll to current stage.
Updates season_map_screen_test to use Key('stage_node_$i') finders.
2026-06-11 23:17:41 +09:00
airkjw 96304cc8a7 feat: serpentine map layout function 2026-06-11 23:12:29 +09:00
airkjw ee364cc2e2 fix: dismiss tutorial when the stage ends
A finished stage ends the tutorial; otherwise the overlay would sit
on top of the result card and leak into the next stage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 23:11:13 +09:00
airkjw 963d0d5dd6 feat: first-play interactive tutorial overlay
Add TutorialOverlay widget (dim veil, message bubble, animated hand on
dragPiece step, skip button) and wire it into game_screen: start on
flow.index==0 when tutorialDone is false, forward onPlaced/onLineCleared
events unconditionally from fxTick handler, and compute hand-path
coordinates from board/tray RenderBox geometry.
2026-06-11 22:58:14 +09:00