haxball-core 1.0.0__tar.gz

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.
@@ -0,0 +1,5 @@
1
+ /target
2
+ /.venv
3
+ __pycache__/
4
+ *.so
5
+ Cargo.lock
@@ -0,0 +1,391 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "autocfg"
7
+ version = "1.5.1"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
10
+
11
+ [[package]]
12
+ name = "cfg-if"
13
+ version = "1.0.4"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
16
+
17
+ [[package]]
18
+ name = "crossbeam-deque"
19
+ version = "0.8.6"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
22
+ dependencies = [
23
+ "crossbeam-epoch",
24
+ "crossbeam-utils",
25
+ ]
26
+
27
+ [[package]]
28
+ name = "crossbeam-epoch"
29
+ version = "0.9.18"
30
+ source = "registry+https://github.com/rust-lang/crates.io-index"
31
+ checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
32
+ dependencies = [
33
+ "crossbeam-utils",
34
+ ]
35
+
36
+ [[package]]
37
+ name = "crossbeam-utils"
38
+ version = "0.8.21"
39
+ source = "registry+https://github.com/rust-lang/crates.io-index"
40
+ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
41
+
42
+ [[package]]
43
+ name = "either"
44
+ version = "1.16.0"
45
+ source = "registry+https://github.com/rust-lang/crates.io-index"
46
+ checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
47
+
48
+ [[package]]
49
+ name = "haxball_core"
50
+ version = "1.0.0"
51
+ dependencies = [
52
+ "libm",
53
+ "numpy",
54
+ "pyo3",
55
+ "rayon",
56
+ "serde",
57
+ "serde_json",
58
+ ]
59
+
60
+ [[package]]
61
+ name = "heck"
62
+ version = "0.5.0"
63
+ source = "registry+https://github.com/rust-lang/crates.io-index"
64
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
65
+
66
+ [[package]]
67
+ name = "indoc"
68
+ version = "2.0.7"
69
+ source = "registry+https://github.com/rust-lang/crates.io-index"
70
+ checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
71
+ dependencies = [
72
+ "rustversion",
73
+ ]
74
+
75
+ [[package]]
76
+ name = "itoa"
77
+ version = "1.0.18"
78
+ source = "registry+https://github.com/rust-lang/crates.io-index"
79
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
80
+
81
+ [[package]]
82
+ name = "libc"
83
+ version = "0.2.186"
84
+ source = "registry+https://github.com/rust-lang/crates.io-index"
85
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
86
+
87
+ [[package]]
88
+ name = "libm"
89
+ version = "0.2.16"
90
+ source = "registry+https://github.com/rust-lang/crates.io-index"
91
+ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
92
+
93
+ [[package]]
94
+ name = "matrixmultiply"
95
+ version = "0.3.10"
96
+ source = "registry+https://github.com/rust-lang/crates.io-index"
97
+ checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
98
+ dependencies = [
99
+ "autocfg",
100
+ "rawpointer",
101
+ ]
102
+
103
+ [[package]]
104
+ name = "memchr"
105
+ version = "2.8.2"
106
+ source = "registry+https://github.com/rust-lang/crates.io-index"
107
+ checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
108
+
109
+ [[package]]
110
+ name = "memoffset"
111
+ version = "0.9.1"
112
+ source = "registry+https://github.com/rust-lang/crates.io-index"
113
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
114
+ dependencies = [
115
+ "autocfg",
116
+ ]
117
+
118
+ [[package]]
119
+ name = "ndarray"
120
+ version = "0.16.1"
121
+ source = "registry+https://github.com/rust-lang/crates.io-index"
122
+ checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
123
+ dependencies = [
124
+ "matrixmultiply",
125
+ "num-complex",
126
+ "num-integer",
127
+ "num-traits",
128
+ "portable-atomic",
129
+ "portable-atomic-util",
130
+ "rawpointer",
131
+ ]
132
+
133
+ [[package]]
134
+ name = "num-complex"
135
+ version = "0.4.6"
136
+ source = "registry+https://github.com/rust-lang/crates.io-index"
137
+ checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
138
+ dependencies = [
139
+ "num-traits",
140
+ ]
141
+
142
+ [[package]]
143
+ name = "num-integer"
144
+ version = "0.1.46"
145
+ source = "registry+https://github.com/rust-lang/crates.io-index"
146
+ checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
147
+ dependencies = [
148
+ "num-traits",
149
+ ]
150
+
151
+ [[package]]
152
+ name = "num-traits"
153
+ version = "0.2.19"
154
+ source = "registry+https://github.com/rust-lang/crates.io-index"
155
+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
156
+ dependencies = [
157
+ "autocfg",
158
+ ]
159
+
160
+ [[package]]
161
+ name = "numpy"
162
+ version = "0.22.1"
163
+ source = "registry+https://github.com/rust-lang/crates.io-index"
164
+ checksum = "edb929bc0da91a4d85ed6c0a84deaa53d411abfb387fc271124f91bf6b89f14e"
165
+ dependencies = [
166
+ "libc",
167
+ "ndarray",
168
+ "num-complex",
169
+ "num-integer",
170
+ "num-traits",
171
+ "pyo3",
172
+ "rustc-hash",
173
+ ]
174
+
175
+ [[package]]
176
+ name = "once_cell"
177
+ version = "1.21.4"
178
+ source = "registry+https://github.com/rust-lang/crates.io-index"
179
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
180
+
181
+ [[package]]
182
+ name = "portable-atomic"
183
+ version = "1.13.1"
184
+ source = "registry+https://github.com/rust-lang/crates.io-index"
185
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
186
+
187
+ [[package]]
188
+ name = "portable-atomic-util"
189
+ version = "0.2.7"
190
+ source = "registry+https://github.com/rust-lang/crates.io-index"
191
+ checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
192
+ dependencies = [
193
+ "portable-atomic",
194
+ ]
195
+
196
+ [[package]]
197
+ name = "proc-macro2"
198
+ version = "1.0.106"
199
+ source = "registry+https://github.com/rust-lang/crates.io-index"
200
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
201
+ dependencies = [
202
+ "unicode-ident",
203
+ ]
204
+
205
+ [[package]]
206
+ name = "pyo3"
207
+ version = "0.22.6"
208
+ source = "registry+https://github.com/rust-lang/crates.io-index"
209
+ checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884"
210
+ dependencies = [
211
+ "cfg-if",
212
+ "indoc",
213
+ "libc",
214
+ "memoffset",
215
+ "once_cell",
216
+ "portable-atomic",
217
+ "pyo3-build-config",
218
+ "pyo3-ffi",
219
+ "pyo3-macros",
220
+ "unindent",
221
+ ]
222
+
223
+ [[package]]
224
+ name = "pyo3-build-config"
225
+ version = "0.22.6"
226
+ source = "registry+https://github.com/rust-lang/crates.io-index"
227
+ checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38"
228
+ dependencies = [
229
+ "once_cell",
230
+ "target-lexicon",
231
+ ]
232
+
233
+ [[package]]
234
+ name = "pyo3-ffi"
235
+ version = "0.22.6"
236
+ source = "registry+https://github.com/rust-lang/crates.io-index"
237
+ checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636"
238
+ dependencies = [
239
+ "libc",
240
+ "pyo3-build-config",
241
+ ]
242
+
243
+ [[package]]
244
+ name = "pyo3-macros"
245
+ version = "0.22.6"
246
+ source = "registry+https://github.com/rust-lang/crates.io-index"
247
+ checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453"
248
+ dependencies = [
249
+ "proc-macro2",
250
+ "pyo3-macros-backend",
251
+ "quote",
252
+ "syn",
253
+ ]
254
+
255
+ [[package]]
256
+ name = "pyo3-macros-backend"
257
+ version = "0.22.6"
258
+ source = "registry+https://github.com/rust-lang/crates.io-index"
259
+ checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe"
260
+ dependencies = [
261
+ "heck",
262
+ "proc-macro2",
263
+ "pyo3-build-config",
264
+ "quote",
265
+ "syn",
266
+ ]
267
+
268
+ [[package]]
269
+ name = "quote"
270
+ version = "1.0.45"
271
+ source = "registry+https://github.com/rust-lang/crates.io-index"
272
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
273
+ dependencies = [
274
+ "proc-macro2",
275
+ ]
276
+
277
+ [[package]]
278
+ name = "rawpointer"
279
+ version = "0.2.1"
280
+ source = "registry+https://github.com/rust-lang/crates.io-index"
281
+ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
282
+
283
+ [[package]]
284
+ name = "rayon"
285
+ version = "1.12.0"
286
+ source = "registry+https://github.com/rust-lang/crates.io-index"
287
+ checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
288
+ dependencies = [
289
+ "either",
290
+ "rayon-core",
291
+ ]
292
+
293
+ [[package]]
294
+ name = "rayon-core"
295
+ version = "1.13.0"
296
+ source = "registry+https://github.com/rust-lang/crates.io-index"
297
+ checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
298
+ dependencies = [
299
+ "crossbeam-deque",
300
+ "crossbeam-utils",
301
+ ]
302
+
303
+ [[package]]
304
+ name = "rustc-hash"
305
+ version = "1.1.0"
306
+ source = "registry+https://github.com/rust-lang/crates.io-index"
307
+ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
308
+
309
+ [[package]]
310
+ name = "rustversion"
311
+ version = "1.0.22"
312
+ source = "registry+https://github.com/rust-lang/crates.io-index"
313
+ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
314
+
315
+ [[package]]
316
+ name = "serde"
317
+ version = "1.0.228"
318
+ source = "registry+https://github.com/rust-lang/crates.io-index"
319
+ checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
320
+ dependencies = [
321
+ "serde_core",
322
+ "serde_derive",
323
+ ]
324
+
325
+ [[package]]
326
+ name = "serde_core"
327
+ version = "1.0.228"
328
+ source = "registry+https://github.com/rust-lang/crates.io-index"
329
+ checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
330
+ dependencies = [
331
+ "serde_derive",
332
+ ]
333
+
334
+ [[package]]
335
+ name = "serde_derive"
336
+ version = "1.0.228"
337
+ source = "registry+https://github.com/rust-lang/crates.io-index"
338
+ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
339
+ dependencies = [
340
+ "proc-macro2",
341
+ "quote",
342
+ "syn",
343
+ ]
344
+
345
+ [[package]]
346
+ name = "serde_json"
347
+ version = "1.0.150"
348
+ source = "registry+https://github.com/rust-lang/crates.io-index"
349
+ checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
350
+ dependencies = [
351
+ "itoa",
352
+ "memchr",
353
+ "serde",
354
+ "serde_core",
355
+ "zmij",
356
+ ]
357
+
358
+ [[package]]
359
+ name = "syn"
360
+ version = "2.0.118"
361
+ source = "registry+https://github.com/rust-lang/crates.io-index"
362
+ checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
363
+ dependencies = [
364
+ "proc-macro2",
365
+ "quote",
366
+ "unicode-ident",
367
+ ]
368
+
369
+ [[package]]
370
+ name = "target-lexicon"
371
+ version = "0.12.16"
372
+ source = "registry+https://github.com/rust-lang/crates.io-index"
373
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
374
+
375
+ [[package]]
376
+ name = "unicode-ident"
377
+ version = "1.0.24"
378
+ source = "registry+https://github.com/rust-lang/crates.io-index"
379
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
380
+
381
+ [[package]]
382
+ name = "unindent"
383
+ version = "0.2.4"
384
+ source = "registry+https://github.com/rust-lang/crates.io-index"
385
+ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
386
+
387
+ [[package]]
388
+ name = "zmij"
389
+ version = "1.0.21"
390
+ source = "registry+https://github.com/rust-lang/crates.io-index"
391
+ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
@@ -0,0 +1,29 @@
1
+ [package]
2
+ name = "haxball_core"
3
+ version = "1.0.0"
4
+ edition = "2021"
5
+ description = "Headless, vectorized Haxball physics core (RocketSim-equivalent for Haxball)."
6
+ license = "MIT"
7
+ repository = "https://github.com/HaxballGym/HaxballGym"
8
+ readme = "README.md"
9
+ keywords = ["haxball", "reinforcement-learning", "physics", "simulation", "rl"]
10
+ categories = ["science", "simulation", "game-development"]
11
+
12
+ [lib]
13
+ name = "haxball_core"
14
+ # cdylib -> Python extension module via PyO3 / maturin
15
+ # rlib -> so we can add `cargo bench` / pure-Rust tests later
16
+ crate-type = ["cdylib", "rlib"]
17
+
18
+ [dependencies]
19
+ pyo3 = { version = "0.22", features = ["extension-module"] }
20
+ numpy = "0.22"
21
+ rayon = "1.10"
22
+ serde = { version = "1", features = ["derive"] }
23
+ serde_json = "1"
24
+ libm = "0.2.16"
25
+
26
+ [profile.release]
27
+ opt-level = 3
28
+ lto = "thin"
29
+ codegen-units = 1
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wazarr
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.
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: haxball_core
3
+ Version: 1.0.0
4
+ Classifier: Development Status :: 5 - Production/Stable
5
+ Classifier: Intended Audience :: Science/Research
6
+ Classifier: Programming Language :: Python :: 3.12
7
+ Classifier: Programming Language :: Rust
8
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
9
+ Classifier: Topic :: Games/Entertainment :: Simulation
10
+ Requires-Dist: numpy>=1.23
11
+ License-File: LICENSE
12
+ Summary: Headless, vectorized Haxball physics core (RocketSim-equivalent for Haxball).
13
+ Keywords: haxball,reinforcement-learning,physics,simulation,rl
14
+ Author-email: Wazarr <jeje_04@live.com>
15
+ License-Expression: MIT
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
18
+ Project-URL: Homepage, https://github.com/HaxballGym/HaxballGym
19
+ Project-URL: Issues, https://github.com/HaxballGym/HaxballGym/issues
20
+ Project-URL: Repository, https://github.com/HaxballGym/HaxballGym
21
+
22
+ # haxball_core — headless, vectorized Haxball physics
23
+
24
+ This is the **RocketSim-equivalent for Haxball**: a standalone physics core with
25
+ **no game engine and no rendering**, designed to run thousands of matches in
26
+ parallel for reinforcement learning.
27
+
28
+ It replaces the Ursina/Panda3D-based simulation in `Ursinaxball`, which made every
29
+ disc a 3D-engine `Entity` and capped throughput at low-thousands of steps/sec for
30
+ a *single* game. The physics math is identical — this is a faithful port of
31
+ Ursinaxball's pure-numpy `fn_base.py`, verified bit-for-bit (see Fidelity below).
32
+
33
+ ## Why this design
34
+
35
+ RLGym is "a million times faster" than a naive Python clone for one reason: the
36
+ physics lives in a headless native core (**RocketSim**, C++) behind a single
37
+ `TransitionEngine` interface, and is stepped in **batches** so the Python↔native
38
+ boundary is crossed once per *batch of envs*, not once per *game step*.
39
+
40
+ `haxball_core` copies that win:
41
+
42
+ - **Rust core, f64 math** — same numbers as the original numpy, native speed.
43
+ - **Batched `VecEnv`** — N matches stepped in parallel with `rayon`, GIL released.
44
+ One `step(actions)` call advances all N envs.
45
+ - **One source of truth** — the same crate compiles to a Python extension (for
46
+ training) and, next, to **WASM** (so a human can play the bot in-browser —
47
+ Haxball is a browser game, so the renderer and the human-play client live there).
48
+
49
+ ## Measured (Apple Silicon, single process)
50
+
51
+ | Mode | 1v1 | 3v3 |
52
+ |---|---|---|
53
+ | Raw sim (no Python in loop) | ~50M env-steps/s | ~18M env-steps/s |
54
+ | Full `env.step` (obs + reward + done numpy I/O) | ~12.5M env-steps/s | ~6.5M env-steps/s |
55
+
56
+ Ursinaxball reference: a single game at low-thousands of steps/sec → this is a
57
+ **~1,000–10,000× speedup**, and it scales to 2v2 / 3v3 (the regime that was out of
58
+ reach before).
59
+
60
+ ## Fidelity
61
+
62
+ `tests/test_fidelity.py` imports Ursinaxball's `fn_base.py` directly (it has no
63
+ ursina import) and feeds 25,000 random inputs through both implementations:
64
+
65
+ ```
66
+ disc_disc OK
67
+ disc_vertex OK
68
+ disc_plane OK
69
+ segment_no_curve OK
70
+ segment_curve OK
71
+ ALL FIDELITY TESTS PASSED — Rust port matches fn_base.py to 1e-9
72
+ ```
73
+
74
+ ## Build & run
75
+
76
+ This crate is the `haxball_core` member of the repo's uv workspace. From the repo
77
+ root:
78
+
79
+ ```bash
80
+ uv sync # builds this crate (maturin) + deps
81
+ uv run rust/haxball_core/tests/test_fidelity.py # fidelity (1e-9)
82
+ uv run rust/haxball_core/bench.py # throughput
83
+ ```
84
+
85
+ Iterating on the Rust? `uv run maturin develop --release` rebuilds in place faster
86
+ than a full `uv sync`.
87
+
88
+ ## Status / what's ported
89
+
90
+ - [x] All 5 collision resolvers (disc-disc, disc-vertex, disc-plane, segment
91
+ straight + curved, segment bias) — **verified vs fn_base.py**
92
+ - [x] Integration (`update_discs`), player movement + kick (`resolve_movement`),
93
+ goal detection (`check_goal`), spawn/reset.
94
+ - [x] Classic stadium (planes + straight ball-area segments + goalposts + goals).
95
+ - [x] Batched `VecEnv` with rayon, obs/reward/done numpy I/O.
96
+ - [ ] Curved goal-net arcs + kickoff-barrier semicircle (math is ported and
97
+ tested; not yet wired into the classic `World` — minor, affects only
98
+ in-net ball behavior).
99
+ - [ ] `.hbs` stadium loader (currently the classic stadium is built in code).
100
+ - [ ] WASM target for in-browser play.
101
+
102
+ ## Next (the RLGym playbook, in order)
103
+
104
+ 1. Wrap `VecEnv` in an RLGym-v2-style API: `TransitionEngine` (this core) +
105
+ `StateMutator` / `ObsBuilder` / `ActionParser` / `RewardFunction` /
106
+ `DoneCondition` — composable, the env design HaxballGym already half-has.
107
+ 2. PettingZoo-parallel multi-agent + a single shared, permutation-invariant
108
+ policy → 1v1/2v2/3v3 from one network.
109
+ 3. Train with PufferLib (highest SPS) or CleanRL PPO, self-play vs a past-policy
110
+ pool.
111
+ 4. Compile to WASM; export the policy to ONNX; play it in the browser.
112
+
@@ -0,0 +1,90 @@
1
+ # haxball_core — headless, vectorized Haxball physics
2
+
3
+ This is the **RocketSim-equivalent for Haxball**: a standalone physics core with
4
+ **no game engine and no rendering**, designed to run thousands of matches in
5
+ parallel for reinforcement learning.
6
+
7
+ It replaces the Ursina/Panda3D-based simulation in `Ursinaxball`, which made every
8
+ disc a 3D-engine `Entity` and capped throughput at low-thousands of steps/sec for
9
+ a *single* game. The physics math is identical — this is a faithful port of
10
+ Ursinaxball's pure-numpy `fn_base.py`, verified bit-for-bit (see Fidelity below).
11
+
12
+ ## Why this design
13
+
14
+ RLGym is "a million times faster" than a naive Python clone for one reason: the
15
+ physics lives in a headless native core (**RocketSim**, C++) behind a single
16
+ `TransitionEngine` interface, and is stepped in **batches** so the Python↔native
17
+ boundary is crossed once per *batch of envs*, not once per *game step*.
18
+
19
+ `haxball_core` copies that win:
20
+
21
+ - **Rust core, f64 math** — same numbers as the original numpy, native speed.
22
+ - **Batched `VecEnv`** — N matches stepped in parallel with `rayon`, GIL released.
23
+ One `step(actions)` call advances all N envs.
24
+ - **One source of truth** — the same crate compiles to a Python extension (for
25
+ training) and, next, to **WASM** (so a human can play the bot in-browser —
26
+ Haxball is a browser game, so the renderer and the human-play client live there).
27
+
28
+ ## Measured (Apple Silicon, single process)
29
+
30
+ | Mode | 1v1 | 3v3 |
31
+ |---|---|---|
32
+ | Raw sim (no Python in loop) | ~50M env-steps/s | ~18M env-steps/s |
33
+ | Full `env.step` (obs + reward + done numpy I/O) | ~12.5M env-steps/s | ~6.5M env-steps/s |
34
+
35
+ Ursinaxball reference: a single game at low-thousands of steps/sec → this is a
36
+ **~1,000–10,000× speedup**, and it scales to 2v2 / 3v3 (the regime that was out of
37
+ reach before).
38
+
39
+ ## Fidelity
40
+
41
+ `tests/test_fidelity.py` imports Ursinaxball's `fn_base.py` directly (it has no
42
+ ursina import) and feeds 25,000 random inputs through both implementations:
43
+
44
+ ```
45
+ disc_disc OK
46
+ disc_vertex OK
47
+ disc_plane OK
48
+ segment_no_curve OK
49
+ segment_curve OK
50
+ ALL FIDELITY TESTS PASSED — Rust port matches fn_base.py to 1e-9
51
+ ```
52
+
53
+ ## Build & run
54
+
55
+ This crate is the `haxball_core` member of the repo's uv workspace. From the repo
56
+ root:
57
+
58
+ ```bash
59
+ uv sync # builds this crate (maturin) + deps
60
+ uv run rust/haxball_core/tests/test_fidelity.py # fidelity (1e-9)
61
+ uv run rust/haxball_core/bench.py # throughput
62
+ ```
63
+
64
+ Iterating on the Rust? `uv run maturin develop --release` rebuilds in place faster
65
+ than a full `uv sync`.
66
+
67
+ ## Status / what's ported
68
+
69
+ - [x] All 5 collision resolvers (disc-disc, disc-vertex, disc-plane, segment
70
+ straight + curved, segment bias) — **verified vs fn_base.py**
71
+ - [x] Integration (`update_discs`), player movement + kick (`resolve_movement`),
72
+ goal detection (`check_goal`), spawn/reset.
73
+ - [x] Classic stadium (planes + straight ball-area segments + goalposts + goals).
74
+ - [x] Batched `VecEnv` with rayon, obs/reward/done numpy I/O.
75
+ - [ ] Curved goal-net arcs + kickoff-barrier semicircle (math is ported and
76
+ tested; not yet wired into the classic `World` — minor, affects only
77
+ in-net ball behavior).
78
+ - [ ] `.hbs` stadium loader (currently the classic stadium is built in code).
79
+ - [ ] WASM target for in-browser play.
80
+
81
+ ## Next (the RLGym playbook, in order)
82
+
83
+ 1. Wrap `VecEnv` in an RLGym-v2-style API: `TransitionEngine` (this core) +
84
+ `StateMutator` / `ObsBuilder` / `ActionParser` / `RewardFunction` /
85
+ `DoneCondition` — composable, the env design HaxballGym already half-has.
86
+ 2. PettingZoo-parallel multi-agent + a single shared, permutation-invariant
87
+ policy → 1v1/2v2/3v3 from one network.
88
+ 3. Train with PufferLib (highest SPS) or CleanRL PPO, self-play vs a past-policy
89
+ pool.
90
+ 4. Compile to WASM; export the policy to ONNX; play it in the browser.