Skip to content

Latest commit

 

History

History
301 lines (249 loc) · 12.9 KB

File metadata and controls

301 lines (249 loc) · 12.9 KB

Architecture

Overview

The project is a Kotlin multi-module Android application using MVVM at the app boundary and one-way data flow through the pose, exercise, combat, and UI layers.

FrameSource (:app)
    -> Bitmap
PoseAnalyzer (:pose)
    -> StateFlow<PoseFrame>
ExerciseDetector (:domain)
    -> SharedFlow<ExerciseEvent> + StateFlow<ExerciseDetectionResult>
BattleSession (:game)
    -> StateFlow<BattleSnapshot>
BattleViewModel (:app)
    -> StateFlow<BattleUiState>
Compose UI (:app)

Modules

:app

Android entry point and composition root.

Responsibilities:

  • Compose screens and reusable UI components.
  • Android lifecycle and runtime permission handling.
  • CameraX and debug video frame sources.
  • ViewModel orchestration and conversion to UI state.
  • State-driven navigation between onboarding, menu, statistics, profile, settings, inventory/equipment, weekly quests, battle, and victory.
  • Root-level Compose handling for the Android navigation bar inset, so screen content does not sit under system controls.
  • Main-menu-only navigation drawer for profile, statistics, and settings destinations.
  • Android string resources and AppCompat per-app locales for device-selected or explicitly selected Russian, English, German, Spanish, French, and Portuguese localization.
  • Enemy choice UI and coroutine-driven presentation timers.
  • Construction of current repository, detector, analyzer, and battle objects.
  • SharedPreferences adapter for the optional user profile and daily fitness aggregates.
  • SharedPreferences adapter for owned inventory IDs, equipped slot mappings, and one-time migrations from prototype ownership rules.
  • Scalable Compose Canvas icons for individual inventory item silhouettes.
  • Generated Vitruvian-style raster art for the equipment loadout board.
  • SharedPreferences adapter for weekly quest progress and granted rewards.

Key files:

  • MainActivity.kt
  • ui/FitnessRpgApp.kt
  • ui/viewmodel/BattleViewModel.kt
  • ui/screens/BattleScreen.kt
  • ui/screens/ProfileScreen.kt
  • ui/screens/SettingsScreen.kt
  • ui/screens/InventoryScreen.kt
  • ui/screens/WeeklyQuestsScreen.kt
  • ui/screens/StatisticsScreen.kt
  • data/local/SharedPreferencesFitnessRepository.kt
  • data/local/SharedPreferencesInventoryRepository.kt
  • data/local/SharedPreferencesWeeklyQuestRepository.kt
  • ui/components/CameraPreview.kt
  • frame/CameraFrameSource.kt
  • frame/VideoFileFrameSource.kt

Allowed dependencies: :pose, :domain, :game, :data, Android/Compose.

:pose

Android adapter for MediaPipe Pose Landmarker.

Responsibilities:

  • Load the .task model from app assets.
  • Run asynchronous live-stream inference.
  • Apply confidence configuration.
  • Convert MediaPipe landmarks to domain PoseFrame objects.
  • Preserve normalized image landmarks for overlays and MediaPipe world landmarks for view-independent exercise geometry.
  • Publish tracking, no-person, and error states.
  • Reject a new frame while inference is already in progress.

It accepts Bitmap, so CameraX conversion, rotation, and mirroring stay in :app.

Allowed dependencies: :domain, MediaPipe, Android, coroutines.

:domain

Shared, framework-light fitness domain.

Responsibilities:

  • Stable pose and landmark models.
  • Exercise identifiers and event contracts.
  • Exercise content model, difficulty, and detector status.
  • Exercise detector interface.
  • Stateful recognition and configuration for every catalog exercise.
  • Shared 3D angle and distance calculations that prefer world coordinates and fall back to normalized coordinates.
  • Experimental front-facing paths for push-up, pull-up, crunch, lunge, jumping jack, and plank; push-up and plank support knee or ankle body endpoints.
  • Typed detector feedback values that the Android UI maps to localized strings.
  • Detector factory and safe experimental detector implementations.

This module must not know about Compose, CameraX, MediaPipe, enemies, or damage.

:game

Framework-light combat domain.

Responsibilities:

  • Enemy and player models.
  • Exercise affinities and enemy ability models.
  • Exercise-to-attack mapping.
  • Damage calculation from exercise config, enemy affinity, and temporary player attack modifiers.
  • Bounded equipment modifiers for attack power, matchup bonuses, opening and three-repetition attacks, enemy ability delay, and debuff duration.
  • Cubic enemy-health scaling from the level-1 catalog baseline to triple HP at player level 12.
  • Replaceable enemy attack timing policy.
  • Battle state transitions and immutable snapshots.

BattleSession reacts to completed repetitions only. It must remain usable in plain unit tests without Android.

:data

Content and configuration sources.

Responsibilities:

  • Enemy repository contracts and current in-memory implementation.
  • Random three-enemy selection with a fair-matchup guarantee.
  • Exercise configuration repository contracts and current in-memory implementation.
  • User profile and exercise statistics repository contracts.
  • Inventory models, equipment slots, icon types, repository contract, 36-item catalog, six-item starter loadout, regular-victory equipment progression, and resistant-victory artifact reward pool.
  • Weekly quest models, three-week rotation catalog, progress rules, and repository contract.
  • Framework-light daily statistics models, calorie estimation, and exponential lifetime-calorie level thresholds.
  • ExerciseCatalog, the single source of truth for names, descriptions, base damage, difficulty, and detector status.

This is the future home for JSON, Room, or remote-backed implementations. The game and domain modules should not depend on concrete storage.

Dependency Direction

:app  -> :pose, :domain, :game, :data
:pose -> :domain
:data -> :domain, :game
:game -> :domain
:domain -> Kotlin/coroutines only

Do not introduce reverse edges. In particular, :game must not depend on :app, :pose, or :data.

Runtime Flow

  1. On first launch, FitnessRpgApp opens ProfileScreen; every field is optional and the player may skip it.
  2. Later launches open MainMenuScreen, where a drawer links to profile, statistics, and language settings, a daily calorie card links directly to seven-day statistics, a backpack button opens inventory, and a sword-in-shield button opens weekly quests. FitnessRpgApp applies the bottom system navigation inset before rendering the current screen.
  3. Inventory and equipment share a horizontal pager. Slot selection filters the inventory, while equip/unequip changes are saved locally. Fresh players start with one common item for each non-artifact slot. A versioned migration removes automatically seeded prototype ownership while preserving starter items, earned rewards, artifacts, quest items, and currently equipped items.
  4. Weekly quests select the active three-quest set from a three-week ISO-week rotation. Each set has one regular, one resistant-matchup, and one resistant-matchup-without-artifact objective. Starting a quest selects its exercise and builds an encounter containing a qualifying resistant enemy when required.
  5. Outside quests, the player selects an ExerciseConfig from ExerciseCatalog.
  6. BattleViewModel.openEnemySelection() obtains and caches three random enemy configs for the selected exercise.
  7. The player chooses one enemy; BattleViewModel derives the player level from lifetime estimated calories, scales the enemy's catalog HP for that level, aggregates the equipped inventory into EquipmentCombatBonuses, and startBattle() creates its Boss, detector, and BattleSession.
  8. CameraPreview selects a FrameSource.
  9. CameraFrameSource converts CameraX frames to correctly oriented/mirrored bitmaps, or VideoFileFrameSource decodes a test video.
  10. PoseAnalyzer.analyze() sends an accepted bitmap to MediaPipe.
  11. MediaPipe results become PoseFrame values.
  12. While tracking, BattleViewModel passes frames to the selected detector.
  13. The selected detector calculates 3D metrics and emits RepetitionCompleted for a valid movement cycle. Squat, push-up, pull-up, crunch, lunge, and jumping jack use exercise-specific start/target/return state machines; plank emits one completed interval for each three seconds of tracked hold.
  14. BattleSession calculates damage from base damage, affinity, and current attack multiplier, mutates HP, and publishes a BattleSnapshot.
  15. After BattleSession accepts a live repetition, BattleViewModel records it in the local daily aggregate. Debug simulation bypasses this write.
  16. A ViewModel timer uses EnemyAttackTimingPolicy; every 15 seconds it applies the selected enemy's 25% attack reduction for 10 seconds.
  17. Every victory grants the next unowned item from the regular equipment progression pool. A resistant matchup can additionally grant the next unowned non-quest artifact. WeeklyQuestProgress then evaluates exercise, resistance, and the artifact state captured at battle start. Completed quest rewards are added once, and the victory screen names all granted items.
  18. BattleViewModel increments hitEventId for every completed attack and combines pose, detector, battle, and presentation information into BattleUiState.
  19. Compose renders battle feedback and switches to VictoryScreen at zero HP.

State Ownership

  • PoseAnalyzer owns the latest pose frame and inference busy flag.
  • Each ExerciseDetector owns its movement phase and repetition count.
  • BattleSession owns enemy HP, repetition total, and game state.
  • BattleSession owns the active player attack multiplier.
  • BattleViewModel owns navigation, encounter choices, timer jobs, presentation messages, profile form state, statistics filters, the current calorie-derived level, and the aggregated UI model.
  • MainMenuScreen owns only transient drawer open/closed state.
  • SharedPreferencesFitnessRepository owns the persisted profile, onboarding completion flag, and daily exercise aggregates.
  • SharedPreferencesInventoryRepository owns persisted inventory, equipped slot IDs, and versioned prototype-save migrations; InventoryCatalog owns item definitions, bonuses, icon types, starter ownership, and deterministic victory reward pools. InventoryItemIcon renders item icons without bitmap assets; vitruvian_loadout.png is the generated loadout-board illustration.
  • SharedPreferencesWeeklyQuestRepository owns persisted week progress; WeeklyQuestCatalog owns the three-week quest rotation, and WeeklyQuestProgress owns quest matching and ISO-week reset rules.
  • EnemyCombatant owns transient shake, red-flash, and slash animation state.
  • Compose components render state and should not contain domain decisions.

Extension Rules

Add an exercise

  1. Add the ExerciseType.
  2. Add one ExerciseConfig entry to ExerciseCatalog.
  3. Implement an ExerciseDetector in :domain.
  4. Register it in ExerciseDetectorFactory.
  5. Add synthetic pose-frame tests before tuning against real video.

Base damage is configured only in ExerciseCatalog. BattleSession composes enemy affinity, temporary debuffs, and bounded EquipmentCombatBonuses before calling DamageCalculator. Future critical hits, combos, and player stats belong in this :game path, not in detectors or Compose.

Add an enemy

  1. Add an EnemyConfig to InMemoryEnemyRepository.
  2. Provide HP, portrait resource name, one weakness, one resistance, and one ability.
  3. Add the portrait mapping in EnemyAssets.kt.
  4. Add selection and damage tests for any new mechanic.

Do not branch combat code on enemy IDs. Add a typed ability or affinity instead.

Add persistence

Keep repository contracts in :data. Android-specific implementations may live behind adapters in :app; inject them at the composition root.

Testing Strategy

  • :domain: deterministic pose sequences for detector state machines, visibility thresholds, boundary angles, reset, hold intervals, and invalid frames.
  • :game: attack mapping, damage, state transitions, victory, reset, and ignored events.
  • :data: repository contract tests when persistence is introduced.
  • :app: ViewModel tests with fakes, Compose UI tests, and debug-video smoke tests.
  • Device/emulator: camera permission, front-camera orientation/mirroring, lifecycle restart, inference throughput, and end-to-end battle flow.

Known Architectural Debt

  • BattleViewModel is currently both composition root and orchestrator.
  • The UI depends on concrete PoseAnalyzer rather than a narrow input contract.
  • Boss state exposes a mutable domain object inside snapshots.
  • No clock abstraction exists for transient UI messages.
  • Enemy attack scheduling currently runs in BattleViewModel; move it to a lifecycle-aware encounter coordinator when pause/resume is introduced.
  • Error information is flattened to tracking state or display strings.
  • The selected exercise is held by BattleViewModel; a formal session config is still needed for exercise switching, Auto Mode, and Free Battle Mode.