Store config as Model attrs, drop Env Data class
PlackettLuce receives the model directly instead of
a separate Env struct.

Assisted-by: Claude Opus 4.6 via pi
change vpponslzvrtrnlmltklkvzsuznurxsuw
commit 3329671d83b6feae91d429134a26c5b67fc55b2f
author Alpha Chen <alpha@kejadlen.dev>
date
parent vlmormwv
diff --git a/lib/open_skill.rb b/lib/open_skill.rb
index 8ede952..7c47e63 100644
--- a/lib/open_skill.rb
+++ b/lib/open_skill.rb
@@ -3,7 +3,6 @@
 # Ruby port of https://github.com/philihp/openskill.js
 # Implements the Plackett-Luce model for skill rating.
 
-require_relative "open_skill/env"
 require_relative "open_skill/rating"
 require_relative "open_skill/statistics"
 require_relative "open_skill/util"
diff --git a/lib/open_skill/env.rb b/lib/open_skill/env.rb
deleted file mode 100644
index 49fb101..0000000
--- a/lib/open_skill/env.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-module OpenSkill
-  Env = Data.define(:mu, :sigma, :beta, :beta_sq, :two_beta_sq,
-                    :epsilon, :z, :alpha, :target, :tau, :limit_sigma)
-end
diff --git a/lib/open_skill/model.rb b/lib/open_skill/model.rb
index e7a5946..c756628 100644
--- a/lib/open_skill/model.rb
+++ b/lib/open_skill/model.rb
@@ -2,31 +2,35 @@
 
 module OpenSkill
   class Model
-    attr_reader :env
+    attr_reader :mu, :sigma, :beta, :beta_sq, :two_beta_sq,
+                :epsilon, :z, :alpha, :target, :tau, :limit_sigma
 
     def initialize(mu: 25, z: 3, sigma: mu.to_f / z, beta: sigma / 2.0,
                    epsilon: 0.0001, alpha: 1, target: 0, tau: nil,
                    prevent_sigma_increase: false, limit_sigma: prevent_sigma_increase)
-
-      beta_sq = beta**2
-
-      @env = Env.new(
-        mu:, sigma:, beta:, beta_sq:,
-        two_beta_sq: 2 * beta_sq,
-        epsilon:, z:, alpha:, target:, tau:, limit_sigma:,
-      )
+      @mu = mu
+      @sigma = sigma
+      @beta = beta
+      @beta_sq = beta**2
+      @two_beta_sq = 2 * @beta_sq
+      @epsilon = epsilon
+      @z = z
+      @alpha = alpha
+      @target = target
+      @tau = tau
+      @limit_sigma = limit_sigma
     end
 
     def rating
-      Rating.new(env.mu, env.sigma)
+      Rating.new(mu, sigma)
     end
 
     def rate(teams, rank: nil, score: nil)
       processed_teams = teams
 
       # If tau is provided, use additive dynamics factor to keep ratings pliable
-      if env.tau
-        tau_sq = env.tau**2
+      if tau
+        tau_sq = tau**2
         processed_teams = teams.map { |team|
           team.map { |p| Rating.new(p.mu, Math.sqrt(p.sigma**2 + tau_sq)) }
         }
@@ -40,12 +44,12 @@ module OpenSkill
       ordered_teams = ordered.map(&:first)
       sorted_rank = ordered.map(&:last)
 
-      new_ratings = PlackettLuce.rate(ordered_teams, env, rank: sorted_rank)
+      new_ratings = PlackettLuce.rate(ordered_teams, self, rank: sorted_rank)
 
       result = rank.zip(new_ratings).sort_by(&:first).map(&:last)
 
       # Cap sigma at pre-tau value so ordinal never drops after a win
-      if env.tau && env.limit_sigma
+      if tau && limit_sigma
         result = result.map.with_index { |team, i|
           team.map.with_index { |p, j|
             Rating.new(p.mu, [p.sigma, teams[i][j].sigma].min)
@@ -57,7 +61,7 @@ module OpenSkill
     end
 
     def ordinal(rating)
-      env.target + env.alpha * (rating.mu - env.z * rating.sigma)
+      target + alpha * (rating.mu - z * rating.sigma)
     end
   end
 end
diff --git a/lib/open_skill/plackett_luce.rb b/lib/open_skill/plackett_luce.rb
index 1b22b96..75ed34f 100644
--- a/lib/open_skill/plackett_luce.rb
+++ b/lib/open_skill/plackett_luce.rb
@@ -4,11 +4,11 @@
 
 module OpenSkill
   module PlackettLuce
-    def self.rate(game, env, rank: nil)
+    def self.rate(game, model, rank: nil)
       return game if game.length <= 1
 
       team_ratings = Util.team_rating(game, rank: rank)
-      c = Util.util_c(team_ratings, env.beta_sq)
+      c = Util.util_c(team_ratings, model.beta_sq)
       sum_q = Util.util_sum_q(team_ratings, c)
       a = Util.util_a(team_ratings)
 
@@ -33,7 +33,7 @@ module OpenSkill
         tr.team.map { |player|
           share = player.sigma**2 / tr.sigma_sq
           new_mu = player.mu + share * omega
-          new_sigma = player.sigma * Math.sqrt([1 - share * delta, env.epsilon].max)
+          new_sigma = player.sigma * Math.sqrt([1 - share * delta, model.epsilon].max)
           Rating.new(new_mu, new_sigma)
         }
       }
diff --git a/test/test_open_skill.rb b/test/test_open_skill.rb
index e63924c..57f6b56 100644
--- a/test/test_open_skill.rb
+++ b/test/test_open_skill.rb
@@ -88,80 +88,80 @@ class TestStatistics < Minitest::Test
 end
 
 # https://github.com/philihp/openskill.js/blob/main/src/__tests__/constants.test.ts
-class TestEnv < Minitest::Test
+class TestModel < Minitest::Test
   def test_z_defaults_to_3
-    env = OpenSkill::Model.new.env
-    assert_equal 3, env.z
+    model = OpenSkill::Model.new
+    assert_equal 3, model.z
   end
 
   def test_z_accepts_override
-    env = OpenSkill::Model.new(z: 2).env
-    assert_equal 2, env.z
-    assert_equal 6.25, env.beta
-    assert_equal 39.0625, env.beta_sq
+    model = OpenSkill::Model.new(z: 2)
+    assert_equal 2, model.z
+    assert_equal 6.25, model.beta
+    assert_equal 39.0625, model.beta_sq
   end
 
   def test_mu_defaults_to_25
-    env = OpenSkill::Model.new.env
-    assert_equal 25, env.mu
+    model = OpenSkill::Model.new
+    assert_equal 25, model.mu
   end
 
   def test_mu_accepts_override
-    env = OpenSkill::Model.new(mu: 300).env
-    assert_equal 50, env.beta
-    assert_equal 2500, env.beta_sq
-    assert_equal 3, env.z
+    model = OpenSkill::Model.new(mu: 300)
+    assert_equal 50, model.beta
+    assert_equal 2500, model.beta_sq
+    assert_equal 3, model.z
   end
 
   def test_tau_defaults_to_nil
-    assert_nil OpenSkill::Model.new.env.tau
+    assert_nil OpenSkill::Model.new.tau
   end
 
   def test_tau_accepts_override
-    env = OpenSkill::Model.new(tau: 0.0042).env
-    assert_equal 0.0042, env.tau
+    model = OpenSkill::Model.new(tau: 0.0042)
+    assert_equal 0.0042, model.tau
   end
 
   def test_sigma_default
-    assert_in_delta 8.333333333333334, OpenSkill::Model.new.env.sigma
+    assert_in_delta 8.333333333333334, OpenSkill::Model.new.sigma
   end
 
   def test_sigma_accepts_override
-    env = OpenSkill::Model.new(sigma: 7).env
-    assert_equal 3.5, env.beta
-    assert_equal 12.25, env.beta_sq
+    model = OpenSkill::Model.new(sigma: 7)
+    assert_equal 3.5, model.beta
+    assert_equal 12.25, model.beta_sq
   end
 
   def test_epsilon_default
-    assert_equal 0.0001, OpenSkill::Model.new.env.epsilon
+    assert_equal 0.0001, OpenSkill::Model.new.epsilon
   end
 
   def test_epsilon_accepts_override
-    env = OpenSkill::Model.new(epsilon: 0.001).env
-    assert_equal 0.001, env.epsilon
+    model = OpenSkill::Model.new(epsilon: 0.001)
+    assert_equal 0.001, model.epsilon
   end
 
   def test_beta_default
-    assert_in_delta 4.166666666666667, OpenSkill::Model.new.env.beta
+    assert_in_delta 4.166666666666667, OpenSkill::Model.new.beta
   end
 
   def test_beta_accepts_override
-    env = OpenSkill::Model.new(beta: 100).env
-    assert_equal 100, env.beta
-    assert_equal 10000, env.beta_sq
+    model = OpenSkill::Model.new(beta: 100)
+    assert_equal 100, model.beta
+    assert_equal 10000, model.beta_sq
   end
 
   def test_beta_sq_default
-    assert_in_delta 17.361111111111114, OpenSkill::Model.new.env.beta_sq
+    assert_in_delta 17.361111111111114, OpenSkill::Model.new.beta_sq
   end
 
   def test_limit_sigma_defaults_false
-    refute OpenSkill::Model.new.env.limit_sigma
+    refute OpenSkill::Model.new.limit_sigma
   end
 
   def test_limit_sigma_accepts_flag
-    env = OpenSkill::Model.new(limit_sigma: true).env
-    assert env.limit_sigma
+    model = OpenSkill::Model.new(limit_sigma: true)
+    assert model.limit_sigma
   end
 end
 
@@ -365,14 +365,14 @@ class TestUtilC < Minitest::Test
     team1 = [r]
     team2 = [r, r]
     team_ratings = OpenSkill::Util.team_rating([team2, team1])
-    c = OpenSkill::Util.util_c(team_ratings, @model.env.beta_sq)
+    c = OpenSkill::Util.util_c(team_ratings, @model.beta_sq)
     assert_in_delta 15.590239, c, 0.001
   end
 
   def test_util_c_5v5
     r = @model.rating
     team_ratings = OpenSkill::Util.team_rating([[r] * 5, [r] * 5])
-    c = OpenSkill::Util.util_c(team_ratings, @model.env.beta_sq)
+    c = OpenSkill::Util.util_c(team_ratings, @model.beta_sq)
     assert_in_delta 27.003, c, 0.001
   end
 end
@@ -388,7 +388,7 @@ class TestUtilSumQ < Minitest::Test
     team1 = [r]
     team2 = [r, r]
     team_ratings = OpenSkill::Util.team_rating([team1, team2])
-    c = OpenSkill::Util.util_c(team_ratings, @model.env.beta_sq)
+    c = OpenSkill::Util.util_c(team_ratings, @model.beta_sq)
     sum_q = OpenSkill::Util.util_sum_q(team_ratings, c)
     assert_equal [29.67892702634643, 24.70819334370875], sum_q
   end
@@ -396,7 +396,7 @@ class TestUtilSumQ < Minitest::Test
   def test_util_sum_q_5v5
     r = @model.rating
     team_ratings = OpenSkill::Util.team_rating([[r] * 5, [r] * 5])
-    c = OpenSkill::Util.util_c(team_ratings, @model.env.beta_sq)
+    c = OpenSkill::Util.util_c(team_ratings, @model.beta_sq)
     sum_q = OpenSkill::Util.util_sum_q(team_ratings, c)
     assert_in_delta 204.84, sum_q[0], 0.01
     assert_in_delta 102.42, sum_q[1], 0.01
@@ -439,20 +439,20 @@ class TestPlackettLuce < Minitest::Test
 
   def test_solo_game_unchanged
     r = @model.rating
-    result = OpenSkill::PlackettLuce.rate([[r]], @model.env)
+    result = OpenSkill::PlackettLuce.rate([[r]], @model)
     assert_equal [[r]], result
   end
 
   def test_2p_ffa
     r = @model.rating
-    result = OpenSkill::PlackettLuce.rate([[r], [r]], @model.env)
+    result = OpenSkill::PlackettLuce.rate([[r], [r]], @model)
     assert_rating 27.63523138347365, 8.065506316323548, result[0][0]
     assert_rating 22.36476861652635, 8.065506316323548, result[1][0]
   end
 
   def test_3p_ffa
     r = @model.rating
-    result = OpenSkill::PlackettLuce.rate([[r], [r], [r]], @model.env)
+    result = OpenSkill::PlackettLuce.rate([[r], [r], [r]], @model)
     assert_rating 27.868876552746237, 8.204837030780652, result[0][0]
     assert_rating 25.717219138186557, 8.057829747583874, result[1][0]
     assert_rating 21.413904309067206, 8.057829747583874, result[2][0]
@@ -460,7 +460,7 @@ class TestPlackettLuce < Minitest::Test
 
   def test_4p_ffa
     r = @model.rating
-    result = OpenSkill::PlackettLuce.rate([[r], [r], [r], [r]], @model.env)
+    result = OpenSkill::PlackettLuce.rate([[r], [r], [r], [r]], @model)
     assert_rating 27.795084971874736, 8.263160757613477, result[0][0]
     assert_rating 26.552824984374855, 8.179213704945203, result[1][0]
     assert_rating 24.68943500312503, 8.083731307186588, result[2][0]
@@ -469,7 +469,7 @@ class TestPlackettLuce < Minitest::Test
 
   def test_5p_ffa
     r = @model.rating
-    result = OpenSkill::PlackettLuce.rate([[r], [r], [r], [r], [r]], @model.env)
+    result = OpenSkill::PlackettLuce.rate([[r], [r], [r], [r], [r]], @model)
     assert_rating 27.666666666666668, 8.290556877154474, result[0][0]
     assert_rating 26.833333333333332, 8.240145629781066, result[1][0]
     assert_rating 25.72222222222222, 8.179996679645559, result[2][0]
@@ -479,7 +479,7 @@ class TestPlackettLuce < Minitest::Test
 
   def test_3_teams_different_sizes
     r = @model.rating
-    result = OpenSkill::PlackettLuce.rate([[r, r, r], [r], [r, r]], @model.env)
+    result = OpenSkill::PlackettLuce.rate([[r, r, r], [r], [r, r]], @model)
     assert_rating 25.939870821784513, 8.247641552260456, result[0][0]
     assert_rating 25.939870821784513, 8.247641552260456, result[0][1]
     assert_rating 25.939870821784513, 8.247641552260456, result[0][2]