# Boosters & Daily Reward Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add a lightweight booster economy (hammer / shuffle / line-bomb) earned via a 7-day login calendar and rewarded ads, used in a stage through a booster bar.

**Architecture:** Pure-Dart booster ops live on `GameEngine` (grid-only mutation, no score/move/objective effects). A `BoosterType` enum + `SaveRepository` counts back a `BoosterInventoryNotifier`. A pure `DailyRewardCalendar` computes the claimable day; `DailyRewardNotifier` grants. `GameSessionNotifier` coordinates engine + inventory on use. UI adds a booster bar (targeting mode) and a daily popup. Rewarded ads reuse `AdService.showRewarded()`.

**Tech Stack:** Flutter, Riverpod (plain Notifiers, no codegen), shared_preferences, google_mobile_ads, flutter gen-l10n, flutter_test.

**Spec:** `docs/superpowers/specs/2026-06-18-boosters-daily-reward-design.md`

**Conventions:** TDD (red → green). Run a single test file with `flutter test path`. Run all with `flutter test`. `flutter analyze` must stay clean. **Do NOT run `flutter build` / Xcode — builds are owner-commanded only** (see CLAUDE.md). Commit after each green task.

---

## File Structure

- Create `lib/game/models/booster.dart` — `BoosterType` enum (pure).
- Modify `lib/game/engine/game_engine.dart` — `useHammer`/`useShuffle`/`useLineBomb`.
- Modify `lib/data/save_repository.dart` — booster counts + daily-claim persistence.
- Create `lib/game/daily/daily_reward.dart` — pure `DailyRewardCalendar` + reward table.
- Create `lib/state/booster_inventory_notifier.dart` — inventory Notifier.
- Create `lib/state/daily_reward_notifier.dart` — daily Notifier.
- Modify `lib/state/game_session_notifier.dart` — booster-use coordination + view fields.
- Modify `lib/state/providers.dart` — new providers.
- Create `lib/ui/widgets/booster_bar.dart` — booster bar + targeting state.
- Modify `lib/ui/screens/game_screen.dart` — mount bar, targeting taps, 0-count ad dialog.
- Create `lib/ui/widgets/daily_reward_sheet.dart` — 7-day popup.
- Modify `lib/ui/screens/home_screen.dart` — show popup when claimable.
- Modify `lib/services/analytics_service.dart` — booster/daily events.
- Modify `lib/l10n/app_en.arb`, `lib/l10n/app_ko.arb` — strings.
- Tests under `test/game/`, `test/data/`, `test/state/`, `test/ui/`.

---

## Task 1: Engine — `useHammer`

**Files:**
- Modify: `lib/game/engine/game_engine.dart`
- Test: `test/game/engine/booster_test.dart`

- [ ] **Step 1: Write the failing test**

```dart
import 'package:block_seasons/game/engine/game_engine.dart';
import 'package:block_seasons/game/models/stage.dart';
import 'package:flutter_test/flutter_test.dart';

StageConfig _stage() => StageConfig.fromJson({
      'id': 'b_test',
      'seed': 1,
      'moveLimit': 20,
      'preset': [
        {'x': 0, 'y': 0, 't': 'filled', 'c': 3},
        {'x': 1, 'y': 0, 't': 'filled', 'c': 4},
      ],
      'objectives': [
        {'type': 'reachScore', 'target': 100000},
      ],
    });

void main() {
  test('useHammer empties a filled cell without scoring or spending a move', () {
    final e = GameEngine(_stage());
    final score0 = e.score;
    final moves0 = e.movesUsed;

    expect(e.grid.isOccupied(0, 0), isTrue);
    final ok = e.useHammer(0, 0);

    expect(ok, isTrue);
    expect(e.grid.isOccupied(0, 0), isFalse);
    expect(e.score, score0, reason: 'no points from a booster');
    expect(e.movesUsed, moves0, reason: 'no move spent');
  });

  test('useHammer on an empty cell returns false and changes nothing', () {
    final e = GameEngine(_stage());
    expect(e.grid.isOccupied(5, 5), isFalse);
    expect(e.useHammer(5, 5), isFalse);
    expect(e.grid.isOccupied(5, 5), isFalse);
  });

  test('useHammer is rejected once the stage is won or lost', () {
    final e = GameEngine(_stage());
    e.declineAndLose();
    expect(e.useHammer(0, 0), isFalse);
  });
}
```

- [ ] **Step 2: Run test to verify it fails**

Run: `flutter test test/game/engine/booster_test.dart`
Expected: FAIL — `useHammer` not defined.

- [ ] **Step 3: Write minimal implementation**

Add to `GameEngine` (after `declineAndLose`):

```dart
  /// Booster: empties one filled cell. No move/score/combo/objective effect.
  /// Allowed only mid-attempt (playing or stuck). Returns false on an empty
  /// cell or finished attempt so the caller keeps the booster.
  bool useHammer(int x, int y) {
    if (_phase == GamePhase.won || _phase == GamePhase.lost) return false;
    if (!_grid.isOccupied(x, y)) return false;
    _grid = _grid.withCell(x, y, const Cell(CellType.empty));
    _checkStuck();
    return true;
  }
```

Ensure `cell.dart` (`Cell`, `CellType`) is imported — it already is.

- [ ] **Step 4: Run test to verify it passes**

Run: `flutter test test/game/engine/booster_test.dart`
Expected: PASS (3 tests).

- [ ] **Step 5: Commit**

```bash
git add lib/game/engine/game_engine.dart test/game/engine/booster_test.dart
git commit -m "feat(engine): hammer booster removes one cell, no scoring"
```

---

## Task 2: Engine — `useShuffle`

**Files:**
- Modify: `lib/game/engine/game_engine.dart`
- Test: `test/game/engine/booster_test.dart` (append)

- [ ] **Step 1: Append the failing test**

```dart
  test('useShuffle replaces the tray without spending a move or scoring', () {
    final e = GameEngine(_stage());
    final before = List.of(e.tray);
    final score0 = e.score;
    final moves0 = e.movesUsed;

    final ok = e.useShuffle();

    expect(ok, isTrue);
    expect(e.tray.length, before.length);
    expect(e.score, score0);
    expect(e.movesUsed, moves0);
  });

  test('useShuffle is rejected after the attempt finishes', () {
    final e = GameEngine(_stage());
    e.declineAndLose();
    expect(e.useShuffle(), isFalse);
  });
```

- [ ] **Step 2: Run test to verify it fails**

Run: `flutter test test/game/engine/booster_test.dart`
Expected: FAIL — `useShuffle` not defined.

- [ ] **Step 3: Write minimal implementation**

```dart
  /// Booster: re-deals the tray. No move/score effect. Re-checks stuck so a
  /// dead board with a hopeless tray can become playable again.
  bool useShuffle() {
    if (_phase == GamePhase.won || _phase == GamePhase.lost) return false;
    _tray = _generator.nextTray(_grid);
    _checkStuck();
    return true;
  }
```

- [ ] **Step 4: Run test to verify it passes**

Run: `flutter test test/game/engine/booster_test.dart`
Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add lib/game/engine/game_engine.dart test/game/engine/booster_test.dart
git commit -m "feat(engine): shuffle booster re-deals the tray"
```

---

## Task 3: Engine — `useLineBomb`

**Files:**
- Modify: `lib/game/engine/game_engine.dart`
- Test: `test/game/engine/booster_test.dart` (append)

- [ ] **Step 1: Append the failing test**

```dart
  test('useLineBomb(row:) empties that row, no scoring or move', () {
    final e = GameEngine(_stage()); // row 0 has cells at x=0,1
    final score0 = e.score;
    final moves0 = e.movesUsed;

    final ok = e.useLineBomb(row: 0);

    expect(ok, isTrue);
    for (var x = 0; x < 8; x++) {
      expect(e.grid.isOccupied(x, 0), isFalse, reason: 'col $x');
    }
    expect(e.score, score0);
    expect(e.movesUsed, moves0);
  });

  test('useLineBomb(col:) empties that column', () {
    final e = GameEngine(_stage()); // (0,0) filled
    expect(e.useLineBomb(col: 0), isTrue);
    for (var y = 0; y < 8; y++) {
      expect(e.grid.isOccupied(0, y), isFalse, reason: 'row $y');
    }
  });

  test('useLineBomb requires exactly one of row/col', () {
    final e = GameEngine(_stage());
    expect(e.useLineBomb(), isFalse);
    expect(e.useLineBomb(row: 0, col: 0), isFalse);
  });

  test('useLineBomb is rejected after the attempt finishes', () {
    final e = GameEngine(_stage());
    e.declineAndLose();
    expect(e.useLineBomb(row: 0), isFalse);
  });
```

- [ ] **Step 2: Run test to verify it fails**

Run: `flutter test test/game/engine/booster_test.dart`
Expected: FAIL — `useLineBomb` not defined.

- [ ] **Step 3: Write minimal implementation**

```dart
  /// Booster: empties one row or one column (exactly one of [row]/[col]).
  /// No move/score/objective effect. Re-checks stuck.
  bool useLineBomb({int? row, int? col}) {
    if (_phase == GamePhase.won || _phase == GamePhase.lost) return false;
    if ((row == null) == (col == null)) return false; // need exactly one
    for (var i = 0; i < GridState.size; i++) {
      final x = col ?? i;
      final y = row ?? i;
      _grid = _grid.withCell(x, y, const Cell(CellType.empty));
    }
    _checkStuck();
    return true;
  }
```

- [ ] **Step 4: Run test to verify it passes**

Run: `flutter test test/game/engine/booster_test.dart`
Expected: PASS (all booster engine tests).

- [ ] **Step 5: Commit**

```bash
git add lib/game/engine/game_engine.dart test/game/engine/booster_test.dart
git commit -m "feat(engine): line-bomb booster clears a row or column"
```

---

## Task 4: `BoosterType` model

**Files:**
- Create: `lib/game/models/booster.dart`

- [ ] **Step 1: Create the enum** (no behavior → no test; it is exercised by later tasks)

```dart
// lib/game/models/booster.dart

/// The three boosters the player can earn and spend. Pure model so the data
/// and state layers can both reference it without a Flutter dependency.
enum BoosterType { hammer, shuffle, lineBomb }
```

- [ ] **Step 2: Verify analyze is clean**

Run: `flutter analyze`
Expected: No issues.

- [ ] **Step 3: Commit**

```bash
git add lib/game/models/booster.dart
git commit -m "feat(model): BoosterType enum"
```

---

## Task 5: SaveRepository — booster inventory

**Files:**
- Modify: `lib/data/save_repository.dart`
- Test: `test/data/save_repository_booster_test.dart`

- [ ] **Step 1: Write the failing test**

```dart
import 'package:block_seasons/data/save_repository.dart';
import 'package:block_seasons/game/models/booster.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  Future<SaveRepository> fresh() async {
    SharedPreferences.setMockInitialValues({});
    return SaveRepository(await SharedPreferences.getInstance());
  }

  test('booster counts start at zero', () async {
    final repo = await fresh();
    for (final t in BoosterType.values) {
      expect(repo.boosterCount(t), 0);
    }
  });

  test('grantBooster adds and persists across reload', () async {
    final repo = await fresh();
    await repo.grantBooster(BoosterType.hammer, 2);
    expect(repo.boosterCount(BoosterType.hammer), 2);

    final reloaded = SaveRepository(await SharedPreferences.getInstance());
    expect(reloaded.boosterCount(BoosterType.hammer), 2);
  });

  test('consumeBooster decrements and returns false at zero', () async {
    final repo = await fresh();
    await repo.grantBooster(BoosterType.shuffle, 1);
    expect(await repo.consumeBooster(BoosterType.shuffle), isTrue);
    expect(repo.boosterCount(BoosterType.shuffle), 0);
    expect(await repo.consumeBooster(BoosterType.shuffle), isFalse);
  });
}
```

- [ ] **Step 2: Run test to verify it fails**

Run: `flutter test test/data/save_repository_booster_test.dart`
Expected: FAIL — `boosterCount` not defined.

- [ ] **Step 3: Write minimal implementation**

In `save_repository.dart`:

1. Add import at top: `import '../game/models/booster.dart';`
2. Add field + parse in constructor (inside the `if (raw != null)` block, after the flags parsing):

```dart
      final boosters = json['boosters'] as Map<String, dynamic>? ?? {};
      for (final t in BoosterType.values) {
        _boosters[t] = boosters[t.name] as int? ?? 0;
      }
```

3. Add field near the other fields:

```dart
  final Map<BoosterType, int> _boosters = {
    for (final t in BoosterType.values) t: 0,
  };
```

4. Add API:

```dart
  int boosterCount(BoosterType type) => _boosters[type] ?? 0;

  Future<void> grantBooster(BoosterType type, [int n = 1]) {
    _boosters[type] = (_boosters[type] ?? 0) + n;
    return _flush();
  }

  /// Spends one booster. Returns false (and changes nothing) when none are left.
  Future<bool> consumeBooster(BoosterType type) async {
    final have = _boosters[type] ?? 0;
    if (have <= 0) return false;
    _boosters[type] = have - 1;
    await _flush();
    return true;
  }
```

5. In `_flush()`'s `jsonEncode({...})`, add a key:

```dart
          'boosters': {for (final t in BoosterType.values) t.name: _boosters[t]},
```

- [ ] **Step 4: Run test to verify it passes**

Run: `flutter test test/data/save_repository_booster_test.dart`
Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add lib/data/save_repository.dart test/data/save_repository_booster_test.dart
git commit -m "feat(save): persist booster inventory"
```

---

## Task 6: SaveRepository — daily-claim persistence

**Files:**
- Modify: `lib/data/save_repository.dart`
- Test: `test/data/save_repository_daily_test.dart`

- [ ] **Step 1: Write the failing test**

```dart
import 'package:block_seasons/data/save_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  Future<SaveRepository> fresh() async {
    SharedPreferences.setMockInitialValues({});
    return SaveRepository(await SharedPreferences.getInstance());
  }

  test('daily claim fields start empty', () async {
    final repo = await fresh();
    expect(repo.dailyLastClaimedYmd, isNull);
    expect(repo.dailyCalendarDay, 0);
  });

  test('recordDailyClaim persists day and ymd across reload', () async {
    final repo = await fresh();
    await repo.recordDailyClaim('2026-06-18', 3);
    expect(repo.dailyLastClaimedYmd, '2026-06-18');
    expect(repo.dailyCalendarDay, 3);

    final reloaded = SaveRepository(await SharedPreferences.getInstance());
    expect(reloaded.dailyLastClaimedYmd, '2026-06-18');
    expect(reloaded.dailyCalendarDay, 3);
  });
}
```

- [ ] **Step 2: Run test to verify it fails**

Run: `flutter test test/data/save_repository_daily_test.dart`
Expected: FAIL — `dailyLastClaimedYmd` not defined.

- [ ] **Step 3: Write minimal implementation**

1. Add fields + parse (in constructor, after boosters parse):

```dart
      final daily = json['daily'] as Map<String, dynamic>?;
      _dailyLastClaimedYmd = daily?['lastYmd'] as String?;
      _dailyCalendarDay = daily?['day'] as int? ?? 0;
```

2. Fields:

```dart
  String? _dailyLastClaimedYmd;
  int _dailyCalendarDay = 0;
```

3. API:

```dart
  String? get dailyLastClaimedYmd => _dailyLastClaimedYmd;
  int get dailyCalendarDay => _dailyCalendarDay;

  Future<void> recordDailyClaim(String ymd, int day) {
    _dailyLastClaimedYmd = ymd;
    _dailyCalendarDay = day;
    return _flush();
  }
```

4. In `_flush()`'s map, add:

```dart
          'daily': {'lastYmd': _dailyLastClaimedYmd, 'day': _dailyCalendarDay},
```

- [ ] **Step 4: Run test to verify it passes**

Run: `flutter test test/data/save_repository_daily_test.dart`
Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add lib/data/save_repository.dart test/data/save_repository_daily_test.dart
git commit -m "feat(save): persist daily-reward claim state"
```

---

## Task 7: Pure `DailyRewardCalendar`

**Files:**
- Create: `lib/game/daily/daily_reward.dart`
- Test: `test/game/daily/daily_reward_test.dart`

This is pure logic: given the last-claimed ymd + stored day + today's ymd, compute
whether today is claimable and which calendar day (1–7) applies, plus the reward
table. Dates are passed as `DateTime` (date-only) to stay testable.

- [ ] **Step 1: Write the failing test**

```dart
import 'package:block_seasons/game/daily/daily_reward.dart';
import 'package:block_seasons/game/models/booster.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  final cal = DailyRewardCalendar();

  DateTime d(int day) => DateTime(2026, 6, day);

  test('first ever claim is day 1', () {
    final r = cal.resolve(lastClaimedYmd: null, storedDay: 0, today: d(18));
    expect(r.claimable, isTrue);
    expect(r.day, 1);
  });

  test('claiming again the same day is not claimable', () {
    final r = cal.resolve(
        lastClaimedYmd: '2026-06-18', storedDay: 1, today: d(18));
    expect(r.claimable, isFalse);
  });

  test('a consecutive day advances the calendar', () {
    final r = cal.resolve(
        lastClaimedYmd: '2026-06-18', storedDay: 3, today: d(19));
    expect(r.claimable, isTrue);
    expect(r.day, 4);
  });

  test('day 7 wraps back to day 1', () {
    final r = cal.resolve(
        lastClaimedYmd: '2026-06-18', storedDay: 7, today: d(19));
    expect(r.day, 1);
  });

  test('a missed day resets to day 1', () {
    final r = cal.resolve(
        lastClaimedYmd: '2026-06-18', storedDay: 4, today: d(20));
    expect(r.claimable, isTrue);
    expect(r.day, 1);
  });

  test('reward table covers days 1..7 and day 7 is the jackpot', () {
    expect(cal.rewardFor(1), {BoosterType.hammer: 1});
    expect(cal.rewardFor(7),
        {BoosterType.hammer: 2, BoosterType.shuffle: 2, BoosterType.lineBomb: 2});
  });

  test('ymd formats a date as YYYY-MM-DD', () {
    expect(cal.ymd(DateTime(2026, 6, 7)), '2026-06-07');
  });
}
```

- [ ] **Step 2: Run test to verify it fails**

Run: `flutter test test/game/daily/daily_reward_test.dart`
Expected: FAIL — `DailyRewardCalendar` not defined.

- [ ] **Step 3: Write minimal implementation**

```dart
// lib/game/daily/daily_reward.dart
import '../models/booster.dart';

/// The result of evaluating the calendar for "today".
class DailyResolution {
  const DailyResolution({required this.claimable, required this.day});
  final bool claimable;
  final int day; // 1..7, the day to show/grant
}

/// Pure 7-day login-calendar logic. No storage or clock — callers pass the
/// persisted state and today's date so it is fully unit-testable.
class DailyRewardCalendar {
  const DailyRewardCalendar();

  static const int cycle = 7;

  String ymd(DateTime date) {
    final m = date.month.toString().padLeft(2, '0');
    final d = date.day.toString().padLeft(2, '0');
    return '${date.year}-$m-$d';
  }

  DailyResolution resolve({
    required String? lastClaimedYmd,
    required int storedDay,
    required DateTime today,
  }) {
    final todayYmd = ymd(today);
    if (lastClaimedYmd == todayYmd) {
      return DailyResolution(claimable: false, day: storedDay);
    }
    final yesterday = ymd(today.subtract(const Duration(days: 1)));
    final int day;
    if (lastClaimedYmd == yesterday) {
      day = (storedDay % cycle) + 1; // advance, wrapping 7 -> 1
    } else {
      day = 1; // first ever, or a day was missed
    }
    return DailyResolution(claimable: true, day: day);
  }

  /// Boosters granted for a given calendar day (1..7). Tunable.
  Map<BoosterType, int> rewardFor(int day) {
    switch (day) {
      case 1:
        return {BoosterType.hammer: 1};
      case 2:
        return {BoosterType.shuffle: 1};
      case 3:
        return {BoosterType.lineBomb: 1};
      case 4:
        return {BoosterType.hammer: 1, BoosterType.shuffle: 1};
      case 5:
        return {BoosterType.shuffle: 1, BoosterType.lineBomb: 1};
      case 6:
        return {BoosterType.hammer: 1, BoosterType.lineBomb: 1};
      case 7:
      default:
        return {
          BoosterType.hammer: 2,
          BoosterType.shuffle: 2,
          BoosterType.lineBomb: 2,
        };
    }
  }
}
```

- [ ] **Step 4: Run test to verify it passes**

Run: `flutter test test/game/daily/daily_reward_test.dart`
Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add lib/game/daily/daily_reward.dart test/game/daily/daily_reward_test.dart
git commit -m "feat(daily): pure 7-day login-calendar logic + reward table"
```

---

## Task 8: `BoosterInventoryNotifier`

**Files:**
- Create: `lib/state/booster_inventory_notifier.dart`
- Modify: `lib/state/providers.dart`
- Test: `test/state/booster_inventory_test.dart`

- [ ] **Step 1: Write the failing test**

```dart
import 'package:block_seasons/data/save_repository.dart';
import 'package:block_seasons/game/models/booster.dart';
import 'package:block_seasons/state/booster_inventory_notifier.dart';
import 'package:block_seasons/state/providers.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  Future<ProviderContainer> container() async {
    SharedPreferences.setMockInitialValues({});
    final repo = SaveRepository(await SharedPreferences.getInstance());
    return ProviderContainer(
      overrides: [saveRepositoryProvider.overrideWithValue(repo)],
    );
  }

  test('exposes counts and updates on grant/consume', () async {
    final c = await container();
    final notifier = c.read(boosterInventoryProvider.notifier);

    expect(c.read(boosterInventoryProvider)[BoosterType.hammer], 0);

    await notifier.grant(BoosterType.hammer, 2);
    expect(c.read(boosterInventoryProvider)[BoosterType.hammer], 2);

    expect(await notifier.consume(BoosterType.hammer), isTrue);
    expect(c.read(boosterInventoryProvider)[BoosterType.hammer], 1);
  });
}
```

- [ ] **Step 2: Run test to verify it fails**

Run: `flutter test test/state/booster_inventory_test.dart`
Expected: FAIL — `booster_inventory_notifier.dart` missing.

- [ ] **Step 3: Write minimal implementation**

```dart
// lib/state/booster_inventory_notifier.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../data/save_repository.dart';
import '../game/models/booster.dart';

/// Live booster counts backed by [SaveRepository]. State is an immutable map.
class BoosterInventoryNotifier extends Notifier<Map<BoosterType, int>> {
  SaveRepository get _save => ref.read(saveRepositoryProvider);

  @override
  Map<BoosterType, int> build() => _snapshot();

  Map<BoosterType, int> _snapshot() =>
      {for (final t in BoosterType.values) t: _save.boosterCount(t)};

  Future<void> grant(BoosterType type, [int n = 1]) async {
    await _save.grantBooster(type, n);
    state = _snapshot();
  }

  Future<bool> consume(BoosterType type) async {
    final ok = await _save.consumeBooster(type);
    if (ok) state = _snapshot();
    return ok;
  }
}
```

Note: `saveRepositoryProvider` is already imported via `providers.dart`; this file
imports `save_repository.dart` directly to read the provider value through `ref`.
Add to `providers.dart` (with the other imports + providers):

```dart
import 'booster_inventory_notifier.dart';
```
```dart
final boosterInventoryProvider =
    NotifierProvider<BoosterInventoryNotifier, Map<BoosterType, int>>(
  BoosterInventoryNotifier.new,
);
```
Also add `import '../game/models/booster.dart';` to `providers.dart` if not present.

- [ ] **Step 4: Run test to verify it passes**

Run: `flutter test test/state/booster_inventory_test.dart`
Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add lib/state/booster_inventory_notifier.dart lib/state/providers.dart test/state/booster_inventory_test.dart
git commit -m "feat(state): BoosterInventoryNotifier"
```

---

## Task 9: `GameSessionNotifier` — booster-use coordination

**Files:**
- Modify: `lib/state/game_session_notifier.dart`
- Test: `test/state/game_session_booster_test.dart`

Coordination rule: call the engine first; **consume inventory only on engine
success**; never call the engine when the count is 0. Returns a small result so
the UI knows whether to show the "watch ad" path.

- [ ] **Step 1: Write the failing test**

```dart
import 'package:block_seasons/data/save_repository.dart';
import 'package:block_seasons/game/engine/piece_generator.dart';
import 'package:block_seasons/core/rng.dart';
import 'package:block_seasons/game/models/booster.dart';
import 'package:block_seasons/game/models/stage.dart';
import 'package:block_seasons/state/booster_inventory_notifier.dart';
import 'package:block_seasons/state/providers.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';

StageConfig _stage() => StageConfig.fromJson({
      'id': 'gs_b',
      'seed': 1,
      'moveLimit': 20,
      'preset': [
        {'x': 0, 'y': 0, 't': 'filled', 'c': 3},
      ],
      'objectives': [
        {'type': 'reachScore', 'target': 100000},
      ],
    });

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  Future<ProviderContainer> container() async {
    SharedPreferences.setMockInitialValues({});
    final repo = SaveRepository(await SharedPreferences.getInstance());
    final c = ProviderContainer(
      overrides: [saveRepositoryProvider.overrideWithValue(repo)],
    );
    c.read(gameSessionProvider.notifier)
        .startStage(_stage(), generator: PieceGenerator(SeededRng(1)));
    return c;
  }

  test('hammer consumes a booster only when one is owned and target valid',
      () async {
    final c = await container();
    final session = c.read(gameSessionProvider.notifier);
    final inv = c.read(boosterInventoryProvider.notifier);

    // No booster -> noBooster, grid unchanged.
    expect(await session.useHammer(0, 0), BoosterUseResult.noBooster);

    await inv.grant(BoosterType.hammer, 1);
    expect(await session.useHammer(0, 0), BoosterUseResult.success);
    expect(c.read(boosterInventoryProvider)[BoosterType.hammer], 0);
    expect(c.read(gameSessionProvider)!.grid.isOccupied(0, 0), isFalse);
  });

  test('an invalid target keeps the booster', () async {
    final c = await container();
    final session = c.read(gameSessionProvider.notifier);
    await c.read(boosterInventoryProvider.notifier).grant(BoosterType.hammer, 1);

    expect(await session.useHammer(5, 5), BoosterUseResult.invalidTarget);
    expect(c.read(boosterInventoryProvider)[BoosterType.hammer], 1);
  });
}
```

- [ ] **Step 2: Run test to verify it fails**

Run: `flutter test test/state/game_session_booster_test.dart`
Expected: FAIL — `useHammer`/`BoosterUseResult` not defined.

- [ ] **Step 3: Write minimal implementation**

In `game_session_notifier.dart`:
1. Imports: `import '../game/models/booster.dart';` and `import 'booster_inventory_notifier.dart';` (and `providers.dart` access to `boosterInventoryProvider` — it is in the same library group; import what is needed).
2. Add the result enum near the top of the file:

```dart
enum BoosterUseResult { success, noBooster, invalidTarget }
```

3. Add methods to the notifier (the engine instance is held as `_engine`; confirm
the field name when implementing and rebuild the view via the existing
`_emit()`/`state = ...` mechanism this file already uses after a move):

```dart
  Future<BoosterUseResult> useHammer(int x, int y) =>
      _useBooster(BoosterType.hammer, () => _engine.useHammer(x, y));

  Future<BoosterUseResult> useShuffle() =>
      _useBooster(BoosterType.shuffle, () => _engine.useShuffle());

  Future<BoosterUseResult> useLineBomb({int? row, int? col}) => _useBooster(
        BoosterType.lineBomb,
        () => _engine.useLineBomb(row: row, col: col),
      );

  Future<BoosterUseResult> _useBooster(
      BoosterType type, bool Function() apply) async {
    final inv = ref.read(boosterInventoryProvider.notifier);
    if (ref.read(boosterInventoryProvider)[type]! <= 0) {
      return BoosterUseResult.noBooster;
    }
    if (!apply()) return BoosterUseResult.invalidTarget;
    await inv.consume(type);
    _emitFromEngine(); // rebuild GameViewState from _engine (use existing helper)
    return BoosterUseResult.success;
  }
```

**Implementation note:** This file already rebuilds `state` (a `GameViewState`)
from the engine after `tryPlace`. Reuse that exact rebuild path for
`_emitFromEngine()` — do not invent a new one. Read the file first and call the
same constructor/helper used after a successful placement.

- [ ] **Step 4: Run test to verify it passes**

Run: `flutter test test/state/game_session_booster_test.dart`
Expected: PASS.

- [ ] **Step 5: Run the full suite + analyze**

Run: `flutter test` then `flutter analyze`
Expected: all green, no issues.

- [ ] **Step 6: Commit**

```bash
git add lib/state/game_session_notifier.dart test/state/game_session_booster_test.dart
git commit -m "feat(state): coordinate booster use with inventory in the session"
```

---

## Task 10: `DailyRewardNotifier`

**Files:**
- Create: `lib/state/daily_reward_notifier.dart`
- Modify: `lib/state/providers.dart`
- Test: `test/state/daily_reward_notifier_test.dart`

State: the current `DailyResolution` (claimable + day). `claim({bool doubled})`
grants the day's boosters (×2 if doubled), records the claim, refreshes state. A
`DateTime Function() now` is injected for testability (default `DateTime.now`).

- [ ] **Step 1: Write the failing test**

```dart
import 'package:block_seasons/data/save_repository.dart';
import 'package:block_seasons/game/models/booster.dart';
import 'package:block_seasons/state/booster_inventory_notifier.dart';
import 'package:block_seasons/state/daily_reward_notifier.dart';
import 'package:block_seasons/state/providers.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  Future<ProviderContainer> container(DateTime today) async {
    SharedPreferences.setMockInitialValues({});
    final repo = SaveRepository(await SharedPreferences.getInstance());
    return ProviderContainer(overrides: [
      saveRepositoryProvider.overrideWithValue(repo),
      dailyNowProvider.overrideWithValue(() => today),
    ]);
  }

  test('first day is claimable as day 1 and claim grants the reward', () async {
    final c = await container(DateTime(2026, 6, 18));
    expect(c.read(dailyRewardProvider).claimable, isTrue);
    expect(c.read(dailyRewardProvider).day, 1);

    await c.read(dailyRewardProvider.notifier).claim();
    expect(c.read(boosterInventoryProvider)[BoosterType.hammer], 1); // day 1
    expect(c.read(dailyRewardProvider).claimable, isFalse);
  });

  test('doubled claim grants twice the reward', () async {
    final c = await container(DateTime(2026, 6, 18));
    await c.read(dailyRewardProvider.notifier).claim(doubled: true);
    expect(c.read(boosterInventoryProvider)[BoosterType.hammer], 2);
  });
}
```

- [ ] **Step 2: Run test to verify it fails**

Run: `flutter test test/state/daily_reward_notifier_test.dart`
Expected: FAIL — `daily_reward_notifier.dart` / `dailyNowProvider` missing.

- [ ] **Step 3: Write minimal implementation**

```dart
// lib/state/daily_reward_notifier.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../data/save_repository.dart';
import '../game/daily/daily_reward.dart';
import '../game/models/booster.dart';
import 'booster_inventory_notifier.dart';
import 'providers.dart';

class DailyRewardNotifier extends Notifier<DailyResolution> {
  static const _cal = DailyRewardCalendar();

  SaveRepository get _save => ref.read(saveRepositoryProvider);
  DateTime Function() get _now => ref.read(dailyNowProvider);

  @override
  DailyResolution build() => _resolve();

  DailyResolution _resolve() => _cal.resolve(
        lastClaimedYmd: _save.dailyLastClaimedYmd,
        storedDay: _save.dailyCalendarDay,
        today: _now(),
      );

  /// Grants the current day's reward (×2 if [doubled]) and records the claim.
  Future<void> claim({bool doubled = false}) async {
    final r = state;
    if (!r.claimable) return;
    final reward = _cal.rewardFor(r.day);
    final inv = ref.read(boosterInventoryProvider.notifier);
    for (final entry in reward.entries) {
      await inv.grant(entry.key, entry.value * (doubled ? 2 : 1));
    }
    await _save.recordDailyClaim(_cal.ymd(_now()), r.day);
    state = _resolve();
  }
}
```

Add to `providers.dart`:

```dart
import 'daily_reward_notifier.dart';
```
```dart
/// Injectable clock for the daily calendar (overridden in tests).
final dailyNowProvider = Provider<DateTime Function()>((ref) => DateTime.now);

final dailyRewardProvider =
    NotifierProvider<DailyRewardNotifier, DailyResolution>(
  DailyRewardNotifier.new,
);
```
Add `import '../game/daily/daily_reward.dart';` to `providers.dart` for `DailyResolution`.

- [ ] **Step 4: Run test to verify it passes**

Run: `flutter test test/state/daily_reward_notifier_test.dart`
Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add lib/state/daily_reward_notifier.dart lib/state/providers.dart test/state/daily_reward_notifier_test.dart
git commit -m "feat(state): DailyRewardNotifier with injectable clock"
```

---

## Task 11: Analytics events

**Files:**
- Modify: `lib/services/analytics_service.dart`
- Test: `test/services/analytics_service_test.dart` (append; this test uses a fake backend already in the file — match its pattern)

- [ ] **Step 1: Read the existing test** to copy its fake-backend setup, then append:

```dart
  test('booster + daily events carry their fields', () {
    final backend = _RecordingBackend(); // reuse the file's existing fake
    final a = AnalyticsService(backend);

    a.boosterUsed(type: 'hammer');
    a.boosterGranted(type: 'hammer', count: 2, source: 'daily');
    a.dailyRewardClaimed(day: 7, doubled: true);

    expect(backend.events, [
      ('booster_used', {'type': 'hammer'}),
      ('booster_granted', {'type': 'hammer', 'count': 2, 'source': 'daily'}),
      ('daily_reward_claimed', {'day': 7, 'doubled': 1}),
    ]);
  });
```

If the existing test file's fake records differently, adapt the assertion to its
shape — the point is that each method forwards the named event + params.

- [ ] **Step 2: Run test to verify it fails**

Run: `flutter test test/services/analytics_service_test.dart`
Expected: FAIL — methods not defined.

- [ ] **Step 3: Write minimal implementation** (append to `AnalyticsService`)

```dart
  void boosterUsed({required String type}) =>
      _backend.logEvent('booster_used', {'type': type});

  void boosterGranted(
          {required String type, required int count, required String source}) =>
      _backend.logEvent(
          'booster_granted', {'type': type, 'count': count, 'source': source});

  void dailyRewardClaimed({required int day, required bool doubled}) =>
      _backend.logEvent(
          'daily_reward_claimed', {'day': day, 'doubled': doubled ? 1 : 0});
```

- [ ] **Step 4: Run test to verify it passes**

Run: `flutter test test/services/analytics_service_test.dart`
Expected: PASS.

- [ ] **Step 5: Wire the calls** (no new behavior, just emit):
- In `BoosterInventoryNotifier.consume` success path is the wrong layer; instead
  emit `boosterUsed` from `GameSessionNotifier._useBooster` on success, and
  `boosterGranted`/`dailyRewardClaimed` from `DailyRewardNotifier.claim` and from
  the rewarded-ad grant path (Task 14). Read `analyticsProvider` via `ref`.
  Add, in `_useBooster` after `await inv.consume(type)`:
  `ref.read(analyticsProvider).boosterUsed(type: type.name);`
  In `DailyRewardNotifier.claim` after recording:
  `ref.read(analyticsProvider).dailyRewardClaimed(day: r.day, doubled: doubled);`
  and per grant: `ref.read(analyticsProvider).boosterGranted(type: e.key.name, count: e.value*(doubled?2:1), source: 'daily');`

- [ ] **Step 6: Run full suite + commit**

Run: `flutter test`
```bash
git add lib/services/analytics_service.dart lib/state/game_session_notifier.dart lib/state/daily_reward_notifier.dart test/services/analytics_service_test.dart
git commit -m "feat(analytics): booster + daily-reward events"
```

---

## Task 12: l10n strings

**Files:**
- Modify: `lib/l10n/app_en.arb`, `lib/l10n/app_ko.arb`

- [ ] **Step 1: Add keys to BOTH arb files** (keys must match exactly; KO values translated). Add:

```
boosterHammer, boosterShuffle, boosterLineBomb (names)
boosterGetWithAd  ("Watch an ad to get one" / "광고 보고 1개 받기")
dailyRewardTitle  ("Daily Reward" / "출석 보상")
dailyClaim ("Claim" / "받기")
dailyDoubleWithAd ("Watch ad for 2x" / "광고 보고 2배")
boosterTapTarget ("Tap a cell" / "칸을 선택하세요"), boosterTapLine ("Tap a row or column" / "줄을 선택하세요")
```

EN example entries:
```json
"boosterHammer": "Hammer",
"boosterShuffle": "Shuffle",
"boosterLineBomb": "Line Bomb",
"boosterGetWithAd": "Watch an ad to get one",
"dailyRewardTitle": "Daily Reward",
"dailyClaim": "Claim",
"dailyDoubleWithAd": "Watch ad for 2×",
"boosterTapTarget": "Tap a cell",
"boosterTapLine": "Tap a row or column",
```
KO values: `해머`, `셔플`, `줄 폭탄`, `광고 보고 1개 받기`, `출석 보상`, `받기`, `광고 보고 2배`, `칸을 선택하세요`, `줄을 선택하세요`.

- [ ] **Step 2: Regenerate + analyze**

Run: `flutter gen-l10n` (or `flutter test` which triggers generation via the build) then `flutter analyze`.
Expected: `AppLocalizations` exposes the new getters; no issues.

- [ ] **Step 3: Commit**

```bash
git add lib/l10n/app_en.arb lib/l10n/app_ko.arb
git commit -m "feat(l10n): booster + daily-reward strings (EN/KO)"
```

---

## Task 13: Booster bar widget

**Files:**
- Create: `lib/ui/widgets/booster_bar.dart`
- Test: `test/ui/booster_bar_test.dart`

A stateless row of three booster buttons (icon + count badge). It is presentational:
it takes the counts map + an `onTap(BoosterType)` callback. Targeting state lives in
the game screen (Task 14).

- [ ] **Step 1: Write the failing widget test**

```dart
import 'package:block_seasons/game/models/booster.dart';
import 'package:block_seasons/ui/widgets/booster_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('renders three boosters with their counts', (tester) async {
    BoosterType? tapped;
    await tester.pumpWidget(MaterialApp(
      home: Scaffold(
        body: BoosterBar(
          counts: const {
            BoosterType.hammer: 3,
            BoosterType.shuffle: 0,
            BoosterType.lineBomb: 1,
          },
          onTap: (t) => tapped = t,
        ),
      ),
    ));

    expect(find.text('3'), findsOneWidget);
    expect(find.text('1'), findsOneWidget);
    await tester.tap(find.byKey(const ValueKey('booster_hammer')));
    expect(tapped, BoosterType.hammer);
  });
}
```

- [ ] **Step 2: Run test to verify it fails**

Run: `flutter test test/ui/booster_bar_test.dart`
Expected: FAIL — `BoosterBar` missing.

- [ ] **Step 3: Implement the widget**

```dart
// lib/ui/widgets/booster_bar.dart
import 'package:flutter/material.dart';

import '../../game/models/booster.dart';

class BoosterBar extends StatelessWidget {
  const BoosterBar({super.key, required this.counts, required this.onTap});

  final Map<BoosterType, int> counts;
  final void Function(BoosterType) onTap;

  static const _icons = {
    BoosterType.hammer: Icons.gavel,
    BoosterType.shuffle: Icons.shuffle,
    BoosterType.lineBomb: Icons.clear_all,
  };

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        for (final t in BoosterType.values)
          _BoosterButton(
            key: ValueKey('booster_${t.name}'),
            icon: _icons[t]!,
            count: counts[t] ?? 0,
            onTap: () => onTap(t),
          ),
      ],
    );
  }
}

class _BoosterButton extends StatelessWidget {
  const _BoosterButton(
      {super.key, required this.icon, required this.count, required this.onTap});
  final IconData icon;
  final int count;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: onTap,
      borderRadius: BorderRadius.circular(12),
      child: Padding(
        padding: const EdgeInsets.all(8),
        child: Column(mainAxisSize: MainAxisSize.min, children: [
          Icon(icon, size: 28),
          const SizedBox(height: 2),
          Text('$count'),
        ]),
      ),
    );
  }
}
```

- [ ] **Step 4: Run test to verify it passes** → `flutter test test/ui/booster_bar_test.dart` (PASS)

- [ ] **Step 5: Commit**

```bash
git add lib/ui/widgets/booster_bar.dart test/ui/booster_bar_test.dart
git commit -m "feat(ui): presentational booster bar"
```

---

## Task 14: Mount bar in the game screen + targeting + 0-count ad

**Files:**
- Modify: `lib/ui/screens/game_screen.dart`
- Test: `test/ui/game_screen_booster_test.dart`

**Read `game_screen.dart` fully first.** Mount `BoosterBar` below the board, reading
`boosterInventoryProvider`. Hold local targeting state (`BoosterType? _arming`).

Behavior:
- Tap a booster with count > 0 → set `_arming = type` and show a hint
  (`boosterTapTarget` for hammer, `boosterTapLine` for line-bomb). Shuffle applies
  immediately via `session.useShuffle()` (no targeting).
- While arming **hammer**: a tap on a board cell calls `session.useHammer(x,y)`;
  clear `_arming`.
- While arming **line-bomb**: show row handles (left edge) + column handles (top
  edge); tapping a row handle calls `session.useLineBomb(row: r)`, a column handle
  `useLineBomb(col: c)`; clear `_arming`.
- Tap a booster with count 0 → show a dialog with `boosterGetWithAd`; on confirm,
  call the rewarded-ad grant (Task 15 helper) and grant +1 on reward.

- [ ] **Step 1: Write the failing widget test** (targeting hammer path)

```dart
// Pump GameScreen with an overridden saveRepository pre-granted 1 hammer and a
// started stage that has a filled cell at a known position; tap the hammer
// booster, then tap that cell; assert the cell becomes empty and the count
// drops to 0. Mirror the ProviderContainer + _wrap pattern used in
// test/tool/generate_store_screenshots_test.dart for mounting a screen.
```

Write the concrete test using that mounting pattern (real font load is NOT needed
for logic assertions; pump + tap + read provider state). Assert:
`expect(container.read(boosterInventoryProvider)[BoosterType.hammer], 0);`

- [ ] **Step 2: Run → FAIL** (`flutter test test/ui/game_screen_booster_test.dart`)

- [ ] **Step 3: Implement** the bar mount + targeting in `game_screen.dart` per the
behavior list above. Keep the board's existing drag handling intact; targeting
mode intercepts taps only while `_arming != null`.

- [ ] **Step 4: Run → PASS**, then `flutter test` (full) + `flutter analyze`.

- [ ] **Step 5: Commit**

```bash
git add lib/ui/screens/game_screen.dart test/ui/game_screen_booster_test.dart
git commit -m "feat(ui): booster bar targeting in the game screen"
```

---

## Task 15: Rewarded-ad booster grants

**Files:**
- Modify: `lib/ui/screens/game_screen.dart` (0-count path) and a small helper
- Test: covered via a fake `AdService` override in `test/ui/game_screen_booster_test.dart`

- [ ] **Step 1: Write a failing test** where the 0-count booster tap, with an
overridden `adServiceProvider` whose `showRewarded()` returns `true`, grants +1 of
that booster. (Override `adServiceProvider` with a fake exposing
`Future<bool> showRewarded()`.)

- [ ] **Step 2: Run → FAIL**

- [ ] **Step 3: Implement** — on confirming the "watch ad" dialog:
```dart
final earned = await ref.read(adServiceProvider).showRewarded();
if (earned) {
  await ref.read(boosterInventoryProvider.notifier).grant(type, 1);
  ref.read(analyticsProvider).boosterGranted(
      type: type.name, count: 1, source: 'ad');
}
```

- [ ] **Step 4: Run → PASS**, full suite green.

- [ ] **Step 5: Commit**

```bash
git add lib/ui/screens/game_screen.dart test/ui/game_screen_booster_test.dart
git commit -m "feat(ui): rewarded-ad grant for an empty booster"
```

---

## Task 16: Daily reward popup + home wiring

**Files:**
- Create: `lib/ui/widgets/daily_reward_sheet.dart`
- Modify: `lib/ui/screens/home_screen.dart`
- Test: `test/ui/daily_reward_sheet_test.dart`

The sheet shows 7 cells (past = checked/dim, today = highlighted, future = locked),
a Claim button, and a "2× with ad" button. Home shows it once when
`dailyRewardProvider.claimable` is true on first build.

- [ ] **Step 1: Write the failing widget test** — pump `DailyRewardSheet` with
`day: 1, onClaim: (doubled) {...}`; assert 7 day cells render and tapping Claim
invokes `onClaim(false)`, tapping the 2× button invokes `onClaim(true)`.

- [ ] **Step 2: Run → FAIL**

- [ ] **Step 3: Implement** `DailyRewardSheet` (presentational: `int day`,
`void Function(bool doubled) onClaim`, uses the `DailyRewardCalendar.rewardFor`
table to render each day's icons). Then in `home_screen.dart`, in a post-frame
callback, if `ref.read(dailyRewardProvider).claimable`, show the sheet as a dialog;
its callbacks call `ref.read(dailyRewardProvider.notifier).claim(doubled: ...)`,
and for the 2× path gate the claim on `adServiceProvider.showRewarded()` first.

- [ ] **Step 4: Run → PASS**, full suite + analyze clean.

- [ ] **Step 5: Commit**

```bash
git add lib/ui/widgets/daily_reward_sheet.dart lib/ui/screens/home_screen.dart test/ui/daily_reward_sheet_test.dart
git commit -m "feat(ui): 7-day daily-reward popup on home"
```

---

## Task 17: Integration pass

**Files:** none new — verification + any glue fixes.

- [ ] **Step 1:** Run the whole suite: `flutter test`. Expected: all green
(existing 194 + new). Fix any regressions following TDD (failing test first).
- [ ] **Step 2:** `flutter analyze` → No issues.
- [ ] **Step 3:** Manual smoke checklist for the owner's eventual build (document only;
**do not build here**): start a stage → use each booster → daily popup grants →
0-count booster offers ad. Note these in the commit body.
- [ ] **Step 4: Commit** any glue fixes:

```bash
git add -A
git commit -m "test: integrate boosters + daily reward, full suite green"
```

---

## Self-Review (completed by author)

- **Spec coverage:** boosters 3× (T1–T3), model (T4), inventory persistence (T5),
  daily persistence (T6), daily logic (T7), inventory notifier (T8), use
  coordination (T9), daily notifier (T10), analytics (T11), l10n (T12), bar UI
  (T13), targeting + 0-count (T14), ad grant (T15), daily popup (T16),
  integration (T17). All spec sections mapped.
- **No-credit rule:** enforced in engine tasks (no score/move/objective asserts).
- **Type consistency:** `BoosterType`, `BoosterUseResult`, `DailyResolution`,
  `DailyRewardCalendar.{resolve,rewardFor,ymd}`, providers
  `boosterInventoryProvider`/`dailyRewardProvider`/`dailyNowProvider` used
  consistently across tasks.
- **Owner build rule:** no `flutter build`/Xcode steps anywhere; verification is
  `flutter test` + `flutter analyze` only.
