MVP Feature-Parity Audit (2026-06-15)

Three background agents swept the legacy app (tmp/range_day) + the legacy DB backup against the new app (server RPCs, web clients, roadmap) under the bar: a legacy feature users rely on, absent from the new app, is an MVP gap (the feature-parity-is-the-bar principle). This note tracks the gaps, their dispositions, and the decisions James owed — all resolved as of 2026-06-19 (deferred 2026-06-15, recorded here so the rationale isn’t lost).

Decisions (James) — ALL 4 RESOLVED as of 2026-06-19

  1. Course-run scoring — ✅ RESOLVED 2026-06-19: MVP. The only parity gap needing new schema/proto (a course-run grouping on scores). Backup-verified as a live, actively-used workflow — 521 runs by 223 users, last run 2026-06-16 (the backup’s final day); low breadth (0.3% of the base) but the target-customer workflow (organized teams running a course of fire), used right up to cutover → ships at MVP. The run-grouping backfill (linking the 521 legacy runs’ stage scores into runs + standings) is data-touching → on the cutover gate; builds at Phase 2 (#6). → course-scoring.
  2. Influencer follow/unfollow — ✅ RESOLVED 2026-06-18: KILL. 2 follow rows ever (single influencer), 0 dedicated screens, abandoned. No skip-list needed — the audit’s framing assumed the sync sees these events, but it doesn’t: influencer_followers is not outbox-captured (docs/migration/02_triggers.sql has zero influencer refs — not a write-hot table) and users.is_influencer is absent from the ETL’s USER_COLUMNS, so both are already dropped in the data path. Realized as a documented exclusion note in 02_triggers.sql (where someone would look for “why isn’t this captured?”). No work item.
  3. hit_factors — ✅ RESOLVED 2026-06-19: Scope B (full parity), MVP. KEEP the calculator (rebuild) and the saved performance log, and MIGRATE the 26,380 rows 1:1 to hit_factor_entries — reversing the earlier “KILL the data-carry” lean (the rows are a real performance log, not scratch calcs). The migration is part of the hit-factor-calculator build (migration-etl finding #6); the ETL skips hit_factors until then.
  4. No-shoot zone — ✅ RESOLVED 2026-06-18. Re-graded migration-etl finding #6 LOW → MVP-parity; aggregate_zones now carries the legacy no-shoot count (reading nsCount/noShootCount) as an always-emitted No-Shoot zone, headline points unchanged → migration-noshoot-zone (done, #103). Field-level-gated by rangeday db migration-dry-run.

Gaps → disposition

GapDispositionItem
Squad (MSD) scoring entryMVP — client UI only (server done)squad-scoring-entry
Course-run scoring✅ MVP, resolved 2026-06-19 (Decision #1) — new course-run grouping (schema/proto); data-touching, gates cutovercourse-scoring
MSD / course leaderboardsMVP (MSD may already rank)msd-course-leaderboards
No-shoot in score breakdown✅ MVP-parity, done 2026-06-18 (Decision #4)migration-noshoot-zone
Native avatar + drill-image displayMVP — mobile-trackfiled by PR-4 worker (2 items)
Web avatar + drill-imagein progressmigration-media PR-4
hit-factor calculator✅ Scope B, MVP, resolved 2026-06-19 (Decision #3) — KEEP calculator + saved loghit-factor-calculator
Influencer follow✅ KILL, resolved 2026-06-18 (Decision #2) — not outbox-captured → documented exclusionnone
hit_factors 26k rows✅ MIGRATE 1:1 (Scope B, Decision #3) — reverses the earlier KILL; lands with the hit-factor-calculator build (#6)hit-factor-calculator
Peer DMsNot a gap — legacy never had peer DMs (the “Messages” inbox = challenge notifications, already a documented skip)none

Confirmed at parity (not gaps)

Friends, leaderboards (per-drill/friends/org), range bag, scheduled range days, events, achievements, content feed, dry-fire, single-shooter scoring, favorites, friend QR, share overlay, locations.

Filing status

All 4 decisions resolved (2026-06-19). The items were authored by the architect 2026-06-15 under James’s one-time direct-write exception (decisions embedded as open questions); each was finalized as its decision landed — #4 no-shoot (migration-noshoot-zone, done, #103) · #2 influencer (KILL → documented exclusion in 02_triggers.sql, no item; #106, skip-table-count follow-up #107) · #3 hit_factors (hit-factor-calculator, Scope B, #111) · #1 course-scoring (course-scoring, MVP, stays draft; #113). The remaining data-path builds (hit_factors un-skip, course-run grouping) land with their features at Phase 2 (#6); the other draft items (squad entry, MSD/course leaderboards) promote draft→ready in the build phase.


Forensic deep-dive (round 2, 2026-06-15) — page-by-page sweep

The first pass was time-boxed; round 2 ran 5 agents over the entire legacy page/component/custom_code/dependency tree (drills+scoring, profile+social, calculators+dry-fire, home+events+nav, hidden custom_code). Trigger: the shooter-drill-authoring + vector-stage-builder gap had gone weeks uncaptured, so we forensically re-checked everything. Home/events/nav (agent D) returned no new gaps (onboarding never existed in legacy; home-hub→tab-shell is a recorded redesign).

Captured this turn (items created, status: draft)

  • shooter-drill-authoring — shooter-side custom-drill create/edit on PWA + native (legacy let any shooter author from their phone; new app authors only on dashboard). ~16k legacy custom drills.
  • stage-diagram-builder — rebuild the legacy vector stage editor (dependencies/r_d_image_creatorv3_ncaqvt). Backup-verified 2026-06-16: 2,362 of 16,098 custom drills carry a vector design (custom_stage json[]), by 990 distinct users — ~5× the flat-photo path (464). Vector-only storage (no rendered PNG) → these don’t display post-cutover without the builder or a renderer. MVP-vs-fast-follow = open scope call.

New gaps from the sweep — disposition owed (James)

#GapEvidenceRec. disposition
F1View a friend’s profile — their shared drills/quals/courses + achievements (the payoff of the friend graph; list has no tap-through)legacy pages/profile/user_page; new friends list is flat, no detail screenMVP — highest consequence
F2Qualifier/Assessment as a first-class category — catalog browse split + “Past Quals” score filterlegacy live_fire_drills “Assessment and Quals” tile; scores_page “PAST QUALS” (category='qualifier')MVP — train-to-standard users
F3Community-drill browsing/discovery — data layer done (visibility/source_drill_id), no browse UIlegacy drillss “Community Drills” section; drill-builder says “no community browsing UI”MVP
F4Drill instructional video — playback (YouTube/stream) + offline download/cachelegacy drill_video_online_offline, drill_description_page download buttonScope call — sizeable; MVP vs Pro
F5Score-history trend chart / progress graphlegacy custom_code/widgets/trend_chart.dart; mobile-score-history is marked done with “Hit factor trend display” but shipped list-onlyMVP — reopen the falsely-done item
F6Score grouping by drill name on history listlegacy completed_drills_p_s .unique(drillName); new = flat listMVP-ish
F7Range-bag curated “suggested items” gear picker + store linkslegacy add_item_to_range_bag ~10 T.REX products w/ itemLinkMVP — low effort, affiliate touchpoint
F8Range-bag packing checklist (per-item check-off) + reset-to-defaultslegacy range_bag_widget itemClicked; orphaned range_bag_complete achievement (migration 0009) depends on a check state the new model can’t produceBorderline + consistency bug
F9Drill loadout / per-drill gear list on drill detaillegacy drill_description_page “LOADOUT”; core required_items is an undecided [ ]Borderline (range-bag promised a future item, never filed)
F10Global/community average hit-factor benchmark on drill detaillegacy drill_description_page:926 “Global average:”; leaderboard-api deferred itBorderline
F11Par-timer RANDOMIZE par-window toggle (random beep, anti-anticipation mode)legacy dry_fire_par_timer isRandomizeActive; new randomizes start-delay onlySmall MVP
F12Unsaved-changes navigation guard on builders/formslegacy custom_code/widgets/drill_pop_guard.dart “Leave drill?”Small polish
F13Marketing-email consent + “Sales” preflegacy edit_user_profile opt_in_emails + sales_notificationsProduct/legal call — is marketing email in-scope at all?

Dispositions resolved (2026-06-25)

Triaged with fresh per-gap agents (state re-checked against current code/docs/git), then filed.

Filed as work items: F1 friend-profile-view (MVP) · F2 drill-qualifier-category (MVP — server side already exists) · F3 community-drill-browsing (MVP) · F5+F6 score-history-trends (MVP; F5 reopens the mobile-score-history “trend display” overclaim) · F7 range-bag-suggested-items (fast-follow) · F8 range-bag-packing-checklist (fast-follow; also resolves the orphaned range_bag_complete achievement) · F11 par-timer-randomize (fast-follow).

Not filed — already owned or not a gap:

  • F4 (drill instructional video) — already deferred: playback in mobile-dry-fire, offline caching in mobile-media. Consolidate there; no new item.
  • F9 (drill loadout / required_items) — defer post-MVP; the migrate-or-drop data call is migration Decision D3 (drills.loadout, 2026-06-22-migration-completeness-audit). Tracked with D3; sibling of F7.
  • F10 (global average hit-factor) — already deferred in leaderboard-api (no proto field/RPC). No new item.
  • F12 (unsaved-changes nav guard) — defer post-MVP polish; the PWA queues to IndexedDB so real data-loss risk is low. Recorded here, not filed.
  • F13 (marketing-email consent) — not a gap: no marketing-email infrastructure exists; becomes a compliance item if/when marketing email is built (push-service defers sales_notifications indefinitely).

Consistency / doc fixes (not feature items)

  • mobile-score-history.md is status: done but the “Hit factor trend display” requirement was shipped as a list — reopen (→ F5).
  • ROADMAP-LEGACY.md Mobile line for mobile-range-bag reads “suggested items per drill” as ✅ done; the item actually defers it (→ F7). Doc correction.
  • PWA share_overlay.rs is text-only, but pwa-media-dryfire claims canvas image compositing — roadmap-claimed-not-implemented (media cluster; worker-relevant).

Recorded decisions re-surfaced (NOT gaps — already dispositioned, listed to re-confirm)

  • Challenge accept/decline dropped (recorded: challenge-api, mobile-challenges — participants added immediately).
  • Offline video caching deferred post-MVP (mobile-media); instructional video playback deferred (mobile-dry-fire).
  • Content-feed runnable-drill card (image/video/link/drill_id) discarded → generic action_url (migration-etl, content-feed-api).
  • Branch.io deep linking rejected → native Universal/App Links (native-app-config).
  • Influencer follow dropped (= pending Decision #2 above).