Skip to content

Conversation

@squadgazzz
Copy link
Contributor

@squadgazzz squadgazzz commented Dec 26, 2025

Description

Refactors the driver Participant struct's score field, which makes it non-optional by introducing new ranking types: Unscored and Scored, that also replace the Unranked type. The Ranked type now only contains participants with scores. One of the prerequisites for #4010 before extracting this logic into a separate crate.

Changes

  • Unranked -> Unscored + Scored
  • Participant's score is non-optional now
  • No more panics/expectations

How to test

Existing tests

@squadgazzz squadgazzz marked this pull request as ready for review December 26, 2025 14:23
@squadgazzz squadgazzz requested a review from a team as a code owner December 26, 2025 14:23
Copilot AI review requested due to automatic review settings December 26, 2025 14:23
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the participant score management from an optional field on Solution to a mandatory part of the participant state, introducing a type-state pattern that tracks scoring status through the type system: UnscoredScoredRanked. This eliminates the need for runtime score validation (expects/panics) by making invalid states unrepresentable at compile time.

Key Changes:

  • Introduced Scored state type containing a non-optional score, replacing the Unranked type
  • Moved score from Solution to Participant state, making it type-safe
  • Renamed Ranked enum to RankType and created a new Ranked state struct containing both rank type and score

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
crates/autopilot/src/domain/competition/participant.rs Core refactor: Added Scored state type, split Ranked into RankType enum and Ranked state, added score() methods on Scored and Ranked, replaced set_score() with type-safe with_score() transition
crates/autopilot/src/domain/competition/mod.rs Removed optional score field from Solution and its associated score() method, updated public exports to include new types
crates/autopilot/src/domain/competition/winner_selection.rs Updated arbitration logic to use type-state transitions, refactored compute_scores_by_solution to return scored participants, updated all score access to use participant.score() instead of solution().score()
crates/autopilot/src/run_loop.rs Updated type signatures to use Unscored instead of Unranked, changed score access from solution().score() to score()
crates/autopilot/src/shadow.rs Updated imports and type signatures, changed score access pattern
crates/autopilot/src/infra/persistence/mod.rs Updated score access from solution().score() to score()

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


pub fn score(&self) -> Score {
self.score.expect(
"this function only gets called after the winner selection populated this value",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:feelsgood:

Copy link
Contributor

@MartinquaXD MartinquaXD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved since this is already an improvement over the status quo.

std::cmp::Reverse(participant.is_winner()),
// high score before low score
std::cmp::Reverse(participant.solution().score()),
std::cmp::Reverse(participant.score()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although it gets the job done having the score on the participant is a bit awkward IMO. This implies to me that the same solution submitted by different participants could have a different score.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies to me that the same solution submitted by different participants could have a different score.

But each participant has a unique solution. Even if internally it has the same numbers, we don't share it across different participants, and the score will be the same with the old approach as well. So this doesn't change the logic.

Copy link
Contributor

@MartinquaXD MartinquaXD Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the logic - or rather the outcome - didn't change. But I'm wondering where the correct place for the score is in the first place. For example if you were to store the solution in the driver instance the outcome would also not change but it would be a weird place to put the data, no?
OTOH the rules for scoring could be changed so the score is not really an inherent property of the solution either. Maybe the actual resolution here would be to rename Participant to Bid or something like that. This avoids the currently awkward situation where the same actual participant (i.e. driver) can show up multiple times in the participants vector. Also filtering out bids makes more sense than participants since we invalidate individual solutions and not ALL solutions of a particular participant. And it somewhat conveys the idea that the score of a solution depends on the rules encoded in the winner selection logic.
Overall I'm just bike shedding over the naming but the core essence of the product we built (ranking and picking solutions) should probably have the most appropriate names to avoid confusion.

(I'm aware that your PR did not suddenly make the name worse but it somewhat signalled that Participant wasn't a great name to begin with)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally makes sense, thanks! But probably as a separate PR.

@squadgazzz squadgazzz enabled auto-merge January 2, 2026 09:52
@squadgazzz squadgazzz added this pull request to the merge queue Jan 2, 2026
Merged via the queue into main with commit c403254 Jan 2, 2026
19 checks passed
@squadgazzz squadgazzz deleted the refactor-participant-scores branch January 2, 2026 10:16
@github-actions github-actions bot locked and limited conversation to collaborators Jan 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants