# How RankingScorer Calculates the Final Score in x-algorithm: A Deep Dive into X's Ranking Pipeline

> Learn how RankingScorer calculates final scores in x-algorithm. Discover the deterministic pipeline that aggregates engagement probabilities and applies normalization for accurate rankings.

- Repository: [SpaceXAI Org/x-algorithm](https://github.com/xai-org/x-algorithm)
- Tags: deep-dive
- Published: 2026-09-12

---

**The RankingScorer transforms Phoenix model predictions into final ranking scores through a deterministic pipeline that aggregates weighted engagement probabilities, applies global offset normalization, and optionally adjusts for author diversity, out-of-network status, and cold-start factors.**

The RankingScorer sits at the heart of the x-algorithm repository (xai-org/x-algorithm), converting raw machine learning outputs into the single numeric values that determine X's home timeline ordering. Implemented in [`home-mixer/scorers/ranking_scorer.rs`](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/scorers/ranking_scorer.rs), this Rust component orchestrates a multi-stage calculation that balances predicted user engagement with product-specific constraints and experimental treatments.

## Loading and Perturbing Scoring Weights

The calculation begins with `ScoringWeights::from_params`, which reads weight constants from the query's feature-switch parameters. These weights correspond to engagement types including favorites, replies, and dwell time.

```rust
// Loading weights from query parameters in ranking_scorer.rs
let weights = ScoringWeights::from_params(&query.params);

```

If the `WeightPerturbationSigma` flag exceeds zero, the scorer applies Gaussian noise through `ScoringWeights::perturbed` to support exploration or A/B testing scenarios.

## Computing Weighted Positive and Negative Contributions

The core calculation happens in `RankingScorer::compute_weighted_parts`. This function multiplies each **PhoenixScore** (predicted action probabilities) by its corresponding weight, handling specialized logic for different interaction types, then aggregates results into two sums: `pos` for non-negative contributions and `neg` for absolute values of negative contributions.

### Bidirectional Follow Boosts

For reply and dwell predictions, the scorer applies specialized weights (`reply_weight_for`, `dwell_weight_for`) when bidirectional following relationships exist between users, as detected in the feature flags.

### Post-Unexplored Modulation

When enabled, the dwell-time term receives multiplicative adjustment through post-unexplored logic (`ranking_scorer.rs#L63-L73`) to handle novel content scenarios differently from familiar content.

### Low-Favorite-Rate Penalties

The scorer applies `low_fav_penalized_click_dwell` penalties to downgrade candidates that generate clicks without corresponding favorite actions, preventing clickbait amplification.

```rust
// Aggregation logic from ranking_scorer.rs
let (pos, neg) = compute_weighted_parts(&weights, candidate);
// pos aggregates all positive contributions
// neg aggregates absolute values of negative contributions

```

## Applying the Global Offset Normalization

`RankingScorer::offset_score` normalizes the `(pos - neg)` differential using the total weight sum (`w.total_sum`). If the total weight sum equals zero, the function clamps the score to a non-negative value. Otherwise, it adds the fixed `NEGATIVE_SCORES_OFFSET` constant or rescales negative values to ensure consistent score distributions across the candidate pool.

```rust
// Normalization logic as implemented
let weighted_score = if w.total_sum == 0.0 {
    0.0
} else {
    (pos - neg) / w.total_sum + NEGATIVE_SCORES_OFFSET
};

```

## Alternative Path: The Dwell-Regret Model

When `ValueModelMode` equals `"dwell_regret_sigmoid"` or passes through a `GateModel` check, the scorer bypasses standard weighted calculations. Instead, it calls `compute_dwell_regret_base_scores`, applying sigmoid-based regression directly on dwell time features to generate the base score without traditional Phoenix weighting.

## Out-of-Network Weight Adjustment

The `effective_oon_weight` function calculates multipliers for Out-of-Network (OON) candidates. New users receive specialized factors distinct from standard `OonWeightFactor` values. The OON adjustment applies only when `oon_applies` returns true—specifically for non-in-network candidates or, when flagged, for in-network replies and retweets.

## Author Diversity Re-ranking

When `EnableAuthorDiversity` is active, the scorer implements a re-ranking stage before final score assembly:

- **Pool Counting**: `author_pool_counts` tallies author frequencies in the pre-diversity ranking.
- **Decay Calculation**: `author_diversity_multipliers` computes per-author decay factors using configurable decay rates and floor values.
- **Score Modification**: `apply_author_diversity` multiplies each candidate's score by its author-specific multiplier to prevent timeline monopolization by single authors.

## Cold-Start Adjustment

Finally, the `AuthorColdStart` component (defined in [`author_cold_start.rs`](https://github.com/xai-org/x-algorithm/blob/main/author_cold_start.rs)) applies a multiplicative boost or penalty through `author_cold_start.apply`. This adjustment targets newly introduced authors based on experimental treatment assignments, ensuring emerging creators receive appropriate visibility calibration according to the platform's cold-start strategy.

## Final Score Assembly and Storage

The complete pipeline executes as a deterministic chain:

1. **Weighted Score**: Base calculation from Phoenix predictions via `compute_weighted_parts`.
2. **Offset**: Global normalization into `weighted_score` via `offset_score`.
3. **Diversity**: Optional author diversity multiplier (if enabled).
4. **OON**: Out-of-network factor (if applicable).
5. **Cold-Start**: Final author treatment adjustment.

The scorer stores the original weighted value in `candidate.weighted_score` for downstream debugging, while `candidate.score` receives the final adjusted value used for timeline ranking.

```rust
use xai_home_mixer::home_mixer::scorers::ranking_scorer::RankingScorer;
use xai_home_mixer::models::query::ScoredPostsQuery;

// Configure query with feature switches
let mut query = ScoredPostsQuery::default();
query.params.override_fs(
    "rust_home_mixer_enable_author_diversity".into(), 
    "true".into()
);
query.params.override_fs(
    "rust_home_mixer_author_diversity_decay".into(), 
    "0.5".into()
);

// Instantiate scorer and process candidates
let scorer = RankingScorer::default();
let results = scorer.score(&query, &candidates).await;

// Access both raw and final scores
for candidate in results {
    println!("Weighted: {:.4}, Final: {:.4}", 
        candidate.weighted_score.unwrap(),
        candidate.score.unwrap()
    );
}

```

## Summary

- **RankingScorer** resides in [`home-mixer/scorers/ranking_scorer.rs`](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/scorers/ranking_scorer.rs) and manages the complete score calculation pipeline from Phoenix predictions to final output.
- The scorer separates positive and negative contributions through `compute_weighted_parts`, handling specialized cases like bidirectional follow boosts, post-unexplored modulation, and low-favorite-rate penalties.
- Global normalization occurs via `offset_score` using the total weight sum and the `NEGATIVE_SCORES_OFFSET` constant to handle edge cases.
- Optional modeling paths include the **dwell-regret sigmoid model** for direct dwell-time regression when specific feature flags are enabled.
- **Out-of-network** candidates receive adjusted weights through `effective_oon_weight`, while **cold-start** treatments apply final multiplicative adjustments via `author_cold_start.apply`.

## Frequently Asked Questions

### How does RankingScorer handle negative predictions in the final score calculation?

The scorer aggregates negative contributions separately as absolute values in the `neg` sum during `compute_weighted_parts`, then subtracts this from the positive `pos` sum during `offset_score` normalization. If the resulting value falls below zero, the offset logic either clamps it to zero or applies the `NEGATIVE_SCORES_OFFSET` constant to rescale it into the valid scoring range.

### What is the difference between weighted_score and the final score in RankingScorer?

`candidate.weighted_score` stores the intermediate value after global offset normalization but before diversity, OON, and cold-start adjustments, preserving the base model prediction for debugging. `candidate.score` contains the fully processed final value after all pipeline stages complete, representing the actual ranking value used for timeline ordering.

### When does RankingScorer use the dwell-regret model instead of standard weighting?

The scorer switches to `compute_dwell_regret_base_scores` when the query's `ValueModelMode` parameter equals `"dwell_regret_sigmoid"` or when a gating model specifically enables this path. This bypasses the standard Phoenix score weighting in favor of direct sigmoid regression on dwell features, providing an alternative ranking signal based purely on attention time.

### How does RankingScorer prevent a single author from dominating the timeline?

When `EnableAuthorDiversity` is true, the scorer counts author frequencies using `author_pool_counts`, calculates decay-based multipliers through `author_diversity_multipliers`, and applies these via `apply_author_diversity`. Each subsequent appearance by the same author receives a progressively lower score multiplier based on the configurable decay factor and floor, ensuring temporal distribution across multiple creators.