Files
PinPoint/docs/architecture.md
T

103 lines
5.8 KiB
Markdown
Raw Normal View History

2026-09-12 17:34:07 +08:00
# Architecture
## Technology stack
| Layer | Choice |
|---|---|
| Platform | Android (min SDK 24, target/compile SDK 36) |
| Languages | Kotlin (feature code), Java (legacy `MainActivity` entry point), C++ (native game shell) |
| Build | Gradle 9.x (version catalog), CMake 3.22.1 via Android's `externalNativeBuild` |
| Camera capture | CameraX (`core`, `camera2`, `lifecycle`, `video`, `view`, `effects`) |
| Pose detection | Google ML Kit Pose Detection (accurate model) |
| Concurrency | Kotlin Coroutines |
| UI (bowling feature) | Android Views + ViewBinding, custom `View`s for the pose overlay |
| UI (menu shell) | Native OpenGL ES 3.0, rendered from C++ via a `GLSurfaceView` |
| Rendering (native) | Custom C++ `GLRenderer` / `UIRenderer` |
## High-level component map
The app is really two loosely-coupled subsystems living in one Gradle module (`app/`), connected by a single JNI call:
```mermaid
flowchart TB
subgraph Native["Native game shell (C++, JNI, OpenGL ES 3.0)"]
GSM[GameStateManager]
States[States: MainMenu / Menu1 / Menu2 / Menu3 / Settings]
UIR[UIRenderer / GLRenderer]
PB[PlatformBridge]
GSM --> States
GSM --> UIR
States --> PB
end
subgraph AndroidHost["Android host (Java/Kotlin)"]
MA[MainActivity\nGLSurfaceView host]
end
subgraph Bowling["Bowling capture + analysis (Kotlin)"]
BCA[BowlingCameraActivity]
CVM[CameraViewModel]
CXC[CameraXController]
PA[PoseAnalyzer]
LSD[LiveStepDetector]
PPD[PosePhaseDetector]
PAC[PoseAngleCalculator]
PLS[PoseLandmarkSmoother /\nAnkleHipMovingAverageFilter]
POV[PoseOverlayView /\nPoseSkeletonRenderer]
FUI[FeedbackUI / StepCounterUiController]
DSL[DebugSessionLogger]
PEA[ParameterEditorActivity /\nDetectorSettings]
AA[AdminAuth /\nAdminLoginPrompt]
end
MLKit[(ML Kit Pose Detection)]
PB -- "JNI: launchBowlingCamera()" --> MA
MA -- "startActivity()" --> BCA
BCA --> CVM --> CXC
CXC -- camera frames --> PA
PA -- landmarks --> MLKit
MLKit -- pose result --> PA
PA --> PLS --> LSD
PA --> PAC
LSD --> PPD
PPD --> FUI
PAC --> FUI
PA --> POV
CVM --> DSL
PEA --> CVM
AA --> PEA
```
## Native game shell
- **`GameStateManager`** (singleton) owns the current `GameState` and drives `Update()` / `Render()` each frame. State transitions are deferred (`RequestStateChange`) so a state can safely trigger its own replacement mid-frame (e.g. from a button click handled during `Render()`).
- **States** (`MainMenuState`, `Menu1State`, `Menu2State`, `Menu3State`, `SettingsState`) implement the actual menu screens; `Menu3State` is the entry point into the bowling feature.
- **`PlatformBridge`** is the seam between shared state-machine code and platform-specific "launch a native feature" hooks, so state code doesn't need `#ifdef`s for Android vs. desktop. On Android, `LaunchBowlingCamera()` calls back into `MainActivity` over JNI (caching a `JavaVM` + global activity ref from `initGL()`); on the Windows/GLFW build it's a no-op log.
- **`MainActivity`** (Java) hosts a `GLSurfaceView` (OpenGL ES 3.0, `RENDERMODE_CONTINUOUSLY`, `setPreserveEGLContextOnPause(true)` so launching `BowlingCameraActivity` on top doesn't tear down and have to re-init the GL context/UI). Touch events are forwarded to native code (`nativeOnTouch`) for in-engine hit-testing.
- **Cross-platform note:** `Platform.h` already defines both `PLATFORM_ANDROID` and `PLATFORM_WINDOWS` (GLFW) branches, and `main.cpp` has a GLFW desktop loop. Only the Android CMake/Gradle build is currently wired up — there is no standalone desktop build target yet, but the state-machine/rendering code is written to be portable.
## Bowling capture & pose analysis pipeline
1. **`BowlingCameraActivity`** hosts the camera preview and UI chrome (switch camera, back, recording indicator); orientation follows the device sensor.
2. **`CameraXController`** wraps CameraX use cases (preview, video, image analysis) and exposes camera frames.
3. **`PoseAnalyzer`** runs each frame through ML Kit's accurate Pose Detection model and produces a `PoseFrame`.
4. Landmarks are smoothed (**`PoseLandmarkSmoother`**, **`AnkleHipMovingAverageFilter`**) before being consumed by:
- **`LiveStepDetector`** / **`StepDetector`** / **`StepCountingSession`** — step counting during the approach.
- **`PosePhaseDetector`** — segments the approach into delivery phases.
- **`PoseAngleCalculator`** — computes joint angles at points of interest.
5. **`PoseOverlayView`** + **`PoseSkeletonRenderer`** draw the live skeleton over the camera preview.
6. **`FeedbackUI`** / **`StepCounterUiController`** surface step count, phase, and feedback to the user.
7. **`CameraViewModel`** coordinates the above and survives configuration changes; **`DebugSessionLogger`** records session data for offline tuning.
8. **`DetectorSettings`** (via **`ParameterEditorActivity`**, gated by **`AdminAuth`**/**`AdminLoginPrompt`**) allows adjusting detection thresholds without a rebuild, for tuning during development/testing.
## Known architectural gap
`BowlingCameraActivity` is reachable two ways today: directly (e.g. via `adb`/launcher shortcut) and from the native menu's `Menu3State` via `PlatformBridge`. Per in-code comments, the direct-launch path exists because the feature isn't yet fully wired into the menu's visual flow — this should converge as the menu integration matures.
## Build & deployment
- Single Gradle module (`app`), AGP + CMake (`externalNativeBuild`) for the native library, targeting `arm64-v8a`, `armeabi-v7a`, `x86`, `x86_64`.
- No CI/CD pipeline exists yet — see `docs/deliverables.md` for the CI/CD deliverable and milestone target.
- No backend/server component exists; the app is fully on-device (no network calls in the current codebase).