Add artistic curriculum and Sprint 2 plan

CURRICULUM.md: full 14-group skill tree drawn from the artistic learning
journey — line control through creative systems, with skill dependency
graph and notes on how exercises translate into module designs.

SPRINT.md: Sprint 2 tickets (AL-010 through AL-015) replacing the Sprint 1
static-stroke prototype with the real scrolling corridor from MODULES.md,
including score model migration, difficulty tiers, and session summary screen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-17 22:51:48 -06:00
parent ac81ec51f4
commit d4c48ed393
2 changed files with 504 additions and 55 deletions

192
SPRINT.md
View File

@@ -1,79 +1,161 @@
# ArtLegend — Current Sprint
**Sprint goal:** Hybrid architecture end-to-end. Compose hub → LibGDX `LineCorridorScreen` with live stylus pressure data.
**Sprint goal:** Line Corridor MVP. Implement the real scrolling-corridor game from MODULES.md, replace the Sprint 1 prototype, add difficulty tiers, and wire real session data into the Progress screen.
> Sprint 1 built a static "draw one stroke" prototype to validate the architecture. Sprint 2 replaces the game logic with the actual design: a continuously scrolling corridor the player tracks with their stylus, scored on Fill and Containment.
---
## Tickets
### AL-001 — shared/ module compiles [DONE]
- `shared/build.gradle.kts` created (pure Kotlin JVM)
- `ModuleType.kt` created with LINE_CORRIDOR, PRESSURE_RAMP, CURVE_SCULPTOR, VECTOR_SNAP
- `settings.gradle.kts` updated to include `"shared"`
### AL-010CorridorScore data model [TODO]
### AL-002 — core/ restructured [DONE]
- `GameHost.kt` replaces `ArtLegend.kt` — accepts `ModuleType`, routes to correct Screen
- `PlaceholderModuleScreen.kt` — stub Screen for unimplemented modules
- `input/StylusFrame.kt` — data class (x, y, pressure, tiltRad, orientRad, isStylus)
- `input/StylusInputBridge.kt` — AtomicReference bridge (GL-thread safe)
- `modules/LineCorridorScreen.kt` — skeleton (dark background, reads bridge)
- `core/build.gradle.kts` updated with `:shared` dependency
Replace the prototype `LineScore` with a score model that matches the Corridor Scroll design from MODULES.md.
### AL-003 — Compose added to android/ [DONE]
- `android/build.gradle.kts` updated: `buildFeatures { compose = true }`, Compose BOM 2024.09.00, navigation-compose, activity-compose, lifecycle
- `gradle.properties` updated with compose versions
- Delete `core/modules/LineScore.kt`
- Create `core/modules/CorridorScore.kt`:
```kotlin
data class CorridorScore(
val fill: Float, // 0..1 — avg coverage of corridor width
val wallPenalties: Int, // raw count of wall crossings
val overall: Float // weighted: fill * 0.7 penalty * 0.05, clamped 0..1
)
```
- Update `GameHost.onSessionComplete` signature: `((CorridorScore) -> Unit)?`
- Update `GameActivity` to pass `fill`, `wallPenalties`, `overall` via Intent extras
- Update `MainActivity.gameResultLauncher` to reconstruct `CorridorScore` from Intent
- Update `SessionEntity`: replace `straightness`/`anglePrecision` fields with `fill: Float` and `wallPenalties: Int`; bump Room schema version to 2 with a destructive migration (dev only — data is test data)
- Update `SessionRepository.save()` to write new fields
- Update `ProgressScreen` `SessionCard` and `SessionBarChart` labels (Overall, Fill, Penalties)
- Update `ProgressViewModel` — no logic change needed
### AL-004 — MainActivity + Compose hub [DONE]
- `MainActivity.kt` created — ComponentActivity, sets Compose content
- `ui/theme/ArtLegendTheme.kt` — dark Material3 theme, violet/cyan accents
- `ui/ArtLegendApp.kt` — NavHost with 4 routes (hub, detail/{moduleType}, progress, settings)
- `ui/screens/HubScreen.kt` — LazyVerticalGrid of 4 module cards
- `ui/screens/ModuleDetailScreen.kt` — module info + "Start Training" button
- `ui/screens/ProgressScreen.kt` — stub
- `ui/screens/SettingsScreen.kt` — stub
- `res/values/themes.xml` — Theme.ArtLegend (Material3) + Theme.ArtLegend.Fullscreen
- `AndroidManifest.xml` updated — MainActivity as LAUNCHER, GameActivity registered
---
### AL-005GameActivity + stylus bridge [DONE]
- `GameActivity.kt` created — AndroidApplication, reads ModuleType from Intent
- Stylus OnTouchListener set on LibGDX view post-initialization
- `handleStylusEvent()` extracts pressure/tilt/orient, posts StylusFrame to bridge
- Logs `pressure` + `isStylus` to logcat for verification
### AL-011Scrolling corridor engine [TODO]
### AL-006 — LineCorridorScreen renders corridor [DONE]
- ShapeRenderer corridor: two cyan walls + dashed centerline at random ±25° angle
- Stroke captured per-frame from StylusInputBridge; yellow alpha stroke rendered live
- `isDown` field added to StylusFrame; DOWN/UP edges detected for clean start/stop
- Y-coordinate flipped (Android top-left → LibGDX bottom-left)
Rewrite `LineCorridorScreen` to implement the scrolling corridor from MODULES.md.
### AL-007 — Score calculation [DONE]
- `LineScore(straightness, pressureConsistency, overall)` data class in core/modules
- RMS perpendicular deviation from centerline → straightness (0..1)
- RMS pressure deviation from 0.5 target → pressureConsistency (0..1)
- overall = straightness×0.6 + pressureConsistency×0.4
- Score logged to logcat on stylus lift
**Geometry:**
- Corridor scrolls across the screen at a configurable pixel/sec speed
- Corridor is represented as a spine (centerline) moving left-to-right (or at an angle on higher difficulties)
- Player tracks the moving gap by holding their stylus inside it
### AL-008 — Score returned to hub [DONE]
- `GameHost.onSessionComplete: ((LineScore) -> Unit)?` wired by GameActivity
- GL thread score callback → `runOnUiThread { setResult + finish() }`
- `MainActivity.gameResultLauncher` (ActivityResultContracts.StartActivityForResult)
- `lastScore: LineScore?` state flows through ArtLegendApp → ModuleDetailScreen
- ScoreCard shows Overall / Line / Pressure % on return; button changes to "Train Again"
**Rendering:**
- Two cyan wall lines scrolling across screen
- Visible lookahead: player can see a few screen-widths ahead
- Stylus trail rendered in yellow (pressure-modulated alpha, same as prototype)
- HUD: session timer (top-right), score preview (fill % running avg, penalty count)
### AL-009 — End-to-end smoke test [TODO]
- `./gradlew :android:assembleDebug` passes
- App launches to Compose hub showing 4 module cards
- Tap card → ModuleDetail with "Start Training" button
- Tap "Start Training" → LibGDX renders dark background with corridor
- Stylus pressure values visible in logcat
**Session lifecycle:**
- Session runs for a fixed duration (`sessionDuration` param, default 30s)
- When time runs out: compute final score, call `onSessionComplete`
- Player must hold stylus the entire time — stylus-up counts as not filling
**Constructor params:**
```kotlin
class LineCorridorScreen(
bridge: StylusInputBridge,
onSessionComplete: (CorridorScore) -> Unit,
corridorWidth: Float = 80f,
scrollSpeed: Float = 180f,
sessionDuration: Float = 30f,
angleChangeEnabled: Boolean = false
)
```
---
### AL-012 — Real-time containment + fill scoring [TODO]
Implement per-frame scoring logic inside `LineCorridorScreen`.
**Per-frame:**
- Compute the corridor bounds at the stylus X position (or nearest corridor segment)
- If stylus is inside bounds: record perpendicular offset → contributes to running fill average
- If stylus crosses a wall: increment `wallPenaltyCount`, enforce a brief visual flash (wall turns red for 0.3s)
- If stylus is up: does not contribute to fill average (neutral, not a penalty)
**At session end:**
- `fill` = mean of per-frame fill samples (each sample = `1 - |offset| / halfWidth`, clamped 0..1)
- `wallPenalties` = total crossing count
- `overall` = `(fill * 0.7 - wallPenalties * 0.05).coerceIn(0f, 1f)`
---
### AL-013 — Difficulty tiers [TODO]
Add selectable difficulty levels that feed into `LineCorridorScreen` params.
**In `shared/`:**
```kotlin
enum class Difficulty { EASY, MEDIUM, HARD }
```
**Difficulty knobs:**
| | EASY | MEDIUM | HARD |
|---|---|---|---|
| Corridor width | 90f | 60f | 38f |
| Scroll speed (px/s) | 140f | 220f | 340f |
| Angle changes | None | Gentle (±15° max) | Frequent (±35° max) |
| Session duration | 30s | 30s | 30s |
**UI — `ModuleDetailScreen`:**
- Add a `Difficulty` selector (three segmented chips: Easy / Medium / Hard) between the module description and the "Start Training" button
- Default: EASY
- Pass selected `Difficulty` as an Intent extra to `GameActivity`
**In `GameActivity`:**
- Read `Difficulty` from Intent, build `LineCorridorScreen` with corresponding params
- Pass `Difficulty.name` through to the score result so it can be stored with the session
**Add `difficulty: String` column to `SessionEntity`** (schema version → 3, destructive migration)
---
### AL-014 — Session summary screen [TODO]
Add a dedicated post-game summary in Compose shown after the LibGDX screen closes, before the user returns to the hub.
**New route in `ArtLegendApp`:** `summary/{moduleType}`
**`SummaryScreen` composable:**
- Score breakdown: Overall %, Fill %, Wall Penalties
- Streak badge: "X-day streak" (see streak logic below)
- Buttons: "Train Again" (relaunch same module/difficulty), "View Progress", "Back to Hub"
**Streak logic (in `ProgressViewModel`):**
- Add `fun streakFor(moduleType: ModuleType): Int` — query sessions ordered by timestamp, count consecutive calendar days ending today that have at least one session
- Expose as `StateFlow<Int>`
**Wire-up:**
- After `gameResultLauncher` receives a result, navigate to `summary/{moduleType}` instead of staying on `ModuleDetailScreen`
- Pass `CorridorScore` to `SummaryScreen` via `ArtLegendApp` state (same pattern as `lastScore` in Sprint 1)
- `ModuleDetailScreen` no longer shows the inline `ScoreCard` — summary screen owns that
---
### AL-015 — Integration smoke test [TODO]
- `./gradlew :android:assembleDebug` passes with no errors
- Hub → Module Detail → difficulty selector renders (Easy selected by default)
- "Start Training" → LibGDX renders scrolling corridor with visible movement
- Stylus held inside corridor: fill HUD increments; crossing a wall: wall flashes red
- Session timer counts down, auto-returns at 0s
- Summary screen shows Fill %, Wall Penalties, Overall %, streak badge
- Progress screen bar chart reflects the session just completed
- All three difficulty tiers launch without crash
---
## Blocked
_Nothing blocked currently._
## Out of Scope This Sprint
- Local score persistence (Phase 1)
- Difficulty progression
- Angle change corridor segments (HARD difficulty — define the knob values but angle changes can ship as a fast-follow if time allows)
- Sound effects / haptic feedback
- Global XP system
- In-app purchases
- Cloud sync