orefinder-estimate 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 241ee2fe99c357eb6a52fe274ab907fb5ddf48379eee6bf0a7f572ca6b795a30
4
+ data.tar.gz: '095401a530362c9a0ac3c58d271a3886fe514db5a2554df68b89344abb8eba63'
5
+ SHA512:
6
+ metadata.gz: 170e51a5dea283bfe8c1321cfd793436d5c8f04fc9cb7c17ef4ee1b08a60f073d730b7f02957f0c0bcb85334c0a06118bfcbb75cf44c106dce7e9acf3f2b5672
7
+ data.tar.gz: 8b79f27cee69fbf594702fc3f6f6fa3cf8a1bf2630c474cca13ae775601a2f0d44ad2f03bf483c31efa063263c0d16583854680425a8a4b9fd7a77bf0d9543a8
data/DISCLAIMER.md ADDED
@@ -0,0 +1,19 @@
1
+ # About these estimates
2
+
3
+ These packages give you a fast, offline **estimate** of where an ore is most
4
+ likely to concentrate in Minecraft - the best mining Y level and the nearby
5
+ areas worth digging - using published Y-level distribution ranges and
6
+ statistical weighting.
7
+
8
+ Think of it as a smart heads-up for planning your next mining trip: quick to
9
+ run, no world load, no network calls, and easy to build on in your own projects.
10
+
11
+ For pinpoint, seed-exact block coordinates (Java and Bedrock), pair it with the
12
+ full tool at:
13
+
14
+ **https://orefinder.io**
15
+
16
+ As with any planning tool, treat the results as guidance and confirm important
17
+ spots in-game. Minecraft, the Minecraft name, and associated assets are property
18
+ of Mojang/Microsoft. These packages are an unofficial, fan-made utility and are
19
+ not affiliated with or endorsed by Mojang.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ore Finder (https://orefinder.io)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # orefinder-estimate (RubyGems)
2
+
3
+ A fast, offline, dependency-free Minecraft ore **estimator** gem. Feed it a
4
+ seed label, version, and player position; get likely ore coordinates and the
5
+ best mining Y for Java or Bedrock.
6
+
7
+ > Fast, offline estimates of the best mining Y and where an ore is likely to
8
+ > concentrate. For pinpoint, seed-exact coordinates (Java & Bedrock), pair it with
9
+ > **[Ore Finder](https://orefinder.io)**.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ gem install orefinder-estimate
15
+ ```
16
+
17
+ ## Ruby
18
+
19
+ ```ruby
20
+ require 'orefinder_estimate'
21
+
22
+ result = OrefinderEstimate.estimate(
23
+ 'seed' => '42',
24
+ 'version' => 'java_1_21',
25
+ 'ore' => 'diamond',
26
+ 'x' => 0, 'y' => 64, 'z' => 0,
27
+ 'biome' => 'plains',
28
+ 'searchRadius' => 220
29
+ )
30
+
31
+ puts result['metadata']['miningPeakY']
32
+ p result['bestCluster'].first
33
+ ```
34
+
35
+ ## CLI
36
+
37
+ ```bash
38
+ orefinder-estimate --version java_1_21 --ore diamond -x 0 -y 64 -z 0
39
+ orefinder-estimate --ore ancient_debris --version java_1_21 --biome crimson_forest --json
40
+ ```
41
+
42
+ ## Supported ores
43
+
44
+ diamond, iron, gold, coal, copper, emerald, redstone, lapis, ancient_debris
45
+ (plus netherite aliases), nether_gold, nether_quartz, gilded_blackstone,
46
+ blackstone.
47
+
48
+ Deterministic and parity-tested against the sibling packages. No world-gen
49
+ simulation, no structures, no network calls. Seed-accurate placement lives at
50
+ [orefinder.io](https://orefinder.io).
51
+
52
+ MIT (c) [Ore Finder](https://orefinder.io).
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Ore Finder Estimate CLI. Approximate distribution estimate; NOT seed-accurate.
5
+ # For exact, seed-based results use https://orefinder.io
6
+ require 'json'
7
+
8
+ begin
9
+ require 'orefinder_estimate'
10
+ rescue LoadError
11
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
12
+ require 'orefinder_estimate'
13
+ end
14
+
15
+ HELP = <<~TXT
16
+ orefinder-estimate - approximate Minecraft ore locator (offline)
17
+
18
+ USAGE:
19
+ orefinder-estimate --version <ver> --ore <ore> [options]
20
+
21
+ OPTIONS:
22
+ --seed <s> World seed label. Default: 42
23
+ --version <v> e.g. java_1_21, 1.20.1 (required)
24
+ --ore <o> e.g. diamond, ancient_debris (required)
25
+ -x -y -z <n> Player position. Default: 0 64 0
26
+ --biome <b> Biome bonus. Default: plains
27
+ --radius <n> Search radius 32-512. Default: 220
28
+ --json Print raw JSON
29
+ --ores List supported ores
30
+ -h, --help Show this help
31
+
32
+ Approximate estimate, not seed-accurate. For exact coords use https://orefinder.io
33
+ TXT
34
+
35
+ opts = {}
36
+ flags = []
37
+ i = 0
38
+ while i < ARGV.length
39
+ a = ARGV[i]
40
+ case a
41
+ when '-h', '--help' then flags << 'help'
42
+ when '--json' then flags << 'json'
43
+ when '--ores' then flags << 'ores'
44
+ else
45
+ if a.start_with?('-') && i + 1 < ARGV.length
46
+ opts[a] = ARGV[i + 1]
47
+ i += 1
48
+ end
49
+ end
50
+ i += 1
51
+ end
52
+
53
+ if flags.include?('help') || ARGV.empty?
54
+ puts HELP
55
+ exit 0
56
+ end
57
+ if flags.include?('ores')
58
+ puts OrefinderEstimate::Data.known_ore_keys.sort.join("\n")
59
+ exit 0
60
+ end
61
+
62
+ version = opts['--version']
63
+ ore = opts['--ore']
64
+ if version.nil? || ore.nil?
65
+ warn 'Error: --version and --ore are required. Use --help.'
66
+ exit 2
67
+ end
68
+
69
+ res = OrefinderEstimate.estimate(
70
+ 'seed' => opts['--seed'] || '42',
71
+ 'version' => version,
72
+ 'ore' => ore,
73
+ 'x' => (opts['-x'] || '0').to_f,
74
+ 'y' => (opts['-y'] || '64').to_f,
75
+ 'z' => (opts['-z'] || '0').to_f,
76
+ 'biome' => opts['--biome'] || 'plains',
77
+ 'searchRadius' => (opts['--radius'] || '220').to_f
78
+ )
79
+
80
+ unless res['success']
81
+ warn "Error (#{res['error']}): #{res['message']}"
82
+ exit 1
83
+ end
84
+
85
+ if flags.include?('json')
86
+ puts JSON.pretty_generate(res)
87
+ exit 0
88
+ end
89
+
90
+ m = res['metadata']
91
+ puts 'Ore Finder Estimate (approximate - verify at https://orefinder.io)'
92
+ puts "#{m['ore']} #{m['versionLabel']} [#{m['edition']}/#{m['era']}] best Y ~ #{m['miningPeakY']}"
93
+ puts "biome #{m['biomeEffective']} (x#{m['biomeEffect']}) dimension #{m['dimension']}"
94
+ puts ''
95
+ puts 'Best mining area:'
96
+ res['bestCluster'].each do |p|
97
+ puts " X #{p['x']} Y #{p['y']} Z #{p['z']} #{p['distance']}m #{p['probabilityPct']}% (conf #{p['confidence']})"
98
+ end
99
+ res['secondaryClusters'].each_with_index do |group, gi|
100
+ puts "Nearby area #{gi + 1}:"
101
+ group.each do |p|
102
+ puts " X #{p['x']} Y #{p['y']} Z #{p['z']} #{p['distance']}m #{p['probabilityPct']}%"
103
+ end
104
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rng'
4
+ require_relative 'probability'
5
+
6
+ # Cluster-based coordinate generator (statistical, not chunk-exact). See ../../../SPEC.md section 6.
7
+ module OrefinderEstimate
8
+ module Cluster
9
+ SECONDARY_XZ_MIN = 10
10
+ SECONDARY_XZ_MAX = 30
11
+ PRIMARY_XZ_MIN = 8
12
+ PRIMARY_XZ_MAX = 15
13
+ SECONDARY_Y_MAX = 3
14
+ PRIMARY_Y_MAX = 2
15
+
16
+ module_function
17
+
18
+ def num_str(v)
19
+ v == v.to_i ? v.to_i.to_s : v.to_s
20
+ end
21
+
22
+ def dist2(x1, z1, x2, z2)
23
+ dx = x1 - x2
24
+ dz = z1 - z2
25
+ Math.sqrt(dx * dx + dz * dz)
26
+ end
27
+
28
+ def adaptive_center_band(cfg, search_radius)
29
+ f = cfg['frequency'].to_s
30
+ lo = 50
31
+ hi = 150
32
+ if %w[very_rare rare].include?(f)
33
+ lo = 40
34
+ hi = 90
35
+ elsif %w[very_common common].include?(f)
36
+ lo = 60
37
+ hi = 140
38
+ end
39
+ band_hi = [hi, search_radius].min
40
+ band_lo = [lo, band_hi].min
41
+ [band_lo, band_hi]
42
+ end
43
+
44
+ def norm_distance(d, search_radius)
45
+ cap = [search_radius, 1].max
46
+ Probability.clamp(d / cap.to_f, 0, 1)
47
+ end
48
+
49
+ def point_ux_score(prob_pct, distance, search_radius)
50
+ (prob_pct / 100.0) * 0.8 - norm_distance(distance, search_radius) * 0.2
51
+ end
52
+
53
+ def cluster_rank_score(max_prob, mean_prob, mean_norm_dist)
54
+ (max_prob * 0.7 + mean_prob * 0.3) - mean_norm_dist * 12
55
+ end
56
+
57
+ def generate_clusters(seed:, ore:, era:, cfg:, px:, pz:, biome:, search_radius: 220)
58
+ peak_y = Probability.optimal_y_for_mining(cfg)
59
+ band_lo, band_hi = adaptive_center_band(cfg, search_radius)
60
+ num_centers = search_radius < 48 ? 2 : 3
61
+ points_per_cluster = [4, 3, 3]
62
+ min_y = cfg['minY']
63
+ max_y = cfg['maxY']
64
+
65
+ expand = lambda do |cx, cz, n, rng, confidence_bonus, primary|
66
+ xz_min = primary ? PRIMARY_XZ_MIN : SECONDARY_XZ_MIN
67
+ xz_max = primary ? PRIMARY_XZ_MAX : SECONDARY_XZ_MAX
68
+ y_max = primary ? PRIMARY_Y_MAX : SECONDARY_Y_MAX
69
+ pts = []
70
+
71
+ push = lambda do |x, y, z|
72
+ prob = Probability.calculate_ore_probability(cfg, y, biome)
73
+ d = dist2(x, z, px, pz)
74
+ conf = Probability.clamp(Probability.calculate_confidence(prob, d, y, peak_y) + confidence_bonus, 0, 100)
75
+ pts << {
76
+ 'x' => x, 'y' => y, 'z' => z,
77
+ 'confidence' => Rng.round_to_tenth(conf),
78
+ 'probabilityPct' => Rng.round_half_up(prob * 1000) / 10.0,
79
+ 'distance' => Rng.round_to_tenth(d)
80
+ }
81
+ end
82
+
83
+ if primary && n > 0
84
+ center_y = Probability.clamp(Rng.round_half_up(peak_y + (rng.call - 0.5) * 2), min_y, max_y)
85
+ push.call(Rng.round_half_up(cx), center_y, Rng.round_half_up(cz))
86
+ end
87
+
88
+ start_i = (primary && n > 0) ? 1 : 0
89
+ (start_i...n).each do
90
+ mag_x = xz_min + rng.call * (xz_max - xz_min)
91
+ mag_z = xz_min + rng.call * (xz_max - xz_min)
92
+ sx = rng.call < 0.5 ? -1 : 1
93
+ sz = rng.call < 0.5 ? -1 : 1
94
+ x = Rng.round_half_up(cx + sx * mag_x)
95
+ z = Rng.round_half_up(cz + sz * mag_z)
96
+ dy = if primary
97
+ Probability.clamp(Rng.round_half_up((rng.call + rng.call - 1) * y_max), -y_max, y_max)
98
+ else
99
+ Probability.clamp(Rng.round_half_up((rng.call + rng.call + rng.call - 1.5) * 2), -y_max, y_max)
100
+ end
101
+ y = Probability.clamp(Rng.round_half_up(peak_y + dy), min_y, max_y)
102
+ push.call(x, y, z)
103
+ end
104
+
105
+ if primary && !pts.empty?
106
+ max_probs = pts.map { |p| p['probabilityPct'] }
107
+ max_p = max_probs.max
108
+ if max_p < 75
109
+ k = max_probs.index(max_p)
110
+ y_best = Probability.clamp(Rng.round_half_up(peak_y), min_y, max_y)
111
+ p0 = pts[k]
112
+ prob = Probability.calculate_ore_probability(cfg, y_best, biome)
113
+ d = dist2(p0['x'], p0['z'], px, pz)
114
+ conf = Probability.clamp(Probability.calculate_confidence(prob, d, y_best, peak_y) + confidence_bonus, 0, 100)
115
+ pts[k] = {
116
+ 'x' => p0['x'], 'y' => y_best, 'z' => p0['z'],
117
+ 'confidence' => Rng.round_to_tenth(conf),
118
+ 'probabilityPct' => Rng.round_half_up(prob * 1000) / 10.0,
119
+ 'distance' => Rng.round_to_tenth(d)
120
+ }
121
+ end
122
+ end
123
+
124
+ # Stable sort: pointUxScore desc, then confidence desc.
125
+ pts.each_with_index.sort_by do |p, i|
126
+ [-point_ux_score(p['probabilityPct'], p['distance'], search_radius), -p['confidence'], i]
127
+ end.map(&:first)
128
+ end
129
+
130
+ base_rng = Rng.make_rng([seed.to_s, ore, era, 'centers', num_str(Rng.round_half_up(px)), num_str(Rng.round_half_up(pz))])
131
+ base_angle = base_rng.call * Math::PI * 2
132
+
133
+ built = []
134
+ (0...num_centers).each do |c|
135
+ r = Rng.make_rng([seed.to_s, ore, era, 'c', c.to_s, num_str(band_lo), num_str(band_hi)])
136
+ angle = base_angle + c * ((2 * Math::PI) / num_centers) + (r.call - 0.5) * 0.45
137
+ rad = band_lo + r.call * (band_hi - band_lo)
138
+ cx = px + Math.cos(angle) * rad
139
+ cz = pz + Math.sin(angle) * rad
140
+
141
+ n = points_per_cluster[c] || 3
142
+ primary = c.zero?
143
+ bonus = primary ? 5 : 0
144
+ pts = expand.call(cx, cz, n, r, bonus, primary)
145
+
146
+ if pts.empty?
147
+ max_prob = 0
148
+ mean_prob = 0
149
+ mean_norm_dist = 1
150
+ else
151
+ max_prob = pts.map { |p| p['probabilityPct'] }.max
152
+ mean_prob = pts.sum { |p| p['probabilityPct'] } / pts.length.to_f
153
+ mean_norm_dist = pts.sum { |p| norm_distance(p['distance'], search_radius) } / pts.length.to_f
154
+ end
155
+ rank = cluster_rank_score(max_prob, mean_prob, mean_norm_dist)
156
+ rank += 22 if c.zero?
157
+ built << { 'pts' => pts, 'rank' => rank }
158
+ end
159
+
160
+ sorted = built.each_with_index.sort_by { |b, i| [-b['rank'], i] }.map(&:first)
161
+ best = sorted.empty? ? [] : sorted[0]['pts']
162
+ secondary = sorted[1..].map { |b| b['pts'] }
163
+ { 'bestCluster' => best, 'secondaryClusters' => secondary }
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'set'
5
+
6
+ # Loads the embedded ore-distribution tables. Fully offline; no network.
7
+ module OrefinderEstimate
8
+ module Data
9
+ ORE_DATA = JSON.parse(File.read(File.join(__dir__, 'ore-data.json'))).freeze
10
+ NETHER_BIOMES = ORE_DATA['netherBiomes'].to_set
11
+ ORE_ALIASES = ORE_DATA['oreAliases']
12
+
13
+ module_function
14
+
15
+ def known_ore_keys
16
+ ORE_DATA['ores'].keys + ORE_ALIASES.keys
17
+ end
18
+
19
+ def resolve_ore_keys(raw)
20
+ display_label = raw.to_s.strip.downcase
21
+ compute_key = ORE_ALIASES[display_label] || display_label
22
+ [compute_key, display_label]
23
+ end
24
+
25
+ def known_ore?(ore_key)
26
+ k = ore_key.to_s.strip.downcase
27
+ ORE_ALIASES.key?(k) || ORE_DATA['ores'].key?(k)
28
+ end
29
+
30
+ def era_config(era, compute_key)
31
+ entry = ORE_DATA['ores'][compute_key]
32
+ return nil unless entry
33
+
34
+ entry[era]
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'data'
4
+ require_relative 'version'
5
+ require_relative 'probability'
6
+ require_relative 'cluster'
7
+ require_relative 'rng'
8
+
9
+ # Public estimate API. See ../../../SPEC.md section 7. Offline only; never calls a network.
10
+ module OrefinderEstimate
11
+ module_function
12
+
13
+ def error(error, message, extra = {})
14
+ { 'success' => false, 'error' => error, 'message' => message }.merge(extra)
15
+ end
16
+
17
+ def estimate(input)
18
+ return error('invalid_body', 'Input must be a hash.') unless input.is_a?(Hash)
19
+
20
+ ore_in = input['ore'].is_a?(String) ? input['ore'].strip.downcase : ''
21
+ compute_key, display_label = Data.resolve_ore_keys(ore_in)
22
+ return error('invalid_ore', 'Unknown ore for estimator.') if ore_in.empty? || !Data.known_ore?(ore_in)
23
+
24
+ px = to_num(input['x'])
25
+ pz = to_num(input['z'])
26
+ py = input['y'].nil? ? 64.0 : to_num(input['y'])
27
+ if px.nil? || pz.nil? || py.nil? || !px.finite? || !pz.finite? || !py.finite?
28
+ return error('invalid_position', 'x, y and z must be finite numbers.')
29
+ end
30
+
31
+ version_field = input['version'] || input['platform'] || ''
32
+ parsed = Version.parse_version(version_field.to_s)
33
+ return error('invalid_version', 'Pass a version like "java_1_21" or "1.20.1".') if parsed.nil?
34
+
35
+ cfg = Data.era_config(parsed['era'], compute_key)
36
+ if cfg.nil?
37
+ msg = compute_key == 'copper' && parsed['era'] == 'legacy' ? 'Copper is modern-only (1.18+).' : 'This ore/version combination has no dataset row.'
38
+ return error('ore_not_in_era', msg)
39
+ end
40
+
41
+ need_mc = cfg['specialRules'] ? cfg['specialRules']['minMcMinor'] : nil
42
+ if need_mc.is_a?(Integer) && parsed['mcMinor'] < need_mc
43
+ return error('version_too_old_for_ore',
44
+ "This target needs Minecraft 1.#{need_mc}+ (you selected 1.#{parsed['mcMinor']}).",
45
+ 'minMcMinor' => need_mc, 'selectedMcMinor' => parsed['mcMinor'])
46
+ end
47
+
48
+ biome = input['biome']
49
+ raw_biome = biome.is_a?(String) && !biome.strip.empty? ? biome.strip.downcase.gsub(/\s+/, '_') : 'plains'
50
+ biome_effective = Probability.normalize_biome(cfg, raw_biome)
51
+
52
+ radius_val = input['searchRadius'].nil? ? 220.0 : to_num(input['searchRadius'])
53
+ search_radius = [[radius_val, 32].max, 512].min.to_i
54
+ seed = input['seed'].nil? ? 'numeric' : input['seed']
55
+
56
+ clusters = Cluster.generate_clusters(
57
+ seed: seed, ore: compute_key, era: parsed['era'], cfg: cfg,
58
+ px: px, pz: pz, biome: raw_biome, search_radius: search_radius
59
+ )
60
+
61
+ peak_y = Probability.optimal_y_for_mining(cfg)
62
+ effect = Probability.biome_multiplier(cfg, biome_effective)
63
+
64
+ {
65
+ 'success' => true,
66
+ 'ore' => display_label,
67
+ 'computeKey' => compute_key,
68
+ 'bestCluster' => clusters['bestCluster'],
69
+ 'secondaryClusters' => clusters['secondaryClusters'],
70
+ 'metadata' => {
71
+ 'ore' => display_label,
72
+ 'computeKey' => compute_key,
73
+ 'edition' => parsed['edition'],
74
+ 'era' => parsed['era'],
75
+ 'mcMinor' => parsed['mcMinor'],
76
+ 'versionLabel' => parsed['raw'],
77
+ 'optimalY' => cfg['optimalY'],
78
+ 'practicalY' => cfg['practicalY'],
79
+ 'actualPeakY' => cfg['actualPeakY'],
80
+ 'yRange' => [cfg['minY'], cfg['maxY']],
81
+ 'biomeRequested' => raw_biome,
82
+ 'biomeEffective' => biome_effective,
83
+ 'dimension' => cfg['dimension'] || 'overworld',
84
+ 'biomeEffect' => Rng.round_half_up(effect * 1000) / 1000.0,
85
+ 'miningPeakY' => peak_y,
86
+ 'generation' => 'distribution_cluster_estimate',
87
+ 'approximate' => true
88
+ }
89
+ }
90
+ end
91
+
92
+ def to_num(v)
93
+ return nil if v.nil?
94
+
95
+ Float(v)
96
+ rescue ArgumentError, TypeError
97
+ nil
98
+ end
99
+ end
@@ -0,0 +1,316 @@
1
+ {
2
+ "version": 1,
3
+ "note": "Approximate Minecraft ore Y-distribution tables. Not seed-accurate. See https://orefinder.io",
4
+ "netherBiomes": [
5
+ "nether_wastes",
6
+ "crimson_forest",
7
+ "warped_forest",
8
+ "soul_sand_valley",
9
+ "basalt_deltas"
10
+ ],
11
+ "oreAliases": {
12
+ "netherite_block": "ancient_debris",
13
+ "netherite_scrap": "ancient_debris",
14
+ "netherite_ingot": "ancient_debris"
15
+ },
16
+ "ores": {
17
+ "coal": {
18
+ "modern": {
19
+ "minY": 0, "maxY": 256, "optimalY": 96, "practicalY": null, "actualPeakY": null,
20
+ "frequency": "very_common", "dimension": "overworld",
21
+ "distributions": [
22
+ { "type": "uniform", "minY": 136, "maxY": 256, "peakY": null, "weight": 0.5 },
23
+ { "type": "triangular", "minY": 0, "maxY": 192, "peakY": 96, "weight": 0.5 }
24
+ ],
25
+ "biomeModifiers": { "stony_peaks": 1.5, "windswept_hills": 1.3 },
26
+ "specialRules": {}
27
+ },
28
+ "legacy": {
29
+ "minY": 0, "maxY": 127, "optimalY": 64, "practicalY": null, "actualPeakY": null,
30
+ "frequency": "very_common", "dimension": "overworld",
31
+ "distributions": [
32
+ { "type": "uniform", "minY": 0, "maxY": 127, "peakY": null, "weight": 1.0 }
33
+ ],
34
+ "biomeModifiers": {},
35
+ "specialRules": {}
36
+ }
37
+ },
38
+ "iron": {
39
+ "modern": {
40
+ "minY": -64, "maxY": 256, "optimalY": 16, "practicalY": null, "actualPeakY": null,
41
+ "frequency": "very_common", "dimension": "overworld",
42
+ "distributions": [
43
+ { "type": "uniform", "minY": -64, "maxY": 72, "peakY": null, "weight": 0.33 },
44
+ { "type": "triangular", "minY": -24, "maxY": 56, "peakY": 16, "weight": 0.33 },
45
+ { "type": "triangular", "minY": 80, "maxY": 256, "peakY": 232, "weight": 0.34 }
46
+ ],
47
+ "biomeModifiers": { "jagged_peaks": 2.0, "snowy_slopes": 1.8, "stony_peaks": 1.7 },
48
+ "specialRules": {}
49
+ },
50
+ "legacy": {
51
+ "minY": 0, "maxY": 63, "optimalY": 32, "practicalY": null, "actualPeakY": null,
52
+ "frequency": "very_common", "dimension": "overworld",
53
+ "distributions": [
54
+ { "type": "uniform", "minY": 0, "maxY": 63, "peakY": null, "weight": 1.0 }
55
+ ],
56
+ "biomeModifiers": {},
57
+ "specialRules": {}
58
+ }
59
+ },
60
+ "copper": {
61
+ "modern": {
62
+ "minY": -16, "maxY": 112, "optimalY": 48, "practicalY": null, "actualPeakY": null,
63
+ "frequency": "common", "dimension": "overworld",
64
+ "distributions": [
65
+ { "type": "triangular", "minY": -16, "maxY": 112, "peakY": 48, "weight": 1.0 }
66
+ ],
67
+ "biomeModifiers": { "dripstone_caves": 3.0 },
68
+ "specialRules": {}
69
+ },
70
+ "legacy": null
71
+ },
72
+ "gold": {
73
+ "modern": {
74
+ "minY": -64, "maxY": 32, "optimalY": -16, "practicalY": null, "actualPeakY": null,
75
+ "frequency": "rare", "dimension": "overworld",
76
+ "distributions": [
77
+ { "type": "triangular", "minY": -64, "maxY": 32, "peakY": -16, "weight": 0.5 },
78
+ { "type": "uniform", "minY": -64, "maxY": -48, "peakY": null, "weight": 0.5 }
79
+ ],
80
+ "biomeModifiers": { "badlands": 5.0, "eroded_badlands": 5.0, "wooded_badlands": 5.0 },
81
+ "specialRules": {}
82
+ },
83
+ "legacy": {
84
+ "minY": 0, "maxY": 31, "optimalY": 16, "practicalY": null, "actualPeakY": null,
85
+ "frequency": "rare", "dimension": "overworld",
86
+ "distributions": [
87
+ { "type": "uniform", "minY": 0, "maxY": 31, "peakY": null, "weight": 1.0 }
88
+ ],
89
+ "biomeModifiers": {},
90
+ "specialRules": {}
91
+ }
92
+ },
93
+ "diamond": {
94
+ "modern": {
95
+ "minY": -64, "maxY": 16, "optimalY": -59, "practicalY": -53, "actualPeakY": null,
96
+ "frequency": "very_rare", "dimension": "overworld",
97
+ "distributions": [
98
+ { "type": "triangular", "minY": -64, "maxY": 16, "peakY": -59, "weight": 1.0 }
99
+ ],
100
+ "biomeModifiers": {},
101
+ "specialRules": {}
102
+ },
103
+ "legacy": {
104
+ "minY": 1, "maxY": 15, "optimalY": 11, "practicalY": null, "actualPeakY": null,
105
+ "frequency": "very_rare", "dimension": "overworld",
106
+ "distributions": [
107
+ { "type": "uniform", "minY": 1, "maxY": 15, "peakY": null, "weight": 1.0 }
108
+ ],
109
+ "biomeModifiers": {},
110
+ "specialRules": {}
111
+ }
112
+ },
113
+ "lapis": {
114
+ "modern": {
115
+ "minY": -64, "maxY": 64, "optimalY": 0, "practicalY": null, "actualPeakY": null,
116
+ "frequency": "uncommon", "dimension": "overworld",
117
+ "distributions": [
118
+ { "type": "triangular", "minY": -32, "maxY": 32, "peakY": 0, "weight": 0.5 },
119
+ { "type": "uniform", "minY": -64, "maxY": 64, "peakY": null, "weight": 0.5 }
120
+ ],
121
+ "biomeModifiers": {},
122
+ "specialRules": {}
123
+ },
124
+ "legacy": {
125
+ "minY": 0, "maxY": 30, "optimalY": 15, "practicalY": null, "actualPeakY": null,
126
+ "frequency": "uncommon", "dimension": "overworld",
127
+ "distributions": [
128
+ { "type": "triangular", "minY": 0, "maxY": 30, "peakY": 15, "weight": 1.0 }
129
+ ],
130
+ "biomeModifiers": {},
131
+ "specialRules": {}
132
+ }
133
+ },
134
+ "redstone": {
135
+ "modern": {
136
+ "minY": -64, "maxY": 16, "optimalY": -59, "practicalY": -53, "actualPeakY": null,
137
+ "frequency": "common", "dimension": "overworld",
138
+ "distributions": [
139
+ { "type": "uniform", "minY": -64, "maxY": 16, "peakY": null, "weight": 0.5 },
140
+ { "type": "triangular", "minY": -64, "maxY": -32, "peakY": -59, "weight": 0.5 }
141
+ ],
142
+ "biomeModifiers": {},
143
+ "specialRules": {}
144
+ },
145
+ "legacy": {
146
+ "minY": 0, "maxY": 15, "optimalY": 8, "practicalY": null, "actualPeakY": null,
147
+ "frequency": "common", "dimension": "overworld",
148
+ "distributions": [
149
+ { "type": "uniform", "minY": 0, "maxY": 15, "peakY": null, "weight": 1.0 }
150
+ ],
151
+ "biomeModifiers": {},
152
+ "specialRules": {}
153
+ }
154
+ },
155
+ "emerald": {
156
+ "modern": {
157
+ "minY": -16, "maxY": 320, "optimalY": 232, "practicalY": null, "actualPeakY": 90,
158
+ "frequency": "very_rare", "dimension": "overworld",
159
+ "distributions": [
160
+ { "type": "triangular", "minY": -16, "maxY": 320, "peakY": 232, "weight": 1.0 }
161
+ ],
162
+ "biomeModifiers": {
163
+ "stony_peaks": 1.0, "jagged_peaks": 1.0, "frozen_peaks": 1.0, "meadow": 1.0,
164
+ "grove": 1.0, "cherry_grove": 1.0, "snowy_slopes": 1.0, "windswept_hills": 1.0,
165
+ "windswept_forest": 1.0, "windswept_gravelly_hills": 1.0
166
+ },
167
+ "specialRules": { "biomeExclusive": true }
168
+ },
169
+ "legacy": {
170
+ "minY": 4, "maxY": 32, "optimalY": 18, "practicalY": null, "actualPeakY": null,
171
+ "frequency": "very_rare", "dimension": "overworld",
172
+ "distributions": [
173
+ { "type": "uniform", "minY": 4, "maxY": 32, "peakY": null, "weight": 1.0 }
174
+ ],
175
+ "biomeModifiers": { "extreme_hills": 1.0 },
176
+ "specialRules": { "biomeExclusive": true }
177
+ }
178
+ },
179
+ "ancient_debris": {
180
+ "modern": {
181
+ "minY": 8, "maxY": 119, "optimalY": 15, "practicalY": 14, "actualPeakY": null,
182
+ "frequency": "very_rare", "dimension": "nether",
183
+ "distributions": [
184
+ { "type": "triangular", "minY": 8, "maxY": 24, "peakY": 16, "weight": 0.45 },
185
+ { "type": "uniform", "minY": 8, "maxY": 119, "peakY": null, "weight": 0.55 }
186
+ ],
187
+ "biomeModifiers": {
188
+ "nether_wastes": 1.0, "crimson_forest": 1.02, "warped_forest": 1.02,
189
+ "soul_sand_valley": 1.0, "basalt_deltas": 0.94
190
+ },
191
+ "specialRules": { "minMcMinor": 16 }
192
+ },
193
+ "legacy": {
194
+ "minY": 8, "maxY": 119, "optimalY": 15, "practicalY": 14, "actualPeakY": null,
195
+ "frequency": "very_rare", "dimension": "nether",
196
+ "distributions": [
197
+ { "type": "triangular", "minY": 8, "maxY": 24, "peakY": 16, "weight": 0.45 },
198
+ { "type": "uniform", "minY": 8, "maxY": 119, "peakY": null, "weight": 0.55 }
199
+ ],
200
+ "biomeModifiers": {
201
+ "nether_wastes": 1.0, "crimson_forest": 1.02, "warped_forest": 1.02,
202
+ "soul_sand_valley": 1.0, "basalt_deltas": 0.94
203
+ },
204
+ "specialRules": { "minMcMinor": 16 }
205
+ }
206
+ },
207
+ "nether_gold": {
208
+ "modern": {
209
+ "minY": 10, "maxY": 114, "optimalY": 48, "practicalY": 45, "actualPeakY": null,
210
+ "frequency": "uncommon", "dimension": "nether",
211
+ "distributions": [
212
+ { "type": "triangular", "minY": 20, "maxY": 96, "peakY": 48, "weight": 0.55 },
213
+ { "type": "uniform", "minY": 10, "maxY": 114, "peakY": null, "weight": 0.45 }
214
+ ],
215
+ "biomeModifiers": {
216
+ "nether_wastes": 1.0, "crimson_forest": 1.05, "warped_forest": 1.05,
217
+ "soul_sand_valley": 0.98, "basalt_deltas": 1.04
218
+ },
219
+ "specialRules": { "minMcMinor": 16 }
220
+ },
221
+ "legacy": {
222
+ "minY": 10, "maxY": 114, "optimalY": 48, "practicalY": 45, "actualPeakY": null,
223
+ "frequency": "uncommon", "dimension": "nether",
224
+ "distributions": [
225
+ { "type": "triangular", "minY": 20, "maxY": 96, "peakY": 48, "weight": 0.55 },
226
+ { "type": "uniform", "minY": 10, "maxY": 114, "peakY": null, "weight": 0.45 }
227
+ ],
228
+ "biomeModifiers": {
229
+ "nether_wastes": 1.0, "crimson_forest": 1.05, "warped_forest": 1.05,
230
+ "soul_sand_valley": 0.98, "basalt_deltas": 1.04
231
+ },
232
+ "specialRules": { "minMcMinor": 16 }
233
+ }
234
+ },
235
+ "nether_quartz": {
236
+ "modern": {
237
+ "minY": 10, "maxY": 114, "optimalY": 42, "practicalY": null, "actualPeakY": null,
238
+ "frequency": "common", "dimension": "nether",
239
+ "distributions": [
240
+ { "type": "triangular", "minY": 10, "maxY": 114, "peakY": 42, "weight": 1.0 }
241
+ ],
242
+ "biomeModifiers": {
243
+ "nether_wastes": 1.0, "crimson_forest": 1.0, "warped_forest": 1.0,
244
+ "soul_sand_valley": 1.0, "basalt_deltas": 1.0
245
+ },
246
+ "specialRules": { "minMcMinor": 8 }
247
+ },
248
+ "legacy": {
249
+ "minY": 10, "maxY": 114, "optimalY": 42, "practicalY": null, "actualPeakY": null,
250
+ "frequency": "common", "dimension": "nether",
251
+ "distributions": [
252
+ { "type": "triangular", "minY": 10, "maxY": 114, "peakY": 42, "weight": 1.0 }
253
+ ],
254
+ "biomeModifiers": {
255
+ "nether_wastes": 1.0, "crimson_forest": 1.0, "warped_forest": 1.0,
256
+ "soul_sand_valley": 1.0, "basalt_deltas": 1.0
257
+ },
258
+ "specialRules": { "minMcMinor": 8 }
259
+ }
260
+ },
261
+ "gilded_blackstone": {
262
+ "modern": {
263
+ "minY": 62, "maxY": 88, "optimalY": 75, "practicalY": null, "actualPeakY": null,
264
+ "frequency": "rare", "dimension": "nether",
265
+ "distributions": [
266
+ { "type": "uniform", "minY": 62, "maxY": 88, "peakY": null, "weight": 1.0 }
267
+ ],
268
+ "biomeModifiers": {
269
+ "nether_wastes": 0.85, "crimson_forest": 0.9, "warped_forest": 0.9,
270
+ "soul_sand_valley": 0.95, "basalt_deltas": 1.1
271
+ },
272
+ "specialRules": { "minMcMinor": 16 }
273
+ },
274
+ "legacy": {
275
+ "minY": 62, "maxY": 88, "optimalY": 75, "practicalY": null, "actualPeakY": null,
276
+ "frequency": "rare", "dimension": "nether",
277
+ "distributions": [
278
+ { "type": "uniform", "minY": 62, "maxY": 88, "peakY": null, "weight": 1.0 }
279
+ ],
280
+ "biomeModifiers": {
281
+ "nether_wastes": 0.85, "crimson_forest": 0.9, "warped_forest": 0.9,
282
+ "soul_sand_valley": 0.95, "basalt_deltas": 1.1
283
+ },
284
+ "specialRules": { "minMcMinor": 16 }
285
+ }
286
+ },
287
+ "blackstone": {
288
+ "modern": {
289
+ "minY": 58, "maxY": 92, "optimalY": 74, "practicalY": null, "actualPeakY": null,
290
+ "frequency": "uncommon", "dimension": "nether",
291
+ "distributions": [
292
+ { "type": "uniform", "minY": 58, "maxY": 92, "peakY": null, "weight": 0.55 },
293
+ { "type": "triangular", "minY": 62, "maxY": 85, "peakY": 74, "weight": 0.45 }
294
+ ],
295
+ "biomeModifiers": {
296
+ "nether_wastes": 0.88, "crimson_forest": 0.92, "warped_forest": 0.92,
297
+ "soul_sand_valley": 0.96, "basalt_deltas": 1.12
298
+ },
299
+ "specialRules": { "minMcMinor": 16 }
300
+ },
301
+ "legacy": {
302
+ "minY": 58, "maxY": 92, "optimalY": 74, "practicalY": null, "actualPeakY": null,
303
+ "frequency": "uncommon", "dimension": "nether",
304
+ "distributions": [
305
+ { "type": "uniform", "minY": 58, "maxY": 92, "peakY": null, "weight": 0.55 },
306
+ { "type": "triangular", "minY": 62, "maxY": 85, "peakY": 74, "weight": 0.45 }
307
+ ],
308
+ "biomeModifiers": {
309
+ "nether_wastes": 0.88, "crimson_forest": 0.92, "warped_forest": 0.92,
310
+ "soul_sand_valley": 0.96, "basalt_deltas": 1.12
311
+ },
312
+ "specialRules": { "minMcMinor": 16 }
313
+ }
314
+ }
315
+ }
316
+ }
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'data'
4
+ require_relative 'rng'
5
+
6
+ # Distribution + confidence model. See ../../../SPEC.md sections 4-5.
7
+ module OrefinderEstimate
8
+ module Probability
9
+ module_function
10
+
11
+ def clamp(n, lo, hi)
12
+ [[n, lo].max, hi].min
13
+ end
14
+
15
+ def triangular_probability(y, min_y, max_y, peak_y)
16
+ return 0.0 if y < min_y || y > max_y
17
+
18
+ range = max_y - min_y
19
+ return 0.0 if range <= 0
20
+
21
+ peak = [[peak_y, min_y].max, max_y].min
22
+ peak_offset = peak - min_y
23
+ fall_len = max_y - peak
24
+ return 0.0 if peak_offset <= 0 && fall_len <= 0
25
+
26
+ if y <= peak
27
+ return (y == min_y ? 1.0 : 0.0) if peak_offset <= 0
28
+
29
+ return (y - min_y).to_f / peak_offset
30
+ end
31
+ return 0.0 if fall_len <= 0
32
+
33
+ (max_y - y).to_f / fall_len
34
+ end
35
+
36
+ def uniform_probability(y, min_y, max_y)
37
+ y >= min_y && y <= max_y ? 1.0 : 0.0
38
+ end
39
+
40
+ def normalize_biome(cfg, biome)
41
+ dimension = cfg['dimension'] || 'overworld'
42
+ b = biome.to_s.strip.downcase.gsub(/\s+/, '_')
43
+ if dimension == 'nether'
44
+ return Data::NETHER_BIOMES.include?(b) ? b : 'nether_wastes'
45
+ end
46
+ return 'plains' if Data::NETHER_BIOMES.include?(b)
47
+
48
+ b
49
+ end
50
+
51
+ def biome_multiplier(cfg, normalized_biome)
52
+ m = cfg['biomeModifiers'][normalized_biome]
53
+ return m unless m.nil?
54
+ return 0.12 if cfg['specialRules'] && cfg['specialRules']['biomeExclusive']
55
+
56
+ 1.0
57
+ end
58
+
59
+ def calculate_ore_probability(cfg, y, raw_biome)
60
+ nb = normalize_biome(cfg, raw_biome)
61
+ total = 0.0
62
+ cfg['distributions'].each do |dist|
63
+ prob = if dist['type'] == 'triangular' && !dist['peakY'].nil?
64
+ triangular_probability(y, dist['minY'], dist['maxY'], dist['peakY'])
65
+ elsif dist['type'] == 'uniform'
66
+ uniform_probability(y, dist['minY'], dist['maxY'])
67
+ else
68
+ 0.0
69
+ end
70
+ total += prob * dist['weight']
71
+ end
72
+ total *= biome_multiplier(cfg, nb)
73
+ [total, 1.0].min
74
+ end
75
+
76
+ def optimal_y_for_mining(cfg)
77
+ return cfg['practicalY'] unless cfg['practicalY'].nil?
78
+ return cfg['actualPeakY'] unless cfg['actualPeakY'].nil?
79
+
80
+ cfg['optimalY']
81
+ end
82
+
83
+ def calculate_confidence(probability, distance, y_level, optimal_y)
84
+ confidence = probability * 100
85
+ confidence -= [(y_level - optimal_y).abs * 2, 30].min
86
+ confidence -= [distance / 100.0, 20].min
87
+ clamp(Rng.round_to_tenth(confidence), 0, 100)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Deterministic RNG shared by every Ore Finder Estimate port. See ../../../SPEC.md section 2.
4
+ module OrefinderEstimate
5
+ module Rng
6
+ MASK = 0xFFFFFFFF
7
+
8
+ module_function
9
+
10
+ # floor(x + 0.5); matches JS Math.round (not banker's rounding).
11
+ def round_half_up(x)
12
+ (x + 0.5).floor
13
+ end
14
+
15
+ def round_to_tenth(x)
16
+ round_half_up(x * 10) / 10.0
17
+ end
18
+
19
+ def imul(a, b)
20
+ ((a & MASK) * (b & MASK)) & MASK
21
+ end
22
+
23
+ # Returns a lambda producing doubles in [0, 1).
24
+ def mulberry32(seed)
25
+ state = seed & MASK
26
+ lambda do
27
+ state = (state + 0x6D2B79F5) & MASK
28
+ t = state
29
+ t = imul(t ^ (t >> 15), t | 1)
30
+ t = (t ^ ((t + imul(t ^ (t >> 7), t | 61)) & MASK)) & MASK
31
+ (t ^ (t >> 14)) / 4_294_967_296.0
32
+ end
33
+ end
34
+
35
+ # FNV-1a hash of parts.join('|') -> mulberry32 generator (ASCII inputs).
36
+ def make_rng(parts)
37
+ h = 2_166_136_261
38
+ s = parts.join('|')
39
+ s.each_char do |ch|
40
+ h ^= ch.ord
41
+ h = imul(h, 16_777_619)
42
+ end
43
+ mulberry32(h & MASK)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Minecraft version string -> era (modern >= 1.18, else legacy). See ../../../SPEC.md section 3.
4
+ module OrefinderEstimate
5
+ module Version
6
+ UNDERSCORE = /\A(?:java|bedrock)_(\d+)_(\d+)(?:_(\d+))?\z/.freeze
7
+ DOTTED = /(\d+)\.(\d+)(?:\.(\d+))?/.freeze
8
+ LOOSE = /(\d{1,2})[^0-9]+(\d{1,2})/.freeze
9
+
10
+ module_function
11
+
12
+ def era(mc_minor)
13
+ mc_minor >= 18 ? 'modern' : 'legacy'
14
+ end
15
+
16
+ def parse_version(raw)
17
+ return nil unless raw.is_a?(String)
18
+
19
+ trimmed = raw.strip
20
+ return nil if trimmed.empty?
21
+
22
+ s = trimmed.downcase
23
+ edition = s.include?('bedrock') ? 'bedrock' : 'java'
24
+
25
+ if (m = UNDERSCORE.match(s))
26
+ mc_minor = m[2].to_i
27
+ patch = m[3] ? m[3].to_i : 0
28
+ return { 'edition' => edition, 'era' => era(mc_minor), 'mcMinor' => mc_minor, 'patch' => patch, 'raw' => trimmed }
29
+ end
30
+ if (m = DOTTED.match(s))
31
+ mc_minor = m[2].to_i
32
+ patch = m[3] ? m[3].to_i : 0
33
+ return { 'edition' => edition, 'era' => era(mc_minor), 'mcMinor' => mc_minor, 'patch' => patch, 'raw' => trimmed }
34
+ end
35
+ if (m = LOOSE.match(s))
36
+ mc_minor = m[2].to_i
37
+ return { 'edition' => edition, 'era' => era(mc_minor), 'mcMinor' => mc_minor, 'patch' => 0, 'raw' => trimmed }
38
+ end
39
+
40
+ nil
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ore Finder Estimate - offline, approximate Minecraft ore-distribution estimator.
4
+ # Approximate only; for seed-accurate results use https://orefinder.io
5
+ require_relative 'orefinder_estimate/version'
6
+ require_relative 'orefinder_estimate/data'
7
+ require_relative 'orefinder_estimate/probability'
8
+ require_relative 'orefinder_estimate/cluster'
9
+ require_relative 'orefinder_estimate/estimate'
10
+
11
+ module OrefinderEstimate
12
+ VERSION = '1.0.0'
13
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: orefinder-estimate
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ore Finder
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: A fast, dependency-free, offline estimator for the best mining Y and
13
+ the areas where ores are most likely to concentrate across Java and Bedrock. Pairs
14
+ with orefinder.io for seed-exact coordinates.
15
+ email:
16
+ - hello@orefinder.io
17
+ executables:
18
+ - orefinder-estimate
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - DISCLAIMER.md
23
+ - LICENSE
24
+ - README.md
25
+ - exe/orefinder-estimate
26
+ - lib/orefinder_estimate.rb
27
+ - lib/orefinder_estimate/cluster.rb
28
+ - lib/orefinder_estimate/data.rb
29
+ - lib/orefinder_estimate/estimate.rb
30
+ - lib/orefinder_estimate/ore-data.json
31
+ - lib/orefinder_estimate/probability.rb
32
+ - lib/orefinder_estimate/rng.rb
33
+ - lib/orefinder_estimate/version.rb
34
+ homepage: https://orefinder.io
35
+ licenses:
36
+ - MIT
37
+ metadata:
38
+ homepage_uri: https://orefinder.io
39
+ source_code_uri: https://github.com/nazzal5448/ore-finder-pkgs
40
+ rubygems_mfa_required: 'true'
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: 2.7.0
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubygems_version: 3.6.7
56
+ specification_version: 4
57
+ summary: Fast offline Minecraft ore estimator (best mining Y and likely ore areas).
58
+ test_files: []