Skip to content

Delete the mascot's points-earned pose, which stated a payout - #220

Merged
maximsan merged 1 commit into
mainfrom
fix/mascot-points-burst-pose
Aug 21, 2026
Merged

Delete the mascot's points-earned pose, which stated a payout#220
maximsan merged 1 commit into
mainfrom
fix/mascot-points-burst-pose

Conversation

@maximsan

Copy link
Copy Markdown
Owner

Closes #212.

Implements a disposition of #16 that #160 was built to carry out, and missed.

What was wrong

#16's disposition table:

'+15 XP' in roasty_particles.dart Dropped Matches no rule in the app; a hardcoded string

#160's vocabulary sweep rewrote it to '+10 PTS' and kept it — fixing the word and leaving the defect. A lesson pays what it authors, so no constant there can be right. Ten was no more read from a rule than fifteen was.

The linter could never have caught it: the particles file is excluded from no-magic-number, and that exclusion is justified for coordinates.

Why deleted, not parameterized

The design does parameterize this burst (roasty.jsx:575 renders +{pointsAmount} PTS), so "pass the real payout in" looked like the faithful option. Triage rejected it on two facts:

  • The pose is unreachable. CompanionReaction.xpGained is fired by nothing, so RoastyState.xp is too. Nothing has ever rendered it, and no test would have caught a wrong number in it.
  • Its moment is taken. The lesson result screen already plays lessonComplete, and §5.1 puts points on that same screen. Wiring a second celebration there is a decision about what it displaces — scope this ticket has no business carrying.

A points moment, if ever wanted, gets authored against a screen chosen for it and reads the real payout.

What went

The reaction, the mascot state, the particle burst, the face painter, the animation timings, and the two imports left with no other user. −85/+23.

Every switch stayed exhaustive on its own — no default arm was added to hide the removal, which is the easy way to turn a compiler error into a silent one. The two pre-existing ignore_for_file: no_default_cases headers are untouched.

Tests were narrowed, not deleted: they assert over all nine mascot states, so removing them would have dropped coverage of the eight that survive.

The guard

A test fails if any companion source states a points amount again — either vocabulary, signed or not. Proven to fail, not just asserted to:

caught: +10 PTS    caught: 10 PTS    caught: 10 points    caught: +15 XP
correctly ignored: a doc comment naming the rule

It reads string literals only, so a comment explaining the rule isn't mistaken for breaking it, and it is scoped to the companion layer — +10 PTS is correct on the result screen, which reads the real payout. The rule is that painted mascot art states no amount, not that the string is banned app-wide.

Both source-scanning guards (this one and the points vocabulary sweep) now share one walker in test/support/.

Verification

  • 981 tests pass
  • dart analyze clean across lib, test, integration_test
  • dart run dart_code_linter:metrics analyze lib clean

Reviewed on both axes. Spec: full pass on every acceptance criterion. Standards: five findings, none hard, four fixed in-branch — the shared walker, changelog section order, the two guard weaknesses above, and a redundant doc comment.

Out of scope

The three genuinely unwired reactions. correct, wrong and cardEarned are also fired from nowhere — and §5.1 asks for the first two by name: "Mid-lesson correct answers show no points toast — feedback is purely qualitative (Roasty reacts)." #160 deleted the toast and never wired its replacement, which was correctly outside its scope. That gap is #219, and deleting all three is a legitimate answer there. Untouched here beyond a doc pointer.

docs/plans/extract-roasty-companion.md still names the deleted value. Left alone: the doc map classes docs/plans/ as "snapshots, not authority", and it is banner-marked complete.

…212)

#16 ruled the mascot's hardcoded `'+15 XP'` particle dropped — "it goes with
the toast" — because it matched no rule in the app. #160's vocabulary sweep
rewrote it to `'+10 PTS'` and kept it, fixing the word and leaving the
defect. A lesson pays what it authors, so no constant here can be right.

Deleted rather than parameterized. The pose was unreachable —
`CompanionReaction.xpGained` was fired by nothing, so `RoastyState.xp` was
too — and the lesson result screen already plays its own celebration, which
makes wiring a second one a decision about what it displaces rather than a
cleanup. A points moment, if ever wanted, gets authored against a screen
chosen for it and reads the real payout.

Gone whole: the reaction, the mascot state, the particle burst, the face,
the animation timings, and the two imports that had no other user. Every
switch stayed exhaustive on its own — no `default` arm was added to hide
the removal. Tests were narrowed to the eight surviving states rather than
deleted, since they assert over the whole enum.

A guard fails if any companion source states a points amount again, in
either vocabulary and with or without a sign. It reads string literals
only, so a comment explaining the rule is not mistaken for breaking it.
Both source-scanning guards now share one walker.

The three genuinely unwired reactions — correct, wrong, cardEarned — are
untouched and split out to #219; §5.1 asks for the first two by name.
@maximsan
maximsan merged commit 2625505 into main Aug 21, 2026
6 checks passed
@maximsan
maximsan deleted the fix/mascot-points-burst-pose branch August 21, 2026 22:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The mascot's points burst: #16 said drop it, #160 renumbered it instead

1 participant