games_dice 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +11 -1
- data/.gitignore +0 -1
- data/.rubocop.yml +13 -48
- data/CHANGELOG.md +19 -0
- data/Gemfile +5 -3
- data/README.md +46 -5
- data/Rakefile +12 -5
- data/ext/games_dice/extconf.rb +0 -3
- data/ext/games_dice/probabilities.c +293 -193
- data/ext/games_dice/probabilities.h +2 -12
- data/games_dice.gemspec +1 -1
- data/lib/games_dice/bunch.rb +50 -36
- data/lib/games_dice/bunch_helpers.rb +35 -14
- data/lib/games_dice/complex_die.rb +64 -22
- data/lib/games_dice/complex_die_helpers.rb +122 -144
- data/lib/games_dice/dice.rb +61 -17
- data/lib/games_dice/dice_helpers.rb +37 -0
- data/lib/games_dice/die_result.rb +6 -8
- data/lib/games_dice/marshal.rb +2 -7
- data/lib/games_dice/parser.rb +30 -0
- data/lib/games_dice/probability_budget.rb +90 -0
- data/lib/games_dice/probability_calculation.rb +95 -0
- data/lib/games_dice/probability_shortcuts.rb +65 -0
- data/lib/games_dice/recipe_cache.rb +155 -0
- data/lib/games_dice/version.rb +1 -1
- data/lib/games_dice.rb +4 -0
- data/script/check_documentation.rb +10 -0
- data/spec/games_dice/bunch_regressions_spec.rb +46 -0
- data/spec/{bunch_spec.rb → games_dice/bunch_spec.rb} +65 -112
- data/spec/games_dice/complex_die_budget_spec.rb +60 -0
- data/spec/games_dice/complex_die_limits_spec.rb +119 -0
- data/spec/games_dice/complex_die_spec.rb +228 -0
- data/spec/games_dice/dice_mutation_spec.rb +165 -0
- data/spec/games_dice/dice_regressions_spec.rb +50 -0
- data/spec/games_dice/dice_spec.rb +61 -0
- data/spec/games_dice/die_result_spec.rb +221 -0
- data/spec/{die_spec.rb → games_dice/die_spec.rb} +15 -14
- data/spec/{map_rule_spec.rb → games_dice/map_rule_spec.rb} +8 -1
- data/spec/{parser_spec.rb → games_dice/parser_spec.rb} +12 -2
- data/spec/games_dice/probabilities_boundaries_spec.rb +161 -0
- data/spec/games_dice/probabilities_marshal_spec.rb +62 -0
- data/spec/games_dice/probabilities_spec.rb +484 -0
- data/spec/games_dice/probability_budget_spec.rb +17 -0
- data/spec/{reroll_rule_spec.rb → games_dice/reroll_rule_spec.rb} +5 -1
- data/spec/games_dice_spec.rb +382 -0
- data/spec/helpers.rb +64 -2
- data/spec/simple_cov_spec.rb +42 -0
- data/spec/support/coverage.rb +12 -0
- data/spec/support/validation.rb +14 -0
- data/spec/yard/cli/stats_spec.rb +43 -0
- metadata +29 -12
- data/spec/complex_die_spec.rb +0 -314
- data/spec/dice_spec.rb +0 -33
- data/spec/die_result_spec.rb +0 -254
- data/spec/fixtures/probs_fair_die_6.dat +0 -2
- data/spec/probability_spec.rb +0 -584
- data/spec/readme_spec.rb +0 -410
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b413f80b11ad9ca9e552ffcf86f24d4405f8750bff01f231e4f0ab20be1963de
|
|
4
|
+
data.tar.gz: 9eae95cf6218d6ad7d047077d5541dd471707059f5ed325098d1031012ae4bcf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7394cf91a9a1c911340fcb3c8edee3ef7adb303172b5106eb0a3956e05be141be2ab874046b4cb99d8ec2f3d7208dc2f6428b10c5d8b12c2c72b3cb6f73af3d0
|
|
7
|
+
data.tar.gz: 9d8c8bd1337c26aa4c3203a62e359af2b180f21bf46831b8929f6f234d60f1e00a26e01f5822b13769c6f30a41b538b6dce20e8df35a3767909428bdb366b8a1
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -4,6 +4,7 @@ on:
|
|
|
4
4
|
push:
|
|
5
5
|
branches:
|
|
6
6
|
- main
|
|
7
|
+
- maintenance
|
|
7
8
|
pull_request:
|
|
8
9
|
schedule:
|
|
9
10
|
- cron: '0 6 8 * *'
|
|
@@ -25,7 +26,15 @@ jobs:
|
|
|
25
26
|
with:
|
|
26
27
|
ruby-version: ${{ matrix.ruby }}
|
|
27
28
|
bundler-cache: true
|
|
28
|
-
- run: bundle exec rake
|
|
29
|
+
- run: bundle exec rake
|
|
30
|
+
- name: Upload Ruby coverage report
|
|
31
|
+
if: always()
|
|
32
|
+
uses: actions/upload-artifact@v6
|
|
33
|
+
with:
|
|
34
|
+
name: ruby-coverage-${{ matrix.ruby }}
|
|
35
|
+
path: coverage/ruby
|
|
36
|
+
retention-days: 14
|
|
37
|
+
if-no-files-found: warn
|
|
29
38
|
|
|
30
39
|
rubocop:
|
|
31
40
|
name: RuboCop (Ruby ${{ matrix.ruby }})
|
|
@@ -41,6 +50,7 @@ jobs:
|
|
|
41
50
|
ruby-version: ${{ matrix.ruby }}
|
|
42
51
|
bundler-cache: true
|
|
43
52
|
- run: bundle exec rubocop
|
|
53
|
+
- run: bundle exec ncs-rubocop-conf-audit
|
|
44
54
|
|
|
45
55
|
audit:
|
|
46
56
|
name: Dependency audit
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -1,53 +1,18 @@
|
|
|
1
|
+
inherit_gem:
|
|
2
|
+
ncs_rubocop_conf:
|
|
3
|
+
- config/base.yml
|
|
4
|
+
- config/rake.yml
|
|
5
|
+
- config/rspec.yml
|
|
6
|
+
- config/native_extension.yml
|
|
7
|
+
|
|
1
8
|
plugins:
|
|
2
9
|
- rubocop-performance
|
|
3
|
-
- rubocop-rake
|
|
4
|
-
- rubocop-rspec
|
|
5
|
-
|
|
6
|
-
AllCops:
|
|
7
|
-
TargetRubyVersion: 3.3
|
|
8
|
-
NewCops: enable
|
|
9
|
-
SuggestExtensions: false
|
|
10
|
-
|
|
11
|
-
Layout/LineLength:
|
|
12
|
-
Max: 120
|
|
13
|
-
|
|
14
|
-
Metrics/BlockLength:
|
|
15
|
-
AllowedMethods:
|
|
16
|
-
- context
|
|
17
|
-
- describe
|
|
18
|
-
- shared_examples
|
|
19
|
-
- define
|
|
20
|
-
|
|
21
|
-
RSpec/ExampleLength:
|
|
22
|
-
Max: 25
|
|
23
|
-
|
|
24
|
-
RSpec/MultipleExpectations:
|
|
25
|
-
Max: 21
|
|
26
|
-
|
|
27
|
-
RSpec/NestedGroups:
|
|
28
|
-
Max: 4
|
|
29
|
-
|
|
30
|
-
# This project keeps specs flat so they are easy to run individually.
|
|
31
|
-
RSpec/SpecFilePathFormat:
|
|
32
|
-
Enabled: false
|
|
33
10
|
|
|
34
|
-
|
|
35
|
-
|
|
11
|
+
inherit_mode:
|
|
12
|
+
merge:
|
|
13
|
+
- Exclude
|
|
36
14
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
- spec/complex_die_spec.rb
|
|
40
|
-
|
|
41
|
-
RSpec/MultipleDescribes:
|
|
42
|
-
Exclude:
|
|
43
|
-
- spec/readme_spec.rb
|
|
44
|
-
|
|
45
|
-
RSpec/DescribeClass:
|
|
46
|
-
Exclude:
|
|
47
|
-
- spec/readme_spec.rb
|
|
48
|
-
|
|
49
|
-
# Probabilities implements #each without mixing in Enumerable; these specs
|
|
50
|
-
# intentionally collect yielded pairs manually.
|
|
51
|
-
Style/MapIntoArray:
|
|
15
|
+
# RuboCop rationale: Local agent guidance and ad-hoc probes are not repository source.
|
|
16
|
+
AllCops:
|
|
52
17
|
Exclude:
|
|
53
|
-
-
|
|
18
|
+
- 'docs/agent/**/*'
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# GamesDice Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0 ( 14 October 2026 )
|
|
4
|
+
|
|
5
|
+
* Fix native repetition termination, keeper allocation leaks, and exception cleanup. Validate
|
|
6
|
+
finite probabilities and check integer endpoints and distribution spans before allocation.
|
|
7
|
+
* Fix bounds with negative multipliers, reject nonpositive keeper counts, and make explanations
|
|
8
|
+
reflect weighted results and the actual mapped keepers.
|
|
9
|
+
* Bound aggregate probability truncation error by `1e-10` before floating-point roundoff, use
|
|
10
|
+
cheap exact shortcuts, and raise `GamesDice::ProbabilityLimitError` for unsupported work.
|
|
11
|
+
Bounds return safe enclosing intervals when cheaply provable, otherwise they also raise.
|
|
12
|
+
* Cache combined probabilities and invalidate derived state when exposed recipe arrays or
|
|
13
|
+
mapping labels change. Previous results and explanations clear until the next roll.
|
|
14
|
+
* Clarify that shorthand replacement rerolls use the existing default limit of 1000.
|
|
15
|
+
* **Breaking serialization change:** remove the legacy `Probabilities._load` protocol and support
|
|
16
|
+
paired Marshal dump/load round trips. Previously serialized probabilities, including those
|
|
17
|
+
cached in dice, may no longer load. Recompute from recipes, or export hashes with the previous
|
|
18
|
+
version and reconstruct using `Probabilities.from_h`.
|
|
19
|
+
* Standardise development linting on `ncs_rubocop_conf` v0.2.0 with its base, Rake, RSpec, and native-extension profiles.
|
|
20
|
+
* Refactor the test suite to meet the shared policy without reducing its assertions or coverage.
|
|
21
|
+
|
|
3
22
|
## 0.5.0 ( 22 July 2026 )
|
|
4
23
|
|
|
5
24
|
* Add support and CI coverage for Ruby 4.0.
|
data/Gemfile
CHANGED
|
@@ -6,12 +6,14 @@ source 'https://rubygems.org'
|
|
|
6
6
|
gemspec
|
|
7
7
|
|
|
8
8
|
gem 'bundler-audit', '~> 0.9', require: false
|
|
9
|
+
gem 'ncs_rubocop_conf',
|
|
10
|
+
github: 'neilslater/ncs_rubocop_conf',
|
|
11
|
+
tag: 'v0.2.1',
|
|
12
|
+
require: false
|
|
9
13
|
gem 'rake', '~> 13.2'
|
|
10
14
|
gem 'rake-compiler', '~> 1.2'
|
|
11
15
|
gem 'redcarpet', '~> 3.6'
|
|
12
16
|
gem 'rspec', '~> 3.13'
|
|
13
|
-
gem 'rubocop', '~> 1.75', require: false
|
|
14
17
|
gem 'rubocop-performance', '~> 1.25', require: false
|
|
15
|
-
gem '
|
|
16
|
-
gem 'rubocop-rspec', '~> 3.6', require: false
|
|
18
|
+
gem 'simplecov', '~> 0.22.0', require: false
|
|
17
19
|
gem 'yard', '~> 0.9'
|
data/README.md
CHANGED
|
@@ -24,7 +24,7 @@ gem as-is, and add them as features within your project code.
|
|
|
24
24
|
|
|
25
25
|
## Supported Ruby Versions
|
|
26
26
|
|
|
27
|
-
GamesDice 0.
|
|
27
|
+
GamesDice 0.6.0 supports MRI Ruby 3.3, 3.4, and 4.0. It uses a native extension and does not support JRuby.
|
|
28
28
|
|
|
29
29
|
GamesDice 0.4.0 and 0.4.1 support MRI Ruby 2.6 and later. Versions before 0.4.0 also provide a
|
|
30
30
|
pure-Ruby implementation of the probability calculations and can be used with JRuby.
|
|
@@ -132,7 +132,8 @@ Returns a GamesDice::Probabilities object that describes the probability distrib
|
|
|
132
132
|
Note that some distributions, involving keeping a number best or worst results, can take
|
|
133
133
|
significant time to calculate. If the theoretical distribution would contain a large number
|
|
134
134
|
of very low probabilities due to a possibility of large numbers re-rolls, then the
|
|
135
|
-
|
|
135
|
+
calculation may omit unfinished branches within the aggregate `1e-10` truncation bound described below,
|
|
136
|
+
or raise `GamesDice::ProbabilityLimitError` when its limits prevent that guarantee.
|
|
136
137
|
|
|
137
138
|
### GamesDice::Probabilities instance methods
|
|
138
139
|
|
|
@@ -166,7 +167,19 @@ Returns the probability of a result equal to the integer n.
|
|
|
166
167
|
probabilities.p_eql( 3 ) # => 0.004629629629
|
|
167
168
|
probabilities.p_eql( 2 ) # => 0.0
|
|
168
169
|
|
|
169
|
-
|
|
170
|
+
Very unlikely sequences of rerolls may be left out to keep calculation time reasonable.
|
|
171
|
+
This changes the probability of any result, or range of results, by at most `1e-10`
|
|
172
|
+
(one in ten billion), in addition to normal floating-point rounding. This limit applies
|
|
173
|
+
to all the dice together, including when keeping only the highest or lowest rolls.
|
|
174
|
+
|
|
175
|
+
GamesDice limits the amount of work spent calculating probabilities. If it cannot achieve
|
|
176
|
+
the accuracy described above within those limits, it raises `GamesDice::ProbabilityLimitError`.
|
|
177
|
+
These calculation limits do not change how the dice are rolled.
|
|
178
|
+
|
|
179
|
+
For complex dice, `dice.min` and `dice.max` may describe a wider range than can actually
|
|
180
|
+
be rolled. Every roll will fall within that range, but its endpoints may not be possible
|
|
181
|
+
results. These methods also raise `GamesDice::ProbabilityLimitError` if they cannot find
|
|
182
|
+
suitable bounds within the calculation limits.
|
|
170
183
|
|
|
171
184
|
#### probabilities.p_gt( n )
|
|
172
185
|
|
|
@@ -200,7 +213,34 @@ Returns the probability of a result less than the integer n.
|
|
|
200
213
|
|
|
201
214
|
Returns the mean result, weighted by the probability of each value.
|
|
202
215
|
|
|
203
|
-
probabilities.expected # => 10.5 (
|
|
216
|
+
probabilities.expected # => 10.5 (mean of the returned distribution)
|
|
217
|
+
|
|
218
|
+
If very unlikely reroll sequences were left out, this mean is also an approximation.
|
|
219
|
+
Rare but unusually high or low results can affect the mean more than their small
|
|
220
|
+
probabilities might suggest, so the `1e-10` accuracy limit for probabilities does
|
|
221
|
+
not apply to the mean.
|
|
222
|
+
|
|
223
|
+
## Changing dice rules
|
|
224
|
+
|
|
225
|
+
You can change existing dice by editing their bunches, multipliers, reroll rules or mapping
|
|
226
|
+
rules. GamesDice recalculates affected probabilities and minimum/maximum values when next
|
|
227
|
+
needed. Previous results and explanations are cleared until you roll again.
|
|
228
|
+
|
|
229
|
+
Each entry in `dice.bunches` has a matching entry in `dice.bunch_multipliers`. If you add,
|
|
230
|
+
remove or reorder bunches, make the corresponding change to their multipliers before
|
|
231
|
+
using the dice.
|
|
232
|
+
|
|
233
|
+
If you supply custom rule callbacks, keep their behavior unchanged while the rules are
|
|
234
|
+
in use. Replace the rule object when changing its behavior, and do not change dice rules
|
|
235
|
+
from inside a callback.
|
|
236
|
+
|
|
237
|
+
## Saving and loading dice
|
|
238
|
+
|
|
239
|
+
Breaking change in 0.6.0: legacy serialized `GamesDice::Probabilities` objects requiring the old
|
|
240
|
+
`_load` protocol are no longer supported. This also affects serialized dice containing such
|
|
241
|
+
cached probabilities. New objects support Marshal dump/load round trips. Recompute persisted
|
|
242
|
+
distributions from their recipes, or export hashes using the previous version and reconstruct
|
|
243
|
+
with `Probabilities.from_h`. Only deserialize trusted data.
|
|
204
244
|
|
|
205
245
|
## String Dice Descriptions
|
|
206
246
|
|
|
@@ -257,7 +297,8 @@ The simple form specifies a low value that will automatically trigger a re-roll
|
|
|
257
297
|
1d6r1
|
|
258
298
|
|
|
259
299
|
When rolled, this die will score from 1 to 6. If it rolls a 1, it will roll again automatically
|
|
260
|
-
and use that result instead.
|
|
300
|
+
and use that result instead, repeating while it rolls 1, up to the default limit of 1000 rerolls.
|
|
301
|
+
Use `1d6r:1,replace,1.` for exactly one replacement attempt.
|
|
261
302
|
|
|
262
303
|
The full version of this modifier, allows you to specify from 1 to 3 parameters:
|
|
263
304
|
|
data/Rakefile
CHANGED
|
@@ -11,7 +11,7 @@ require 'yard'
|
|
|
11
11
|
|
|
12
12
|
desc 'GamesDice unit tests'
|
|
13
13
|
RSpec::Core::RakeTask.new(:spec) do |t|
|
|
14
|
-
t.pattern = 'spec
|
|
14
|
+
t.pattern = 'spec/**/*_spec.rb'
|
|
15
15
|
t.verbose = false
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -28,7 +28,14 @@ Rake::ExtensionTask.new do |ext|
|
|
|
28
28
|
ext.gem_spec = gemspec
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
namespace :docs do
|
|
32
|
+
desc 'Check Ruby API documentation for YARD warnings and undocumented objects'
|
|
33
|
+
task :check do
|
|
34
|
+
ruby 'script/check_documentation.rb'
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
task default: %i[compile spec docs:check]
|
|
32
39
|
|
|
33
40
|
rebuild_and_test_native = lambda do |mode, test: true|
|
|
34
41
|
tasks = %w[clobber compile]
|
|
@@ -46,7 +53,6 @@ rebuild_and_test_native = lambda do |mode, test: true|
|
|
|
46
53
|
end
|
|
47
54
|
|
|
48
55
|
# Native quality orchestration is kept together so each task shares the same rebuild contract.
|
|
49
|
-
# rubocop:disable Metrics/BlockLength
|
|
50
56
|
namespace :c do
|
|
51
57
|
desc 'Compile the C extension with strict warnings'
|
|
52
58
|
task :lint do
|
|
@@ -94,8 +100,10 @@ namespace :c do
|
|
|
94
100
|
|
|
95
101
|
rebuild_and_test_native.call('sanitize', test: false)
|
|
96
102
|
|
|
103
|
+
# MRI owns its threads' alternate signal stacks; ASan must not unmap them on thread exit.
|
|
104
|
+
# See https://bugs.ruby-lang.org/issues/20256 (also applies when preloading ASan into stock Ruby).
|
|
97
105
|
sh(
|
|
98
|
-
{ 'ASAN_OPTIONS' => 'detect_leaks=0', 'LD_PRELOAD' => libasan },
|
|
106
|
+
{ 'ASAN_OPTIONS' => 'detect_leaks=0:use_sigaltstack=0', 'LD_PRELOAD' => libasan },
|
|
99
107
|
RbConfig.ruby,
|
|
100
108
|
'-S',
|
|
101
109
|
'bundle',
|
|
@@ -105,4 +113,3 @@ namespace :c do
|
|
|
105
113
|
)
|
|
106
114
|
end
|
|
107
115
|
end
|
|
108
|
-
# rubocop:enable Metrics/BlockLength
|
data/ext/games_dice/extconf.rb
CHANGED
|
@@ -6,7 +6,6 @@ require 'mkmf'
|
|
|
6
6
|
require 'rbconfig'
|
|
7
7
|
|
|
8
8
|
# mkmf exposes compiler and linker flags through these globals.
|
|
9
|
-
# rubocop:disable Style/GlobalVars
|
|
10
9
|
case ENV.fetch('GAMES_DICE_NATIVE_MODE', 'release')
|
|
11
10
|
when 'release'
|
|
12
11
|
# Use Ruby's normal extension build flags.
|
|
@@ -31,6 +30,4 @@ when 'sanitize'
|
|
|
31
30
|
else
|
|
32
31
|
abort "Unknown GAMES_DICE_NATIVE_MODE: #{ENV.fetch('GAMES_DICE_NATIVE_MODE', nil)}"
|
|
33
32
|
end
|
|
34
|
-
# rubocop:enable Style/GlobalVars
|
|
35
|
-
|
|
36
33
|
create_makefile('games_dice/games_dice')
|