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)
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.ktui/FitnessRpgApp.ktui/viewmodel/BattleViewModel.ktui/screens/BattleScreen.ktui/screens/ProfileScreen.ktui/screens/SettingsScreen.ktui/screens/InventoryScreen.ktui/screens/WeeklyQuestsScreen.ktui/screens/StatisticsScreen.ktdata/local/SharedPreferencesFitnessRepository.ktdata/local/SharedPreferencesInventoryRepository.ktdata/local/SharedPreferencesWeeklyQuestRepository.ktui/components/CameraPreview.ktframe/CameraFrameSource.ktframe/VideoFileFrameSource.kt
Allowed dependencies: :pose, :domain, :game, :data, Android/Compose.
Android adapter for MediaPipe Pose Landmarker.
Responsibilities:
- Load the
.taskmodel from app assets. - Run asynchronous live-stream inference.
- Apply confidence configuration.
- Convert MediaPipe landmarks to domain
PoseFrameobjects. - 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.
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.
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.
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.
: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.
- On first launch,
FitnessRpgAppopensProfileScreen; every field is optional and the player may skip it. - 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.FitnessRpgAppapplies the bottom system navigation inset before rendering the current screen. - 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.
- 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.
- Outside quests, the player selects an
ExerciseConfigfromExerciseCatalog. BattleViewModel.openEnemySelection()obtains and caches three random enemy configs for the selected exercise.- The player chooses one enemy;
BattleViewModelderives the player level from lifetime estimated calories, scales the enemy's catalog HP for that level, aggregates the equipped inventory intoEquipmentCombatBonuses, andstartBattle()creates itsBoss, detector, andBattleSession. CameraPreviewselects aFrameSource.CameraFrameSourceconverts CameraX frames to correctly oriented/mirrored bitmaps, orVideoFileFrameSourcedecodes a test video.PoseAnalyzer.analyze()sends an accepted bitmap to MediaPipe.- MediaPipe results become
PoseFramevalues. - While tracking,
BattleViewModelpasses frames to the selected detector. - The selected detector calculates 3D metrics and emits
RepetitionCompletedfor 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. BattleSessioncalculates damage from base damage, affinity, and current attack multiplier, mutates HP, and publishes aBattleSnapshot.- After
BattleSessionaccepts a live repetition,BattleViewModelrecords it in the local daily aggregate. Debug simulation bypasses this write. - A ViewModel timer uses
EnemyAttackTimingPolicy; every 15 seconds it applies the selected enemy's 25% attack reduction for 10 seconds. - 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.
WeeklyQuestProgressthen 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. BattleViewModelincrementshitEventIdfor every completed attack and combines pose, detector, battle, and presentation information intoBattleUiState.- Compose renders battle feedback and switches to
VictoryScreenat zero HP.
PoseAnalyzerowns the latest pose frame and inference busy flag.- Each
ExerciseDetectorowns its movement phase and repetition count. BattleSessionowns enemy HP, repetition total, and game state.BattleSessionowns the active player attack multiplier.BattleViewModelowns navigation, encounter choices, timer jobs, presentation messages, profile form state, statistics filters, the current calorie-derived level, and the aggregated UI model.MainMenuScreenowns only transient drawer open/closed state.SharedPreferencesFitnessRepositoryowns the persisted profile, onboarding completion flag, and daily exercise aggregates.SharedPreferencesInventoryRepositoryowns persisted inventory, equipped slot IDs, and versioned prototype-save migrations;InventoryCatalogowns item definitions, bonuses, icon types, starter ownership, and deterministic victory reward pools.InventoryItemIconrenders item icons without bitmap assets;vitruvian_loadout.pngis the generated loadout-board illustration.SharedPreferencesWeeklyQuestRepositoryowns persisted week progress;WeeklyQuestCatalogowns the three-week quest rotation, andWeeklyQuestProgressowns quest matching and ISO-week reset rules.EnemyCombatantowns transient shake, red-flash, and slash animation state.- Compose components render state and should not contain domain decisions.
- Add the
ExerciseType. - Add one
ExerciseConfigentry toExerciseCatalog. - Implement an
ExerciseDetectorin:domain. - Register it in
ExerciseDetectorFactory. - 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
EnemyConfigtoInMemoryEnemyRepository. - Provide HP, portrait resource name, one weakness, one resistance, and one ability.
- Add the portrait mapping in
EnemyAssets.kt. - Add selection and damage tests for any new mechanic.
Do not branch combat code on enemy IDs. Add a typed ability or affinity instead.
Keep repository contracts in :data. Android-specific implementations may live
behind adapters in :app; inject them at the composition root.
: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.
BattleViewModelis currently both composition root and orchestrator.- The UI depends on concrete
PoseAnalyzerrather 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.