47 KiB
nigig-traffic — Remediation Execution Plan
Plan date: 2026-09-12
Audit baseline: e899a271c69efca0e11ae274b879378d2f26485f (main)
Scope: crates/apps/nigig-traffic, its Makepad game dependencies, learning content, persistence, assets, UI, and release evidence
Status: Proposed; no implementation tranche below is complete
Release posture: Not releasable as a driving-theory assessment. The pinned dependency does not compile, several lessons do not evaluate what they advertise, and current progress/paywall copy is false.
1. Executive verdict
nigig-traffic has a compact codebase and a useful fixed-step simulation seam, but its educational claims are not supported by the implementation. The app currently rewards reaching a finish circle for most advertised lessons. Roundabout exits/give-way, signs, priority, and much of hazard behavior are encoded in objective payloads but not evaluated. Some hazard scenarios spawn no hazard. Parking ignores required orientation. Collision feedback is inferred from a coarse distance/AABB approximation rather than a confirmed contact. A learner can therefore “pass” behaviors they did not perform.
The core coordinate bug is worse than missing rules: lane evaluation compares absolute world z with a lane's relative authored lateral offset. Translation and road orientation change the verdict. The first scenario records repeated lane-drift mistakes while the car is idle at its authored start, and a unit test explicitly treats those false mistakes as the expected performance distribution.
Lifecycle semantics are also broken. Intro accepts and simulates player input. Complete and Failed runs continue simulating. Scenario.max_points is ignored in favor of 100. There is no progress persistence despite settings copy saying scores are saved locally. Mobile/touch has no driving controls. The render loop schedules frames continuously, even after terminal phases. The Kenney asset path is comments and a no-op while runtime code suggests props will load.
Before any of that can run, the workspace's pinned Makepad revision is internally inconsistent: makepad-game-blocks and render call camera_boom_limit with four arguments while the pinned makepad-game-sim signature requires a fifth ignore: u64 argument. The Traffic workflow's clippy wrapper can suppress Cargo failure and then report zero Traffic-owned diagnostics, so the dependency break can evade that gate.
Finally, settings reintroduced an MTB Premium sales panel and inert Upgrade action after accepted ADR 001 explicitly prohibited fictional paywall copy. This is a product-integrity failure, not a styling preference.
Current grade: buildability F, simulation correctness D, assessment validity F, UX/accessibility D-, persistence F, performance evidence D+, code quality C-, policy compliance F.
2. Evidence and baseline
2.1 Observed validation
| Check | Result at baseline | Interpretation |
|---|---|---|
cargo check --locked -p nigig-traffic --all-targets |
Fails in pinned Makepad dependency | No Traffic target is currently buildable at the repository pin. |
| Failure | controller.rs:279/renderer call camera_boom_limit with 4 args; queries.rs:241 requires 5 including ignore: u64 |
Repair/pin a coherent upstream revision; never edit Cargo's checkout. |
Runtime tests in tests/ui.rs |
All 4 ignored | Real Makepad navigation/render/settings behavior is not an effective gate. |
tests/ui_basic.rs |
4 headless simulation/label smoke tests | It does not launch the GUI or render a frame. |
| Scenario catalog | 47 scenarios at the documented baseline | Count does not establish lesson validity. |
| Performance baseline | Debug, idle input, partial timing; no current compiling release build | Not a release budget or GPU/rendering measurement. |
2.2 Primary evidence locations
src/traffic/scenario.rs: recursively self-callingScenario::object_type, advertised objective payloads, static catalog and product descriptions.src/traffic/road.rs: coordinate conversion, oriented visual boxes vs axis-aligned physics bounds, incomplete hazard spawns and metadata.src/traffic/objective.rs: absolute-world lane bug, finish-circle-only rules, ignored payloads, approximate collision/parking/U-turn checks.src/traffic/world.rs: phase/input simulation, ignoredScenario.max_points, scoring and false-idle test.src/ui/game_view.rs: keyboard-only input, continuousNextFrame, partial timing, no-op prop seam, build-tree resource path.src/game_frame/pages/settings.rs: fictional Premium panel/action and false persistence claim..forgejo/workflows/traffic.yml,tests/{ui,ui_basic,perf}.rs,docs/{ADR,PERF*}: suppressed dependency error, ignored runtime tests, idle benchmark limitations, and policy contradictions.
3. Non-negotiable product invariants
- Coherent pinned dependencies. A clean checkout builds without modifying registry/git checkouts. Cargo/clippy failure always fails CI, including dependency failures.
- One coordinate contract. Authored, course-local, and world coordinates are distinct types. Every rule uses course-local geometry or transformed world primitives consistently.
- Transform invariance. Translating/rotating an otherwise identical course cannot change educational verdicts.
- No motion outside Active. Player drive input is zeroed during Intro, Paused, Complete, and Failed. Terminal attempts are immutable.
- Rules assess claims. A scenario is marked assessable only when every claimed required behavior has world observability, stateful rule logic, success/violation tests, and curriculum approval.
- No finish-circle laundering. Reaching the finish cannot imply sign compliance, yielding, correct roundabout exit, hazard response, or correct parking orientation without evidence.
- Real contact is named contact. Collision uses physics contact/sweep events or a precisely documented geometric intersection. Proximity warnings are not labelled collisions.
- Scenario payloads are enforced or absent. Reserved/ignored fields do not appear in assessable scenario descriptions or scoring.
- Scenario-defined score.
max_points, criteria weights, penalties, threshold, and terminal result use one validated scoring policy without overflow. - Deterministic attempts. Fixed scenario/content version + seeded initial state + tick-indexed normalized input produces the same rule trace/result on a supported target within declared numeric guarantees.
- Persistent claims are true. Scores/progress are only said to be saved after versioned, atomic, recovery-tested persistence exists.
- No fictional commerce. ADR 001 remains binding until intentionally superseded by a real entitlement architecture and product decision.
- Supported devices can drive. Mobile/touch is first-class; keyboard-only cannot be a mobile release.
- Assets are explicit. Required signs/hazards/models are bundled, licensed, bounded, and validated. Missing visual assets disable dependent lessons rather than silently substituting cubes.
- Performance evidence covers the shipped path. Release profile, active input, actual rendering/GPU, target hardware, frame pacing, input latency, and idle/terminal lifecycle are measured.
4. Severity-ranked findings
P0 — release blockers
| ID | Finding | Failure mode |
|---|---|---|
| TRAFFIC-P0-01 | Pinned Makepad game crates have incompatible camera_boom_limit APIs. |
Clean build fails before Traffic code compiles. |
| TRAFFIC-P0-02 | Clippy command ignores Cargo exit and filters only Traffic package diagnostics. | Dependency compiler failure can be reported as zero owned diagnostics. |
| TRAFFIC-P0-03 | Scenario::object_type() calls itself unconditionally. |
Any call stack-overflows instead of returning objective/category type. |
| TRAFFIC-P0-04 | Lane rule compares player_pos.z to relative lane_y, ignoring road origin and orientation. |
False mistakes/correct passes vary with translation/rotation. |
| TRAFFIC-P0-05 | Existing idle histogram test intentionally expects repeated false lane mistakes. | Test suite protects the bug and invalidates the benchmark workload. |
| TRAFFIC-P0-06 | Roundabout/sign/priority/hazard payloads such as enter, exit_at, give_way, signs, yield_, stop, and hazard brake are ignored; completion feedback asserts behavior from finish position. |
Learners pass unperformed safety/rule behaviors. |
| TRAFFIC-P0-07 | Hazard scenarios haz_child_run, haz_branch, and haz_rain spawn no hazard, as an existing test expects. |
Advertised hazard practice contains no corresponding hazard event/model. |
| TRAFFIC-P0-08 | Intro and terminal phases still run player/physics simulation. | Countdown start advantage and post-result movement mutate attempt/world state. |
| TRAFFIC-P0-09 | Scenario.max_points is overwritten with 100. |
Catalog contract and displayed results diverge. |
| TRAFFIC-P0-10 | Settings' Premium panel and no-op Upgrade action directly contradict accepted ADR 001. | The app advertises a nonexistent subscription and locked content. |
| TRAFFIC-P0-11 | Settings says per-learner progress/scores are saved, but no Traffic persistence exists. | User is promised durable learning history that is discarded. |
| TRAFFIC-P0-12 | Accepted ADR 002 honestly locks finish-circle/proximity behavior, but that deliberately limited evaluator cannot support the catalog's assessment-like lesson claims. | Keep it only for explicitly labelled practice, or supersede it criterion-by-criterion with geometry, observation, rules, tests, and reviewed copy. |
P1 — major correctness and learning-validity defects
| ID | Finding | Consequence |
|---|---|---|
| TRAFFIC-P1-01 | Parking passes on near-stop AABB overlap and ignores BayOrientation, vehicle heading, full containment, slope, gear, and handbrake claims. |
Incorrect parking passes. |
| TRAFFIC-P1-02 | U-turn checks a world forward-x threshold, not heading relative to course/start or turn zone. | Rotated courses and three-point/legal-zone lessons are wrong. |
| TRAFFIC-P1-03 | Signs are generic geometry and objective checks no recognition, stop line, dwell, approach speed, or downstream action. | Sign lessons assess only finish reach. |
| TRAFFIC-P1-04 | Priority/yield checks only coarse overlap/proximity with at most one crossing car. | Passing does not prove a stop, yield, accepted gap, or right-of-way behavior. |
| TRAFFIC-P1-05 | Hazard collision computes centre-distance with one half-extent scalar and labels negative clearance a collision. | False positives/negatives for rotated/unequal bodies and misleading feedback. |
| TRAFFIC-P1-06 | Speed creates repeated throttled mistakes but does not define grace distance/time, zone entry, duration, or scoring trace. | One sampling artifact can determine assessment inconsistently. |
| TRAFFIC-P1-07 | Scenario descriptions require indicators/mirror/blind-spot/gear behavior for which no input or observation exists. | Unobservable criteria are presented as assessed skills. |
| TRAFFIC-P1-08 | Keyboard is the only drive input; focus/page loss can leave held state and mobile cannot drive. | Major supported-device and safety/accessibility failure. |
| TRAFFIC-P1-09 | Frame scheduling continues indefinitely after completion/failure and while view lifecycle does not need simulation. | Battery/CPU/GPU waste and misleading idle metrics. |
| TRAFFIC-P1-10 | ensure_props only comments the intended loader and sets props_loaded; it never parses/uploads assets. |
Asset-present path still does nothing while logs/docs imply upgrade behavior. |
| TRAFFIC-P1-11 | Runtime UI tests are ignored due harness failure; headless smoke is labelled UI coverage. | Navigation, render, touch, lifecycle, and persistence regressions are untested. |
| TRAFFIC-P1-12 | Scenario data and learning copy have no jurisdiction/content version or qualified curriculum sign-off. | Rules can be legally/pedagogically wrong or become stale. |
| TRAFFIC-P1-13 | The workflow's supply-chain job only checks lockfile presence and whitespace; tool installer/action integrity, advisories, source policy, and licenses are not gated. |
A pinned but vulnerable/unlicensed dependency or compromised mutable CI bootstrap can enter builds without Traffic's named gate detecting it. |
P2 — performance, design, and maintainability debt
Objectivemixes lesson definition with weak implementation-specific flags and has no explicit ordered criteria/state machine.- Rule evaluation returns one verdict string, not a trace of observations, satisfied criteria, violations, and evidence.
- Scenario geometry uses yawed boxes while collision metadata often remains axis-aligned and approximate.
- Render/game view owns input, timing, simulation, camera, assets, rendering, HUD, and instrumentation.
- Performance docs quote averages as scenario medians and infer rendering health from CPU submission/tick timing.
- Catalog count and test floors reward quantity rather than valid scenario coverage.
Security and privacy posture
Traffic is currently an offline, non-authenticated app with no legitimate payment or network boundary. Do not invent web-style encryption/authentication work. Its real security surfaces are the Rust/CI dependency supply chain, parsing of packaged or future imported scenario/asset/profile data, path and resource exhaustion, and local learner privacy. Source-level unsafe grep does not constrain unsafe code in dependencies. Progress should be anonymous by default, bounded, and protected by platform storage; do not collect names, telemetry, or instructor data without a separately reviewed purpose, retention policy, consent flow, and threat model.
5. Target architecture
ScenarioPackage (id + content_version + jurisdiction + reviewed claims)
├─ CourseSpec -> validated CourseFrame + geometry/zones/routes/assets
├─ ObjectiveSpec -> ordered criteria + violations + scoring policy
└─ Capability requirements -> input/physics/asset/rule features
│ validate before catalog publication
▼
AttemptController
├─ phase: Intro | Active | Paused | Complete | Failed
├─ FixedStepSimulation (seeded, bounded catch-up)
├─ InputRouter -> normalized tick-indexed DriveIntent
├─ ObservationBuilder
│ ├─ course position/heading/lane occupancy
│ ├─ speed/acceleration/brake/handbrake/gear/indicator
│ ├─ zone entry/exit/stop dwell/path history
│ ├─ physics contacts/clearance
│ └─ traffic state/gap/priority
├─ RuleEngine -> RuleTrace / criterion state / violation
└─ ScoreEngine -> immutable AttemptResult
│
├─ LearnerRepository (versioned local progress)
└─ RenderSnapshot (presentation only)
Coordinate types
AuthoredPoint2: scenario file coordinates only.CoursePoint { along, lateral }: relative to a validatedCourseFrame { origin, tangent, normal }.WorldPoint3: simulation XZ plane.Heading: normalized angular type;relative_heading(course_frame)for rules.- No public rule API accepts an unlabelled
[f64; 2], rawVec3f, or “x component means forward.”
Assessment state
A rule yields structured evidence such as EnteredZone, StoppedAtLine { ticks }, YieldedTo { vehicle, min_gap }, SelectedExit, IndicatorActive, Contact, ContainedInBay, and HeadingWithin. Feedback text is rendered from evidence; text itself is not the rule state.
6. Initial resource and performance budgets
Budgets are release targets to measure on declared devices after build correctness. They are not inferred from current debug/idle logs.
| Resource | Initial limit/target | Enforcement/evidence |
|---|---|---|
| Simulation rate | 60 fixed ticks/s | Tick-indexed input/replay. |
| Catch-up per rendered frame | max 5 ticks | Record/drop excess wall-time; no 15-tick UI stall. |
| Entities per scenario | 256 | Scenario validator before load. |
| Simultaneous movers | 32 | Scenario validator; revisit spatial index at threshold. |
| Static course primitives | 2,048 | Scenario validator. |
| Objective criteria | 64 | Objective validator. |
| Rule/observation events per attempt | 4,096 retained plus bounded summary | Ring/journal cap. |
| Attempt duration | 30 minutes | Pause/timeout policy. |
| Simulation CPU | p99 <= 2 ms/tick on max-content scenario, target device | Release scripted workload. |
| Main-thread non-render event | p95 <= 8 ms | Instrumented runtime. |
| Frame time | p95 <= 16.7 ms desktop 60 Hz; <=33.3 ms supported mobile 30 Hz | CPU + GPU timestamps. |
| Input-to-visible response | p95 <= 50 ms | Device/runtime measurement. |
| Scenario load | p95 <= 100 ms after shared assets warm | Runtime trace. |
| Draw submissions | <= 200 visible per reference scenario | Render stats, not entity count proxy. |
| Visible triangles | <= 500,000 | Asset/scenario manifest validation. |
| Texture residency | <= 256 MiB desktop / 128 MiB mobile | Renderer metrics/eviction. |
| Curated packaged assets | <= 150 MiB compressed | Build artifact gate with license manifest. |
| Learner profile | <= 8 MiB; 10,000 attempts before compaction/export | Repository. |
| Idle/hidden/terminal simulation | 0 ticks and no continuous redraw | Lifecycle test. |
An over-budget scenario is excluded from the production catalog until deliberately reviewed; budgets are not weakened to make a count pass.
7. Dependency-ordered implementation tranches
TRAFFIC-00 — Remove false product claims immediately
Priority: P0 Effort: 1–2 person-days Depends on: none; can land before dependency repair as a source-tested policy patch
Change
- Delete
section_paywall,PaywallAction, Upgrade dispatch, and all Premium/FREE/locked sales copy in accordance with accepted ADR 001. - Remove “progress and scores are saved locally per learner” until persistence is real.
- Add one capability/assessment-status matrix. Hide or label every unvalidated scenario as
Practice preview — finish marker only; do not render behavior-confirming completion text. - Remove counts/copy that conflate scenario existence with free/validated content.
Tests / exit
- Source/UI snapshot gate rejects
MTB Premium, Upgrade, subscription/unlock claims, and local-save claims. - Default catalog/action dispatch remains available equally regardless of reserved
premiumfield. - Unsupported scenarios cannot show “priority respected,” “sign obeyed,” or equivalent assessment success.
- Update ignored UI expectations rather than keeping tests that require policy violations.
Rollback: false sales/persistence/assessment claims stay removed.
TRAFFIC-01 — Pin a coherent Makepad game API and make CI fail honestly
Priority: P0 Effort: 3–6 person-days depending on upstream fix Depends on: none
Change
- Repair the four-vs-five argument
camera_boom_limitmismatch in the upstream Makepad fork and pin the workspace to that reviewed coherent commit, or select an existing coherent commit after full compatibility review. - Do not patch files under Cargo's checkout and do not add a local unreviewed copy solely for Traffic.
- Update all workspace references/lockfile atomically and document relevant API change.
- Rewrite clippy step to preserve and fail on Cargo's exit status before/alongside diagnostic ratcheting. Dependency errors are hard failures even when no Traffic package diagnostic exists.
- Add a build matrix for all packages using changed Makepad game crates.
Tests / exit
- Fresh cache/clean checkout passes
cargo metadata --lockedand Traffic all-target check. - A fixture dependency compiler error makes both check and clippy jobs fail.
makepad-game-sim,makepad-game-blocks, andmakepad-game-renderAPI/unit tests pass at the pin.- Workspace consumer checks show no hidden regression; exact affected package list comes from
cargo metadatareverse dependencies.
Rollback: revert to the prior workspace pin only if it is coherent for all released apps; otherwise block Traffic. Never mutate checkout or suppress failure.
TRAFFIC-02 — Catalog schema and integrity validation
Priority: P0 Effort: 4–6 person-days Depends on: TRAFFIC-01
Change
- Fix
Scenario::object_typeto delegate to the objective/category and add a regression test that calls it for every scenario. - Introduce
ScenarioId,ContentVersion, jurisdiction/curriculum metadata, typed positions/headings, validated positivemax_points, seed, and required capabilities. - Validate unique IDs, finite coordinates/headings/limits, nonzero course length, finish reachability, valid bay/exit/sign references, supported objective fields, asset availability, and budgets before catalog publication.
- Replace closure-heavy static construction with data definitions/builders that return
Resultand produce a validation report. - Treat scenario count as informational; require per-scenario validity/coverage instead of a floor.
Tests / exit
- All catalog entries validate; malformed duplicate/non-finite/zero-length/missing-reference/unsupported-capability fixtures fail precisely.
- Every scenario's category/objective type agrees without recursion.
- Catalog order changes do not change persistent identity.
- A scenario cannot be marked Assessable without a named success and violation replay.
Migration: reserve current string IDs and set content version 1 after review; persistence maps by ID/version, never list index.
Rollback: invalid entries are excluded; do not bypass catalog validation.
TRAFFIC-03 — One course/world coordinate model
Priority: P0 Effort: 6–9 person-days Depends on: TRAFFIC-02
Change
- Implement typed
CourseFramefrom origin/start and normalized tangent; derive lateral normal and explicit world transform. - Centralize authored ↔ course ↔ world point/vector/heading conversions with inverse functions.
- Store lane centre/width, bay oriented rectangle, stop lines, zones, routes, exits, signs, and finish in course geometry or explicit world primitives—not mixed relative scalar/AABB fields.
- Remove
lane_ycomparison and raw world-forward-x rule assumptions. - Distinguish visual mesh bounds from rule/physics geometry.
Tests / exit
- Round-trip coordinate/heading properties across translations, all quadrants, near-axis angles, and valid magnitudes.
- Metamorphic tests translate/rotate complete scenarios and prove identical course observations/verdict traces.
- Idle car starts inside its intended lane with zero drift mistakes in every lane scenario.
- Delete/replace
verdict_histogram_proves_continue_dominates; a new test rejects any idle false violation.
Rollback: disable affected scenarios. Never restore absolute-world vs relative-lane comparisons.
TRAFFIC-04 — Correct phase/input/fixed-step lifecycle
Priority: P0 Effort: 4–6 person-days Depends on: TRAFFIC-01, TRAFFIC-03
Change
- Replace ad hoc phase bookkeeping with explicit
AttemptControllertransitions and timestamps/ticks. - Normalize drive input through one gate; force neutral player input in Intro/Paused/Complete/Failed.
- Decide whether non-player traffic animates during Intro; test it separately. Terminal phases freeze assessment simulation and result.
- Cap wall-clock catch-up at five ticks/frame and report discontinuity; reset accumulator safely on hide/resume/pause.
- Clear held input on focus loss, page switch, app suspend, restart, scenario switch, and terminal transition.
- Make no-scenario state explicit instead of transitioning to Driving after 60 ticks.
Tests / exit
- Holding throttle during Intro produces no displacement/velocity/head start.
- Complete/Failed result/world state is stable over 10,000 frames/ticks.
- Pause/resume/focus loss and 250 ms stall cannot cause stuck input or unbounded catch-up.
- Transition table rejects illegal/repeated terminal transitions.
- Same tick-indexed input replay produces same phase/world/rule result twice.
Rollback: freeze simulation outside Active even if countdown animation is temporarily lost.
TRAFFIC-05 — Observation and real-contact layer
Priority: P0/P1 Effort: 7–11 person-days Depends on: TRAFFIC-03, TRAFFIC-04
Change
- Build immutable per-tick
Observation: course pose/relative heading, speed/accel, controls, lane occupancy/crossings, zone entries/exits/dwell, path, vehicle footprint, traffic states/gaps, and physics contacts. - Expose actual contact/sweep events from Makepad simulation with involved entity IDs and contact class.
- Use oriented vehicle/bay geometry for containment and clearance. If only proximity is available, call it proximity and never terminal collision without policy.
- Bound event history and make state transitions edge-triggered to prevent repeated-frame penalties.
Tests / exit
- Contact vs near-miss, rotated unequal bodies, grazing edge, static prop, traffic car, road boundary, and tunnelling/sweep fixtures.
- Observation is translation/rotation invariant in course coordinates.
- Zone/lane/contact events fire once per transition and retain deterministic tick IDs.
- Rule layer has no direct raw
GameWorldcoordinate guessing after this tranche.
Rollback: scenarios requiring unavailable contact/observation stay non-assessable.
TRAFFIC-06 — Stateful objective/rule engine
Priority: P0 educational validity Effort: 10–16 person-days Depends on: TRAFFIC-05
Change
- Replace one-tick
Verdictmatching with validated ordered criteria, violation rules, state, and structured evidence trace. - Supersede ADR 002 one objective variant at a time only when geometry/query plumbing and success/violation tests land together; retain its honest finish-only scope for clearly labelled practice until then.
- Implement only observable claims, in this order:
- lane occupancy/change sequence and road departure;
- speed-zone grace/duration/overspeed;
- parking target, full containment, relative heading, stop dwell, handbrake/gear if claimed;
- U-turn legal zone, relative reversal, path/kerb limits, three-point sequence if claimed;
- sign approach/recognition response, stop line/dwell and relevant downstream action;
- priority/yield with conflict zone, right-of-way, accepted gap, stop/yield behavior;
- roundabout approach/give-way/entry/circulation/lane/indicator/selected exit;
- hazard detection zone, speed/brake/clearance/contact response.
- Finish is one criterion at most, never proof of preceding criteria.
- Generate feedback from unsatisfied/satisfied evidence rather than unconditional success strings.
Tests / exit
- For each rule: one minimal valid replay, every named violation replay, boundary/grace cases, and transform-invariance test.
- Wrong roundabout exit, no yield, rolling stop, no indicator, wrong parking angle, wrong bay, forbidden turn, absent hazard response, and finish-only shortcuts cannot pass.
- Reordered/duplicate observation delivery cannot double-score.
- Rule trace explains result with criterion IDs/ticks/evidence.
Rollback: individual rule/capability is disabled and dependent scenarios return to clearly labelled practice mode.
TRAFFIC-07 — Scenario scoring and immutable results
Priority: P0 Effort: 4–6 person-days Depends on: TRAFFIC-02, TRAFFIC-06
Change
- Construct
ScorePolicyfrom validated scenariomax_points, weighted criteria, penalties, terminal violations, and pass threshold. - Use checked integer/fixed-point arithmetic; define rounding and bounds.
- Award once per criterion transition; repeated telemetry cannot farm or multiply penalties.
- Freeze
AttemptResultat terminal transition with scenario ID/content version, ticks/duration, criteria, violations, score/max/percent/pass, and replay digest. - Separate Failed (terminal safety violation) from CompletedButNotPassed where needed.
Tests / exit
- Non-100
max_pointsfixture proves HUD/result/use all scenario value. - Weight sum, overflow, negative/zero max, duplicate criteria, boundary threshold, penalty floor, repeated events, and terminal immutability.
- Result recomputation from rule trace equals displayed/persisted score exactly.
- HUD never renders impossible
score/maxor a pass unsupported by criteria.
Migration: no old in-memory scores are durable; content-versioned results start when persistence lands.
Rollback: show criterion completion without numeric score; never hardcode 100.
TRAFFIC-08 — Curriculum and complete scenario-content review
Priority: P0 before assessment release Effort: 10–20 person-days plus expert review Depends on: TRAFFIC-02, TRAFFIC-06, TRAFFIC-07
Change
- Choose/document target jurisdiction, learner level, curriculum source/version, and date.
- Build a 47-row matrix: learning claim, required input, geometry/assets, actors/hazards, observations, rule criteria, success replay, violation replays, scoring, accessibility, and reviewer.
- Add real hazards for child/branch/rain or remove/rename those scenarios. Model rain/braking-distance effects if claimed.
- Add correct signs/road markings/routes/traffic for sign, junction, and roundabout lessons.
- Remove mirror/blind-spot, signal, gear, slope, weather, or kerb claims until corresponding controls/observations exist.
- Have a qualified driving instructor/traffic-law expert approve content and feedback; version approved packages.
Tests / exit
- Every production Assessable scenario has complete matrix, assets, success/violation replays, and signed review.
- Automated validator detects ignored objective fields/capabilities and missing actors.
- Scenario descriptions, lesson pages, HUD feedback, and actual criteria derive from one content source.
- Law/curriculum version and known limitations are visible.
Rollback: remove the scenario from assessable catalog; practice content remains plainly non-assessment.
TRAFFIC-09 — Unified keyboard, touch, and controller input
Priority: P1 Effort: 7–11 person-days Depends on: TRAFFIC-04; add criteria controls before TRAFFIC-08 sign-off
Change
- Add
InputRouterproducing normalizedDriveIntentfrom keyboard, touch, and supported controller. - Provide reachable mobile steering/throttle/brake/handbrake and required indicator/gear controls; support left/right-handed layout and safe zones.
- Define brake-to-reverse behavior explicitly and avoid accidental reverse on touch.
- Clear/cancel all sources on focus/page/suspend/terminal lifecycle.
- Add keyboard remapping where feasible, accessibility labels, non-color feedback, scalable controls, and optional haptics.
Tests / exit
- Equivalent scripted input across keyboard/touch/controller produces equivalent normalized tick stream.
- Multi-touch steering+throttle+indicator, finger cancellation, viewport rotation, notch/safe area, focus loss, and stuck-control tests.
- Real Android/iOS or supported mobile-device run completes at least one validated scenario without keyboard.
- Input-to-visible latency meets §6.
Rollback: unsupported platform is excluded from release; do not show an undriveable game.
TRAFFIC-10 — Event-driven frame lifecycle and rendering correctness
Priority: P1 Effort: 5–8 person-days Depends on: TRAFFIC-04, TRAFFIC-09
Change
- Schedule continuous frames only while visible and Active or during a bounded camera/UI animation.
- Stop simulation/redraw in hidden/paused/terminal states; schedule one redraw on state/setting/result changes.
- Reset wall-clock accumulator on lifecycle changes.
- Separate simulation timing, render preparation, draw submission, GPU duration, frame presentation, and HUD shaping metrics.
- Ensure render mode changes affect presentation only, not assessment coordinates/rules.
Tests / exit
- Hidden/paused/Complete/Failed view produces zero ticks and no continuous
NextFramerequests over an observation window. - Resume has no catch-up burst.
- All modes show the same scenario/result and pass transform/render consistency snapshots.
- Runtime metrics prove CPU/GPU frame budgets under active representative play.
Rollback: cap to lower frame rate or disable expensive mode; lifecycle stop behavior remains.
TRAFFIC-11 — Versioned atomic learner progress
Priority: P1 Effort: 6–9 person-days Depends on: TRAFFIC-07, TRAFFIC-08
Change
- Define local
LearnerProfileandAttemptRecordschema with opaque learner ID, scenario ID/content version, best/latest result, attempts, timestamps, settings, and migration version. - Persist only immutable terminal results through an injected repository using unique temp, flush/sync, atomic replace, bounded input, and recovery states.
- Distinguish absent, valid empty, corrupt, unsupported future, permission, and I/O failure. No false success/reset.
- Add export/reset/delete profile and privacy copy; do not imply instructor sharing or accounts unless implemented.
- Decide result invalidation/migration when scenario rule/content version changes; never compare incomparable scores silently.
Tests / exit
- Complete/pass/fail/retry/best-score semantics, non-100 max, content version change, 10k attempt compaction, A/B learner isolation, restart, and clock changes.
- Fault injection at serialize/write/flush/sync/rename preserves previous profile and surfaces failure.
- Corrupt/future data enters recovery/read-only state, not empty defaults.
- Only after these pass may settings say scores are saved locally.
Migration: current app has no legitimate Traffic history; do not infer it from unrelated nigig-core state.
Rollback: retain in-memory results and remove persistence claim; preserve unreadable profile bytes.
TRAFFIC-12 — Curated licensed asset pipeline
Priority: P1 Effort: 7–12 person-days Depends on: TRAFFIC-02, TRAFFIC-08
Change
- Delete the no-op “one implementation away” seam and runtime
CARGO_MANIFEST_DIRlookup. - Curate only required road signs, vehicles, people/hazards, markings, and environment models; check in or reproducibly package them according to license.
- Add manifest with logical ID, content hash, license/attribution, source, byte/triangle/texture bounds, scale/up axis, material set, and collider/rule geometry reference.
- Load through Makepad's packaged resource system with bounded GLB/image parsing and deterministic fallback.
- Keep rule geometry independent and reviewed. Missing required visual asset disables dependent scenario; decorative fallback cannot alter assessment.
Tests / exit
- Clean packaged desktop/mobile build resolves every required asset without source tree/network.
- Corrupt/oversized/missing/hash-mismatch/unsupported GLB fails clearly within budgets.
- License/attribution artifact covers every asset.
- Visual sign/hazard identity matches scenario metadata and curriculum screenshots.
- Render residency/submission/triangle budgets pass.
Rollback: use explicit reviewed primitive assets for supported practice modes or disable scenario; no phantom auto-upgrade claim.
TRAFFIC-13 — Supply-chain, parser, and learner-privacy hardening
Priority: P1 security/release integrity Effort: 5–8 person-days Depends on: TRAFFIC-01; integrate with TRAFFIC-02, TRAFFIC-11, TRAFFIC-12
Change
- Write a scoped threat model for the offline app: dependency/toolchain compromise, malicious/corrupt scenario/profile/asset input, path traversal, decompression/resource exhaustion, local data disclosure, and log leakage. Explicitly record that network/payment/auth are absent.
- Pin third-party CI actions to reviewed immutable commit SHAs. Provision the declared Rust toolchain from a controlled runner image or verify downloaded installers against an independently pinned digest/signature before execution.
- Add lockfile-scoped advisory, license, duplicate/ban, yanked, and allowed-source policy with reviewed, owned, expiring exceptions. Pin git dependencies by full commit and audit the coherent Makepad fork change.
- Generate an SBOM/license/source manifest for release dependencies and packaged assets; archive it with build provenance and content hashes.
- Put byte/count/depth/string/path limits before allocation/parse for scenario, profile, replay, and asset inputs. Reject absolute/parent-traversal asset IDs and unsupported file types. Fuzz the parsers/converters that accept bytes or structured data.
- Use anonymous local learner IDs by default, restrictive platform file permissions, bounded logs with no profile contents/home paths, and explicit export/delete controls. Do not add telemetry, names, cloud sync, instructor sharing, or home-grown at-rest encryption in this tranche.
Tests / exit
- CI fixture with an unapproved git source, advisory, yanked/unlicensed dependency, mutable action reference, or unverified installer fails the appropriate gate; every exception has owner/reason/expiry.
- Traversal, oversized length/count/depth, malformed UTF-8/serialization, decompression-bomb, corrupt GLB/image, and fuzz corpus cases fail within memory/time budgets without panic.
- Release artifact includes SBOM, licenses/attributions, dependency/source hashes, scenario content hashes, and build commit.
- Profile/export/log inspection contains no undeclared personal data or absolute developer path; delete/reset behavior is verified.
Rollback: disable imports/assets/export/persistence surface that cannot meet bounds; keep the offline minimal-data model rather than adding unverifiable security claims.
TRAFFIC-14 — Real runtime and end-to-end learning tests
Priority: P0 release gate Effort: 7–12 person-days Depends on: TRAFFIC-01 through enabled UX/content/security tranches
Change
- Repair Makepad test harness so Traffic binary reaches
AppStartedin CI; remove ignores. - Add runtime journeys for navigation, scenario selection, every render mode, keyboard/touch, phase gating, result, restart/next/previous, persistence/restart, settings, and asset failure.
- Drive validated scenario replays through production UI/input/simulation/rules, not copied headless logic.
- Capture screenshot/widget/log artifacts on failure without substituting a fake success path.
Tests / exit
- All runtime tests execute assertions; harness exit is a failed job.
ui_basicremains a headless smoke test and is labelled/counts as such, not GUI coverage.- At least one success and each critical violation per category run end to end.
- Mobile touch journey runs on real/emulated supported target with rendering.
- No unexplained/expired ignored test.
Rollback: release blocked or affected platform/scenario excluded.
TRAFFIC-15 — Valid performance, soak, and release evidence
Priority: P0 final release gate Effort: 6–10 person-days plus device lab/content review Depends on: all enabled tranches
Change
- Replace idle-only timing with deterministic active workloads: steering, braking, contacts, traffic, rule transitions, HUD updates, and worst validated scenario.
- Measure release builds on declared desktop/mobile hardware: fixed-step CPU, input latency, main thread, draw submission, GPU, presented frame pacing, startup/load, memory/texture residency, persistence, idle/terminal battery behavior.
- Report statistically correct sample distribution; do not call averages medians.
- Profile p95/p99 hitches before optimization. Revisit ADR 003 only at its measured thresholds.
- Run 30-minute active soak, repeated scenario switching, suspend/resume, and 10k attempt persistence.
- Publish build/test/curriculum/capability/performance evidence by commit and content version.
Tests / exit
- All §6 budgets pass on every supported tier or documented mode/platform is disabled.
- Measurement includes actual rendering/GPU and active input; debug/Rosetta results are historical only.
- No unbounded memory/entity/event growth over soak.
- Qualified curriculum sign-off and all scenario evidence remain tied to shipped content hashes.
- Clean release build, migration/recovery, runtime UI, assets/licenses, accessibility, and policy gates are green.
Rollback: lower visual mode/frame tier or remove unvalidated scenarios; do not alter rule correctness or falsify documentation.
8. Persistence and content migration
- Current scenario IDs remain aliases into versioned content records; list indices are never persisted.
- Assign a content version only after the scenario's geometry/rules/copy are reviewed. Existing static catalog is
LegacyUnvalidated, not automatically version 1 assessment content. - Attempt records bind scenario ID, content version, rule-engine version, score policy, replay/input digest, and result.
- When rules change materially, retain old attempts with their version and recalculate only if a deterministic compatible migration is explicitly provided.
- Settings migrate independently from attempts. Unknown future fields/version open read-only/recovery and are not overwritten.
- Repository migration operates on copies, writes atomically, reopens/verifies, and preserves original bytes until rollback window ends.
- Reserved
premiummay remain internal for compatibility but has no user-facing semantic and is excluded from assessment/access until a separate accepted entitlement ADR and implementation exist. - Demo/perf attempts are never written into learner progress.
9. Required CI command matrix
After TRAFFIC-01 establishes a coherent pin:
cargo metadata --locked --format-version 1 >/dev/null
cargo fmt -p nigig-traffic -- --check
cargo check --locked -p nigig-traffic --all-targets
cargo test --locked -p nigig-traffic --lib -- --test-threads=1
cargo test --locked -p nigig-traffic --test ui_basic -- --test-threads=1
cargo test --locked -p nigig-traffic --test scenario_replays -- --test-threads=1
cargo test --locked -p nigig-traffic --test runtime_ui -- --test-threads=1
cargo clippy --locked -p nigig-traffic --all-targets -- -D warnings
git diff --check
Dependency-pin changes additionally run all reverse dependencies of makepad-game-sim, makepad-game-blocks, and makepad-game-render, derived from cargo metadata. The workflow must fail on Cargo exit status even if diagnostics belong to a dependency.
Scheduled/release jobs:
cargo test --locked -p nigig-traffic --test coordinate_properties -- --test-threads=1
cargo test --locked -p nigig-traffic --test rule_adversarial -- --test-threads=1
cargo test --locked -p nigig-traffic --test persistence_faults -- --test-threads=1
cargo test --locked -p nigig-traffic --test asset_validation -- --test-threads=1
cargo test --locked -p nigig-traffic --test perf_release -- --test-threads=1 --nocapture
Rules:
- No Cargo/clippy failure suppression or package-only filtering that can hide dependency errors.
- No test-count floor substitutes for named per-scenario success/violation coverage.
- Ignored runtime/device tests require issue, owner, reason, and expiry and still block supported-platform release.
- Timing from idle, debug, CPU-only, or no-GPU paths is labelled diagnostic, never a release pass.
10. Release gates
Practice-preview release
- Coherent Makepad pin builds all targets and CI fails honestly on dependency errors.
- Premium/Upgrade and false persistence/assessment copy is absent.
- Recursive
object_type, lane coordinate, phase/input, terminal simulation, and score-max bugs are fixed. - Every visible scenario is labelled Practice unless its assessment gates pass.
- Keyboard and supported touch controls work with lifecycle input clearing.
- Runtime UI tests execute and no required test is ignored.
- Missing/no-op assets cannot be presented as loaded functionality.
- Performance lifecycle proves no continuous hidden/terminal work.
Assessment release (additional)
- Typed coordinate model passes translation/rotation metamorphic tests.
- Observation/contact layer distinguishes collision, proximity, zones, and course geometry correctly.
- Every production scenario enforces every claimed objective field with success/violation replays.
- Finish-only shortcuts cannot pass behavior lessons.
- All required hazards/signs/traffic/routes/inputs/assets exist and are packaged/licensed.
- Scenario scoring uses validated
max_points/criteria and immutable result trace. - Curriculum/jurisdiction/content version has qualified sign-off.
- Versioned atomic learner persistence/recovery passes before saved-progress copy appears.
- Desktop/mobile end-to-end assessment journeys pass.
- Release active-render performance, input latency, memory, soak, suspend/resume, and idle/terminal budgets pass.
- ADR 001 is obeyed; any future entitlement requires a separate explicit product/architecture decision and real enforcement.
11. Delivery, remote-safety, and rollback protocol
For every TRAFFIC-NN tranche:
- Start from a clean worktree and record the parent SHA/current failing evidence.
- Add a regression/replay/runtime test that fails against that parent.
- Run targeted tests, dependency consumers when applicable, and
git diff --check. - Commit one independently reviewable criterion using the tranche ID.
- Immediately before push, run
git fetch origin mainand inspect merge-base/remote changes. - Rebase/merge without dropping remote work; rerun the complete tranche matrix after conflict resolution.
- Push without force and verify the exact commit is on remote before beginning the next pushed chunk.
- If authentication is unavailable, report the blocker and preserve the tested local commit. Never claim it was pushed or overwrite remote history later.
Rollback prioritizes learner safety and product honesty: disable the scenario/mode/asset/persistence claim while preserving valid profiles. Never roll back to false completion feedback, absolute-coordinate rules, input during Intro/terminal phases, suppressed build failures, or fictional commerce.
12. Critical path and cross-plan dependencies
TRAFFIC-00 policy containment
TRAFFIC-01 coherent build → TRAFFIC-02 catalog → TRAFFIC-03 coordinates → TRAFFIC-04 lifecycle
└──────────────→ TRAFFIC-05 observations
└→ TRAFFIC-06 rules
└→ TRAFFIC-07 score
└→ TRAFFIC-08 curriculum
TRAFFIC-04 → TRAFFIC-09 input → TRAFFIC-10 frame lifecycle
TRAFFIC-07 + TRAFFIC-08 → TRAFFIC-11 persistence
TRAFFIC-08 → TRAFFIC-12 assets
TRAFFIC-01 → TRAFFIC-13 security; TRAFFIC-02/11/12 feed its parser/privacy/asset gates
All enabled paths → TRAFFIC-14 runtime → TRAFFIC-15 release evidence
- The Makepad dependency fix is workspace-wide and must be tested as such; do not patch dependency checkouts.
- Traffic persistence may reuse a proven repository primitive from
nigig-core, but only after verifying its atomic/recovery semantics; importing a module name is not evidence. - No CAD/Build/Site work is required to ship a safe Traffic practice preview, so those plans do not justify delaying P0 policy/build/correctness fixes.