TiGa-RCE commited on
Commit
95a1fbc
·
verified ·
1 Parent(s): a3b395b

Pin simulator d8ab74c; explicit math profile and fail-closed CUDA worker

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +6 -0
  2. .gitattributes +4 -0
  3. Dockerfile +22 -3
  4. PACKAGE_MANIFEST.json +328 -0
  5. README.md +9 -5
  6. UPSTREAM.json +12 -0
  7. backend_policy.py +21 -0
  8. batch_runner.py +42 -0
  9. cloud-contract.json +1 -0
  10. cloud_core.py +5 -0
  11. demo.evolution +0 -0
  12. github-simulator/source/Cargo.lock +792 -0
  13. github-simulator/source/Cargo.toml +77 -0
  14. github-simulator/source/GODOT_LICENSE +43 -0
  15. github-simulator/source/LICENSE +21 -0
  16. github-simulator/source/README.md +183 -0
  17. github-simulator/source/assets/models/formula.json +73 -0
  18. github-simulator/source/assets/models/rally.json +74 -0
  19. github-simulator/source/assets/networks/formula.json +31 -0
  20. github-simulator/source/assets/networks/rally.json +31 -0
  21. github-simulator/source/assets/random_track_settings.json +7 -0
  22. github-simulator/source/assets/scenes/formula_template.json +111 -0
  23. github-simulator/source/assets/scenes/rally_template.json +112 -0
  24. github-simulator/source/assets/scenes/snowmobile_template.json +123 -0
  25. github-simulator/source/assets/scenes/truck_template.json +112 -0
  26. github-simulator/source/docs/cli.md +115 -0
  27. github-simulator/source/docs/design.md +59 -0
  28. github-simulator/source/docs/fidelity.md +37 -0
  29. github-simulator/source/docs/gpu.md +31 -0
  30. github-simulator/source/docs/math-profiles.html +415 -0
  31. github-simulator/source/docs/performance.md +72 -0
  32. github-simulator/source/docs/reviews/2026-10-05-native-server-review.md +51 -0
  33. github-simulator/source/docs/server.md +102 -0
  34. github-simulator/source/docs/superpowers/specs/2026-10-05-native-server-design.md +322 -0
  35. github-simulator/source/docs/wasm.md +27 -0
  36. github-simulator/source/gpu/README.md +105 -0
  37. github-simulator/source/gpu/benchmark.py +117 -0
  38. github-simulator/source/gpu/build-cuda.ps1 +55 -0
  39. github-simulator/source/gpu/build-cuda.sh +35 -0
  40. github-simulator/source/gpu/build.sh +21 -0
  41. github-simulator/source/gpu/gen_tables.py +74 -0
  42. github-simulator/source/gpu/profile.sh +225 -0
  43. github-simulator/source/gpu/release-targets +27 -0
  44. github-simulator/source/gpu/sim/altd_gpu.hip +1384 -0
  45. github-simulator/source/gpu/sim/compat.h +135 -0
  46. github-simulator/source/gpu/sim/double_tables.h +691 -0
  47. github-simulator/source/gpu/sim/engine_exceptions.h +9 -0
  48. github-simulator/source/gpu/sim/math.h +540 -0
  49. github-simulator/source/gpu/sim/rays.h +118 -0
  50. github-simulator/source/gpu/sim/sensors.h +98 -0
.dockerignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ __pycache__
2
+ **/__pycache__
3
+ *.pyc
4
+ **/target
5
+ .cache
6
+ runs
.gitattributes CHANGED
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ tracks/R06[[:space:]]Mix.track filter=lfs diff=lfs merge=lfs -text
37
+ tracks/R07[[:space:]]Mirage[[:space:]]Marina.track filter=lfs diff=lfs merge=lfs -text
38
+ tracks/R09[[:space:]]Eau[[:space:]]Noir.track filter=lfs diff=lfs merge=lfs -text
39
+ tracks/The[[:space:]]Champions[[:space:]]Trial.track filter=lfs diff=lfs merge=lfs -text
Dockerfile CHANGED
@@ -1,5 +1,24 @@
1
- FROM python:3.12-slim
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  WORKDIR /app
3
- COPY index.html .
 
 
 
 
 
 
4
  EXPOSE 7860
5
- CMD ["python", "-m", "http.server", "7860"]
 
1
+ FROM rust:1.93-bookworm AS build
2
+ WORKDIR /build
3
+ COPY github-simulator/source /build
4
+ ENV RUSTFLAGS="-C target-cpu=x86-64"
5
+ RUN cargo build --locked --release --no-default-features --features cli
6
+ FROM nvidia/cuda:13.0.0-devel-ubuntu24.04 AS cuda
7
+ RUN apt-get update && apt-get install -y --no-install-recommends python3 g++ && rm -rf /var/lib/apt/lists/*
8
+ WORKDIR /build
9
+ COPY github-simulator/source /build
10
+ ENV CUDA_ARCH=sm_75,sm_86
11
+ RUN bash gpu/build-cuda.sh
12
+ FROM nvidia/cuda:13.0.0-runtime-ubuntu24.04
13
+ RUN apt-get update && apt-get install -y --no-install-recommends python3 && rm -rf /var/lib/apt/lists/*
14
+ RUN useradd -m -o -u 1000 user
15
  WORKDIR /app
16
+ COPY --chown=user:user . /app
17
+ COPY --from=build --chown=user:user /build/target/release/altd-sim /app/github-simulator/source/target/release/altd-sim
18
+ COPY --from=cuda --chown=user:user /build/target/gpu/libaltd_gpu.so /app/libaltd_gpu.so
19
+ RUN chown user:user /app
20
+ USER user
21
+ ENV ALD_DASHBOARD_PORT=7860 PYTHONUNBUFFERED=1
22
+ ENV ALD_REQUIRE_GPU=1 ALTD_GPU_LIB=/app/libaltd_gpu.so
23
  EXPOSE 7860
24
+ CMD ["python3","server.py"]
PACKAGE_MANIFEST.json ADDED
@@ -0,0 +1,328 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ ".dockerignore": "640b480c91c572b6d77411d9165f786a095d04fe5bc5b74e983ab6831aa4ba23",
3
+ ".gitattributes": "478a5f208decd29019ad7be4fba9fdffe25a4a4c9c652032e98e4cf3cbd6ef97",
4
+ "Dockerfile": "c592ca9eb888525821563ddb2707ecba0eeea3b06adfbe1aaa5e811339d032a7",
5
+ "README.md": "e7ed341a32da89a48dd5194ede7d3066fea0700aab2752f1886f23b7970d6ca8",
6
+ "UPSTREAM.json": "73d973fe445d7b51caea4980f35902f393c84720c8a256fd11622f84bb843806",
7
+ "backend_policy.py": "3991fb88e25bebb9c36b49c0547af4e4630738c504e0c7b58a5917081deea5fe",
8
+ "batch_runner.py": "c14777da239116a8025f800d59b33b307bf62fcfb80e3bcb59fdf81104a7c742",
9
+ "cloud-contract.json": "ac4d1946a278d3cf3aae6d750249603adfd89f3c421eda4c62bb69e2bbf7490f",
10
+ "cloud_core.py": "be517e165180baf610f6cb7e863bc76a6704233118952045a0366d72000d821a",
11
+ "demo.evolution": "2743e838371d737f3309c47dc84bc7b43f13657f4b008b2b6eb6f4b429531920",
12
+ "github-simulator/source/Cargo.lock": "d8b8dfcfd4ddd90388a9aadae6c12a675e46277a3f26dabcce5ba8af6ff12183",
13
+ "github-simulator/source/Cargo.toml": "fe4d592043df0b10f6699dcf6fe6cc7b588ed3df23bfadae11afb61924f25bd9",
14
+ "github-simulator/source/GODOT_LICENSE": "fb2b3bb82c60f1e76841ba2122eb902f8e39c151476fe21ce10c9a05d1045304",
15
+ "github-simulator/source/LICENSE": "53d5851b6440feeae36f72a5f57fcab37858c48b7084765ece4b0c54b9a28ed8",
16
+ "github-simulator/source/README.md": "cc11a5a17701416fbf11b392be303e0485aa308170ef2bf2f52815f60cac15de",
17
+ "github-simulator/source/assets/models/formula.json": "fc7849fac5e20a14427ca8adafd4dab61518920d385411a144a8798ae02de2ed",
18
+ "github-simulator/source/assets/models/rally.json": "5155711ab44e1db4aee6d3cb509d321e60d2a4ffabe1937603b3dd9daf187e1f",
19
+ "github-simulator/source/assets/networks/formula.json": "452647361c18ee196c7abd1059c384f9281ca57890135e5b164f9669fe45250c",
20
+ "github-simulator/source/assets/networks/rally.json": "75ecde48a073d1f343ced743955ffc3a7eaffcd035884452ce408b4d12707932",
21
+ "github-simulator/source/assets/random_track_settings.json": "a0c3daae1436e659a3157fcb1e4520d5a59c16f739eb6a9eb53b397862cf7179",
22
+ "github-simulator/source/assets/scenes/formula_template.json": "80f745df82bb7be45af52785fd582460bcddc63c5a4e1d8375d85b3122db4c6b",
23
+ "github-simulator/source/assets/scenes/rally_template.json": "7350a3fe28d9b4da7a0965ae6ea7ca3472fb7885303e342310d63a0dce80851c",
24
+ "github-simulator/source/assets/scenes/snowmobile_template.json": "12d30545876011fd6fccdd3e721f9c465938c9c58ac99338205481d5e7ac6742",
25
+ "github-simulator/source/assets/scenes/truck_template.json": "a83bffea4d5e818c135ab3aba68a47aa288d6ad0e7b8bd9c95ed0dea9e9df2f7",
26
+ "github-simulator/source/docs/cli.md": "095468c685d68362880323cd0aea56c0951517d2b9f5dbc281b67499e712cf28",
27
+ "github-simulator/source/docs/design.md": "5e768d010d41f021aca8bc88119709a87fe4b346aed95d31b24bb1563c7974fd",
28
+ "github-simulator/source/docs/fidelity.md": "1a58be54cc167aba2f8f78992382f3ae58005dfb7ecd6f7dd552466f10d66f54",
29
+ "github-simulator/source/docs/gpu.md": "ecdd400d988344da2a076db2b32a09d06b3578d44c6b17da67c151b2dfbecff7",
30
+ "github-simulator/source/docs/math-profiles.html": "69f7e3c9165e6c3c969805e77669ca0b13965a4e2b23c4a6e4dd8e234a9755f7",
31
+ "github-simulator/source/docs/performance.md": "fa47013ff2940458a951250166b8b4f1c406cd99734d979c701aea7b2792cca3",
32
+ "github-simulator/source/docs/reviews/2026-10-05-native-server-review.md": "4ed44f436e4348591d8728659d94e00f48bd8a2d559fda7c3fe2c695831e5b85",
33
+ "github-simulator/source/docs/server.md": "4b8899b87ba53008b66eeb878794b961f10d16280134f4716453efcab347e37a",
34
+ "github-simulator/source/docs/superpowers/specs/2026-10-05-native-server-design.md": "99ac6ee8e335ab71ddcfdd28ab4ed2d7d1cfbad7935101d72ebe39fe017fdf6b",
35
+ "github-simulator/source/docs/wasm.md": "888386a1b77546382c7ba39d4897e62b83015c6ce2ef725b436b6561031b47ef",
36
+ "github-simulator/source/gpu/README.md": "e831018c053fd7a47162d89b72b789d8c5ecd455e46f57282b23be2f9c460a43",
37
+ "github-simulator/source/gpu/benchmark.py": "413db669422dba553e54adcf083aa11372c7925232a02a23a96aef8b55954380",
38
+ "github-simulator/source/gpu/build-cuda.ps1": "dc5b4dbdf277c8522fe927f13db6e68df1554ccf5ecf78cb79128d34a1c63550",
39
+ "github-simulator/source/gpu/build-cuda.sh": "a1d64a98c968dffde896e6634914ae7312820ff04171bc29ddc4519f112c8a6d",
40
+ "github-simulator/source/gpu/build.sh": "563dfba35d55d33e1340fc80bd063931cc88129ff2214cc556d7205c71308183",
41
+ "github-simulator/source/gpu/gen_tables.py": "f0fe3dbcefa271a63093c1c7ee7c5c55166bdfd96a3daa0e7113ea37da0f6e2e",
42
+ "github-simulator/source/gpu/profile.sh": "f08fbab3a38ed6414548009e1b116f7cc289ea67d1baef7262c1c886926bde88",
43
+ "github-simulator/source/gpu/release-targets": "fdca8679baa13af1191fbc0179201e8aafdf7355ba8c5684ea378b6afeffe1a2",
44
+ "github-simulator/source/gpu/sim/altd_gpu.hip": "212f169713fd96a065822ef3c6fe712491a144b0cb1b63df0f083967f509065e",
45
+ "github-simulator/source/gpu/sim/compat.h": "23c60db69ca371dd8da6a7949911a6f69d52250448a3623e73a5c3c290331ff8",
46
+ "github-simulator/source/gpu/sim/double_tables.h": "472dbcd916293dcd91dcd3f882ba86ed36267ede819ffb1692daf8b6ad5a38ec",
47
+ "github-simulator/source/gpu/sim/engine_exceptions.h": "4568607466a307d1520d763ea2f1c7424219299caf365ffa03dd62084673a551",
48
+ "github-simulator/source/gpu/sim/math.h": "ae93aadb2d49492176a08a1bed4a1c5b687f99ae16da57736ef30661dcaaeb8b",
49
+ "github-simulator/source/gpu/sim/rays.h": "414fc93b6a5180c9ed906a0b802eea48a5d81bba8920097989dfa568bfa82f12",
50
+ "github-simulator/source/gpu/sim/sensors.h": "64390b210bf388abb49026ba0b8529144eb025b0d306fb3198745b345fe38942",
51
+ "github-simulator/source/gpu/sim/state.h": "3672b2e48d43e6696cb9a61abff7a1de6a131dc2b7cbf6d99413596565a1bb20",
52
+ "github-simulator/source/gpu/sim/stats.h": "dc0d785dceaa06e27d90f5aa2a4513eb75aea6d149bbfde9ed8965f79c233d82",
53
+ "github-simulator/source/gpu/sim/step.h": "022390d8f2eb5276969826c5b650dba05de9ffc9264f03c1cee012060693bec7",
54
+ "github-simulator/source/gpu/sim/track.h": "2dcb2a8ae356115965a5047cf3843e0bdb9c547e0b297b9ab87772385549184c",
55
+ "github-simulator/source/gpu/sim/vec.h": "715afdaaff174668892bc5ada76edab317445a770bc0d6e7e0e0e83cb459740c",
56
+ "github-simulator/source/gpu/sim/world.h": "cff41441c08ff645384d972ec56412122b5a38ff5d618e09c979870f70d15f6f",
57
+ "github-simulator/source/gpu/test_constructor_cleanup.py": "bdd1fc42aa1494e20558f8c7ce20a9ca4a3775aeae4c836fd9688581f5bb87e6",
58
+ "github-simulator/source/math/README.md": "33b210248e6ed5c4abe4104940abc27fd1728af2a5af07bf442c1dfa2bd4caf4",
59
+ "github-simulator/source/math/fixtures/atan2f.bin": "9ecf9251878ddcb789a43119f1f060e24025db3e3485cccdbcb1f4366cb68ed2",
60
+ "github-simulator/source/math/fixtures/cosf.bin": "c84017673fdbe2a29f03e8ae4f410292da3ee93f5f74d91e169053a04c6273d6",
61
+ "github-simulator/source/math/fixtures/exp.bin": "b0b063fc5bba32294b399f7305718a15797f4df65ba81cfbc785264e11126a9a",
62
+ "github-simulator/source/math/fixtures/log.bin": "9ef1de3473a7b1d8a2416eee3f2263a88eea073ad9c2af53bb751bca7e45813c",
63
+ "github-simulator/source/math/fixtures/pow.bin": "d2475d6b050e7b842f868c093591b4095509cd2e1350ba8ebb06df64452bf3cc",
64
+ "github-simulator/source/math/fixtures/sinf.bin": "bae316adc7a9ceb4a61a8375a7eb94004d9e9058e5563cc81e14c9ffe911a104",
65
+ "github-simulator/source/math/fixtures/tanh.bin": "b0f438bcf53c685c622158e0ab2f47b9a2c714e8c35cb8b90da1e539a53c7e2c",
66
+ "github-simulator/source/math/kernels/ucrt.h": "cf0787112fc4f3cadd88d965ba69f9250ea6fced7ab33fe2a3d669caae5dda5a",
67
+ "github-simulator/source/math/kernels/ucrt_tables.h": "0f2fd20c1513b5c88c6a138cdabdedfa68d652d07f6f19910e1a6a8a290af028",
68
+ "github-simulator/source/math/verification.md": "419e78815edbc8e47e3a39919d973a399cfa2d97b1556ac25fdfdd71540431c1",
69
+ "github-simulator/source/src/bin/altd-sim/args.rs": "9f833f026d1f71ef76fe05d948464c619c76c15e43cedc11b5f510bfc337bda2",
70
+ "github-simulator/source/src/bin/altd-sim/bench.rs": "76ddf4778cfafa0e8f2f821605c876fd250f2df37ef0d025028e7acaafc6ea0f",
71
+ "github-simulator/source/src/bin/altd-sim/comparisons.rs": "9e6859b759f5f0f181e3a0130478237187472a242d2b615d545cb3d19aad7c6e",
72
+ "github-simulator/source/src/bin/altd-sim/inputs.rs": "7c9c1b814dc9c072c2f77399836421c54547ef3029a2626f32c717434ef9c51d",
73
+ "github-simulator/source/src/bin/altd-sim/io.rs": "e90bd9b1dd7028d5187872bed08763e7ee8437a80b1491cd9a9bcfb8c47fcc97",
74
+ "github-simulator/source/src/bin/altd-sim/main.rs": "841956a5a1a927b1a31369ccbd9aa18161b6328745a5a3ca4f02bdcfedd7230c",
75
+ "github-simulator/source/src/bin/altd-sim/platform.rs": "da8d6ffbf03990ed04e9fe8ac6ccecc1c5f5da292708eebd2f68621043b882d0",
76
+ "github-simulator/source/src/bin/altd-sim/scratch/checkpoint.rs": "408f4c8438f8d8a8db2dd3888a55a7390a89723d1af1656ab7399897bf207eb5",
77
+ "github-simulator/source/src/bin/altd-sim/scratch/mod.rs": "524e9033a1fb29ac7b4d689a21fede12b7bb77a772c078fce4c0bf4763568bbe",
78
+ "github-simulator/source/src/bin/altd-sim/scratch/runner.rs": "20a78bd99dc6bd69cc9314ba2d531dac13b5d9f21fb6ca676d34c0fa472a46c0",
79
+ "github-simulator/source/src/bin/altd-sim/scratch/stop.rs": "b13d0b473376b0fb90d71d82713ae79d862337f5b1d874c1d53fa14ecf677c50",
80
+ "github-simulator/source/src/bin/altd-sim/scratch/tests.rs": "c59cbe78aca660ee9327c316d260e3d18cfbf302f41430e1985856090409a740",
81
+ "github-simulator/source/src/bin/altd-sim/train.rs": "7397016461d6d8a6642f03a2466d9bf22f1daabc1ab74ab22aaf7212821e23f1",
82
+ "github-simulator/source/src/data/tilemap_initial_cells.json": "97b5c28b80993f263e35fe22279944a91e7016f6f1471ef05317239c655acb53",
83
+ "github-simulator/source/src/data/track_catalog.json": "b1f71cd4c15aee7dcd81897ebe8549115bb270a26dc33728e9c5a4546f4408fe",
84
+ "github-simulator/source/src/data/track_resources.json": "e8b23c8a98f9c0c6573044ab0e7a01e8f707e6c46b234e96f9834a5370a61fab",
85
+ "github-simulator/source/src/gpu/hip.rs": "6618d96c39049e95a8dee98aa9656d27b1998ac71dd15fdb036210fedd68884f",
86
+ "github-simulator/source/src/gpu/mod.rs": "c8be62ad28ac998af80ebc9f5fb06320f8b791ab7f8a53db47c72b496506a7ae",
87
+ "github-simulator/source/src/gpu/simulation.rs": "3da4252813c8d7e83d48858a67b0f1a64ca75c274f4d930f656f80bc6e509b05",
88
+ "github-simulator/source/src/lib.rs": "a5d365de4631e9d4f7e1d049307f510c315dd74bb6a9ad6dec0e4e12a5c72138",
89
+ "github-simulator/source/src/math/double_math.rs": "9e5cce0ab7ab000ad2a40bd0315a09bc3af7910db97bc9296a8813b36ee6804b",
90
+ "github-simulator/source/src/math/double_math_tables.rs": "d27b8bbb61a1493efabb0a6a24dc095568fe096e4e34ba58470aefcdeb9735e2",
91
+ "github-simulator/source/src/math/float_reduction.rs": "21a5b0a46ecebe0c7fcb7bdeb1c9d3ae2589bc1796573615e8e00f2a1674d0ed",
92
+ "github-simulator/source/src/math/godot_math.rs": "5537ed27280374a776615baa9bfe8a134c479d38a15cacb9cba1dccb4e0c9d50",
93
+ "github-simulator/source/src/math/managed_sine_tables.rs": "fc4534f09a984ad78ca64f5584a2c2c868291841ff96f35353b8f82e89c31d8a",
94
+ "github-simulator/source/src/math/managed_trig.rs": "7497fdc77380ffc09dfea7921a938906cd12f6831faca118ad745c70053fc8c2",
95
+ "github-simulator/source/src/math/mod.rs": "c94f7656088d04220b5d59a9d28a340843f3f101fb39baec5aa48411d2290fd7",
96
+ "github-simulator/source/src/math/native_math.rs": "67efb2ce5a45c69c13006aa8b37c52ab3feb807b2d4b1e0d6bf04738e2ce8414",
97
+ "github-simulator/source/src/math/profile.rs": "df925dfdd799fed5691865c272e3e0ec1dea5bdd5961663edce4ccbd8c962d0c",
98
+ "github-simulator/source/src/math/pymath.rs": "ccadf6379fd4f4c7ba38e3c5aaab5e09d6d3f0388a7861a5c4525c70e7852357",
99
+ "github-simulator/source/src/math/ucrt.rs": "5cb27a66798d592f053ee1ecd8b5cc71a5cf250c31d2cbfbe00eb194fa4a57a4",
100
+ "github-simulator/source/src/math/vec2.rs": "18111a5d6412b7e4d6010c90d36f1892dd5553611b0c0778cea44db26b527afc",
101
+ "github-simulator/source/src/nn/mod.rs": "56200ce58b1191aadf594a44d2d967eb8ecd0e19d5f0d68b1299fdefd5d49e12",
102
+ "github-simulator/source/src/nn/network.rs": "af897658264b6bcc0c0cb4c6218da28a417fdee82b3531b90f833bab59502ca3",
103
+ "github-simulator/source/src/nn/network_simd.rs": "b4339e7da61d54aa617098981c3e27bb4757bac160f6d39c5c7bbb0c5ab86e60",
104
+ "github-simulator/source/src/physics/broadphase.rs": "65e90c5bbdd975b7dfa4ab9f81130e36a21df28f04faf193213c8522c39a837b",
105
+ "github-simulator/source/src/physics/car.rs": "b47eee90cd42d1bb9c9f8f98b1794ec85c3e3ca13b862d053f2dcc8048dab615",
106
+ "github-simulator/source/src/physics/collision.rs": "eb4b3ccc9d668a8054b843bd31624dd5602bd3db92c01f11dda8d8852a1d4a31",
107
+ "github-simulator/source/src/physics/mod.rs": "f24b54fe129527d6032bfda7f24704db6abc500130cc0d499937593dd4a61b48",
108
+ "github-simulator/source/src/physics/simulation.rs": "730da7a2a910f924cb23e2c4f67f9e59ac822ca3011596766953cf6188617e4f",
109
+ "github-simulator/source/src/physics/trace_state.rs": "83ac2de24529f195c506f90445ffef8e6c03d4d310264509d2a8ac915cc59699",
110
+ "github-simulator/source/src/server/dispatch.rs": "cf5206817e1eb2bcf52ae1a55f5cafde5d4b3583638eb6f8a250dfc933c29a82",
111
+ "github-simulator/source/src/server/mod.rs": "17672296ff7e9c6562443a913bdf290a12688b23bdadd61d57982ec40eb522e5",
112
+ "github-simulator/source/src/server/protocol.rs": "05b17adc585357659bd301495bd2026a39f52faa45fca61eb94fc42bc16c29c1",
113
+ "github-simulator/source/src/track/bsp.rs": "1555c30fdc8f1c2f68a877986095c6bb5146546e82394ed2c412bb89bade8be3",
114
+ "github-simulator/source/src/track/curve.rs": "b01a5d140b92a8eca03bfe7ca1d58fcf5e8455388d394b2f86d3094d98352b00",
115
+ "github-simulator/source/src/track/mod.rs": "4548f19925c2f61fac6a076ddbafacd062b29b75c8eecffcd7d7eb3037482900",
116
+ "github-simulator/source/src/track/path_segments.rs": "afa22462a3c5486d5bd9873313786cbb3aa28b932293fbebce37721ad9add24c",
117
+ "github-simulator/source/src/track/random_track.rs": "f60118f42f04b257188f89199956bad52139727ed4cff4376377a7bfb64d56e6",
118
+ "github-simulator/source/src/track/segment_grid.rs": "636b56c997ee10c5a56ed19a06f8525ff1e7a0756fbdc4dd4a73f8a02534f642",
119
+ "github-simulator/source/src/track/training_tracks.rs": "af7207f94db09f8e5d221844de25bb2a7fba44e82cacaa973c58e058a95332a3",
120
+ "github-simulator/source/src/track/world.rs": "81b4fab7ad2030629859cd7d725461b4e6adf6667f88b68629d289e8eb18fe52",
121
+ "github-simulator/source/src/training/agent.rs": "e3c0362788b7a23e47104d897c1d12025c8658f361c79f6328db0504c9416e02",
122
+ "github-simulator/source/src/training/ars.rs": "74672f1717b1eeea227e3f04479bbb951dc8289850a25e0c8ca59cecd6b77218",
123
+ "github-simulator/source/src/training/ars/tests.rs": "4273896832a470a0f10dd7c913b07726d21e3dceb6b7786abc89a195ecee05fe",
124
+ "github-simulator/source/src/training/batch_evaluation.rs": "6b975431eac59bfb197f3e5632ad58e1baa86c96f272360d351a4908df896f23",
125
+ "github-simulator/source/src/training/evaluation.rs": "66a6b86cdd3a052be533271a0012fb38a5a2ca2f973644681d013350f0b5ea27",
126
+ "github-simulator/source/src/training/evolution.rs": "d050036c9346561411feefc6c94d598cb0f3ee69e85a8960cf911d60b2129a61",
127
+ "github-simulator/source/src/training/game_random.rs": "04e90273f4960b36dbbba831bc4f95390998e31fd874fc3cca308580fe59228b",
128
+ "github-simulator/source/src/training/lineage.rs": "35ae0755d172ce54b7e26682ff626ef71d4ab724c553a2bb958084540d257c42",
129
+ "github-simulator/source/src/training/mod.rs": "8ef89fe999edd56502e85aba2b20664ab63871b9a03977c5c4b40293d945d905",
130
+ "github-simulator/source/src/training/optimizer.rs": "ee0ccc79494840e566e919d75879c2ceddd126fe5b5b49622f8c8d08af4aca92",
131
+ "github-simulator/source/src/training/pyrandom.rs": "1890f3338964d87e892589fea12d673ff181b2945ad7c121ecc0cde586e9d758",
132
+ "github-simulator/source/src/training/runner.rs": "5dcac344de3256dd27c6dead9cee92ab6ec1e782db38409002c7540fa11f2d42",
133
+ "github-simulator/source/src/training/runner/gpu.rs": "12475cb92731f5e9c311a9ca62c4e17f0dfde17bc46efe9f827f6ba4a91ebbac",
134
+ "github-simulator/source/src/training/runner/rays.rs": "e6467baf46fb091e81ca40aaa0ccc5b0f316f264cd8871e2a7f897cc4ae75320",
135
+ "github-simulator/source/src/training/runner/schedule.rs": "7bae3c881f2f2b086f1007734008056fcedab3316626cd9a5b13909daec02890",
136
+ "github-simulator/source/src/training/runner/tests.rs": "26fe931db6894bd72813136daf77fe0991307d1d715ffceea5f0e398e3e05b86",
137
+ "github-simulator/source/src/training/sensors.rs": "c3fe5cf32fb6ae0f75eaf08d826b482d3002ba9871a17e5c9b224c37cc9eda7c",
138
+ "github-simulator/source/src/training/session.rs": "608545b42d523766492d58e927b77e1beb5f4914476480ea5e64d0c325d62289",
139
+ "github-simulator/source/src/training/session/hip.rs": "a498f4712d2482d4229869748e3958c2ed502309b33e3465ac939d6d50ab3b2b",
140
+ "github-simulator/source/src/training/session/input.rs": "8d2ae4c9121a0a561797dbd394493c73a012d1687fbd5248ced4aa06c49eae90",
141
+ "github-simulator/source/src/training/stats.rs": "94075a6d675927b083c734e36049d12ec3faed67cb420e9a4c35e1de15a2a764",
142
+ "github-simulator/source/src/training/training_profile.rs": "2f801070e7e5b93fa774f2fe253642940f605167c8bb8a9e5f697e9dbacc36bc",
143
+ "github-simulator/source/src/wasm/float.wgsl": "6bfd8df71111352061ae5da4c6a0d496cfa3ff801a9c9ac9346a8dd940225584",
144
+ "github-simulator/source/src/wasm/gpu.rs": "065e78fe19c01f9ce4f09ff370d47696f77c3f53512fe54bb3d239f3a2860567",
145
+ "github-simulator/source/src/wasm/math_profile.rs": "63a59813aeb49686fa20ced1f0f379338ac12bffcda5d124f32abaa6a8fa524c",
146
+ "github-simulator/source/src/wasm/mod.rs": "96caf3e5bd084cbfb16e589430c952c6f4713fd843a63d3ca087d1b6aa391f2d",
147
+ "github-simulator/source/src/wasm/rays.wgsl": "0717e2a36bab9dd88cf2225104f6a5db198b767e9e4997ad40681a9bdf567448",
148
+ "github-simulator/source/src/wasm/sim/f64.wgsl": "b171bed33b9761f90ffd8222c36ff2532e9a260ff706fa85a10fb6f3ccb7cc46",
149
+ "github-simulator/source/src/wasm/sim/generated.wgsl": "a2f2947ecbd4b7eef088e074cf339c99e485c0d6c03365502686e3f9c80b0cf2",
150
+ "github-simulator/source/src/wasm/sim/helpers.wgsl": "bded7a38f12f5f5b37ccebb9e5a0701b476d353dad5f1cde230d97cae30e2737",
151
+ "github-simulator/source/src/wasm/sim/mod.rs": "1f4f7d45024a70768c6f0ff771991eadfb844118c0b64b17305f3ae40df611b7",
152
+ "github-simulator/source/src/wasm/sim/runtime.js": "fa6094b141f2db440d9627eb5eb203ed01a7670c3f3ba3933cc72cf49a046834",
153
+ "github-simulator/source/src/wasm/sim/window.wgsl": "532b56c9dd694381269513e0aec5831244aa7e5eb5dfdacf8ff66ec35b649f30",
154
+ "github-simulator/source/src/wasm/sim/world.rs": "76ebd261bb47ffe59f671451df00a769bc450c595780abeabfe542ed2aa31954",
155
+ "github_adapter.py": "7555cff8bceab47777386ee90a50074befa3711c433ad22cdca2ad4c1f652b3b",
156
+ "godot/assets/imported/A01 Beginner.model.json": "1d46f92b1c7d91af75e9f16423194f0fce5453d78b2cc0e7fb18eeb1842b5dd8",
157
+ "godot/assets/imported/A02 Sharp Turns.model.json": "40dbec0a1fa0bb029524cc71e0b420fe7dbcb7f4135a1f9ac21e1a6cd32786dc",
158
+ "godot/assets/imported/A03 High Speed.model.json": "2e7406e4292259a3bddf66c92dca6bf37237d80694cf6a68003744d179bf22bb",
159
+ "godot/assets/imported/A04 Dirt.model.json": "2dbcd29b0db5344dc2ae81be60c352a2dbddc3f2ac7b3427d2b76a8512d9733d",
160
+ "godot/assets/imported/A05 Two Terrains.model.json": "562b16348c0d79dfa5a22f483922acedd36446461bf44d2e4f94693f4910c58f",
161
+ "godot/assets/imported/A06 Ice.model.json": "56ccc3fa4149f3be4b25b5808482ca21edfcbf9559d497ff6fdb300aea14c76c",
162
+ "godot/assets/imported/A07 Three Terrains.model.json": "e0dad6f3dfdb8261e7a88b584a8120b69573634403c411ab48b9c69648d453e9",
163
+ "godot/assets/imported/A08 Long.model.json": "304998ba1effaecdfe8d9fcdaf59d8811db779c877458c454b355f70ad142d65",
164
+ "godot/assets/imported/A09 Sharpest.model.json": "a6948598cad6238f0c5802d67e1be56bb1748b648dfff675d23f6a61a05d0f56",
165
+ "godot/assets/imported/B01 Oval.model.json": "921fedb31214281dff3a64717da07abc6d85bb1bf0679411768c623a2dde02e9",
166
+ "godot/assets/imported/B02 Snake.model.json": "b319d59a1a21ca094ce50b781938e108582de7752036e5da0c028447964f462d",
167
+ "godot/assets/imported/B03 Slalom.model.json": "43008c2ea7475da7319f882a11a58d185ad093e46e002e5325eddbf80b00887a",
168
+ "godot/assets/imported/B04 Rally.model.json": "d5458902717421ab99680f8106de6472425b34f85a8e7842fdc639be442a6c1b",
169
+ "godot/assets/imported/B05 Racetrack.model.json": "203217155ac7d3d37e3bce4bf50199cfdece32a75533557435e78f3b1375f1d1",
170
+ "godot/assets/imported/B06 Hard.model.json": "c4460d0bddedd7d4be6d00976b6554994476d68bec39684a680cd6019cd32e7c",
171
+ "godot/assets/imported/B07 Drift.model.json": "d5b8b4418b84fe8a9d20f90e65abb1600510e122905b5eaa3e9d4f942549c97e",
172
+ "godot/assets/imported/B08 Arctic Circle.model.json": "e9b12a0194692bd90980ade5e5d852eb91ab43a55522a9cfb997c79811d4131a",
173
+ "godot/assets/imported/B09 Checkered.model.json": "183036504d32c8dfc1a58bae183d64501c629ab5c40e31908da4784cbdef8e0c",
174
+ "godot/assets/imported/R01 Blitzring.model.json": "3b0ec1c62d4d75f1ff7a27e8bcfc7e88495de97482037d213a5d661ca8249855",
175
+ "godot/assets/imported/R02 Desert Rally.model.json": "d3794fc462b24b6eaadd17f88c50fd98843cbae5b63f5e9765f9997e0244e04b",
176
+ "godot/assets/imported/R03 Glacier.model.json": "ec9b3c8201c8f7546c366c1aa9715dc76d875acbe5c0b0771c4abf0a25270d36",
177
+ "godot/assets/imported/R04 Truck Maze.model.json": "3ce6b887fdbf31a98e581715d77f7101251a1225e52bc072d0e8642a5d5e2fbe",
178
+ "godot/assets/imported/R05 Endurance 500.model.json": "19529f1a12cf2c5cd9ca5f1f9479e7323eb598d7db6b12332d16bf69f561c33e",
179
+ "godot/assets/imported/R06 Mix.model.json": "bee132139cf13457e293ce8cffe81ca8e8d5885e3276eea5a65465cc53594531",
180
+ "godot/assets/imported/R07 Mirage Marina.model.json": "b541eb4cd0cd714dff4e7e5e493e15ea0a01d99a6aa3a65b4a97044a87003a09",
181
+ "godot/assets/imported/R08 Demolition Derby.model.json": "b391029ce0b19f3ba6dea0831e53838337710136c9433ef84fce80317f4c9c07",
182
+ "godot/assets/imported/R09 Eau Noir.model.json": "25b9bf89b5ebb2fac8d18a547a1086d37f92563ff667b6cfa2a3afff83e4539d",
183
+ "godot/assets/imported/The Champions Trial.model.json": "9230869539197143ace6762107286000dba2cc050e6d35df7ef1d0c13fb5dc9b",
184
+ "index.html": "78e54dd9173c70d57b15003bf44f69f2a299c2e12b64216d9cedfca6f4177ca3",
185
+ "network_legality.py": "90196a2a6366768dda19fbb91dc03b2eeffa913b9af2ebffd33baa8f7a5067ec",
186
+ "preset_store.py": "c0edd11430c93d7015373d3e8579c0781e1a4ff51372808aed7872247d84b735",
187
+ "reward-catalog.json": "53b25e10a9e14b2ab1bd27259ca93d8fb0d49c4e27b3987f0ba2fc572bc63bf6",
188
+ "server.py": "0a261ed7fe3c9d0c532c4062d76fba24a4fef7bb719de0d66f9c31ae4d79e805",
189
+ "tracks/A01 Beginner-compatible.track": "c52056d88edf9bebb9d3daf6f9839ddf67deb43ff93d93a93fb0fbb3566993c1",
190
+ "tracks/A01 Beginner.saved.json": "23c93ec4443c269978ede5901b4c66a3f627cc273f3b5f6fa96d8a190a7e365b",
191
+ "tracks/A01 Beginner.scene.json": "d79cc49de2a5dce8690cf3422ac74b76f3980f845f98235188a443a717cd1f10",
192
+ "tracks/A01 Beginner.spawn.json": "c491511a8202b0bb4ba996c9f96a8d579dc30b443f5ff09984a18c5daee2ad89",
193
+ "tracks/A01 Beginner.track": "d3261eda8f6cbf73c55fb55ced0404aa6cba162fbf24bdc7780638b23c29d6ed",
194
+ "tracks/A02 Sharp Turns.saved.json": "cab708be9732a30f67d6cb3f2c639260844b003744ab061c6b06f5986024cb6d",
195
+ "tracks/A02 Sharp Turns.scene.json": "ded6165101f191332485a311f453537816248adb177822558dd86d917db882cd",
196
+ "tracks/A02 Sharp Turns.spawn.json": "03f1c1a3e2c720643e64f812a7bd619da8bc9ef27c108ab29be6fea982880763",
197
+ "tracks/A02 Sharp Turns.track": "ade4031c6292f0342008a3479948bb32ed6f95235d74b5a4d0fe2762fdf9790f",
198
+ "tracks/A03 High Speed.saved.json": "f21aa853ba2becd9c9e9118404545d19c4bd6c45d64ee3ecd4b6bafcefd97fbc",
199
+ "tracks/A03 High Speed.scene.json": "b486b6b47cd36e609f5ec1be67d385e373df351c379c47193f8a0aba54c88924",
200
+ "tracks/A03 High Speed.spawn.json": "7f8a9020b2aef433d876679262fc337928a4218cea3d2efa3ac6798b2eb122eb",
201
+ "tracks/A03 High Speed.track": "0fb8666063a4ee503cb5ad453d3135d7aae00a030254c5538581e73e7b94325e",
202
+ "tracks/A04 Dirt.saved.json": "5feac332a0b8e72fdbc5b9aa29f38750ba22b02ef809eaacb9f58daae54e1b64",
203
+ "tracks/A04 Dirt.scene.json": "dd28eaccaf7d270ff2ce4ebe572bf69a61349e107d05dff9792ea2f2c793ccb1",
204
+ "tracks/A04 Dirt.spawn.json": "28d8b916b84e3073c1ba5d0dff9c9a7d85c375858ac8c6818b1d6314847961fe",
205
+ "tracks/A04 Dirt.track": "483adf3e712436d448bf1187c467649f93cc7812f60db43345d4d0e061daa752",
206
+ "tracks/A05 Two Terrains.saved.json": "847f869d1d9f3e410043ccbf253d3def0725afa730c695b1519ef642cebab60c",
207
+ "tracks/A05 Two Terrains.scene.json": "a1e767ed6ddfd1fdaabed07a13ca71beaa074c051cb9ce70ad6c476880562bf1",
208
+ "tracks/A05 Two Terrains.spawn.json": "f8969c365d01c04a7a95b8abd5596618b70f52531eb03689ff908a27228056c3",
209
+ "tracks/A05 Two Terrains.track": "3493c825514e7304110db828e5a0fc58cce18dfa1a2a118b50addb2d71d4ff08",
210
+ "tracks/A06 Ice.saved.json": "97b93faa93ffea17dbac296c610228b5e452d0c1dfb14de30062e3e680d6cc1c",
211
+ "tracks/A06 Ice.scene.json": "da6fce1651fbb318fcc312632feca8961dcf85a79b7e74813b2427e696c716a1",
212
+ "tracks/A06 Ice.spawn.json": "b0f8af0dc885fc751eab1522da10592b5493bbc64843869e2426e1eca64e440d",
213
+ "tracks/A06 Ice.track": "a9acc1478f1d48808b30077f94c8a55ed64027cd0aa4c65cf0a2e1718c524bd1",
214
+ "tracks/A07 Three Terrains.saved.json": "a13bc4079a744791d2831aa971781bec349daedbe6bc104084741a561a637825",
215
+ "tracks/A07 Three Terrains.scene.json": "2888b82fa94b84c3b679a7c0ce81b7b2ccaaac7756907c893f85aed17f1059b8",
216
+ "tracks/A07 Three Terrains.spawn.json": "6e010816e7b8acd2401b89473fb22e9894cbfb88ec58060b47ae48e3091cbd21",
217
+ "tracks/A07 Three Terrains.track": "c157d8246d250940b9601b7c87b6c4edeef1bd75b5bab9aa69427d76d95cc7e6",
218
+ "tracks/A08 Long.saved.json": "99557126538ff8d2fac553262178427a91094eceaca39b32949141a310673113",
219
+ "tracks/A08 Long.scene.json": "f02099b86907f7cad9696ccadd814c626c59da47550f00d53307a4da535774e0",
220
+ "tracks/A08 Long.spawn.json": "6c082f62cb29b0a3cbc791b7531a4c9aeaf774f7e6dfa644f9ab40a2c3c6a815",
221
+ "tracks/A08 Long.track": "ef34644dd6f9900bb76a41fbe759a183830b17b1b6eea7f19cab1ba43ea7d96e",
222
+ "tracks/A09 Sharpest.saved.json": "8eae9b874f9b05050afa36370fa47f78b762ccba6d0d9665113b947a1a9e18d7",
223
+ "tracks/A09 Sharpest.scene.json": "05d4907988d0b9343c1800c74d0b3d0ea3f13ecd96514426b2303a211a9610b4",
224
+ "tracks/A09 Sharpest.spawn.json": "1027c03c0ef1bfd373e913c7dc77b256d0058e7b3b3cc6dc7d014a534019d972",
225
+ "tracks/A09 Sharpest.track": "407106de14243819966ddf30cad20979d979f94a798e94551a31a5538b705b5d",
226
+ "tracks/B01 Oval.saved.json": "0c42ed69583b7a6041837b10a4d2de7d4bcf4426ca91f657b5c1ce005a77f9b7",
227
+ "tracks/B01 Oval.scene.json": "f90567dcbbf2e7ed4dd8e578c63efe2962bc8fa836692e6c8c9494f4788b05e8",
228
+ "tracks/B01 Oval.spawn.json": "38bbe40879c18d9187eda23c9850c70f7762ea769b5d72208607b49bc2327c71",
229
+ "tracks/B01 Oval.track": "b4152d06083e5c871ec356ceff917de1ef5f72d6e27b9618959125a5e41e5a8c",
230
+ "tracks/B02 Snake.saved.json": "54c4f010a73e3549115ab9b160c9823d0f96fa920602507fcf251a9fd29619b4",
231
+ "tracks/B02 Snake.scene.json": "5ed52574b620de959a0c2005c7fd371f6d2f29b5dc2f20e930b13e8d93fc0f4c",
232
+ "tracks/B02 Snake.spawn.json": "2fb95139538ed66324e10750278ee250e8bc159ccab3d27320efc21de7601e5b",
233
+ "tracks/B02 Snake.track": "fbd98acba4c3580b6e37cf72ed63b5b2e0477b86ce64320a8423449b59df7e0d",
234
+ "tracks/B03 Slalom.saved.json": "6132213bdb3ece3f3ba1297200169aebd818fdff7e64ddc37d212c097322e9fc",
235
+ "tracks/B03 Slalom.scene.json": "13acb54facddc61b033070621dc92bdcd82cc35deadfa811020b788f15c90b5c",
236
+ "tracks/B03 Slalom.spawn.json": "f8969c365d01c04a7a95b8abd5596618b70f52531eb03689ff908a27228056c3",
237
+ "tracks/B03 Slalom.track": "f0014f7d33fd982fdf56d3f5fcfbccf5470ae5b3735480d4bcc959f2d5e61707",
238
+ "tracks/B04 Rally.saved.json": "1ba74c6c2e3dedddc9864379b6f824e8f32d45bd9cf566fcfdcf4074d633d817",
239
+ "tracks/B04 Rally.scene.json": "b61381b0932d40bc99f422f550d5d04612b8902dafb48e6e99752de817f40c2e",
240
+ "tracks/B04 Rally.spawn.json": "a864506108c968ab8e5949403825a387a9ab679aa3c97c2f85f51f593530cf17",
241
+ "tracks/B04 Rally.track": "6e03ca93bbd59f6e1cb05e3382c7959cc09f3abb5d9f76e8a224dd7365a21844",
242
+ "tracks/B05 Racetrack.saved.json": "d8fddedd268be9dda3e30f337663c3f1d42777c1cbcd6cb92055a8b50d154471",
243
+ "tracks/B05 Racetrack.scene.json": "1b69b006af727c249097fe996bd280f2f66751d78463678045db4bdfa7b7ccda",
244
+ "tracks/B05 Racetrack.spawn.json": "caf4e703eeef12f246e0456e9d4fecfd92020c86d6295813136c4dd68226aae9",
245
+ "tracks/B05 Racetrack.track": "43cea4d2ffd3ca3bc19de5b662b67228f7a9a35adfccf1af84a17ade07b5124a",
246
+ "tracks/B06 Hard.saved.json": "fe3bf789f34b9092b0c8486c80705f1aaeb38603d9a7263bbc2e4bf24fa0a7e6",
247
+ "tracks/B06 Hard.scene.json": "18b7dadde263aac8316ddb4b10e8d28321b799bdd17acb4c69508281dbbe949b",
248
+ "tracks/B06 Hard.spawn.json": "64cd8fd30bc1909522aaa0cf2df52bf980e6e4df2c5f3c4be173e4ecdd6881bb",
249
+ "tracks/B06 Hard.track": "b45330b2a4ec12b6cc7a62ab403c5cd7b864aa3ca7ce698da9678d80fefd5277",
250
+ "tracks/B07 Drift.saved.json": "549247a1c38d9f5462d416a2d3a00c3d032e8ca0358d75f2503bae9e277b3d79",
251
+ "tracks/B07 Drift.scene.json": "d8e3bfeea412780ae10a46bfcfee216bec1241f70dcab408a8de3b26bb7abd65",
252
+ "tracks/B07 Drift.spawn.json": "0c39c94ec48e01cde57873716585fc104b61d5bca9c3eff841342129d9b02078",
253
+ "tracks/B07 Drift.track": "73e0f3b11458f16e99b5c67987253a0d2e31a8dddf93bbae95ccf2ef09f206a0",
254
+ "tracks/B08 Arctic Circle.saved.json": "02b2be3a2c62083835f7fefe31788e666e523e4681b9c840ef6b8f2375b213d6",
255
+ "tracks/B08 Arctic Circle.scene.json": "af5c3bbb39ba6c143244d604df0c8b68ad7d8ef2889299cca52ed53dbf2b0980",
256
+ "tracks/B08 Arctic Circle.spawn.json": "ee1454cbec1c9b48ca0cf88e4337ada23a62827716a1ff10e1a480cace798043",
257
+ "tracks/B08 Arctic Circle.track": "4ec9803238b933999c5a1a16f8718b7a6b12ebb2aa88615212b9caddef3cc154",
258
+ "tracks/B09 Checkered.saved.json": "9c015b35fb7085c622852b27f30881e28c0c02aeb5c82cc24e069cd5b838b878",
259
+ "tracks/B09 Checkered.scene.json": "1f8927da796828fde4b2f86bec1650a7896ab0cbf3855d59894b9c499e43c52d",
260
+ "tracks/B09 Checkered.spawn.json": "cd6e0efcdf24be3ce06afbeaefa2c956085172612f73b2c773dfcc4220debd5b",
261
+ "tracks/B09 Checkered.track": "a9df6dc6a7817eaab6d7233846e29ae44f826a9e035aa578ceaef386e12dcdd3",
262
+ "tracks/R01 Blitzring.saved.json": "92741bab01c659d294d6e5ddcb09905655f681de170dd9b9c850bb2a50ddb19b",
263
+ "tracks/R01 Blitzring.scene.json": "a2e6d19aadc4cc7e614f974c5db399e0ff0a105682e38a8fbfe6e9e4ece4686e",
264
+ "tracks/R01 Blitzring.spawn.json": "7795cac05e10bfe8f16a0cb0ef5a93edf0df443085df22d81d8955a8fb39ab2d",
265
+ "tracks/R01 Blitzring.track": "560aafb1ecb7fa07671cfaa79ea429e5db7c64ffa625f25a24fbb046732f13bc",
266
+ "tracks/R02 Desert Rally.saved.json": "3f5e88bb2c3ea08ed933d529112b8e99cce6817cb19e1896ac7f3f478eb4256e",
267
+ "tracks/R02 Desert Rally.scene.json": "3ae7ee8cb62758d5ac09004536dce9ce19b99b0d732459e1a753bdf433c28ef1",
268
+ "tracks/R02 Desert Rally.spawn.json": "fc8dc10779bb6750039b84308aaeb8e5b046fffd07932f82c88eb17654cfbe9c",
269
+ "tracks/R02 Desert Rally.track": "15269225ce25cc964e79783ba5833c9135f561a7ae7c482b1f1f402feb83aa67",
270
+ "tracks/R03 Glacier.saved.json": "899727f504a4552522a66f65803498dbc535b943280ba2e41979018ed7a6d77f",
271
+ "tracks/R03 Glacier.scene.json": "5926a121ab9fe5a37cda9037357ef27a53d1ba79c217a6ee1e8561bb199d23b7",
272
+ "tracks/R03 Glacier.spawn.json": "ae91e75e037ba1d8b039a638860b6a93535d25e0804638dd1ec533296394e82f",
273
+ "tracks/R03 Glacier.track": "93d85045b4af30be986fb1cf9e5a2462ed25f34dd17b1dc5bdebcc06f2f58f0c",
274
+ "tracks/R04 Truck Maze.saved.json": "3a0de310132581c730acdf493dbd9d6506f9d78479e5cfe83c55f8f788f0895e",
275
+ "tracks/R04 Truck Maze.scene.json": "6d0955f94394a2786601053aa6c3da3135457b470df069ae102e90238a96e295",
276
+ "tracks/R04 Truck Maze.spawn.json": "6632d1b815145ef63173b33f740a8038ace886fc80d3d19d153dc0317e700b64",
277
+ "tracks/R04 Truck Maze.track": "69f3b7813716bc408853f76368b9640a8b5fac60c0fe84bf68bb19f2f8cf5680",
278
+ "tracks/R05 Endurance 500.saved.json": "b13fe6d518cf8754ca362f8b04e09e5f1b58c5255c113879645b01c09df5c09f",
279
+ "tracks/R05 Endurance 500.scene.json": "3d61c23d726261d3d05f69561e80d21ae9335fe40d311953a9f1833b4130e4c1",
280
+ "tracks/R05 Endurance 500.spawn.json": "4012030b01cb4fd551aa5944a442ad6ec13e5afa66804728a4df855854eee339",
281
+ "tracks/R05 Endurance 500.track": "ab701400945b302b600270a3d510e6a3604a9ae29a3f7aa00df9628f7ecd9ac3",
282
+ "tracks/R06 Mix.saved.json": "e897ba1f8697b137c4866ebb76b8a50b873cd1ec2d185cb86389b1e8e166341c",
283
+ "tracks/R06 Mix.scene.json": "ead016d8e9ad39bb56f58d7456284321aef6d31f67684a5573845a5ac63a7c97",
284
+ "tracks/R06 Mix.spawn.json": "c491511a8202b0bb4ba996c9f96a8d579dc30b443f5ff09984a18c5daee2ad89",
285
+ "tracks/R06 Mix.track": "140d6aa9a0e4bfa1a6776ac05597bbcd7a75c073935426a74b540374312f9460",
286
+ "tracks/R07 Mirage Marina.saved.json": "a87a65b016d8ae787ec60f928c55184615d7bdcae9417ccae728d2ed8284295f",
287
+ "tracks/R07 Mirage Marina.scene.json": "6c9df3b4faf505605db29b48e4d86ca99c86ff6493859f93724b06ba8cfbf3cf",
288
+ "tracks/R07 Mirage Marina.spawn.json": "5f60a1fa3a25211fa2a9b3b4320565001c65e7600c8de6bf1bf33206b1fe6a2d",
289
+ "tracks/R07 Mirage Marina.track": "5eb0d06262c6e78dc00872591dec541780a1c9a0835ad608cea474f4b038d68f",
290
+ "tracks/R08 Demolition Derby.saved.json": "f1d4f738570d6f4d3139839c1b1bec01f22f4fb43ac272d971df30960e7a7736",
291
+ "tracks/R08 Demolition Derby.scene.json": "8526629ee38c06c7d10b490316aae5fde5be185c286678d7304bd5e0208bb743",
292
+ "tracks/R08 Demolition Derby.spawn.json": "62cf46bf13fd9a3fb57a8891edcbd74af430174346bd5782f57796c18653a12c",
293
+ "tracks/R08 Demolition Derby.track": "00816f68f620008cbe560a9098883834d5fb3eef664c37f00504c8fe9d025f0e",
294
+ "tracks/R09 Eau Noir.saved.json": "be13d95668e41982fe0aac22a3a69df934fdbb57234e80c53c5ef9bbdfbd6f55",
295
+ "tracks/R09 Eau Noir.scene.json": "e27ca05758a1ae98c7021f62d255745c1fd25350076ee9735131f4ce407a6a5e",
296
+ "tracks/R09 Eau Noir.spawn.json": "045e758d23b577e4a44597e1fef831b8689253bb3cbae3ad6d4309f4a4d79359",
297
+ "tracks/R09 Eau Noir.track": "ba77ee9396d44f13bf079ebcbbfb6aa6f44bae83fd8725b20eb559ac383ed79a",
298
+ "tracks/The Champions Trial.saved.json": "ccb4b73d7c01c607ae7afc655c476d92d15809cfd7ac66c1750646368ddfa2f2",
299
+ "tracks/The Champions Trial.scene.json": "5aead756212996a349023f4cfa2046ab0eced2a226777f9f32e27f9d533bf332",
300
+ "tracks/The Champions Trial.spawn.json": "b0ffd429583ecc6806854b672c2cf683ea305f17a49800942c9242d4490a95da",
301
+ "tracks/The Champions Trial.track": "27e612b7e5f4c0356a02c2d6ee0d0d03383543ce84dc36fc74a62a259e8493c3",
302
+ "tracks/catalog.json": "a043d34c557d35871bbf3b9c5ba018e882158a53b7e55d9ee8eec9782efad7ce",
303
+ "tracks/compatibility-receipt.json": "00948fe53db0995281e9a4593186d1322baaeaae0c41414b77724d8e59980122",
304
+ "vendor/games/__init__.py": "f6701b872373bd606865daad0cb3d55c6815b78a6bdf1a780ac6fa99f53f0a3a",
305
+ "vendor/games/ai_learns_to_drive/__init__.py": "53b2d4f6dac65249b79fd40331d2668f3d4ecf999616cf728912be39bf77431f",
306
+ "vendor/games/ai_learns_to_drive/batched_controller.py": "f1386798ab6a74a095720003fc4a7dbb5e01ada6e114c130e558e8a54215d2c1",
307
+ "vendor/games/ai_learns_to_drive/cli.py": "716d092b390dd205f2689c779f1a3a47b8c2dbd77597e01049ce4ba1d1f63856",
308
+ "vendor/games/ai_learns_to_drive/collision.py": "6179d7581c1a2f42d8f028ab30e701dd11c3ecd125eec270fc73c256ae114a39",
309
+ "vendor/games/ai_learns_to_drive/evolution.py": "adee47e38d62f1c9b7f303171552a7000f4c281384b03daa8410bf8c6fbd7fc7",
310
+ "vendor/games/ai_learns_to_drive/evolutionary_trainer.py": "739d11b924f2232ae57570da5d9f6dd395f2a272a2ac9f2ea383b9646ab0420a",
311
+ "vendor/games/ai_learns_to_drive/managed_math.py": "90d88e3376d8ec49b5913e26c6a5923d355a3aa303977d28ec74a027f690d59b",
312
+ "vendor/games/ai_learns_to_drive/physics.py": "942abdf7dee4d9d5b0644decf6351e9f9d7956a8e7613e46575fa361da68d547",
313
+ "vendor/games/ai_learns_to_drive/physics_profiles.py": "5e5dd4cf5ebc5be09c2180b0b69af9833b95e6a7e801da5cbd1b46f4527cddc9",
314
+ "vendor/games/ai_learns_to_drive/presentation.py": "6ffc09d2ca5d1791d7147ec1fded6c94860d66b51c411441d16c9a93b5e095d3",
315
+ "vendor/games/ai_learns_to_drive/provenance.py": "e739f162e3c863085b3fb960638f1d66a726456e2b4434a2e964c6b9888b8b2e",
316
+ "vendor/games/ai_learns_to_drive/records.py": "b7c65c165adb814b75dabad5595a139220a1a549958990315a9d3659194b0345",
317
+ "vendor/games/ai_learns_to_drive/reproduction.py": "02f096286d3f629f6a0feecf173dbcdfca4b7f898f07e55b853a490c5a953be9",
318
+ "vendor/games/ai_learns_to_drive/rewards.py": "a9f2833ca923dcc5c51d35f802615559cbd7cf0c8468803d83c94ce3efbcf3de",
319
+ "vendor/games/ai_learns_to_drive/runtime_cache.py": "b5d0f9e92cc15da25e5039b666733a7d7dd0d660b9db7516672945f2498ac846",
320
+ "vendor/games/ai_learns_to_drive/scoring.py": "66038035a63d181c60a4a657f39ac38ebf72aab6e0d91432edbcdb46d1a03f6f",
321
+ "vendor/games/ai_learns_to_drive/sensors.py": "77309aa3cbda26d521db81413cd2be8bee18f305358c9f849e346523efc9377c",
322
+ "vendor/games/ai_learns_to_drive/sessions.py": "072a306ebb1c8286a875c1476f4e56a4de1b7468b6f168d0a7778407391c672d",
323
+ "vendor/games/ai_learns_to_drive/settings.py": "b802210677437f0537e2902dd3fdbe72006a943e519f08ff53c7c2ee40383dc7",
324
+ "vendor/games/ai_learns_to_drive/simulation.py": "30b69106673f62e8ced5e58077058941a75abda2865d704c2a4e715781d7e406",
325
+ "vendor/games/ai_learns_to_drive/tracks.py": "24d8e098108d8be033da1292c9c72d2047935dd5dc1084fb2cdd24e6c55efa3d",
326
+ "vendor/games/ai_learns_to_drive/trainer.py": "5e17bcc7bc594a4bc27223106313497112488f7e6bb6c75618239878426355cd",
327
+ "vendor/reward_extraction.py": "69d10fb82594ec5f391ac6814f2677355a6448c5eb1a9d28a3c677333cdc6384"
328
+ }
README.md CHANGED
@@ -1,11 +1,15 @@
1
  ---
2
- title: TAO GPU Training Lab · backend pending
3
- emoji: 🧪
4
- colorFrom: blue
5
- colorTo: purple
6
  sdk: docker
7
  app_port: 7860
8
  pinned: false
9
  ---
10
 
11
- Private GPU comparison deployment reserved. Not a working GPU trainer yet. Native comparison backend currently uses AMD HIP/ROCm; Hugging Face offers NVIDIA GPU hardware. A verified CUDA/NVIDIA backend or independent compatible implementation is required. No paid hardware has been requested. CPU runs belong in ald-tao-cpu. This app must not silently fall back to CPU and label it GPU.
 
 
 
 
 
1
  ---
2
+ title: TAO GPU Training Lab
3
+ emoji: 🏎️
4
+ colorFrom: green
5
+ colorTo: blue
6
  sdk: docker
7
  app_port: 7860
8
  pinned: false
9
  ---
10
 
11
+ Private CUDA deployment candidate pinned to Zorro909/driving_sim_rs d8ab74c25df70b6a86ed101080e33d5fb785ea6f, matching ald-tao-cpu. Builds unchanged upstream Rust CLI and CUDA library together. Explicit proton math profile; no fast-math override. CUDA 13.0 sm_75/sm_86 plus PTX for compatible newer devices. Driver/device acceptance remains unverified.
12
+
13
+ Hardware stays CPU Basic until separately authorized. This is NOT a verified GPU trainer yet. Without a usable GPU library/device, jobs fail preflight; CPU fallback is disabled. Only positive-mutation train-scratch jobs are supported by this wrapper; frozen replay stays on ald-tao-cpu. Existing Formula-only, bounded network/sensor and steering/acceleration restrictions remain. No upstream WebSocket service is exposed. CPU/GPU matched-output and benchmark acceptance remain pending.
14
+
15
+ Open upstream PRs 19 and 21 are not included. Keep use private and bounded. No original-game parity claim. Storage is ephemeral: download checkpoints and receipts before restart. PACKAGE_MANIFEST.json covers deployment files; UPSTREAM.json records provenance. No local saved-car library is uploaded.
UPSTREAM.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "repository": "https://github.com/Zorro909/driving_sim_rs",
3
+ "commit": "d8ab74c25df70b6a86ed101080e33d5fb785ea6f",
4
+ "previous_space_commit": "a3b395b0b608fb7caa2081348be153feabc38a69",
5
+ "math_profile": "proton",
6
+ "role": "gpu",
7
+ "gpu_runtime_verified": false,
8
+ "open_prs_not_included": [
9
+ 19,
10
+ 21
11
+ ]
12
+ }
backend_policy.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Deployment identity and fail-closed GPU selection, independent of UI labels."""
2
+ import os
3
+ import json
4
+ import subprocess
5
+
6
+ UPSTREAM = 'd8ab74c25df70b6a86ed101080e33d5fb785ea6f'
7
+
8
+
9
+ def backend_args(config, binary, scratch):
10
+ args = ['--math-profile', 'proton']
11
+ if os.environ.get('ALD_REQUIRE_GPU') != '1':
12
+ return args, []
13
+ if not scratch:
14
+ raise ValueError('GPU worker currently supports positive-mutation training only; replay stays on the CPU Space')
15
+ check = subprocess.run([str(binary), 'gpu-info'], capture_output=True, text=True, timeout=30)
16
+ if check.returncode:
17
+ raise RuntimeError('GPU preflight failed; CPU fallback is disabled: ' + check.stderr[-1500:])
18
+ info=json.loads(check.stdout)
19
+ if not info.get('device') or info.get('kernels') != 'ok' or info.get('layout') != 'ok':
20
+ raise RuntimeError('GPU device and kernel acceptance missing; CPU fallback is disabled')
21
+ return args, ['--gpu']
batch_runner.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Serial finite batches, frozen recipes, explicit parent lineage, no auto-resume."""
2
+ import base64,copy,hashlib,json,threading,time,uuid
3
+ from pathlib import Path
4
+ class Batches:
5
+ def __init__(self,root,store,parse,make_job,execute):
6
+ self.root=Path(root);self.root.mkdir(exist_ok=True);self.store=store;self.parse=parse;self.make_job=make_job;self.execute=execute;self.items={};self.guard=threading.RLock()
7
+ for p in sorted(self.root.glob('*/status.json')):
8
+ b=json.loads(p.read_text());b['cancel']=threading.Event()
9
+ if b['state'] in ('queued','running'):b['state']='interrupted';self.save(b)
10
+ self.items[b['id']]=b
11
+ def public(self,b):return {k:v for k,v in b.items() if k!='cancel'}
12
+ def save(self,b):
13
+ with self.guard:
14
+ f=self.root/b['id']/'status.json';tmp=f.with_suffix('.tmp');tmp.write_text(json.dumps(self.public(b),indent=2));tmp.replace(f)
15
+ def active(self):return any(b['state'] in ('queued','running') for b in self.items.values())
16
+ def start(self,slot,data):
17
+ template,_=self.parse(data);manifest=self.store.resolve(slot)
18
+ id='batch-'+time.strftime('%Y%m%d-%H%M%S')+'-'+uuid.uuid4().hex[:6];d=self.root/id;d.mkdir();(d/'input.evolution').write_bytes(data)
19
+ manifest['input_sha256']=hashlib.sha256(data).hexdigest();(d/'manifest.json').write_text(json.dumps(manifest,indent=2))
20
+ b={'id':id,'name':manifest['name'],'slot':slot,'state':'queued','step':0,'total':len(manifest['steps']),'carry':manifest['carry'],'manifest':manifest,'jobs':[],'current_job':None,'cancel':threading.Event()};self.items[id]=b;self.save(b)
21
+ threading.Thread(target=self.worker,args=(b,data),daemon=True).start();return self.public(b)
22
+ def worker(self,b,original):
23
+ current=original
24
+ try:
25
+ b['state']='running';self.save(b)
26
+ for i,step in enumerate(b['manifest']['steps']):
27
+ if b['cancel'].is_set():raise InterruptedError('Batch stopped')
28
+ parent=current if b['carry'] else original
29
+ job=self.make_job(copy.deepcopy(step['config']),parent,b['id']);b['current_job']=job['id'];b['step']=i+1;b['jobs'].append({'id':job['id'],'run_slot':step['run_slot'],'input_sha256':hashlib.sha256(parent).hexdigest()});self.save(b)
30
+ if b['cancel'].is_set():job['cancel'].set()
31
+ self.execute(job)
32
+ if job['state']=='stopped' or b['cancel'].is_set():raise InterruptedError('Batch stopped')
33
+ if job['state']!='complete':raise RuntimeError(f'Run {step["run_slot"]} failed: {job.get("error",job["state"])}')
34
+ current=(Path(job['output_dir'])/'candidate.evolution').read_bytes();b['last_candidate']=str(Path(job['output_dir'])/'candidate.evolution');self.save(b)
35
+ b['state']='complete'
36
+ except InterruptedError:b['state']='stopped'
37
+ except Exception as e:b['state']='failed';b['error']=str(e)
38
+ finally:self.save(b)
39
+ def stop(self,id,jobs):
40
+ b=self.items[id];b['cancel'].set();j=jobs.get(b['current_job'])
41
+ if j:j['cancel'].set()
42
+ return self.public(b)
cloud-contract.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"sensors": [{"$type": "raycast", "Length": 200.00003, "Degrees": -90}, {"$type": "raycast", "Length": 719.61523, "Degrees": -30}, {"$type": "raycast", "Length": 800, "Degrees": 0}, {"$type": "raycast", "Length": 719.61523, "Degrees": 30}, {"$type": "raycast", "Length": 200.00003, "Degrees": 90}, {"$type": "speed"}]}
cloud_core.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import json
2
+ from pathlib import Path
3
+ ENTRY=json.loads((Path(__file__).parent/'cloud-contract.json').read_text())
4
+ def network(v):
5
+ return {'weights':[[v[i*4:(i+1)*4] for i in range(6)],[v[28+i*2:28+(i+1)*2] for i in range(4)]],'biases':[v[24:28],v[36:38]]}
demo.evolution ADDED
Binary file (1 kB). View file
 
github-simulator/source/Cargo.lock ADDED
@@ -0,0 +1,792 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "altd_sim"
7
+ version = "1.1.0"
8
+ dependencies = [
9
+ "clap",
10
+ "console_error_panic_hook",
11
+ "js-sys",
12
+ "libc",
13
+ "libloading",
14
+ "rayon",
15
+ "serde",
16
+ "serde_json",
17
+ "sha2",
18
+ "tungstenite",
19
+ "wasm-bindgen",
20
+ "wasm-bindgen-futures",
21
+ "wasm-bindgen-rayon",
22
+ "web-sys",
23
+ "windows-sys",
24
+ ]
25
+
26
+ [[package]]
27
+ name = "anstream"
28
+ version = "1.0.0"
29
+ source = "registry+https://github.com/rust-lang/crates.io-index"
30
+ checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
31
+ dependencies = [
32
+ "anstyle",
33
+ "anstyle-parse",
34
+ "anstyle-query",
35
+ "anstyle-wincon",
36
+ "colorchoice",
37
+ "is_terminal_polyfill",
38
+ "utf8parse",
39
+ ]
40
+
41
+ [[package]]
42
+ name = "anstyle"
43
+ version = "1.0.14"
44
+ source = "registry+https://github.com/rust-lang/crates.io-index"
45
+ checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
46
+
47
+ [[package]]
48
+ name = "anstyle-parse"
49
+ version = "1.0.0"
50
+ source = "registry+https://github.com/rust-lang/crates.io-index"
51
+ checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
52
+ dependencies = [
53
+ "utf8parse",
54
+ ]
55
+
56
+ [[package]]
57
+ name = "anstyle-query"
58
+ version = "1.1.5"
59
+ source = "registry+https://github.com/rust-lang/crates.io-index"
60
+ checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
61
+ dependencies = [
62
+ "windows-sys",
63
+ ]
64
+
65
+ [[package]]
66
+ name = "anstyle-wincon"
67
+ version = "3.0.11"
68
+ source = "registry+https://github.com/rust-lang/crates.io-index"
69
+ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
70
+ dependencies = [
71
+ "anstyle",
72
+ "once_cell_polyfill",
73
+ "windows-sys",
74
+ ]
75
+
76
+ [[package]]
77
+ name = "block-buffer"
78
+ version = "0.10.4"
79
+ source = "registry+https://github.com/rust-lang/crates.io-index"
80
+ checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
81
+ dependencies = [
82
+ "generic-array",
83
+ ]
84
+
85
+ [[package]]
86
+ name = "block-buffer"
87
+ version = "0.12.1"
88
+ source = "registry+https://github.com/rust-lang/crates.io-index"
89
+ checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
90
+ dependencies = [
91
+ "hybrid-array",
92
+ ]
93
+
94
+ [[package]]
95
+ name = "bumpalo"
96
+ version = "3.20.3"
97
+ source = "registry+https://github.com/rust-lang/crates.io-index"
98
+ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
99
+
100
+ [[package]]
101
+ name = "bytes"
102
+ version = "1.12.1"
103
+ source = "registry+https://github.com/rust-lang/crates.io-index"
104
+ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
105
+
106
+ [[package]]
107
+ name = "cfg-if"
108
+ version = "1.0.5"
109
+ source = "registry+https://github.com/rust-lang/crates.io-index"
110
+ checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
111
+
112
+ [[package]]
113
+ name = "chacha20"
114
+ version = "0.10.2"
115
+ source = "registry+https://github.com/rust-lang/crates.io-index"
116
+ checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
117
+ dependencies = [
118
+ "cfg-if",
119
+ "cpufeatures 0.3.1",
120
+ "rand_core",
121
+ ]
122
+
123
+ [[package]]
124
+ name = "clap"
125
+ version = "4.6.7"
126
+ source = "registry+https://github.com/rust-lang/crates.io-index"
127
+ checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
128
+ dependencies = [
129
+ "clap_builder",
130
+ "clap_derive",
131
+ ]
132
+
133
+ [[package]]
134
+ name = "clap_builder"
135
+ version = "4.6.7"
136
+ source = "registry+https://github.com/rust-lang/crates.io-index"
137
+ checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
138
+ dependencies = [
139
+ "anstream",
140
+ "anstyle",
141
+ "clap_lex",
142
+ "strsim",
143
+ ]
144
+
145
+ [[package]]
146
+ name = "clap_derive"
147
+ version = "4.6.7"
148
+ source = "registry+https://github.com/rust-lang/crates.io-index"
149
+ checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"
150
+ dependencies = [
151
+ "heck",
152
+ "proc-macro2",
153
+ "quote",
154
+ "syn",
155
+ ]
156
+
157
+ [[package]]
158
+ name = "clap_lex"
159
+ version = "1.1.1"
160
+ source = "registry+https://github.com/rust-lang/crates.io-index"
161
+ checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
162
+
163
+ [[package]]
164
+ name = "colorchoice"
165
+ version = "1.0.5"
166
+ source = "registry+https://github.com/rust-lang/crates.io-index"
167
+ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
168
+
169
+ [[package]]
170
+ name = "console_error_panic_hook"
171
+ version = "0.1.7"
172
+ source = "registry+https://github.com/rust-lang/crates.io-index"
173
+ checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
174
+ dependencies = [
175
+ "cfg-if",
176
+ "wasm-bindgen",
177
+ ]
178
+
179
+ [[package]]
180
+ name = "const-oid"
181
+ version = "0.10.2"
182
+ source = "registry+https://github.com/rust-lang/crates.io-index"
183
+ checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
184
+
185
+ [[package]]
186
+ name = "cpufeatures"
187
+ version = "0.2.17"
188
+ source = "registry+https://github.com/rust-lang/crates.io-index"
189
+ checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
190
+ dependencies = [
191
+ "libc",
192
+ ]
193
+
194
+ [[package]]
195
+ name = "cpufeatures"
196
+ version = "0.3.1"
197
+ source = "registry+https://github.com/rust-lang/crates.io-index"
198
+ checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
199
+ dependencies = [
200
+ "libc",
201
+ ]
202
+
203
+ [[package]]
204
+ name = "crossbeam-channel"
205
+ version = "0.5.17"
206
+ source = "registry+https://github.com/rust-lang/crates.io-index"
207
+ checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1"
208
+ dependencies = [
209
+ "crossbeam-utils",
210
+ ]
211
+
212
+ [[package]]
213
+ name = "crossbeam-deque"
214
+ version = "0.8.8"
215
+ source = "registry+https://github.com/rust-lang/crates.io-index"
216
+ checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
217
+ dependencies = [
218
+ "crossbeam-epoch",
219
+ "crossbeam-utils",
220
+ ]
221
+
222
+ [[package]]
223
+ name = "crossbeam-epoch"
224
+ version = "0.9.21"
225
+ source = "registry+https://github.com/rust-lang/crates.io-index"
226
+ checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
227
+ dependencies = [
228
+ "crossbeam-utils",
229
+ ]
230
+
231
+ [[package]]
232
+ name = "crossbeam-utils"
233
+ version = "0.8.23"
234
+ source = "registry+https://github.com/rust-lang/crates.io-index"
235
+ checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
236
+
237
+ [[package]]
238
+ name = "crypto-common"
239
+ version = "0.1.7"
240
+ source = "registry+https://github.com/rust-lang/crates.io-index"
241
+ checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
242
+ dependencies = [
243
+ "generic-array",
244
+ "typenum",
245
+ ]
246
+
247
+ [[package]]
248
+ name = "crypto-common"
249
+ version = "0.2.2"
250
+ source = "registry+https://github.com/rust-lang/crates.io-index"
251
+ checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
252
+ dependencies = [
253
+ "hybrid-array",
254
+ ]
255
+
256
+ [[package]]
257
+ name = "data-encoding"
258
+ version = "2.11.1"
259
+ source = "registry+https://github.com/rust-lang/crates.io-index"
260
+ checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
261
+
262
+ [[package]]
263
+ name = "digest"
264
+ version = "0.10.7"
265
+ source = "registry+https://github.com/rust-lang/crates.io-index"
266
+ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
267
+ dependencies = [
268
+ "block-buffer 0.10.4",
269
+ "crypto-common 0.1.7",
270
+ ]
271
+
272
+ [[package]]
273
+ name = "digest"
274
+ version = "0.11.3"
275
+ source = "registry+https://github.com/rust-lang/crates.io-index"
276
+ checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
277
+ dependencies = [
278
+ "block-buffer 0.12.1",
279
+ "const-oid",
280
+ "crypto-common 0.2.2",
281
+ ]
282
+
283
+ [[package]]
284
+ name = "either"
285
+ version = "1.18.0"
286
+ source = "registry+https://github.com/rust-lang/crates.io-index"
287
+ checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
288
+
289
+ [[package]]
290
+ name = "equivalent"
291
+ version = "1.0.2"
292
+ source = "registry+https://github.com/rust-lang/crates.io-index"
293
+ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
294
+
295
+ [[package]]
296
+ name = "futures-core"
297
+ version = "0.3.34"
298
+ source = "registry+https://github.com/rust-lang/crates.io-index"
299
+ checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
300
+
301
+ [[package]]
302
+ name = "futures-task"
303
+ version = "0.3.34"
304
+ source = "registry+https://github.com/rust-lang/crates.io-index"
305
+ checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
306
+
307
+ [[package]]
308
+ name = "futures-util"
309
+ version = "0.3.34"
310
+ source = "registry+https://github.com/rust-lang/crates.io-index"
311
+ checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
312
+ dependencies = [
313
+ "futures-core",
314
+ "futures-task",
315
+ "pin-project-lite",
316
+ "slab",
317
+ ]
318
+
319
+ [[package]]
320
+ name = "generic-array"
321
+ version = "0.14.7"
322
+ source = "registry+https://github.com/rust-lang/crates.io-index"
323
+ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
324
+ dependencies = [
325
+ "typenum",
326
+ "version_check",
327
+ ]
328
+
329
+ [[package]]
330
+ name = "getrandom"
331
+ version = "0.4.3"
332
+ source = "registry+https://github.com/rust-lang/crates.io-index"
333
+ checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
334
+ dependencies = [
335
+ "cfg-if",
336
+ "libc",
337
+ "r-efi",
338
+ "rand_core",
339
+ ]
340
+
341
+ [[package]]
342
+ name = "hashbrown"
343
+ version = "0.17.1"
344
+ source = "registry+https://github.com/rust-lang/crates.io-index"
345
+ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
346
+
347
+ [[package]]
348
+ name = "heck"
349
+ version = "0.5.0"
350
+ source = "registry+https://github.com/rust-lang/crates.io-index"
351
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
352
+
353
+ [[package]]
354
+ name = "http"
355
+ version = "1.5.0"
356
+ source = "registry+https://github.com/rust-lang/crates.io-index"
357
+ checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
358
+ dependencies = [
359
+ "bytes",
360
+ "itoa",
361
+ ]
362
+
363
+ [[package]]
364
+ name = "httparse"
365
+ version = "1.10.1"
366
+ source = "registry+https://github.com/rust-lang/crates.io-index"
367
+ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
368
+
369
+ [[package]]
370
+ name = "hybrid-array"
371
+ version = "0.4.15"
372
+ source = "registry+https://github.com/rust-lang/crates.io-index"
373
+ checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
374
+ dependencies = [
375
+ "typenum",
376
+ ]
377
+
378
+ [[package]]
379
+ name = "indexmap"
380
+ version = "2.14.2"
381
+ source = "registry+https://github.com/rust-lang/crates.io-index"
382
+ checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
383
+ dependencies = [
384
+ "equivalent",
385
+ "hashbrown",
386
+ ]
387
+
388
+ [[package]]
389
+ name = "is_terminal_polyfill"
390
+ version = "1.70.2"
391
+ source = "registry+https://github.com/rust-lang/crates.io-index"
392
+ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
393
+
394
+ [[package]]
395
+ name = "itoa"
396
+ version = "1.0.18"
397
+ source = "registry+https://github.com/rust-lang/crates.io-index"
398
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
399
+
400
+ [[package]]
401
+ name = "js-sys"
402
+ version = "0.3.106"
403
+ source = "registry+https://github.com/rust-lang/crates.io-index"
404
+ checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
405
+ dependencies = [
406
+ "cfg-if",
407
+ "futures-util",
408
+ "wasm-bindgen",
409
+ ]
410
+
411
+ [[package]]
412
+ name = "libc"
413
+ version = "0.2.189"
414
+ source = "registry+https://github.com/rust-lang/crates.io-index"
415
+ checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
416
+
417
+ [[package]]
418
+ name = "libloading"
419
+ version = "0.9.0"
420
+ source = "registry+https://github.com/rust-lang/crates.io-index"
421
+ checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60"
422
+ dependencies = [
423
+ "cfg-if",
424
+ "windows-link",
425
+ ]
426
+
427
+ [[package]]
428
+ name = "log"
429
+ version = "0.4.34"
430
+ source = "registry+https://github.com/rust-lang/crates.io-index"
431
+ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
432
+
433
+ [[package]]
434
+ name = "memchr"
435
+ version = "2.8.3"
436
+ source = "registry+https://github.com/rust-lang/crates.io-index"
437
+ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
438
+
439
+ [[package]]
440
+ name = "once_cell"
441
+ version = "1.21.4"
442
+ source = "registry+https://github.com/rust-lang/crates.io-index"
443
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
444
+
445
+ [[package]]
446
+ name = "once_cell_polyfill"
447
+ version = "1.70.2"
448
+ source = "registry+https://github.com/rust-lang/crates.io-index"
449
+ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
450
+
451
+ [[package]]
452
+ name = "pin-project-lite"
453
+ version = "0.2.17"
454
+ source = "registry+https://github.com/rust-lang/crates.io-index"
455
+ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
456
+
457
+ [[package]]
458
+ name = "proc-macro2"
459
+ version = "1.0.107"
460
+ source = "registry+https://github.com/rust-lang/crates.io-index"
461
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
462
+ dependencies = [
463
+ "unicode-ident",
464
+ ]
465
+
466
+ [[package]]
467
+ name = "quote"
468
+ version = "1.0.47"
469
+ source = "registry+https://github.com/rust-lang/crates.io-index"
470
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
471
+ dependencies = [
472
+ "proc-macro2",
473
+ ]
474
+
475
+ [[package]]
476
+ name = "r-efi"
477
+ version = "6.0.0"
478
+ source = "registry+https://github.com/rust-lang/crates.io-index"
479
+ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
480
+
481
+ [[package]]
482
+ name = "rand"
483
+ version = "0.10.3"
484
+ source = "registry+https://github.com/rust-lang/crates.io-index"
485
+ checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af"
486
+ dependencies = [
487
+ "chacha20",
488
+ "getrandom",
489
+ "rand_core",
490
+ ]
491
+
492
+ [[package]]
493
+ name = "rand_core"
494
+ version = "0.10.1"
495
+ source = "registry+https://github.com/rust-lang/crates.io-index"
496
+ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
497
+
498
+ [[package]]
499
+ name = "rayon"
500
+ version = "1.12.0"
501
+ source = "registry+https://github.com/rust-lang/crates.io-index"
502
+ checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
503
+ dependencies = [
504
+ "either",
505
+ "rayon-core",
506
+ "wasm_sync",
507
+ ]
508
+
509
+ [[package]]
510
+ name = "rayon-core"
511
+ version = "1.13.0"
512
+ source = "registry+https://github.com/rust-lang/crates.io-index"
513
+ checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
514
+ dependencies = [
515
+ "crossbeam-deque",
516
+ "crossbeam-utils",
517
+ "wasm_sync",
518
+ ]
519
+
520
+ [[package]]
521
+ name = "rustversion"
522
+ version = "1.0.23"
523
+ source = "registry+https://github.com/rust-lang/crates.io-index"
524
+ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
525
+
526
+ [[package]]
527
+ name = "serde"
528
+ version = "1.0.229"
529
+ source = "registry+https://github.com/rust-lang/crates.io-index"
530
+ checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
531
+ dependencies = [
532
+ "serde_core",
533
+ "serde_derive",
534
+ ]
535
+
536
+ [[package]]
537
+ name = "serde_core"
538
+ version = "1.0.229"
539
+ source = "registry+https://github.com/rust-lang/crates.io-index"
540
+ checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
541
+ dependencies = [
542
+ "serde_derive",
543
+ ]
544
+
545
+ [[package]]
546
+ name = "serde_derive"
547
+ version = "1.0.229"
548
+ source = "registry+https://github.com/rust-lang/crates.io-index"
549
+ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
550
+ dependencies = [
551
+ "proc-macro2",
552
+ "quote",
553
+ "syn",
554
+ ]
555
+
556
+ [[package]]
557
+ name = "serde_json"
558
+ version = "1.0.151"
559
+ source = "registry+https://github.com/rust-lang/crates.io-index"
560
+ checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
561
+ dependencies = [
562
+ "indexmap",
563
+ "itoa",
564
+ "memchr",
565
+ "serde",
566
+ "serde_core",
567
+ "zmij",
568
+ ]
569
+
570
+ [[package]]
571
+ name = "sha1"
572
+ version = "0.11.0"
573
+ source = "registry+https://github.com/rust-lang/crates.io-index"
574
+ checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
575
+ dependencies = [
576
+ "cfg-if",
577
+ "cpufeatures 0.3.1",
578
+ "digest 0.11.3",
579
+ ]
580
+
581
+ [[package]]
582
+ name = "sha2"
583
+ version = "0.10.9"
584
+ source = "registry+https://github.com/rust-lang/crates.io-index"
585
+ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
586
+ dependencies = [
587
+ "cfg-if",
588
+ "cpufeatures 0.2.17",
589
+ "digest 0.10.7",
590
+ ]
591
+
592
+ [[package]]
593
+ name = "slab"
594
+ version = "0.4.12"
595
+ source = "registry+https://github.com/rust-lang/crates.io-index"
596
+ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
597
+
598
+ [[package]]
599
+ name = "strsim"
600
+ version = "0.11.1"
601
+ source = "registry+https://github.com/rust-lang/crates.io-index"
602
+ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
603
+
604
+ [[package]]
605
+ name = "syn"
606
+ version = "3.0.6"
607
+ source = "registry+https://github.com/rust-lang/crates.io-index"
608
+ checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
609
+ dependencies = [
610
+ "proc-macro2",
611
+ "quote",
612
+ "unicode-ident",
613
+ ]
614
+
615
+ [[package]]
616
+ name = "thiserror"
617
+ version = "2.0.21"
618
+ source = "registry+https://github.com/rust-lang/crates.io-index"
619
+ checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
620
+ dependencies = [
621
+ "thiserror-impl",
622
+ ]
623
+
624
+ [[package]]
625
+ name = "thiserror-impl"
626
+ version = "2.0.21"
627
+ source = "registry+https://github.com/rust-lang/crates.io-index"
628
+ checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
629
+ dependencies = [
630
+ "proc-macro2",
631
+ "quote",
632
+ "syn",
633
+ ]
634
+
635
+ [[package]]
636
+ name = "tokio"
637
+ version = "1.53.1"
638
+ source = "registry+https://github.com/rust-lang/crates.io-index"
639
+ checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
640
+ dependencies = [
641
+ "pin-project-lite",
642
+ ]
643
+
644
+ [[package]]
645
+ name = "tungstenite"
646
+ version = "0.30.0"
647
+ source = "registry+https://github.com/rust-lang/crates.io-index"
648
+ checksum = "e48ac77174b19c110a50ab2128b24215ac9cb40e0e12e093fb602d175c569d22"
649
+ dependencies = [
650
+ "bytes",
651
+ "data-encoding",
652
+ "http",
653
+ "httparse",
654
+ "log",
655
+ "rand",
656
+ "sha1",
657
+ "thiserror",
658
+ ]
659
+
660
+ [[package]]
661
+ name = "typenum"
662
+ version = "1.20.1"
663
+ source = "registry+https://github.com/rust-lang/crates.io-index"
664
+ checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
665
+
666
+ [[package]]
667
+ name = "unicode-ident"
668
+ version = "1.0.26"
669
+ source = "registry+https://github.com/rust-lang/crates.io-index"
670
+ checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
671
+
672
+ [[package]]
673
+ name = "utf8parse"
674
+ version = "0.2.2"
675
+ source = "registry+https://github.com/rust-lang/crates.io-index"
676
+ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
677
+
678
+ [[package]]
679
+ name = "version_check"
680
+ version = "0.9.5"
681
+ source = "registry+https://github.com/rust-lang/crates.io-index"
682
+ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
683
+
684
+ [[package]]
685
+ name = "wasm-bindgen"
686
+ version = "0.2.129"
687
+ source = "registry+https://github.com/rust-lang/crates.io-index"
688
+ checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
689
+ dependencies = [
690
+ "cfg-if",
691
+ "once_cell",
692
+ "rustversion",
693
+ "wasm-bindgen-macro",
694
+ "wasm-bindgen-shared",
695
+ ]
696
+
697
+ [[package]]
698
+ name = "wasm-bindgen-futures"
699
+ version = "0.4.79"
700
+ source = "registry+https://github.com/rust-lang/crates.io-index"
701
+ checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e"
702
+ dependencies = [
703
+ "js-sys",
704
+ "tokio",
705
+ "wasm-bindgen",
706
+ ]
707
+
708
+ [[package]]
709
+ name = "wasm-bindgen-macro"
710
+ version = "0.2.129"
711
+ source = "registry+https://github.com/rust-lang/crates.io-index"
712
+ checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
713
+ dependencies = [
714
+ "quote",
715
+ "wasm-bindgen-macro-support",
716
+ ]
717
+
718
+ [[package]]
719
+ name = "wasm-bindgen-macro-support"
720
+ version = "0.2.129"
721
+ source = "registry+https://github.com/rust-lang/crates.io-index"
722
+ checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
723
+ dependencies = [
724
+ "bumpalo",
725
+ "proc-macro2",
726
+ "quote",
727
+ "syn",
728
+ "wasm-bindgen-shared",
729
+ ]
730
+
731
+ [[package]]
732
+ name = "wasm-bindgen-rayon"
733
+ version = "1.3.0"
734
+ source = "registry+https://github.com/rust-lang/crates.io-index"
735
+ checksum = "9a16c60a56c81e4dc3b9c43d76ba5633e1c0278211d59a9cb07d61b6cd1c6583"
736
+ dependencies = [
737
+ "crossbeam-channel",
738
+ "js-sys",
739
+ "rayon",
740
+ "wasm-bindgen",
741
+ ]
742
+
743
+ [[package]]
744
+ name = "wasm-bindgen-shared"
745
+ version = "0.2.129"
746
+ source = "registry+https://github.com/rust-lang/crates.io-index"
747
+ checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
748
+ dependencies = [
749
+ "unicode-ident",
750
+ ]
751
+
752
+ [[package]]
753
+ name = "wasm_sync"
754
+ version = "0.1.2"
755
+ source = "registry+https://github.com/rust-lang/crates.io-index"
756
+ checksum = "cff360cade7fec41ff0e9d2cda57fe58258c5f16def0e21302394659e6bbb0ea"
757
+ dependencies = [
758
+ "js-sys",
759
+ "wasm-bindgen",
760
+ "web-sys",
761
+ ]
762
+
763
+ [[package]]
764
+ name = "web-sys"
765
+ version = "0.3.106"
766
+ source = "registry+https://github.com/rust-lang/crates.io-index"
767
+ checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4"
768
+ dependencies = [
769
+ "js-sys",
770
+ "wasm-bindgen",
771
+ ]
772
+
773
+ [[package]]
774
+ name = "windows-link"
775
+ version = "0.2.1"
776
+ source = "registry+https://github.com/rust-lang/crates.io-index"
777
+ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
778
+
779
+ [[package]]
780
+ name = "windows-sys"
781
+ version = "0.61.2"
782
+ source = "registry+https://github.com/rust-lang/crates.io-index"
783
+ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
784
+ dependencies = [
785
+ "windows-link",
786
+ ]
787
+
788
+ [[package]]
789
+ name = "zmij"
790
+ version = "1.0.23"
791
+ source = "registry+https://github.com/rust-lang/crates.io-index"
792
+ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
github-simulator/source/Cargo.toml ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [package]
2
+ name = "altd_sim"
3
+ version = "1.1.0"
4
+ edition = "2021"
5
+ description = "CPU game-fidelity simulator and trainer for AI Learns To Drive"
6
+ license = "MIT"
7
+ repository = "https://github.com/Zorro909/driving_sim_rs"
8
+ readme = "README.md"
9
+ keywords = ["simulation", "driving", "training", "neural-network", "wasm"]
10
+ categories = ["simulation", "science", "command-line-utilities"]
11
+ rust-version = "1.93"
12
+ exclude = ["tests/fixtures/", "wasm/test/"]
13
+
14
+ [lib]
15
+ # Keep native test artifacts distinct. wasm/build.sh selects cdylib explicitly.
16
+ crate-type = ["rlib"]
17
+
18
+ [features]
19
+ default = ["cli", "server"]
20
+ # The `altd-sim` command line (native only).
21
+ cli = ["dep:clap"]
22
+ # `altd-sim serve`: the WebSocket server Drive Lab can train on (native only).
23
+ server = ["cli", "dep:tungstenite"]
24
+ # The WebAssembly library in src/wasm: JavaScript bindings and the WebGPU raycaster.
25
+ wasm = ["dep:wasm-bindgen", "dep:js-sys", "dep:wasm-bindgen-futures", "dep:web-sys", "dep:console_error_panic_hook"]
26
+ wasm-threads = ["wasm", "dep:wasm-bindgen-rayon"]
27
+ # Browser integration diagnostics, excluded from distributed packages.
28
+ wasm-thread-test = ["wasm"]
29
+
30
+ [dependencies]
31
+ clap = { version = "4", features = ["derive"], optional = true }
32
+ rayon = "1"
33
+ serde = { version = "1", features = ["derive"] }
34
+ # float_roundtrip parses JSON floats exactly like Python's float().
35
+ serde_json = { version = "1", features = ["float_roundtrip", "preserve_order"] }
36
+ wasm-bindgen = { version = "0.2", optional = true }
37
+ js-sys = { version = "0.3", optional = true }
38
+ wasm-bindgen-futures = { version = "0.4", optional = true }
39
+ console_error_panic_hook = { version = "0.1", optional = true }
40
+ web-sys = { version = "0.3", optional = true, features = [
41
+ "Window", "Navigator", "WorkerGlobalScope", "WorkerNavigator",
42
+ "Gpu", "GpuAdapter", "GpuDevice", "GpuQueue",
43
+ "GpuBuffer", "GpuBufferDescriptor", "GpuBufferBinding",
44
+ "GpuShaderModule", "GpuShaderModuleDescriptor", "GpuCompilationInfo", "GpuCompilationMessage", "GpuCompilationMessageType",
45
+ "GpuComputePipeline", "GpuComputePipelineDescriptor", "GpuProgrammableStage", "GpuPipelineLayout",
46
+ "GpuBindGroup", "GpuBindGroupDescriptor", "GpuBindGroupEntry", "GpuBindGroupLayout",
47
+ "GpuCommandEncoder", "GpuComputePassEncoder", "GpuCommandBuffer",
48
+ "gpu_buffer_usage", "gpu_map_mode",
49
+ ] }
50
+
51
+ [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
52
+ # Runtime loading of the HIP simulator (src/gpu/hip.rs).
53
+ libloading = "0.9"
54
+ sha2 = "0.10"
55
+ # Synchronous WebSockets for `altd-sim serve`, without TLS.
56
+ tungstenite = { version = "0.30", default-features = false, features = ["handshake"], optional = true }
57
+
58
+ # Process statistics in the CLI (src/bin/altd-sim/platform.rs).
59
+ [target.'cfg(unix)'.dependencies]
60
+ libc = "0.2"
61
+
62
+ [target.'cfg(windows)'.dependencies]
63
+ windows-sys = { version = "0.61", features = ["Wdk_System_SystemServices", "Win32_Foundation", "Win32_System_ProcessStatus", "Win32_System_SystemInformation", "Win32_System_Threading"] }
64
+
65
+ [target.'cfg(target_arch = "wasm32")'.dependencies]
66
+ wasm-bindgen-rayon = { version = "=1.3.0", optional = true, features = ["no-bundler"] }
67
+
68
+ [profile.release]
69
+ codegen-units = 1
70
+ lto = "fat"
71
+ panic = "abort"
72
+ debug = "line-tables-only"
73
+
74
+ [[bin]]
75
+ name = "altd-sim"
76
+ path = "src/bin/altd-sim/main.rs"
77
+ required-features = ["cli"]
github-simulator/source/GODOT_LICENSE ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Godot Engine source adaptations in src/physics/broadphase.rs
2
+
3
+ Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md).
4
+ Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ Modified tree-balancing algorithm, originally Randy Gaul qu3e:
24
+ /**
25
+ * @file q3DynamicAABBTree.h
26
+ * @author Randy Gaul
27
+ * @date 10/10/2014
28
+ * Copyright (c) 2014 Randy Gaul http://www.randygaul.net
29
+ * This software is provided 'as-is', without any express or implied
30
+ * warranty. In no event will the authors be held liable for any damages
31
+ * arising from the use of this software.
32
+ * Permission is granted to anyone to use this software for any purpose,
33
+ * including commercial applications, and to alter it and redistribute it
34
+ * freely, subject to the following restrictions:
35
+ * 1. The origin of this software must not be misrepresented; you must not
36
+ * claim that you wrote the original software. If you use this software
37
+ * in a product, an acknowledgment in the product documentation would be
38
+ * appreciated but is not required.
39
+ * 2. Altered source versions must be plainly marked as such, and must not
40
+ * be misrepresented as being the original software.
41
+ * 3. This notice may not be removed or altered from any source distribution.
42
+ */
43
+
github-simulator/source/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zorro909
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.
github-simulator/source/README.md ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # altd-sim
2
+
3
+ [![CI](https://github.com/Zorro909/driving_sim_rs/actions/workflows/ci.yml/badge.svg)](https://github.com/Zorro909/driving_sim_rs/actions/workflows/ci.yml)
4
+
5
+ `altd-sim` is a Rust vehicle simulator and neural-network trainer for AI Learns To Drive. It runs headless on the CPU, through a runtime-loaded AMD HIP library, or in WebAssembly. It reproduces the game's floating-point arithmetic, contact ordering, sensors and evolution behavior against captured regression fixtures. Exactness claims apply to the tested inputs; [fidelity](docs/fidelity.md) records the limits.
6
+
7
+ The repository includes vehicle and sensor templates, tile resources, fixed generated tracks and self-contained tests. Campaign tracks are not distributed.
8
+
9
+ ## Getting started
10
+
11
+ This guide sets up `altd-sim serve`, the local simulator for [Drive Lab](https://drivinglab.jectrum.de). Drive Lab keeps your tracks, runs and downloads in the browser; the server drives the cars on your CPU threads or an AMD GPU. That removes the browser's limits on cars and network size and its eight-thread cap.
12
+
13
+ ### 1. Download
14
+
15
+ Download the archive for your system from [Releases](https://github.com/Zorro909/driving_sim_rs/releases). Tagged versions are stable; the `nightly` pre-release is rebuilt from every push to `main` and keeps the same file names. `SHA256SUMS` lists the archive checksums.
16
+
17
+ | System | Archive | Training on |
18
+ | --- | --- | --- |
19
+ | Linux x86_64 | `altd-sim-<version>-x86_64-unknown-linux-gnu.tar.gz` | CPU, or AMD GPU through HIP |
20
+ | Windows x86_64 | `altd-sim-<version>-x86_64-pc-windows-msvc.zip` | CPU |
21
+ | macOS, Apple silicon | `altd-sim-<version>-aarch64-apple-darwin.tar.gz` | CPU |
22
+
23
+ Unpack it and open a terminal in the unpacked folder:
24
+
25
+ ```sh
26
+ tar -xzf altd-sim-nightly-x86_64-unknown-linux-gnu.tar.gz
27
+ cd altd-sim-nightly-x86_64-unknown-linux-gnu
28
+ ./altd-sim --version
29
+ ```
30
+
31
+ On Windows, extract the `.zip`, then open PowerShell in the folder and use `.\altd-sim.exe` wherever this guide says `./altd-sim`. macOS binaries are unsigned, so first remove the download quarantine with `xattr -d com.apple.quarantine altd-sim`. Apple silicon uses the portable trigonometry described in [fidelity](docs/fidelity.md).
32
+
33
+ ### 2. Start the server
34
+
35
+ ```sh
36
+ ./altd-sim serve
37
+ ```
38
+
39
+ ```text
40
+ altd-sim serve: listening on ws://127.0.0.1:47800/v1
41
+ allowed origins: https://drivinglab.jectrum.de
42
+ CPU threads: 16
43
+ HIP: available (AMD Radeon RX 7900 XTX)
44
+ ```
45
+
46
+ Keep the terminal open while you train; <kbd>Ctrl</kbd>+<kbd>C</kbd> stops the server. It listens only on `127.0.0.1`, so the browser must run on the same computer.
47
+
48
+ ### 3. Connect Drive Lab
49
+
50
+ 1. In Drive Lab, open **New run**. Under **Compute**, enable **Use a local altd-sim**. The port field defaults to 47800.
51
+ 2. If the browser asks to allow access to devices on your local network, allow it.
52
+ 3. The status shows the connection, the CPU thread count and the GPU, if HIP is available. Choose **Native CPU** or **Native GPU (HIP)** and start the run.
53
+
54
+ Each browser connection gets its own session. Only one session at a time can use the GPU; another one asking for HIP trains on CPU and says why. If the server stops during training, the run stops and keeps its last saved generation. Start the server again and resume the run.
55
+
56
+ ### Options
57
+
58
+ | Option | Use it to |
59
+ | --- | --- |
60
+ | `--threads N` | Limit the CPU threads, for example to keep the computer responsive. The default is every logical CPU the process may use. |
61
+ | `--math-profile PROFILE` | Reproduce `proton`, `win10-fma3` or `win11-fma3` arithmetic. The default follows the server's operating system; clients can select a profile for each session. |
62
+ | `--port N` | Listen on another port when 47800 is taken. Set the same port in Drive Lab. |
63
+ | `--allow-origin ORIGIN` | Allow another Drive Lab address, such as a development copy: `--allow-origin http://127.0.0.1:5173`. It replaces the default; repeat the option to allow several. |
64
+
65
+ ```sh
66
+ ./altd-sim serve --threads 8 --port 47801
67
+ ```
68
+
69
+ All builds include all three math profiles. Windows 11 24H2 and newer default to `win11-fma3`; earlier Windows releases use `win10-fma3`; Linux and macOS use `proton`. To train on Linux for a game running on current Windows, for example, pass `--math-profile win11-fma3`. Checkpoints retain their profile. [Math profiles](math/README.md) describes the kernels and captured reference data.
70
+
71
+ ### Training on an AMD GPU
72
+
73
+ The Linux archive contains `libaltd_gpu.so` next to `altd-sim`. It needs a ROCm 7.x runtime and one of the [covered GPU architectures](gpu/README.md). `./altd-sim gpu-info` checks that the library loads and prints `"layout": "ok"`; with a GPU present it also runs one kernel and prints the device, and fails if the library has no code for that GPU; the server's `HIP:` line shows the device or why HIP is unavailable.
74
+
75
+ A HIP session drives its first 12 ticks on both the GPU and the CPU and compares them. If they differ, or the network or track needs something HIP does not support, the session trains on CPU and Drive Lab shows the reason. Native CPU and HIP produce the same results. Browser training can differ from both in the last digits of a few values ([issue #3](https://github.com/Zorro909/driving_sim_rs/issues/3)), so a run moved between the browser and the server can develop differently from then on.
76
+
77
+ NVIDIA GPUs use the same backend: build the library with `gpu/build-cuda.sh` (Linux) or `gpu/build-cuda.ps1` (Windows) as described in the [GPU guide](gpu/README.md#nvidia-cuda). The release archives do not include it.
78
+
79
+ ### Troubleshooting
80
+
81
+ - **`cannot listen on 127.0.0.1:47800: Address already in use`**: another server is already running, or another program uses the port. Stop it or choose another `--port`.
82
+ - **Drive Lab cannot connect**: check that the server is running and that both use the same port. A Drive Lab address other than `https://drivinglab.jectrum.de` must be allowed with `--allow-origin`, including its scheme and port. If you denied local network access, allow it again in the browser's site settings.
83
+ - **`HIP: unavailable`**: the line gives the reason. `./altd-sim gpu-info` reports library problems, and [GPU setup](gpu/README.md) covers ROCm and other GPU architectures.
84
+
85
+ [Server documentation](docs/server.md) describes the protocol and its security checks for other clients.
86
+
87
+ ### Training without Drive Lab
88
+
89
+ The CLI can also train on its own. This example trains Rally networks on generated tracks and writes them to `runs/example`:
90
+
91
+ ```sh
92
+ ./altd-sim train-scratch --scene assets/scenes/rally_template.json --track-mode random \
93
+ --random-track-settings assets/random_track_settings.json \
94
+ --population 256 --generations 3 --ticks 600 --out-dir runs/example
95
+ ```
96
+
97
+ Add `--gpu` to train on an AMD GPU. `./altd-sim --help` lists every command, and the [CLI reference](docs/cli.md) describes their inputs and outputs.
98
+
99
+ ## Build from source
100
+
101
+ Rust 1.94 or newer is the tested native toolchain. Build from the repository root. `--offline` works when dependencies are already cached; omit it on the first build if needed.
102
+
103
+ ```sh
104
+ cargo build --release --offline
105
+ python3 tools/benchmark.py --population 1024 --ticks 600 --repeats 5 \
106
+ --report target/benchmark.json
107
+
108
+ target/release/altd-sim --threads 8 train-scratch \
109
+ --scene assets/scenes/rally_template.json --track-mode random \
110
+ --random-track-settings assets/random_track_settings.json \
111
+ --population 256 --generations 3 --ticks 600 --seed 1729 \
112
+ --eliminate-on-wall --idle-eliminate --save-final-candidate \
113
+ --out-dir runs/example
114
+ ```
115
+
116
+ The training command starts Xavier networks and generates a reproducible track each generation. Increase the population, generations and tick limit for longer runs. Supply the same scene and track settings when resuming, with `--resume` and a higher `--generations` target.
117
+
118
+ `.cargo/config.toml` uses `target-cpu=native` for x86 local builds, including the published CPU measurements. For a portable x86_64 binary, override it explicitly:
119
+
120
+ ```sh
121
+ RUSTFLAGS='-C target-cpu=x86-64' cargo build --release --offline
122
+ ```
123
+
124
+ AVX2 network and geometry paths use runtime detection and retain scalar fallbacks. Exact native trigonometry uses x87 on x86; other targets use the portable implementation described in the fidelity notes.
125
+
126
+ ## Other backends
127
+
128
+ AMD GPU builds need ROCm and `hipcc`. The default architecture is `gfx1100`, for the RX 7900 XTX.
129
+
130
+ ```sh
131
+ gpu/build.sh
132
+ # Add --gpu to the train-scratch command above.
133
+ ```
134
+
135
+ For other AMD GPUs, set `GPU_ARCH` to one or more architectures when building. Rust builds do not require ROCm. See [GPU setup](gpu/README.md) for validation and profiling.
136
+
137
+ The default build also includes a local WebSocket server for native CPU or HIP sessions:
138
+
139
+ ```sh
140
+ target/release/altd-sim --threads 8 serve
141
+ ```
142
+
143
+ [Getting started](#getting-started) explains its options and how to connect Drive Lab; [server documentation](docs/server.md) covers the protocol, security checks and checkpoint resume.
144
+
145
+ WebAssembly builds need the wasm32 target and the matching `wasm-bindgen` CLI:
146
+
147
+ ```sh
148
+ rustup target add wasm32-unknown-unknown
149
+ cargo install wasm-bindgen-cli --version "$(wasm/bindgen-version.sh)" --locked
150
+ wasm/build.sh
151
+ ```
152
+
153
+ The JavaScript library includes CPU simulation, a threaded CPU package and optional WebGPU backends. See [WASM setup](wasm/README.md) for initialization, browser requirements and tests. Expanded native/WASM comparisons have known last-bit differences; serial and threaded WASM are compared separately.
154
+
155
+ ## CLI and output
156
+
157
+ `target/release/altd-sim --help` and `target/release/altd-sim COMMAND --help` show arguments. `bench` measures a fixed population; `train` evolves an exported network; `train-scratch` starts Xavier networks and supports checkpoints, generated-track batches and stop conditions; `evaluate` runs a frozen candidate against a hash-validated suite; `serve` exposes sessions over a loopback WebSocket. The `compare-*` commands compare user-supplied captures with the simulator. Scenes and recorded traces are explicit inputs. [CLI reference](docs/cli.md) covers defaults, resume behavior and report formats.
158
+
159
+ A `train-scratch` run writes:
160
+
161
+ | File | Contents |
162
+ | --- | --- |
163
+ | `run.json`, `log.jsonl`, `progress.json` | Effective options, completed generations and current progress |
164
+ | `checkpoint.json`, `checkpoint_gNNNNN.bin` | Generation-boundary population and RNG state |
165
+ | `best.json`, `best_laps/` | Networks that set a new best lap |
166
+ | `candidate.json` | Final fitness leader when `--save-final-candidate` is enabled |
167
+ | `tracks/` | Generated track geometry for replay |
168
+
169
+ ## Code and tests
170
+
171
+ `src/math/` contains runtime-compatible arithmetic and vectors. `src/physics/` implements vehicles and contacts, while `src/track/` owns scene geometry, spatial queries and generated tracks. `src/nn/` provides scalar and AVX2 inference. `src/training/` owns populations, evolution, RNGs, evaluation and embedding sessions. `src/gpu/hip.rs` loads HIP; `src/gpu/simulation.rs` shares device layouts with `src/wasm/`, which supplies JavaScript and WebGPU bindings. The CLI entrypoint is `src/bin/altd-sim/main.rs`, beside its argument, comparison, benchmark and checkpoint modules. Rust imports follow these groups, such as `altd_sim::physics::car::Car` and `altd_sim::training::session::Session`. See [design notes](docs/design.md).
172
+
173
+ ```sh
174
+ cargo test --release --offline
175
+ cargo test --offline
176
+ cargo build --offline --target wasm32-unknown-unknown --no-default-features --features wasm
177
+ ```
178
+
179
+ Tests retain self-contained native captures and label generated golden data as current-code regressions. Browser integration checks live in `wasm/test/`; HIP equivalence checks use `gpu_check`. The exhaustive trigonometry scan and hardware-dependent tests are opt-in. See [performance](docs/performance.md), [GPU internals](docs/gpu.md) and [WASM fidelity](docs/wasm.md).
180
+
181
+ GitHub Actions checks formatting, native and WASM Clippy, native release and debug tests, Windows and macOS Clippy and release tests, the HIP library build and layout, rustdoc, Rust 1.93 compatibility, and WASM packages with CPU browser comparisons. The release workflow builds and smoke-tests the archives, then publishes tags and nightly builds. Its native jobs override `target-cpu=native` with `x86-64` and include the flags in their cache keys, so cached builds can move between runner CPUs. Serial/threaded browser tests retain the documented expanded native differences. HIP equivalence and hardware WebGPU checks run separately because the hosted runners have no GPU.
182
+
183
+ The project uses the [MIT license](LICENSE). Godot-derived code retains the [Godot notice](GODOT_LICENSE), and numerical modules retain their source notices.
github-simulator/source/assets/models/formula.json ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "vehicle_type": "formula",
3
+ "vision": [
4
+ {
5
+ "angle": -135,
6
+ "length": 624.2640380859375
7
+ },
8
+ {
9
+ "angle": -90,
10
+ "length": 200.00003051757812
11
+ },
12
+ {
13
+ "angle": -60,
14
+ "length": 499.9999694824219
15
+ },
16
+ {
17
+ "angle": -35,
18
+ "length": 691.4912109375
19
+ },
20
+ {
21
+ "angle": -20,
22
+ "length": 763.8155517578125
23
+ },
24
+ {
25
+ "angle": -10,
26
+ "length": 790.8846435546875
27
+ },
28
+ {
29
+ "angle": 0,
30
+ "length": 800
31
+ },
32
+ {
33
+ "angle": 10,
34
+ "length": 790.8846435546875
35
+ },
36
+ {
37
+ "angle": 20,
38
+ "length": 763.8155517578125
39
+ },
40
+ {
41
+ "angle": 35,
42
+ "length": 691.4912109375
43
+ },
44
+ {
45
+ "angle": 60,
46
+ "length": 499.9999694824219
47
+ },
48
+ {
49
+ "angle": 90,
50
+ "length": 200.00003051757812
51
+ },
52
+ {
53
+ "angle": 135,
54
+ "length": 624.2640380859375
55
+ }
56
+ ],
57
+ "sensors": [
58
+ "angular_velocity",
59
+ "boost_capacity",
60
+ "wheel_angle",
61
+ "grip",
62
+ "velocity_front",
63
+ "velocity_side",
64
+ "track_curvature"
65
+ ],
66
+ "outputs": [
67
+ "steering",
68
+ "acceleration",
69
+ "handbrake",
70
+ "brake",
71
+ "boost"
72
+ ]
73
+ }
github-simulator/source/assets/models/rally.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "vehicle_type": "rally",
3
+ "vision": [
4
+ {
5
+ "angle": -135,
6
+ "length": 624.2640380859375
7
+ },
8
+ {
9
+ "angle": -90,
10
+ "length": 200.00003051757812
11
+ },
12
+ {
13
+ "angle": -60,
14
+ "length": 499.9999694824219
15
+ },
16
+ {
17
+ "angle": -35,
18
+ "length": 691.4912109375
19
+ },
20
+ {
21
+ "angle": -20,
22
+ "length": 763.8155517578125
23
+ },
24
+ {
25
+ "angle": -10,
26
+ "length": 790.8846435546875
27
+ },
28
+ {
29
+ "angle": 0,
30
+ "length": 800
31
+ },
32
+ {
33
+ "angle": 10,
34
+ "length": 790.8846435546875
35
+ },
36
+ {
37
+ "angle": 20,
38
+ "length": 763.8155517578125
39
+ },
40
+ {
41
+ "angle": 35,
42
+ "length": 691.4912109375
43
+ },
44
+ {
45
+ "angle": 60,
46
+ "length": 499.9999694824219
47
+ },
48
+ {
49
+ "angle": 90,
50
+ "length": 200.00003051757812
51
+ },
52
+ {
53
+ "angle": 135,
54
+ "length": 624.2640380859375
55
+ }
56
+ ],
57
+ "sensors": [
58
+ "angular_velocity",
59
+ "boost_capacity",
60
+ "correct_direction",
61
+ "grip",
62
+ "velocity_front",
63
+ "velocity_side",
64
+ "track_curvature"
65
+ ],
66
+ "outputs": [
67
+ "steering",
68
+ "acceleration",
69
+ "handbrake",
70
+ "brake",
71
+ "boost"
72
+ ],
73
+ "fidelity_provenance": "Sensor lengths retain the captured single-precision values."
74
+ }
github-simulator/source/assets/networks/formula.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "inputs": [
3
+ "↑ -135°",
4
+ "↑ -90°",
5
+ "↑ -60°",
6
+ "↑ -35°",
7
+ "↑ -20°",
8
+ "↑ -10°",
9
+ "↑ 0°",
10
+ "↑ 10°",
11
+ "↑ 20°",
12
+ "↑ 35°",
13
+ "↑ 60°",
14
+ "↑ 90°",
15
+ "↑ 135°",
16
+ "AngVel",
17
+ "Boost",
18
+ "Wheel",
19
+ "Grip",
20
+ "VelF",
21
+ "VelS",
22
+ "Curve"
23
+ ],
24
+ "outputs": [
25
+ "Steering",
26
+ "Acceleration",
27
+ "Handbrake",
28
+ "Brake",
29
+ "Boost"
30
+ ]
31
+ }
github-simulator/source/assets/networks/rally.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "inputs": [
3
+ "↑ -135°",
4
+ "↑ -90°",
5
+ "↑ -60°",
6
+ "↑ -35°",
7
+ "↑ -20°",
8
+ "↑ -10°",
9
+ "↑ 0°",
10
+ "↑ 10°",
11
+ "↑ 20°",
12
+ "↑ 35°",
13
+ "↑ 60°",
14
+ "↑ 90°",
15
+ "↑ 135°",
16
+ "AngVel",
17
+ "Boost",
18
+ "Dir",
19
+ "Grip",
20
+ "VelF",
21
+ "VelS",
22
+ "Curve"
23
+ ],
24
+ "outputs": [
25
+ "Steering",
26
+ "Acceleration",
27
+ "Handbrake",
28
+ "Brake",
29
+ "Boost"
30
+ ]
31
+ }
github-simulator/source/assets/random_track_settings.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "length": { "min": 12, "max": 40 },
3
+ "allow_double": [false, true],
4
+ "surfaces": { "pool": [0, 1, 2], "count": [1, 2, 3] },
5
+ "distribution": [0, 1],
6
+ "start": null
7
+ }
github-simulator/source/assets/scenes/formula_template.json ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "vehicle": {
3
+ "type": "Formula",
4
+ "mass": 2,
5
+ "inertia_property": 0,
6
+ "inertia_server": 1038.1666259765625,
7
+ "shape_size": [
8
+ 73,
9
+ 30
10
+ ],
11
+ "shape_position": [
12
+ 0,
13
+ 0
14
+ ],
15
+ "shape_rotation": 1.5708,
16
+ "gravity_scale": 1,
17
+ "linear_damp": 0,
18
+ "angular_damp": 0,
19
+ "max_contacts_reported": 4,
20
+ "friction": 0.95,
21
+ "bounce": 0.1,
22
+ "grip": 2,
23
+ "steering_speed": 0.4000000059604645,
24
+ "center_steering": true,
25
+ "air_resistance": 0.10000000149011612,
26
+ "max_velocity": 1200,
27
+ "surfaces": {
28
+ "asphalt": {
29
+ "grip": 1,
30
+ "power": 1,
31
+ "steering": 1
32
+ },
33
+ "dirt": {
34
+ "grip": 0.8,
35
+ "power": 0.5,
36
+ "steering": 1
37
+ },
38
+ "ice": {
39
+ "grip": 0.3,
40
+ "power": 0.5,
41
+ "steering": 1
42
+ }
43
+ },
44
+ "wheels": [
45
+ {
46
+ "position": [
47
+ -11,
48
+ -17
49
+ ],
50
+ "steering": true,
51
+ "max_angle_deg": 35,
52
+ "power": 0,
53
+ "brake_power": 3.5,
54
+ "handbrake_power": 0
55
+ },
56
+ {
57
+ "position": [
58
+ 11,
59
+ -17
60
+ ],
61
+ "steering": true,
62
+ "max_angle_deg": 35,
63
+ "power": 0,
64
+ "brake_power": 3.5,
65
+ "handbrake_power": 0
66
+ },
67
+ {
68
+ "position": [
69
+ -13,
70
+ 29
71
+ ],
72
+ "steering": false,
73
+ "max_angle_deg": 0,
74
+ "power": 6,
75
+ "brake_power": 3,
76
+ "handbrake_power": 3
77
+ },
78
+ {
79
+ "position": [
80
+ 13,
81
+ 29
82
+ ],
83
+ "steering": false,
84
+ "max_angle_deg": 0,
85
+ "power": 6,
86
+ "brake_power": 3,
87
+ "handbrake_power": 3
88
+ }
89
+ ]
90
+ },
91
+ "physics": {
92
+ "real_precision": "single",
93
+ "contact_recycle_radius": 1,
94
+ "contact_max_separation": 1.5,
95
+ "contact_max_allowed_penetration": 0.3,
96
+ "contact_default_bias": 0.8,
97
+ "solver_iterations": 16
98
+ },
99
+ "track": {
100
+ "name": "Generated track template",
101
+ "tiles": [],
102
+ "path": [],
103
+ "walls": [],
104
+ "physics_shapes": []
105
+ },
106
+ "reset_position": [
107
+ 0,
108
+ 0
109
+ ],
110
+ "reset_rotation": 0
111
+ }
github-simulator/source/assets/scenes/rally_template.json ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "vehicle": {
3
+ "type": "Rally",
4
+ "mass": 3,
5
+ "inertia_property": 0,
6
+ "inertia_server": 1171.25,
7
+ "shape_size": [
8
+ 62,
9
+ 29
10
+ ],
11
+ "shape_position": [
12
+ 0,
13
+ 0
14
+ ],
15
+ "shape_rotation": 1.5708,
16
+ "gravity_scale": 1,
17
+ "linear_damp": 0,
18
+ "angular_damp": 0,
19
+ "max_contacts_reported": 4,
20
+ "continuous_cd": "Disabled",
21
+ "friction": 0.95,
22
+ "bounce": 0.1,
23
+ "grip": 2.25,
24
+ "steering_speed": 0.4000000059604645,
25
+ "center_steering": true,
26
+ "air_resistance": 0.15,
27
+ "max_velocity": 1000,
28
+ "surfaces": {
29
+ "asphalt": {
30
+ "grip": 1,
31
+ "power": 0.77,
32
+ "steering": 1
33
+ },
34
+ "dirt": {
35
+ "grip": 0.9,
36
+ "power": 0.82,
37
+ "steering": 1
38
+ },
39
+ "ice": {
40
+ "grip": 0.42,
41
+ "power": 0.6,
42
+ "steering": 1
43
+ }
44
+ },
45
+ "wheels": [
46
+ {
47
+ "position": [
48
+ -14,
49
+ -24
50
+ ],
51
+ "steering": true,
52
+ "max_angle_deg": 35,
53
+ "power": 2.5,
54
+ "brake_power": 3.5,
55
+ "handbrake_power": 0
56
+ },
57
+ {
58
+ "position": [
59
+ 14,
60
+ -24
61
+ ],
62
+ "steering": true,
63
+ "max_angle_deg": 35,
64
+ "power": 2.5,
65
+ "brake_power": 3.5,
66
+ "handbrake_power": 0
67
+ },
68
+ {
69
+ "position": [
70
+ -14,
71
+ 24
72
+ ],
73
+ "steering": false,
74
+ "max_angle_deg": 0,
75
+ "power": 3.5,
76
+ "brake_power": 0,
77
+ "handbrake_power": 0
78
+ },
79
+ {
80
+ "position": [
81
+ 14,
82
+ 24
83
+ ],
84
+ "steering": false,
85
+ "max_angle_deg": 0,
86
+ "power": 3.5,
87
+ "brake_power": 0,
88
+ "handbrake_power": 0
89
+ }
90
+ ]
91
+ },
92
+ "physics": {
93
+ "real_precision": "single",
94
+ "contact_recycle_radius": 1,
95
+ "contact_max_separation": 1.5,
96
+ "contact_max_allowed_penetration": 0.3,
97
+ "contact_default_bias": 0.8,
98
+ "solver_iterations": 16
99
+ },
100
+ "track": {
101
+ "name": "Generated track template",
102
+ "tiles": [],
103
+ "path": [],
104
+ "walls": [],
105
+ "physics_shapes": []
106
+ },
107
+ "reset_position": [
108
+ 0,
109
+ 0
110
+ ],
111
+ "reset_rotation": 0
112
+ }
github-simulator/source/assets/scenes/snowmobile_template.json ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "vehicle": {
3
+ "type": "Snowmobile",
4
+ "mass": 1.5,
5
+ "inertia_property": 0,
6
+ "inertia_server": 552.625732421875,
7
+ "shape_size": [
8
+ 65,
9
+ 14.0002
10
+ ],
11
+ "shape_position": [
12
+ -0.000114441,
13
+ 0.499986
14
+ ],
15
+ "shape_rotation": 1.5708,
16
+ "gravity_scale": 1,
17
+ "linear_damp": 0,
18
+ "angular_damp": 0,
19
+ "max_contacts_reported": 4,
20
+ "continuous_cd": "Disabled",
21
+ "friction": 0.95,
22
+ "bounce": 0.1,
23
+ "grip": 2,
24
+ "steering_speed": 0.2,
25
+ "center_steering": true,
26
+ "air_resistance": 0.3,
27
+ "max_velocity": 600,
28
+ "surfaces": {
29
+ "asphalt": {
30
+ "grip": 0.9,
31
+ "power": 0.9,
32
+ "steering": 0.25
33
+ },
34
+ "dirt": {
35
+ "grip": 0.7,
36
+ "power": 0.8,
37
+ "steering": 0.7
38
+ },
39
+ "ice": {
40
+ "grip": 0.4,
41
+ "power": 0.8,
42
+ "steering": 0.7
43
+ }
44
+ },
45
+ "wheels": [
46
+ {
47
+ "position": [
48
+ -10,
49
+ -25
50
+ ],
51
+ "steering": true,
52
+ "max_angle_deg": 35,
53
+ "power": 0,
54
+ "brake_power": 0,
55
+ "handbrake_power": 0
56
+ },
57
+ {
58
+ "position": [
59
+ 10,
60
+ -25
61
+ ],
62
+ "steering": true,
63
+ "max_angle_deg": 35,
64
+ "power": 0,
65
+ "brake_power": 0,
66
+ "handbrake_power": 0
67
+ },
68
+ {
69
+ "position": [
70
+ 0,
71
+ 28
72
+ ],
73
+ "steering": false,
74
+ "max_angle_deg": 0,
75
+ "power": 1.3,
76
+ "brake_power": 1.5,
77
+ "handbrake_power": 0
78
+ },
79
+ {
80
+ "position": [
81
+ 0,
82
+ 22
83
+ ],
84
+ "steering": false,
85
+ "max_angle_deg": 0,
86
+ "power": 1.3,
87
+ "brake_power": 1.5,
88
+ "handbrake_power": 0
89
+ },
90
+ {
91
+ "position": [
92
+ 0,
93
+ 16
94
+ ],
95
+ "steering": false,
96
+ "max_angle_deg": 0,
97
+ "power": 1.3,
98
+ "brake_power": 1.5,
99
+ "handbrake_power": 0
100
+ }
101
+ ]
102
+ },
103
+ "physics": {
104
+ "real_precision": "single",
105
+ "contact_recycle_radius": 1,
106
+ "contact_max_separation": 1.5,
107
+ "contact_max_allowed_penetration": 0.3,
108
+ "contact_default_bias": 0.8,
109
+ "solver_iterations": 16
110
+ },
111
+ "track": {
112
+ "name": "Generated track template",
113
+ "tiles": [],
114
+ "path": [],
115
+ "walls": [],
116
+ "physics_shapes": []
117
+ },
118
+ "reset_position": [
119
+ 0,
120
+ 0
121
+ ],
122
+ "reset_rotation": 0
123
+ }
github-simulator/source/assets/scenes/truck_template.json ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "vehicle": {
3
+ "type": "Truck",
4
+ "mass": 8,
5
+ "inertia_property": 0,
6
+ "inertia_server": 11059.5,
7
+ "shape_size": [
8
+ 124.5,
9
+ 33
10
+ ],
11
+ "shape_position": [
12
+ 0,
13
+ 1.25
14
+ ],
15
+ "shape_rotation": 1.5708,
16
+ "gravity_scale": 1,
17
+ "linear_damp": 0,
18
+ "angular_damp": 0,
19
+ "max_contacts_reported": 4,
20
+ "continuous_cd": "Disabled",
21
+ "friction": 0.95,
22
+ "bounce": 0.1,
23
+ "grip": 12,
24
+ "steering_speed": 0.15,
25
+ "center_steering": true,
26
+ "air_resistance": 0.4,
27
+ "max_velocity": 600,
28
+ "surfaces": {
29
+ "asphalt": {
30
+ "grip": 1,
31
+ "power": 0.85,
32
+ "steering": 1
33
+ },
34
+ "dirt": {
35
+ "grip": 0.65,
36
+ "power": 0.8,
37
+ "steering": 1
38
+ },
39
+ "ice": {
40
+ "grip": 0.15,
41
+ "power": 0.7,
42
+ "steering": 1
43
+ }
44
+ },
45
+ "wheels": [
46
+ {
47
+ "position": [
48
+ -17,
49
+ -51
50
+ ],
51
+ "steering": true,
52
+ "max_angle_deg": 22,
53
+ "power": 0,
54
+ "brake_power": 3.5,
55
+ "handbrake_power": 0
56
+ },
57
+ {
58
+ "position": [
59
+ 17,
60
+ -51
61
+ ],
62
+ "steering": true,
63
+ "max_angle_deg": 22,
64
+ "power": 0,
65
+ "brake_power": 3.5,
66
+ "handbrake_power": 0
67
+ },
68
+ {
69
+ "position": [
70
+ -17,
71
+ 46
72
+ ],
73
+ "steering": false,
74
+ "max_angle_deg": 0,
75
+ "power": 6,
76
+ "brake_power": 3,
77
+ "handbrake_power": 3
78
+ },
79
+ {
80
+ "position": [
81
+ 17,
82
+ 46
83
+ ],
84
+ "steering": false,
85
+ "max_angle_deg": 0,
86
+ "power": 6,
87
+ "brake_power": 3,
88
+ "handbrake_power": 3
89
+ }
90
+ ]
91
+ },
92
+ "physics": {
93
+ "real_precision": "single",
94
+ "contact_recycle_radius": 1,
95
+ "contact_max_separation": 1.5,
96
+ "contact_max_allowed_penetration": 0.3,
97
+ "contact_default_bias": 0.8,
98
+ "solver_iterations": 16
99
+ },
100
+ "track": {
101
+ "name": "Generated track template",
102
+ "tiles": [],
103
+ "path": [],
104
+ "walls": [],
105
+ "physics_shapes": []
106
+ },
107
+ "reset_position": [
108
+ 0,
109
+ 0
110
+ ],
111
+ "reset_rotation": 0
112
+ }
github-simulator/source/docs/cli.md ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CLI reference
2
+
3
+ Run commands from the repository root or an unpacked release archive; both contain `assets/`. `altd-sim --version` identifies the build, including nightly packages. `--threads N` sets the Rayon pool; the default respects CPU affinity. `--mode independent|lockstep` selects native scheduling. Both are global flags. Each command's `--help` is the complete argument reference.
4
+
5
+ `--math-profile proton|win10-fma3|win11-fma3` is also global and selects the game's C runtime arithmetic independently of the CPU or GPU backend. Every build includes all three profiles. Omitted profiles use `proton` on Linux/macOS, `win10-fma3` on Windows before build 26100, and `win11-fma3` on Windows build 26100 and newer. For example, `altd-sim --math-profile win11-fma3 train-scratch ...` trains for current Windows on any supported host. [Math profiles](../math/README.md) describes the version boundaries and implementation.
6
+
7
+ ## Commands and inputs
8
+
9
+ | Command | Required inputs and purpose |
10
+ | --- | --- |
11
+ | `bench` | `--scene`, `--network` with weights, `--spawn-trace`; fixed-population timing and optional state dumps |
12
+ | `train` | `--scene`, weighted `--network`, `--model`, `--output`; evolution of an existing network |
13
+ | `train-scratch` | `--scene`; Xavier initialization, checkpoints, generated batches and candidate export |
14
+ | `evaluate` | `--network`, `--model`, `--suite`, `--report`; evaluate a frozen candidate without evolution |
15
+ | `serve` | No input files; expose CPU or HIP sessions over a loopback WebSocket |
16
+ | `compare-trace` | Positional scene, trace and report; replay controls from a recorded trace |
17
+ | `compare-one-step` | Positional scene, trace and report; restore each recorded frame before stepping |
18
+ | `compare-closed-loop` | Positional scene, trace, network, model and report; network-driven trajectory comparison |
19
+ | `compare-network` | Positional network, trace and report; compare inference with recorded controls |
20
+ | `compare-sensors` | Positional scene, trace, model, trajectory and report; compare sensor observations |
21
+ | `compare-score` | Positional scene, trace and report; compare lap/score statistics |
22
+ | `gpu-info` | Optional `--library`; load the GPU library used by `--gpu`, check its struct layouts and exports and, when a GPU is present, run one kernel on it |
23
+
24
+ Scene and capture paths have no campaign defaults. `bench` and `train-scratch` default their sensor model to `assets/models/rally.json`. Scratch training defaults its sensor/control names to `assets/networks/rally.json`; these templates have no trained weights. The defaults are compiled into the binary, and `run.json` records them as `builtin:<path>`. Formula runs should explicitly use the Formula network/model assets and scene template.
25
+
26
+ A fixed track requires `--spawn-trace` for `train` and `train-scratch`. The trace's first frame supplies the initial pose. Random mode needs only a geometry-free scene template; generated geometry supplies its reset pose. `--spawn-trace` is ignored in random mode. `bench` always requires its explicit spawn trace and supports `--spawn-index`.
27
+
28
+ ```sh
29
+ target/release/altd-sim --threads 8 bench \
30
+ --scene wasm/test/scenarios/rally_mixed.scene.json \
31
+ --spawn-trace wasm/test/scenarios/rally_mixed.spawn.json \
32
+ --network wasm/test/scenarios/benchmark.network.json \
33
+ --population 1024 --ticks 600
34
+
35
+ cargo run --release --offline --example extract_saved_track -- \
36
+ tests/fixtures/generated_saved_track.json example \
37
+ assets/scenes/rally_template.json target/imported-scene.json
38
+ ```
39
+
40
+ The importer takes a saved-track export, a track name, a scene template and an output scene. Its bundled input is generated regression data. `tools/benchmark.py` runs the public generated-track bench repeatedly and keeps every report.
41
+
42
+ ## Local server
43
+
44
+ ```sh
45
+ target/release/altd-sim --threads 8 serve --port 47800
46
+ target/release/altd-sim serve --allow-origin http://127.0.0.1:5173
47
+ ```
48
+
49
+ `serve` binds `127.0.0.1` only and defaults to port 47800. Repeat `--allow-origin` to replace the default `https://drivinglab.jectrum.de` allowlist. The shared Rayon pool uses the global `--threads` flag. The server prints its address, allowed origins, thread count and HIP availability. It is included by the default `server` feature. See [the server protocol](server.md) for handshake checks, session operations, binary frames and resume behavior. Drive Lab client integration is a separate change.
50
+
51
+ ## Scratch training
52
+
53
+ Defaults are 8,192 cars, 50,000 target generations, a 5,400-tick time limit, seed 1, two inference batches and network shape `20,16,16,16,16,12,12,8,5`. The output directory defaults to `runs/example`. Pass short explicit limits for a smoke run. `--init-network` uses supplied weights; `--init-population` imports a compatible checkpoint into a new run directory.
54
+
55
+ Evolution starts with tournament selection of 20 parents, no crossover, four preserved parents, adaptive mutation and no weight decay. Mutation decays geometrically from 0.4 to 0.0125; `--schedule linear` changes that schedule. `--settings FILE` overrides evolution defaults except requested population and scheduled mutation. It accepts a plain object or a nested `settings` object. Explicit elimination switches and `--reward` take precedence over the file.
56
+
57
+ Elimination defaults off. `--eliminate-on-wall` deactivates cars after native wall contact. `--idle-eliminate` deactivates cars after more than 80 consecutive statistics updates with insufficient forward progress. The corresponding `--no-*` switches disable them. Passive physics and pending contacts continue for inactive cars.
58
+
59
+ `--reward distance` selects on `total_score`; `--reward best-lap-time` selects on reciprocal best completed lap time. Without an explicit reward, settings-file rewards remain, with distance as the fallback. Cars without a completed lap have no best-lap signal.
60
+
61
+ ## Generated track batches
62
+
63
+ ```sh
64
+ target/release/altd-sim --threads 8 train-scratch \
65
+ --scene assets/scenes/rally_template.json --track-mode random \
66
+ --random-track-settings assets/random_track_settings.json \
67
+ --tracks-per-generation 3 --track-buffer-size 8 \
68
+ --population 256 --generations 3 --ticks 600 --seed 1729 \
69
+ --save-final-candidate --out-dir runs/example
70
+ ```
71
+
72
+ Each unchanged population evaluates the same track batch, with fresh spawn and a separate tick limit on each track. Selection uses the mean of normalized per-track reward ranks. Reproduction and the mutation schedule advance once after the complete batch. Fixed mode allows one track only. Queue depth changes preparation capacity, not the RNG stream.
73
+
74
+ Without a settings file, track lengths range from 12 to 40 even tiles, all block types are enabled, and one, two or three distinct surfaces are sampled with equal probability into consecutive sections. `assets/random_track_settings.json` is an explicit variant that also samples double-block choices and random/section surface distribution.
75
+
76
+ | Track field | Values |
77
+ | --- | --- |
78
+ | `length` | Fixed even count, or inclusive `{"min": 12, "max": 40}` range; supported grid capacity 4 to 76 |
79
+ | `allow_double` | Boolean or list of boolean choices |
80
+ | `surfaces` | Fixed distinct IDs, list of sets, or `{"pool": [0,1,2], "count": [1,2,3]}` |
81
+ | `distribution` | `0` random per tile, `1` consecutive sections, or choices |
82
+ | `start` | `null` for a fresh position, or fixed grid `[x,y]` |
83
+
84
+ Surface IDs are asphalt 0, dirt 1 and ice 2. Failed generation retries are bounded; invalid or impossible settings fail rather than silently changing a fixed requested length. Track seeds depend on run seed, generation and batch slot independently of reproduction RNG, worker count and backend.
85
+
86
+ ## Outputs, resume and stopping
87
+
88
+ `run.json` stores effective options from the latest invocation. `log.jsonl` has one completed-generation row, with per-track statistics for random batches. `progress.json` publishes current batch progress atomically. Generated track exports live under `tracks/`. `best.json` and `best_laps/` retain the fastest individual lap, even when selection uses another reward.
89
+
90
+ Checkpoints are saved every `--checkpoint-every` generations, default 100, and after normal termination. Zero disables them. `checkpoint.json` identifies a binary population file and stores shape, population, generation and RNG state. `--resume` restores the latest complete generation boundary and removes replayed log/geometry entries. It cannot preserve every mid-tick native contact cache.
91
+
92
+ ```sh
93
+ target/release/altd-sim train-scratch --resume \
94
+ --scene assets/scenes/rally_template.json --track-mode random \
95
+ --random-track-settings assets/random_track_settings.json \
96
+ --population 256 --generations 6 --ticks 600 --out-dir runs/example
97
+ ```
98
+
99
+ `--generations` is the total target, including saved generations. Supply desired options again: omitted options use CLI defaults, except track seed/count retained in checkpoint metadata. An explicit `--tracks-per-generation` replaces the saved count. Preserve scene and track settings to reproduce the original sequence. Saved networks/RNG take precedence over a new `--seed` or `--init-network` during resume.
100
+
101
+ `train-scratch --resume` also retains the checkpoint's math profile; an explicit different `--math-profile` is rejected. Checkpoints predating profiles use `proton`. New `run.json` and checkpoint metadata record `math_profile` so moving a run to another host does not change its arithmetic.
102
+
103
+ Shape must match every checkpoint layer; matching parameter counts alone is insufficient. Population reductions keep the first networks, while expansions repeat them in order. Mutation uses the newly supplied schedule at the restored generation. Earlier checkpoints without shape/population recover those fields from adjacent `run.json` before updating current options.
104
+
105
+ `--save-final-candidate` writes the complete-batch fitness leader before breeding, with lowest-index tie breaking. It preserves exact weights, ordered names, generation and reward metadata without consuming RNG. A completed checkpoint confirms its filename/hash/generation. A candidate without that confirmation is not a completed-stage result.
106
+
107
+ `--stop-score-above`, `--stop-lap-below`, `--stop-lapped-percent` and `--stop-plateau` stop after a qualifying complete batch. Lap targets require a car to lap every track. Plateau counters restart per invocation, using `--plateau-metric lap|score`. A `stop_request` file in the run directory stops after the current batch; a stale file is removed at startup. SIGTERM does not write a final checkpoint. Negative thresholds use `=`, for example `--stop-score-above=-100`.
108
+
109
+ ## Frozen evaluation
110
+
111
+ `evaluate` reads a version-1 suite containing `vehicle`, `options` and distinct `tracks`. Every track specifies relative scene/spawn filenames, their SHA-256 hashes and a finite tick limit. Options specify CPU backend, inference batch count and elimination. The evaluator validates finite network parameters, shape, ordered sensor/control names, vehicle compatibility and input hashes.
112
+
113
+ A suite may set `options.math_profile` to a supported profile. An explicit CLI profile must agree with it. Without either, evaluation detects the host's profile; the report records the effective `math_profile`.
114
+
115
+ Each track gets fresh state, contacts, controls, sensors and statistics. The evaluator never breeds or modifies training checkpoints/logs. Its atomic report records candidate/suite/simulator hashes, per-track score, progress, lap completion, optional best lap, collisions and executed ticks. An interrupted run does not publish a successful report. Existing tests build fully contained suites from fixed generated tracks.
github-simulator/source/docs/design.md ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Design notes
2
+
3
+ The simulator keeps arithmetic and execution ordering visible. A small algebraic rewrite can change rounding, contact choices or evolution, so numeric code and scheduling need stronger regression evidence than ordinary I/O changes.
4
+
5
+ ## Modules and ownership
6
+
7
+ | Directory | Modules | Responsibility |
8
+ | --- | --- | --- |
9
+ | `src/math/` | `godot_math`, `native_math`, `managed_trig`, `double_math`, `pymath`, `vec2` | Captured rounding, runtime arithmetic and vectors |
10
+ | `src/physics/` | `car`, `collision`, `simulation`, `broadphase`, `trace_state` | Forces, passive bodies, contacts, population physics and recorded-state reconstruction |
11
+ | `src/track/` | `world`, `curve`, `bsp`, `segment_grid`, `path_segments`, `random_track`, `training_tracks` | Scene loading, geometry queries, tile generation and bounded preparation queue |
12
+ | `src/nn/` | `network`, `network_simd` | Flat parameters and scalar/AVX2 inference |
13
+ | `src/training/` | `agent`, `lineage`, `sensors`, `stats`, `runner`, `evolution`, `batch_evaluation`, `game_random`, `pyrandom` | Populations, statistics, ordered execution, reproduction and RNG backends |
14
+ | `src/training/` | `session`, `evaluation` | Embedding, generation checkpoints and evaluation reports |
15
+ | `src/gpu/` | `hip`, `simulation` | Runtime-loaded HIP C API, shared layouts and GPU windows |
16
+ | `src/wasm/` | `gpu`, `sim` | JavaScript bindings, WebGPU rays and full simulation |
17
+
18
+ Library imports follow the directory groups. For example, `physics::car::Car` and `track::world::World` describe their roles directly. The training scheduler types remain available from `training`, and embedding sessions live in `training::session`. HIP loading uses `gpu::hip`; portable GPU layouts live in `gpu::simulation`.
19
+
20
+ The CLI entrypoint is `src/bin/altd-sim/main.rs`. Its sibling modules use Cargo's standard binary layout: `args` defines the command contract, `comparisons` produces recorded-trace reports, `bench` and `train` run their workloads, and `scratch` owns run configuration, stopping rules, checkpoint I/O and execution. Scratch parameter checkpoints and Session lineage checkpoints remain separate formats.
21
+
22
+ The GPU validation example uses private modules in `examples/gpu_check/` for math, rays, simulation, evolution and benchmarks. Shared fixture setup and mismatch reporting stay outside the checked numerical implementations.
23
+
24
+ CPU runners own populations and checkpoint serialization. HIP retains uploaded networks and geometry between windows; CPU evolution installs offspring and preserves the CPU/GPU resume format. WebGPU full simulation mirrors state in browser buffers and commits complete windows back to the Session. A failed window leaves the last committed state available for CPU continuation.
25
+
26
+ ## Execution modes
27
+
28
+ Independent mode advances each car through a window, keeping its state and network near the CPU. Lockstep advances the population one tick at a time. Scenes requesting native shared broadphase require tick-major execution to preserve population contact ordering. Generated CLI training disables that shared broadphase so CPU and GPU use independent car physics.
29
+
30
+ Statistics run before driving. Inference uses eight original index-based batches, so compacting inactive work must preserve each car's batch identity. Eliminated cars stop driving and scoring, but passive physics and pending contact accounting continue. The generation limit is checked on the statistics callback, so actual callback counts can slightly exceed a requested tick cap.
31
+
32
+ Raycaster windows keep physics and inference on the CPU and replace only ray queries. HIP and full WebGPU windows run those phases on-device. These paths have distinct scheduling and memory costs; shared arithmetic names do not make their loop structure interchangeable.
33
+
34
+ ## Reproducible tracks and checkpoints
35
+
36
+ Track seeds depend on the run seed, generation and batch slot. Their RNG stream is separate from network reproduction. Bounded queue depth and worker timing therefore do not affect which tracks a generation receives. Each batch shares a track across its population and breeds once after all tracks.
37
+
38
+ Scratch CLI checkpoints save the next generation's population and RNG state at generation boundaries. Session checkpoints can encode parent lineage instead of every child network while retaining exact reconstruction. Those formats have different consumers and should be treated as separate contracts. Neither promises to serialize every mid-tick native contact cache.
39
+
40
+ ## Numerical constraints
41
+
42
+ Keep float32/float64 transitions, reduction order, runtime transcendental behavior and equal-distance query decisions explicit. Disable FMA contraction in HIP builds. Preserve cached Gaussian draws and both captured game RNG streams. Generate shared numeric tables from their bit patterns and check the generated header before building; GPU builds must not rewrite source files.
43
+
44
+ Performance changes need workload medians as well as exact state, checkpoint and RNG comparisons. Profiling paths alter launch behavior, so final timing should use production settings. [Fidelity](fidelity.md) and [performance](performance.md) describe the evidence and its boundaries.
45
+
46
+ ## Formatting and lint checks
47
+
48
+ `cargo fmt --all -- --check` uses the repository's 120-column width. Native checks use `cargo clippy --offline --all-targets -- -D warnings`. Browser bindings use `cargo clippy --offline --target wasm32-unknown-unknown --no-default-features --features wasm -- -D warnings`. Scoped lint allowances explain preserved runtime constants, nonfinite comparisons, indexed numerical loops and packed GPU layouts. They do not relax warnings for unrelated code.
49
+
50
+ ## Optimizers
51
+
52
+ `TrainingRunner` evaluates a whole population, scores it with `reward_values` (ranks within the generation), and hands the cars and scores to an `Optimizer` (`src/training/optimizer.rs`), which returns the next population. `settings.algorithm` chooses one; the runner replaces its optimizer when the name changes, and the new one continues from the cars it is next given.
53
+
54
+ - `"ga"` is the default: selection, crossover, mutation and decay as in `evolution.rs`. Its random draws, and so every GA checkpoint and test, are unchanged.
55
+ - `"ars"` (`src/training/ars.rs`) is Augmented Random Search V2-t with an elite pool. It keeps a search point `theta`; the population is the elites, `theta`, and antithetic probes `theta ± nu·d`, and the scores steer one step `alpha/(b·nu)·Σ(r+ − r−)·d` over the best `top_frac` of the pairs. The step is skipped when the scores are all alike. Elites are the best distinct cars of the last population, old elites included; they are re-run unchanged every generation, so ranks from different generations are never compared. Settings live under `settings.ars` (`nu`, `alpha`, `top_frac`, `elite_count`, `max_weight`), and `population` must be at least `elite_count + 3`.
56
+
57
+ Rewards stay rank based for every algorithm. ARS therefore behaves like rank-based OpenAI-ES with top-b pair selection rather than reference ARS on raw magnitudes.
58
+
59
+ Session checkpoints record how a population was made, so each optimizer has its own format: `ALTDCKP2` for GA parent lineage and `ALTDCKP3` for ARS, which stores `theta`, the elite pool, the sampling settings and the generator before the directions were drawn. Restoring one resamples the population and the directions exactly. A full population checkpoint (`ALTDCKP1`, JSON checkpoints, scratch CLI checkpoints) carries no search state, so ARS continues from the best restored car. A changed population size does the same.
github-simulator/source/docs/fidelity.md ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Fidelity and regression coverage
2
+
3
+ The simulator targets the captured behavior of AI Learns To Drive. Physics mixes float32 state with float64 calculations, and callbacks, contact caches and inference batches have observable ordering. Matching formulas alone does not reproduce those results.
4
+
5
+ Numerical implementations retain operation order, intermediate precision, rounding boundaries, tie-breaking and RNG consumption. Scalar and AVX2 inference accumulate in the same order. HIP builds disable multiply-add contraction and require correctly rounded float32 division and square root. Spatial acceleration preserves traversal and first-equal-result decisions. A change to any of these rules needs exact comparisons before performance measurements.
6
+
7
+ ## What the tests establish
8
+
9
+ The game captures in `tests/fixtures/` exercise native vehicle transitions, population lifecycle, contact/reset behavior, runtime math, sensors, statistics and training. Their track geometry comes from synthetic cases or generated tracks. Campaign tracks are not distributed. Short recorded vehicle-state samples retain positions, contact points and scalar sensor outputs, with collider exports removed. They are finite oracle samples. Older traces sometimes omit body matrices or contact caches, so restoring their visible state cannot prove complete native state reconstruction.
10
+
11
+ Packed-game captures use Godot 4.3 build 77dcf97d8 and Windows .NET 8.0.2 where the fixture records that setup. The capture harnesses and raw logs are not distributed. Fixture provenance names the runtime and harness without presenting unavailable paths as public tooling. Packed-sine lookup tables were extracted from Windows .NET 8.0.2 coreclr.dll; their source hash remains in the table header, and the extraction script is not distributed.
12
+
13
+ Generated-track game captures cover parked TileMaps, native wall insertion order, single-vehicle and population contacts, live resizing, redraws, coalesced creation, Truck and Snowmobile behavior, terrain grip and ordered/path sensors. Their `recipe` fields select a fixed generator seed, generation and slot using the public settings and vehicle template. Tests rebuild the geometry and apply explicit scene overrides; output states, curve samples and sensor values come from the game. These captures are separate from simulator-generated goldens.
14
+
15
+ Generated-track goldens record the simulator's current results for controlled contacts, restored states, scoring, population redraw and Session checkpoints. They detect regressions; they are not independent game captures. Fixtures and their origins are documented in [the fixture README](../tests/fixtures/README.md). Differential tests compare accelerated queries with brute force, and CLI tests cover settings, checkpoints, resume, generated batches and evaluation.
16
+
17
+ A historical diagnostic comparison against a captured campaign run matched 1,393 of 1,398 one-step frames exactly. The remaining five differed in angular velocity, rotation or velocity. Network inference matched all 1,398 frames with zero error. These results predate the public generated-track replacements and describe that capture, not every scene or closed-loop run. The campaign run and its geometry are not public inputs.
18
+
19
+ ## Platform limits
20
+
21
+ On x86, native `engine_sin_cos` uses x87 `FSINCOS`. Its large-argument reduction preserves the extended pi constant across x87 precision modes. Tests cover 24, 53 and 64-bit precision and check that the caller's control word is unchanged. The portable fdlibm-based implementation is validated against x87 over float32 arguments with absolute value at most 16; the regular suite samples the domain and an ignored exhaustive test scans it. Larger arguments are not covered by that claim.
22
+
23
+ Nonfinite float32 trig outputs retain the captured NaN signs and payloads on every target. Scalar and engine routines quiet NaNs. Managed `SinCos` preserves the sine input bits and clears the sign while quieting cosine. Both infinities produce the captured x86 indefinite NaN, `0xffc00000`.
24
+
25
+ The distance-from-wall sensor also returns the x86 indefinite for zero divided by zero and infinity divided by infinity, including float32 conversion boundaries. Quotients from NaN operands retain their payloads.
26
+
27
+ Seeded Python Xavier tests keep the captured parameter checksum on Linux and Windows. On macOS, `cargo test --lib nn::network::tests::xavier_matches_python -- --exact` requires `python3` and compares every parameter bit, the MT state and the cached Gaussian draw with CPython on that host. This checks host parity without assuming libSystem and glibc round identically.
28
+
29
+ Seeded initialization uses host float64 transcendental functions. Equal RNG draws can therefore produce different parameter bits across native hosts. Session regressions compare both execution modes with the legacy `TrainingRunner` on each host and require exact checkpoint replay. Stored Session snapshot and checkpoint goldens remain exact comparisons on their capture platform, Linux x86_64 GNU.
30
+
31
+ Native and WASM use different host implementations for some float64 transcendental operations. The public generated Formula state scenario matched 2,773 native values, while expanded snapshots found 2,285 native differences per scheduling mode: 703 controls, 405 metrics and 1,177 checkpoint bytes. Serial and threaded WASM had the same native differences and matched each other in all 253,341 checked values per mode, including the optional score aggregates. A passing state-only comparison does not imply exact expanded native parity. Counts depend on the scenario and checkpoint layout.
32
+
33
+ The RX 7900 XTX WebGPU raycaster matched 4,000 random rays, 4,000 nearest-wall points and 119,743 in-simulation rays in that baseline. WGSL floating-point behavior, subnormal handling and NaN payloads still vary by device. Runtime verification counts discrepancies without replacing GPU results. See [WASM notes](wasm.md) for the comparison layers.
34
+
35
+ ## Checking a change
36
+
37
+ Run native tests in debug and release, regenerate observations to temporary outputs, and compare exact float bits, RNG state and checkpoint bytes. Exclude only named timing and path metadata when comparing reports. Keep generated references separate from captured oracle data. Measure throughput only after those checks pass.
github-simulator/source/docs/gpu.md ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HIP implementation notes
2
+
3
+ The HIP library runs statistics, sensing, network inference and vehicle physics for independent cars. It also supports GPU turnover through the runner API. Population mean and novelty run on the GPU with reductions in the original order; that turnover path keeps offspring weights uploaded for the next generation. Rust owns resets and checkpoints.
4
+
5
+ The same sources build for NVIDIA GPUs with `nvcc` (`gpu/build-cuda.*`, `gpu/sim/compat.h`); this page applies to both. NVIDIA warps are 32 lanes and use the `*_sync` warp operations with a full mask, so every lane of a wave must reach `reserve`, `append` and the active-count ballot, as on AMD. NVIDIA arithmetic returns its canonical NaN where the CPU returns a NaN operand, so those results are built explicitly on both vendors (`altd_nan_operand`, `altd_invalid`).
6
+
7
+ ## HIP sessions
8
+
9
+ Native `SessionOptions` accepts `backend: "hip"` as well as the default `"cpu"`. The same option is available to [local server](server.md) clients. WASM sessions reject HIP. The process opens the HIP library once and allows one HIP session at a time; a session that cannot use HIP reports CPU through `backend()` and a reason through `backend_note()`.
10
+
11
+ HIP sessions drive through the existing GPU window and generation paths, then read car and agent state back into the CPU runner. Readers, metrics and checkpoints therefore use the same runner state as CPU sessions. The simulator grows when a restored or started population exceeds its capacity, and track replacement uploads a new GPU world.
12
+
13
+ Before the first window after a start or restore, a session compares 12 CPU and HIP ticks exactly from a backed-up state. It restores that state before real driving. Track replacement also requires a new comparison. A mismatch or verification error switches the session to CPU; a later HIP window error returns an error without retrying.
14
+
15
+ Session turnover uses CPU `next_generation_traced`, which retains the parent records needed by format-2 checkpoints. The next HIP window uploads the new networks. Start, restore and network replacement also invalidate the uploaded network cache. This differs from the runner's GPU turnover path, which does not produce those parent records.
16
+
17
+ Native CPU and HIP sessions share checkpoint formats with WASM. Native and WASM results have known last-bit differences; switching engines preserves the checkpoint but may change later training.
18
+
19
+ ## Kernels and validation
20
+
21
+ Kernels compact live work into eight original inference batches. Only due batches run sensing and inference. A separate physics list retains inactive cars until they settle exactly. Windows replay a HIP graph over the 24-tick statistics/inference period, with direct launches for remainders. Compatible windows reuse the graph; changing track, schedule, shape, population or output mapping invalidates it.
22
+
23
+ The split physics path separates collision phases and preserves ordered per-car contact generation. `ALTD_GPU_SPLIT=0` selects the fused alternative. `ALTD_GPU_GRAPH=0` bypasses graphs. These are differential validation options, not promises of better performance.
24
+
25
+ The network forward path uses double parameters and adds products in input order. This can dominate at large populations on GPUs with low double-precision throughput. Host buffers retain mutation noise and upload packing space. A 262,144-car population with 1,661 parameters requires about 3.24 GiB for that host double buffer alone; device memory, car state and checkpoint populations are additional.
26
+
27
+ `gpu::simulation` mirrors C layouts explicitly and exports/imports canonical car and agent state. The loader resolves the shared library at runtime, so ordinary CPU builds do not link ROCm. Rebuild the Rust executable and library together when their C API or layouts change.
28
+
29
+ Historical CPU/HIP checks cover primitive math, queries, sensors, inference, physics, turnover, batch wrapping, uneven populations, simulator reuse and resumed training. They compare flags, exact values, RNG state and population bytes. These finite checks do not establish exactness for every scene or device. Run the generated-input checks on your own GPU using [the GPU guide](../gpu/README.md). [Performance notes](performance.md) retain measured results and timing boundaries.
30
+
31
+ On Linux, `python3 gpu/test_constructor_cleanup.py` checks the built library's constructor failure paths without allocating GPU memory. It requires a C++17 compiler and the ROCm runtime. A temporary HIP shim injects allocation and copy failures, then checks that device buffers, host buffers, events and streams are released. The ROCm CI job runs this check without a GPU.
github-simulator/source/docs/math-profiles.html ADDED
@@ -0,0 +1,415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Math profiles: supporting Windows UCRT variants on every backend</title>
6
+ <style>
7
+ :root {
8
+ --bg: #0f1216; --panel: #171b21; --ink: #e6e9ee; --mute: #98a2b3; --line: #2a313b;
9
+ --musl: #3b82f6; --ucrt: #f59e0b; --ucrt2: #ef4444; --ok: #22c55e; --gen: #a855f7; --hand: #f97316;
10
+ }
11
+ * { box-sizing: border-box; }
12
+ body { margin: 0; background: var(--bg); color: var(--ink); font: 15px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; }
13
+ main { max-width: 1240px; margin: 0 auto; padding: 32px 28px 64px; }
14
+ h1 { font-size: 28px; margin: 0 0 6px; }
15
+ h2 { font-size: 21px; margin: 44px 0 12px; padding-bottom: 6px; border-bottom: 1px solid var(--line); }
16
+ h3 { font-size: 16px; margin: 22px 0 8px; }
17
+ p, li { color: #cfd5de; }
18
+ .sub { color: var(--mute); margin: 0 0 22px; }
19
+ code { font: 13px ui-monospace, "SF Mono", Menlo, Consolas, monospace; background: #1f252d; padding: 1px 5px; border-radius: 4px; color: #e2e8f0; }
20
+ .tldr { background: var(--panel); border: 1px solid var(--line); border-left: 4px solid var(--ok); border-radius: 8px; padding: 14px 18px; }
21
+ .tldr ul { margin: 6px 0 0; padding-left: 20px; }
22
+ table { border-collapse: collapse; width: 100%; margin: 10px 0; font-size: 14px; }
23
+ th, td { border: 1px solid var(--line); padding: 7px 9px; text-align: left; vertical-align: top; }
24
+ th { background: #1b2027; color: #dbe1ea; font-weight: 600; }
25
+ td { background: var(--panel); }
26
+ .matrix td { text-align: center; font: 13px ui-monospace, Menlo, monospace; }
27
+ .matrix td:first-child, .matrix th:first-child { text-align: left; font-family: system-ui, sans-serif; }
28
+ .v0 { background: #14243f !important; color: #93c5fd; }
29
+ .v1 { background: #3a2a0c !important; color: #fcd34d; }
30
+ .v2 { background: #3f1a12 !important; color: #fdba74; }
31
+ .v3 { background: #3b1220 !important; color: #fda4af; }
32
+ .v4 { background: #2a1840 !important; color: #d8b4fe; }
33
+ .na { color: #5b6574 !important; }
34
+ .grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
35
+ .card { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 14px 16px; }
36
+ .card h3 { margin-top: 0; }
37
+ .figure { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 10px; margin: 10px 0 6px; overflow-x: auto; }
38
+ .caption { color: var(--mute); font-size: 13px; margin: 4px 2px 0; }
39
+ .legend { display: flex; gap: 16px; flex-wrap: wrap; font-size: 13px; color: var(--mute); margin: 6px 2px; }
40
+ .sw { display: inline-block; width: 12px; height: 12px; border-radius: 3px; vertical-align: -1px; margin-right: 5px; }
41
+ .bad { color: #fca5a5; } .good { color: #86efac; }
42
+ .tag { display: inline-block; font-size: 11px; padding: 1px 7px; border-radius: 10px; border: 1px solid var(--line); color: var(--mute); margin-left: 6px; }
43
+ ol.phases > li { margin-bottom: 10px; }
44
+ svg text { font-family: system-ui, -apple-system, "Segoe UI", sans-serif; }
45
+ </style>
46
+ </head>
47
+ <body>
48
+ <main>
49
+
50
+ <h1>Math profiles</h1>
51
+ <p class="sub">Historical design draft for issue #7, based on code at <code>e96a2e8</code> and the 56-build UCRT survey. The original proposal and diagrams below are retained as design history.</p>
52
+
53
+ <div class="tldr">
54
+ <strong>Implemented profiles</strong>
55
+ <p>The implementation supports <code>proton</code>, <code>win10-fma3</code> and <code>win11-fma3</code>. Every native/WASM/GPU build includes all three. Defaults detect the host's operating system and version; resumed runs retain their saved profile. SSE2 variants are outside the supported scope.</p>
56
+ <p>The new Windows kernels share restricted C source in <code>math/kernels/ucrt.h</code>, translated to Rust/WASM and WGSL and included by HIP/CUDA. The established musl and AVX2 ports remain. WebGPU pipeline creation specializes the generated module for the session's profile. <a href="../math/README.md">Current kernel, fixture and verification documentation</a> describes the implemented architecture.</p>
57
+ </div>
58
+
59
+ <h2>Original proposal</h2>
60
+
61
+ <div class="tldr">
62
+ <strong>TL;DR</strong>
63
+ <ul>
64
+ <li>Make the platform's C runtime a <b>runtime parameter</b> (<code>MathProfile</code>) instead of a fact baked into every backend. Five profiles cover everything we measured: Proton and four Windows ones.</li>
65
+ <li>Write each math variant <b>once</b>, in the restricted C subset that <code>gpu/sim/*.h</code> already uses, and <b>generate</b> the Rust, AVX2, HIP and WGSL versions from it. This extends the existing <code>gen-sim.py</code> / <code>gen_tables.py</code> pipeline instead of adding a new one.</li>
66
+ <li>Use <b>exception tables</b> where exhaustive proof is possible (<code>sinf</code>/<code>cosf</code>: every float32 can be checked, as <code>engine_exceptions.h</code> already does for x87). Use <b>algorithm ports</b> for the rest (<code>atan2f</code>, <code>exp</code>, <code>log</code>, <code>pow</code>, <code>tanh</code>).</li>
67
+ <li>Verify each (profile × backend) pair against golden vectors captured from genuine DLLs under Wine. Only the output bits are stored; the DLLs are not.</li>
68
+ <li>The default stays <code>proton</code>, so existing goldens, checkpoints and behaviour don't change until a user chooses another profile.</li>
69
+ </ul>
70
+ </div>
71
+
72
+ <h2>1. What has to be supported (measured)</h2>
73
+ <p>Results of the 56-build survey: genuine x64 <code>ucrtbase.dll</code> builds signed 2020–2026, each tested with FMA3 on and off. Each cell shows which implementation a profile needs. A different colour means a different implementation, i.e. not bit-identical.</p>
74
+
75
+ <table class="matrix">
76
+ <tr><th>Function (who uses it)</th><th>proton</th><th>win10-fma3<br><span class="tag">Win10, Win11 ≤23H2</span></th><th>win10-sse2</th><th>win11-fma3<br><span class="tag">Win11 24H2+</span></th><th>win11-sse2</th><th>impls</th></tr>
77
+ <tr><td><code>sinf</code> (ray directions, vision)</td><td class="v0">musl</td><td class="v1">ucrt·F</td><td class="v2">ucrt·S</td><td class="v1">ucrt·F</td><td class="v2">ucrt·S</td><td>3</td></tr>
78
+ <tr><td><code>cosf</code> (ray directions, vision)</td><td class="v0">musl</td><td class="v1">ucrt·F</td><td class="v2">ucrt·S</td><td class="v1">ucrt·F</td><td class="v2">ucrt·S</td><td>3</td></tr>
79
+ <tr><td><code>atan2f</code> (rotation, sensors, slip, tracks)</td><td class="v0">musl</td><td class="v1">ucrt</td><td class="v1">ucrt</td><td class="v1">ucrt</td><td class="v1">ucrt</td><td>2</td></tr>
80
+ <tr><td><code>exp</code> (tyre grip)</td><td class="v0">musl</td><td class="v1">A·F</td><td class="v2">S</td><td class="v3">B·F</td><td class="v2">S</td><td>4</td></tr>
81
+ <tr><td><code>log</code> (Box-Muller, evolution; CPU only)</td><td class="v0">musl</td><td class="v1">ucrt</td><td class="v1">ucrt</td><td class="v1">ucrt</td><td class="v1">ucrt</td><td>2</td></tr>
82
+ <tr><td><code>pow</code> (input easing, novelty)</td><td class="v0">musl</td><td class="v1">A·F</td><td class="v2">A·S</td><td class="v3">B·F</td><td class="v4">B·S<sup>†</sup></td><td>5</td></tr>
83
+ <tr><td><code>tanh</code> (network activation)</td><td class="v0">musl</td><td class="v1">A</td><td class="v1">A</td><td class="v3">B·F</td><td class="v1">A</td><td>3</td></tr>
84
+ <tr><th>Total implementations</th><th colspan="5" style="text-align:left">today: 7 (one per function) · needed: <b>22</b></th><th>22</th></tr>
85
+ </table>
86
+ <p class="caption">A = Windows 10 / Win11 ≤23H2 behaviour, B = Win11 24H2+ behaviour, F = FMA3 path, S = SSE2 path. Profile-independent and unchanged: x87 <code>engine_sin_cos</code>, .NET <code>MathF.SinCos</code> (<code>managed_sin_cos</code>). <sup>†</sup>B·F and B·S <code>pow</code> agree on all game-range inputs and differ on 10 of 4M wide inputs.</p>
87
+
88
+ <div class="grid2">
89
+ <div class="card">
90
+ <h3>Where each function runs today</h3>
91
+ <table>
92
+ <tr><th></th><th>CPU scalar</th><th>AVX2</th><th>HIP</th><th>WebGPU</th></tr>
93
+ <tr><td>sinf / cosf</td><td>✓</td><td class="na">–</td><td>✓</td><td>✓</td></tr>
94
+ <tr><td>atan2f</td><td>✓</td><td class="na">–</td><td>✓</td><td>✓</td></tr>
95
+ <tr><td>exp, pow</td><td>✓</td><td class="na">–</td><td>✓</td><td>✓</td></tr>
96
+ <tr><td>log</td><td>✓</td><td class="na">–</td><td class="na">–</td><td class="na">–</td></tr>
97
+ <tr><td>tanh</td><td>✓</td><td>✓ <code>tanh4</code></td><td>✓</td><td>✓</td></tr>
98
+ </table>
99
+ <p class="caption">WASM CPU compiles the same Rust as native scalar. <code>log</code> only runs in reproduction, which stays on the CPU in HIP and WebGPU sessions.</p>
100
+ </div>
101
+ <div class="card">
102
+ <h3>Why a naive port doesn't scale</h3>
103
+ <p>Hand-porting 22 implementations to every backend would mean 22 Rust + 20 HIP + 3 AVX2 bit-exact ports, each needing review, plus 22 generated WGSL functions to validate. Today 7 Rust + 6 HIP + 1 AVX2 are maintained by hand.</p>
104
+ <p>The FMA3 variants also need <b>explicit fused multiply-adds</b>. HIP builds deliberately use <code>-ffp-contract=off</code>, and WGSL has no <code>f64</code> at all (<code>f64.wgsl</code> is software binary64). So "just enable FMA" isn't an option anywhere.</p>
105
+ </div>
106
+ </div>
107
+
108
+ <h2>2. Current state</h2>
109
+ <div class="figure">
110
+ <svg viewBox="0 0 1200 470" width="100%" role="img" aria-label="Current architecture">
111
+ <defs>
112
+ <marker id="ah" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#98a2b3"/></marker>
113
+ <marker id="ahh" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#f97316"/></marker>
114
+ <marker id="ahg" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#a855f7"/></marker>
115
+ </defs>
116
+ <!-- column labels -->
117
+ <text x="20" y="28" fill="#98a2b3" font-size="13">SPECIFICATION (one fixed platform: Proton / musl)</text>
118
+ <text x="470" y="28" fill="#98a2b3" font-size="13">BACKEND IMPLEMENTATIONS</text>
119
+ <text x="900" y="28" fill="#98a2b3" font-size="13">CALLERS (hard-wired)</text>
120
+
121
+ <!-- Rust spec -->
122
+ <rect x="20" y="45" width="370" height="170" rx="8" fill="#14243f" stroke="#3b82f6"/>
123
+ <text x="36" y="70" fill="#e6e9ee" font-size="15" font-weight="600">Rust scalar = the reference</text>
124
+ <text x="36" y="94" fill="#cfd5de" font-size="13">src/math/native_math.rs — sinf, cosf, atan2f</text>
125
+ <text x="36" y="114" fill="#cfd5de" font-size="13">src/math/double_math.rs — exp, log, pow</text>
126
+ <text x="36" y="134" fill="#cfd5de" font-size="13">src/nn/network.rs — game_tanh / game_expm1</text>
127
+ <text x="36" y="154" fill="#cfd5de" font-size="13">src/math/double_math_tables.rs</text>
128
+ <text x="36" y="182" fill="#93c5fd" font-size="12.5">Free functions; no notion of "which platform".</text>
129
+ <text x="36" y="200" fill="#93c5fd" font-size="12.5">Also compiled for WASM CPU.</text>
130
+
131
+ <!-- AVX2 -->
132
+ <rect x="470" y="45" width="360" height="62" rx="8" fill="#2a1d10" stroke="#f97316"/>
133
+ <text x="486" y="70" fill="#e6e9ee" font-size="14" font-weight="600">AVX2 src/nn/network_simd.rs</text>
134
+ <text x="486" y="92" fill="#fdba74" font-size="12.5">tanh4 / expm1_tanh — hand port of game_tanh</text>
135
+
136
+ <!-- HIP -->
137
+ <rect x="470" y="127" width="360" height="88" rx="8" fill="#2a1d10" stroke="#f97316"/>
138
+ <text x="486" y="152" fill="#e6e9ee" font-size="14" font-weight="600">HIP gpu/sim/math.h</text>
139
+ <text x="486" y="174" fill="#fdba74" font-size="12.5">native_sin/cos/atan2, dexp, dpow, game_tanh</text>
140
+ <text x="486" y="194" fill="#fdba74" font-size="12.5">hand port; -ffp-contract=off</text>
141
+
142
+ <!-- tables gen -->
143
+ <rect x="470" y="235" width="360" height="50" rx="8" fill="#1f1530" stroke="#a855f7"/>
144
+ <text x="486" y="258" fill="#e6e9ee" font-size="14" font-weight="600">gpu/gen_tables.py → double_tables.h</text>
145
+ <text x="486" y="276" fill="#d8b4fe" font-size="12.5">bit-copied from Rust tables</text>
146
+
147
+ <!-- WGSL -->
148
+ <rect x="470" y="305" width="360" height="88" rx="8" fill="#1f1530" stroke="#a855f7"/>
149
+ <text x="486" y="330" fill="#e6e9ee" font-size="14" font-weight="600">WebGPU src/wasm/sim/generated.wgsl</text>
150
+ <text x="486" y="352" fill="#d8b4fe" font-size="12.5">wasm/gen-sim.py transpiles gpu/sim/*.h (pycparser)</text>
151
+ <text x="486" y="372" fill="#d8b4fe" font-size="12.5">doubles via f64.wgsl (software binary64)</text>
152
+
153
+ <!-- arrows spec -> backends -->
154
+ <path d="M390,90 C430,90 430,76 466,76" stroke="#f97316" stroke-width="2" fill="none" marker-end="url(#ahh)"/>
155
+ <path d="M390,150 C430,150 430,170 466,170" stroke="#f97316" stroke-width="2" fill="none" marker-end="url(#ahh)"/>
156
+ <path d="M390,165 C430,190 430,255 466,258" stroke="#a855f7" stroke-width="2" fill="none" marker-end="url(#ahg)"/>
157
+ <path d="M650,215 L650,232" stroke="#a855f7" stroke-width="2" fill="none" marker-end="url(#ahg)"/>
158
+ <path d="M845,170 C880,170 880,250 845,335" stroke="#a855f7" stroke-width="2" fill="none" marker-end="url(#ahg)" />
159
+ <text x="862" y="260" fill="#d8b4fe" font-size="11.5">gen</text>
160
+
161
+ <!-- callers -->
162
+ <rect x="900" y="45" width="280" height="170" rx="8" fill="#171b21" stroke="#2a313b"/>
163
+ <text x="916" y="70" fill="#e6e9ee" font-size="14" font-weight="600">21 Rust call sites</text>
164
+ <text x="916" y="92" fill="#cfd5de" font-size="12.5">physics/car.rs, training/*.rs,</text>
165
+ <text x="916" y="110" fill="#cfd5de" font-size="12.5">track/curve.rs, random_track.rs …</text>
166
+ <text x="916" y="134" fill="#cfd5de" font-size="12.5">call native_math::atan2(...) etc.</text>
167
+ <text x="916" y="158" fill="#cfd5de" font-size="12.5">gpu/sim step/sensors/stats/track.h</text>
168
+ <text x="916" y="176" fill="#cfd5de" font-size="12.5">call native_* / dexp / dpow</text>
169
+ <text x="916" y="200" fill="#fca5a5" font-size="12.5">→ no way to choose a platform</text>
170
+
171
+ <!-- problems box -->
172
+ <rect x="20" y="240" width="420" height="215" rx="8" fill="#2a1416" stroke="#ef4444"/>
173
+ <text x="36" y="266" fill="#fecaca" font-size="15" font-weight="600">Limits for issue #7</text>
174
+ <text x="36" y="292" fill="#fca5a5" font-size="13">• One implementation per function, compiled in.</text>
175
+ <text x="36" y="314" fill="#fca5a5" font-size="13">• Rust and HIP are two hand-maintained sources;</text>
176
+ <text x="48" y="332" fill="#fca5a5" font-size="13">AVX2 is a third for tanh.</text>
177
+ <text x="36" y="354" fill="#fca5a5" font-size="13">• FMA is forbidden everywhere (contract=off);</text>
178
+ <text x="48" y="372" fill="#fca5a5" font-size="13">no explicit fma primitive in Rust/HIP/WGSL code.</text>
179
+ <text x="36" y="394" fill="#fca5a5" font-size="13">• Golden fixtures describe Proton only.</text>
180
+ <text x="36" y="416" fill="#fca5a5" font-size="13">• Adding Windows naïvely = 22 × (Rust + HIP)</text>
181
+ <text x="48" y="434" fill="#fca5a5" font-size="13">+ 3 AVX2 bit-exact hand ports.</text>
182
+
183
+ <!-- legend -->
184
+ <rect x="470" y="410" width="12" height="12" fill="#f97316"/><text x="488" y="421" fill="#98a2b3" font-size="12.5">hand-written port</text>
185
+ <rect x="610" y="410" width="12" height="12" fill="#a855f7"/><text x="628" y="421" fill="#98a2b3" font-size="12.5">generated</text>
186
+ <rect x="720" y="410" width="12" height="12" fill="#3b82f6"/><text x="738" y="421" fill="#98a2b3" font-size="12.5">source of truth</text>
187
+ </svg>
188
+ </div>
189
+ <p class="caption">Rust is the reference; HIP is a second hand-written copy; WGSL is generated from HIP; AVX2 has a third copy of tanh. Each backend's math is fixed when it is compiled.</p>
190
+
191
+ <h2>3. Proposed architecture</h2>
192
+ <div class="figure">
193
+ <svg viewBox="0 0 1200 760" width="100%" role="img" aria-label="Proposed architecture">
194
+ <defs>
195
+ <marker id="a2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#a855f7"/></marker>
196
+ <marker id="a3" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#22c55e"/></marker>
197
+ <marker id="a4" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#98a2b3"/></marker>
198
+ </defs>
199
+
200
+ <!-- Layer labels -->
201
+ <text x="20" y="26" fill="#98a2b3" font-size="13">① EVIDENCE (dev-only, Wine)</text>
202
+ <text x="330" y="26" fill="#98a2b3" font-size="13">② SINGLE SOURCE</text>
203
+ <text x="690" y="26" fill="#98a2b3" font-size="13">③ GENERATED BACKENDS</text>
204
+ <text x="990" y="26" fill="#98a2b3" font-size="13">④ RUNTIME SELECTION</text>
205
+
206
+ <!-- Evidence -->
207
+ <rect x="20" y="40" width="280" height="250" rx="8" fill="#171b21" stroke="#2a313b"/>
208
+ <text x="36" y="64" fill="#e6e9ee" font-size="14" font-weight="600">UCRT reference harness</text>
209
+ <text x="36" y="86" fill="#cfd5de" font-size="12.5">tools/ucrt-ref (from ucrt_harness.c)</text>
210
+ <text x="36" y="104" fill="#cfd5de" font-size="12.5">genuine DLL × FMA3 on/off, under Wine</text>
211
+ <text x="36" y="130" fill="#e6e9ee" font-size="13" font-weight="600">Outputs (committed):</text>
212
+ <text x="36" y="150" fill="#cfd5de" font-size="12.5">• golden vectors per profile</text>
213
+ <text x="48" y="168" fill="#cfd5de" font-size="12.5">tests/fixtures/math_profiles/*.bin</text>
214
+ <text x="36" y="188" fill="#cfd5de" font-size="12.5">• exhaustive sinf/cosf scans</text>
215
+ <text x="48" y="206" fill="#cfd5de" font-size="12.5">→ exception tables</text>
216
+ <text x="36" y="226" fill="#cfd5de" font-size="12.5">• build → profile map (profiles.toml)</text>
217
+ <text x="36" y="252" fill="#fcd34d" font-size="12.5">DLLs are never committed;</text>
218
+ <text x="36" y="270" fill="#fcd34d" font-size="12.5">only versions, hashes and output bits.</text>
219
+
220
+ <!-- Single source -->
221
+ <rect x="330" y="40" width="330" height="330" rx="8" fill="#14243f" stroke="#3b82f6"/>
222
+ <text x="346" y="64" fill="#e6e9ee" font-size="14" font-weight="600">math/kernels/ (restricted C subset)</text>
223
+ <text x="346" y="86" fill="#93c5fd" font-size="12.5">the subset gen-sim.py already parses</text>
224
+ <rect x="346" y="98" width="298" height="122" rx="6" fill="#0f1a2e" stroke="#2b4a7a"/>
225
+ <text x="358" y="118" fill="#cfd5de" font-size="12.5">musl/ sinf cosf atan2f exp log pow tanh</text>
226
+ <text x="358" y="138" fill="#cfd5de" font-size="12.5">ucrt/ atan2f log</text>
227
+ <text x="358" y="158" fill="#cfd5de" font-size="12.5">ucrt/ exp_{a_fma,b_fma,sse2}</text>
228
+ <text x="358" y="178" fill="#cfd5de" font-size="12.5">ucrt/ pow_{a_fma,a_sse2,b_fma,b_sse2}</text>
229
+ <text x="358" y="198" fill="#cfd5de" font-size="12.5">ucrt/ tanh_{a,b_fma} sincos = musl + table</text>
230
+ <text x="346" y="244" fill="#e6e9ee" font-size="13" font-weight="600">Primitives every backend must provide</text>
231
+ <text x="346" y="264" fill="#cfd5de" font-size="12.5">add/sub/mul/div (correctly rounded),</text>
232
+ <text x="346" y="282" fill="#cfd5de" font-size="12.5">fma() explicit and exact, bits casts, select()</text>
233
+ <text x="346" y="306" fill="#e6e9ee" font-size="13" font-weight="600">profiles.toml</text>
234
+ <text x="346" y="326" fill="#cfd5de" font-size="12.5">profile → variant per function (the matrix in §1)</text>
235
+ <text x="346" y="346" fill="#cfd5de" font-size="12.5">+ the UCRT builds each profile stands for</text>
236
+
237
+ <!-- Generator -->
238
+ <rect x="330" y="400" width="330" height="80" rx="8" fill="#1f1530" stroke="#a855f7"/>
239
+ <text x="346" y="424" fill="#e6e9ee" font-size="14" font-weight="600">wasm/gen-math.py</text>
240
+ <text x="346" y="446" fill="#d8b4fe" font-size="12.5">reuses gen-sim.py's pycparser front end;</text>
241
+ <text x="346" y="464" fill="#d8b4fe" font-size="12.5">4 emitters; CI runs it with --check</text>
242
+ <path d="M495,370 L495,396" stroke="#a855f7" stroke-width="2" marker-end="url(#a2)"/>
243
+
244
+ <!-- evidence -> source -->
245
+ <path d="M300,190 L326,190" stroke="#98a2b3" stroke-width="2" marker-end="url(#a4)"/>
246
+
247
+ <!-- Backends -->
248
+ <g>
249
+ <rect x="690" y="40" width="270" height="78" rx="8" fill="#1f1530" stroke="#a855f7"/>
250
+ <text x="706" y="64" fill="#e6e9ee" font-size="14" font-weight="600">Rust scalar (+ WASM CPU)</text>
251
+ <text x="706" y="84" fill="#d8b4fe" font-size="12.5">src/math/platform/generated.rs</text>
252
+ <text x="706" y="102" fill="#d8b4fe" font-size="12.5">fma → f64::mul_add (always exact)</text>
253
+
254
+ <rect x="690" y="132" width="270" height="78" rx="8" fill="#1f1530" stroke="#a855f7"/>
255
+ <text x="706" y="156" fill="#e6e9ee" font-size="14" font-weight="600">AVX2 (tanh variants only)</text>
256
+ <text x="706" y="176" fill="#d8b4fe" font-size="12.5">emitted from branch-free kernels</text>
257
+ <text x="706" y="194" fill="#d8b4fe" font-size="12.5">fma → _mm256_fmadd_pd / scalar fallback</text>
258
+
259
+ <rect x="690" y="224" width="270" height="78" rx="8" fill="#1f1530" stroke="#a855f7"/>
260
+ <text x="706" y="248" fill="#e6e9ee" font-size="14" font-weight="600">HIP</text>
261
+ <text x="706" y="268" fill="#d8b4fe" font-size="12.5">gpu/sim/platform_math.h (#include)</text>
262
+ <text x="706" y="286" fill="#d8b4fe" font-size="12.5">fma → __fma_rn; contract stays off</text>
263
+
264
+ <rect x="690" y="316" width="270" height="96" rx="8" fill="#1f1530" stroke="#a855f7"/>
265
+ <text x="706" y="340" fill="#e6e9ee" font-size="14" font-weight="600">WGSL, one module per profile</text>
266
+ <text x="706" y="360" fill="#d8b4fe" font-size="12.5">src/wasm/sim/math_&lt;profile&gt;.wgsl</text>
267
+ <text x="706" y="378" fill="#d8b4fe" font-size="12.5">fma → new f64_fma in f64.wgsl,</text>
268
+ <text x="706" y="396" fill="#d8b4fe" font-size="12.5">f32 fma via exact f64 product</text>
269
+ </g>
270
+ <path d="M660,440 C690,440 670,80 686,80" stroke="#a855f7" stroke-width="2" fill="none" marker-end="url(#a2)"/>
271
+ <path d="M660,440 C690,440 672,172 686,172" stroke="#a855f7" stroke-width="2" fill="none" marker-end="url(#a2)"/>
272
+ <path d="M660,440 C690,440 674,264 686,264" stroke="#a855f7" stroke-width="2" fill="none" marker-end="url(#a2)"/>
273
+ <path d="M660,440 C690,440 676,364 686,364" stroke="#a855f7" stroke-width="2" fill="none" marker-end="url(#a2)"/>
274
+
275
+ <!-- Runtime selection -->
276
+ <rect x="990" y="40" width="190" height="372" rx="8" fill="#12261a" stroke="#22c55e"/>
277
+ <text x="1004" y="64" fill="#e6e9ee" font-size="14" font-weight="600">MathProfile</text>
278
+ <text x="1004" y="86" fill="#bbf7d0" font-size="12.5">proton | win10-fma3 |</text>
279
+ <text x="1004" y="104" fill="#bbf7d0" font-size="12.5">win10-sse2 | win11-fma3 |</text>
280
+ <text x="1004" y="122" fill="#bbf7d0" font-size="12.5">win11-sse2</text>
281
+ <text x="1004" y="150" fill="#e6e9ee" font-size="12.5" font-weight="600">SessionOptions</text>
282
+ <text x="1004" y="168" fill="#bbf7d0" font-size="12.5">mathProfile (default proton)</text>
283
+ <text x="1004" y="192" fill="#e6e9ee" font-size="12.5" font-weight="600">Checkpoint header</text>
284
+ <text x="1004" y="210" fill="#bbf7d0" font-size="12.5">records profile; resume</text>
285
+ <text x="1004" y="228" fill="#bbf7d0" font-size="12.5">rejects a mismatch</text>
286
+ <text x="1004" y="252" fill="#e6e9ee" font-size="12.5" font-weight="600">Rust</text>
287
+ <text x="1004" y="270" fill="#bbf7d0" font-size="12.5">Math{profile} passed to</text>
288
+ <text x="1004" y="288" fill="#bbf7d0" font-size="12.5">world/runner; 21 sites call</text>
289
+ <text x="1004" y="306" fill="#bbf7d0" font-size="12.5">m.atan2(), m.exp() …</text>
290
+ <text x="1004" y="330" fill="#e6e9ee" font-size="12.5" font-weight="600">HIP</text>
291
+ <text x="1004" y="348" fill="#bbf7d0" font-size="12.5">wave-uniform profile arg</text>
292
+ <text x="1004" y="372" fill="#e6e9ee" font-size="12.5" font-weight="600">WebGPU</text>
293
+ <text x="1004" y="390" fill="#bbf7d0" font-size="12.5">concat chosen module only</text>
294
+ <path d="M960,80 L986,80" stroke="#22c55e" stroke-width="2" marker-end="url(#a3)"/>
295
+ <path d="M960,172 L986,172" stroke="#22c55e" stroke-width="2" marker-end="url(#a3)"/>
296
+ <path d="M960,264 L986,264" stroke="#22c55e" stroke-width="2" marker-end="url(#a3)"/>
297
+ <path d="M960,364 L986,364" stroke="#22c55e" stroke-width="2" marker-end="url(#a3)"/>
298
+
299
+ <!-- Verification band -->
300
+ <rect x="20" y="510" width="1160" height="230" rx="8" fill="#171b21" stroke="#22c55e" stroke-dasharray="5 4"/>
301
+ <text x="36" y="536" fill="#e6e9ee" font-size="15" font-weight="600">⑤ Verification matrix (every profile × every backend, bit-exact)</text>
302
+ <g font-size="12.5">
303
+ <rect x="36" y="552" width="260" height="170" rx="6" fill="#0f1a14" stroke="#1f5133"/>
304
+ <text x="50" y="574" fill="#e6e9ee" font-weight="600">Math level</text>
305
+ <text x="50" y="596" fill="#bbf7d0">Golden vectors vs. Rust scalar</text>
306
+ <text x="50" y="616" fill="#bbf7d0">and AVX2 (cargo test).</text>
307
+ <text x="50" y="640" fill="#bbf7d0">HIP math_kernel op tests</text>
308
+ <text x="50" y="660" fill="#bbf7d0">gain a profile argument.</text>
309
+ <text x="50" y="684" fill="#bbf7d0">WGSL: wasm/test/exact.js</text>
310
+ <text x="50" y="704" fill="#bbf7d0">over the same vectors.</text>
311
+
312
+ <rect x="316" y="552" width="260" height="170" rx="6" fill="#0f1a14" stroke="#1f5133"/>
313
+ <text x="330" y="574" fill="#e6e9ee" font-weight="600">Exhaustive (f32 unary)</text>
314
+ <text x="330" y="596" fill="#bbf7d0">All 2³² inputs for sinf/cosf</text>
315
+ <text x="330" y="616" fill="#bbf7d0">per variant: musl + table</text>
316
+ <text x="330" y="636" fill="#bbf7d0">== genuine DLL. Ignored test,</text>
317
+ <text x="330" y="656" fill="#bbf7d0">runs under Wine (~45 s/pair),</text>
318
+ <text x="330" y="676" fill="#bbf7d0">like examples/</text>
319
+ <text x="330" y="694" fill="#bbf7d0">gpu_engine_scan.rs.</text>
320
+
321
+ <rect x="596" y="552" width="260" height="170" rx="6" fill="#0f1a14" stroke="#1f5133"/>
322
+ <text x="610" y="574" fill="#e6e9ee" font-weight="600">Simulation level</text>
323
+ <text x="610" y="596" fill="#bbf7d0">Existing CPU↔HIP↔WebGPU</text>
324
+ <text x="610" y="616" fill="#bbf7d0">parity tests, parameterised</text>
325
+ <text x="610" y="636" fill="#bbf7d0">by profile. proton goldens</text>
326
+ <text x="610" y="656" fill="#bbf7d0">must stay byte-identical</text>
327
+ <text x="610" y="676" fill="#bbf7d0">(refactor guard).</text>
328
+
329
+ <rect x="876" y="552" width="290" height="170" rx="6" fill="#0f1a14" stroke="#1f5133"/>
330
+ <text x="890" y="574" fill="#e6e9ee" font-weight="600">Game level (optional)</text>
331
+ <text x="890" y="596" fill="#bbf7d0">Windows player records a trace</text>
332
+ <text x="890" y="616" fill="#bbf7d0">via the game's MCP server; the</text>
333
+ <text x="890" y="636" fill="#bbf7d0">sim replays it under each profile.</text>
334
+ <text x="890" y="656" fill="#bbf7d0">The matching profile confirms the</text>
335
+ <text x="890" y="676" fill="#bbf7d0">setup end to end and can</text>
336
+ <text x="890" y="696" fill="#bbf7d0">auto-detect a user's profile.</text>
337
+ </g>
338
+ <path d="M600,412 L600,505" stroke="#22c55e" stroke-width="2" stroke-dasharray="4 3" marker-end="url(#a3)"/>
339
+ </svg>
340
+ </div>
341
+ <p class="caption">One source per variant, four generated backends, and the profile chosen at run time. The Wine harness that produced the survey becomes the permanent oracle.</p>
342
+
343
+ <h2>4. Current vs proposed</h2>
344
+ <table>
345
+ <tr><th style="width:18%">Concern</th><th style="width:41%">Current</th><th>Proposed</th></tr>
346
+ <tr><td>Platform choice</td><td class="bad">Fixed at compile time: Proton/musl only</td><td class="good"><code>MathProfile</code> chosen per session and recorded in checkpoints; default <code>proton</code></td></tr>
347
+ <tr><td>Sources of truth</td><td>Rust (reference) + HIP (hand copy) + AVX2 tanh (hand copy)</td><td>One C-subset kernel per variant; Rust, AVX2, HIP and WGSL are generated</td></tr>
348
+ <tr><td>Cost of one new variant</td><td class="bad">Up to 3 bit-exact hand ports + regenerate WGSL + new fixtures</td><td class="good">1 kernel + 1 line in <code>profiles.toml</code> + regenerated golden vectors</td></tr>
349
+ <tr><td>FMA</td><td>Forbidden everywhere (<code>-ffp-contract=off</code>); no fma primitive</td><td>Still no implicit contraction; an explicit <code>fma()</code> primitive with an exact implementation on each backend</td></tr>
350
+ <tr><td>sinf / cosf</td><td>musl port</td><td>musl port + per-variant exception table (6–51k entries), proven over every float32</td></tr>
351
+ <tr><td>atan2f, exp, log, pow, tanh</td><td>musl ports</td><td>musl + UCRT algorithm ports (can't be proven exhaustively; validated with golden vectors)</td></tr>
352
+ <tr><td>HIP selection</td><td>n/a</td><td>Profile kernel argument (uniform per launch, so no divergence); template instantiation only if profiling shows register pressure</td></tr>
353
+ <tr><td>WebGPU selection</td><td>n/a</td><td>Assemble the shader with only the chosen <code>math_&lt;profile&gt;.wgsl</code>, keeping compile cost flat</td></tr>
354
+ <tr><td>Track generation</td><td>Uses <code>atan2f</code> (random_track.rs, curve.rs)</td><td>Uses the profile too, so generated geometry matches the player's platform</td></tr>
355
+ <tr><td>Fixtures</td><td>Proton captures only</td><td>Proton captures unchanged + per-profile golden vectors from genuine DLLs (outputs only)</td></tr>
356
+ <tr><td>Unaffected</td><td colspan="2">x87 <code>engine_sin_cos</code>, .NET <code>managed_sin_cos</code>, all ordinary arithmetic, RNG streams, and <code>pyrandom</code>'s host libm (separate CPython-compat concern)</td></tr>
357
+ </table>
358
+
359
+ <h2>5. Implementation strategy per function</h2>
360
+ <table>
361
+ <tr><th>Function</th><th>Approach</th><th>Why</th><th>Proof level</th></tr>
362
+ <tr><td>sinf, cosf</td><td>musl kernel + sorted exception table + branch-free binary search (existing <code>engine_sin_cos</code> pattern)</td><td>Only 6,254–50,872 of 2³² inputs differ, all 1 ULP; the table is generated from the scan</td><td class="good">Exhaustive</td></tr>
363
+ <tr><td>atan2f</td><td>Port the UCRT algorithm</td><td>16% of inputs differ, too many for a table, and there are 2 arguments</td><td>Golden vectors</td></tr>
364
+ <tr><td>tanh</td><td>Port both UCRT variants in branch-free form (also feeds AVX2)</td><td>About 10% of inputs differ; it's the hottest path (inference)</td><td>Golden vectors</td></tr>
365
+ <tr><td>exp, pow, log</td><td>Port UCRT algorithms; FMA and SSE2 variants differ only where <code>fma()</code> is used</td><td>Double-precision inputs can't be enumerated</td><td>Golden vectors</td></tr>
366
+ </table>
367
+
368
+ <h2>6. Phased rollout</h2>
369
+ <ol class="phases">
370
+ <li><b>Phase 0: spike (decides feasibility).</b> Recover the algorithms behind UCRT <code>atan2f</code> and <code>tanh</code>, the two highest-impact functions, and reproduce the golden vectors bit-exactly in a throwaway Rust prototype. Turn <code>ucrt_harness.c</code> and the 56-build survey into <code>tools/ucrt-ref</code>. <i>Exit criterion:</i> 0 differences on golden vectors for both functions on both paths.</li>
371
+ <li><b>Phase 1: plumbing, no behaviour change.</b> Add <code>MathProfile</code>, <code>SessionOptions.mathProfile</code> and the checkpoint field; route the 21 Rust call sites and the HIP/WGSL call sites through a profile-aware layer that still only knows <code>proton</code>. All existing goldens stay byte-identical.</li>
372
+ <li><b>Phase 2: single source, no behaviour change.</b> Move the musl kernels into <code>math/kernels/</code>, write <code>gen-math.py</code> with Rust/AVX2/HIP/WGSL emitters, and replace the hand ports with generated code. Guard: exhaustive f32 checks and fixture checks against the old code.</li>
373
+ <li><b>Phase 3: Windows variants.</b> Add them in order of impact: atan2f, tanh, exp, pow, log, then the sin/cos tables. Add <code>f64_fma</code> to <code>f64.wgsl</code> and the scalar fma fallback for CPUs without FMA.</li>
374
+ <li><b>Phase 4: parity matrix.</b> Parameterise CPU↔HIP↔WebGPU parity and session regressions by profile; run the full set for <code>proton</code> and one Windows profile in CI, all profiles nightly.</li>
375
+ <li><b>Phase 5: user-facing.</b> Profile picker in Drive Lab and the CLI (<code>--math-profile</code>), with a short "which one am I?" helper (Windows version, FMA3 support); optional detection from a recorded game trace.</li>
376
+ </ol>
377
+
378
+ <h2>7. Risks and open questions</h2>
379
+ <div class="grid2">
380
+ <div class="card">
381
+ <h3>Deriving the UCRT algorithms <span class="tag">largest risk</span></h3>
382
+ <p>For atan2f, exp, log, pow and tanh we need Microsoft's actual algorithm, not just its outputs. Studying the binary may conflict with Microsoft's licence terms, and interoperability exceptions vary by jurisdiction, so <b>this needs a decision before Phase 0</b>. Black-box fitting against golden vectors is a fallback, but it may not converge to bit-exactness. One unverified hypothesis: the x64 CRT's transcendentals may share ancestry with AMD's open-source libm. If so, that would be a clean starting point to test against the vectors.</p>
383
+ </div>
384
+ <div class="card">
385
+ <h3>Coverage the survey didn't test</h3>
386
+ <ul>
387
+ <li>All results came from one CPU (Zen 4 with AVX-512). It's unknown whether newer builds use AVX-512 paths that CPUs without AVX-512 wouldn't take.</li>
388
+ <li>FMA3 vs SSE2 was forced with <code>_set_FMA3_enable</code>, not observed on a CPU without FMA3.</li>
389
+ <li>Windows on ARM (x64 emulation) wasn't tested.</li>
390
+ <li>Profiles may need splitting if any of these show differences; <code>profiles.toml</code> makes that a data change.</li>
391
+ </ul>
392
+ </div>
393
+ <div class="card">
394
+ <h3>Performance</h3>
395
+ <ul>
396
+ <li>FMA3 profiles on targets without hardware FMA: <code>f64::mul_add</code> on WASM and pre-Haswell CPUs, and <code>f64_fma</code> in WGSL soft-float, are much slower than a multiply plus an add. This mainly affects tanh in inference.</li>
397
+ <li>The per-call profile <code>match</code> in Rust should be close to free (well predicted); switch to monomorphisation only if benchmarks show otherwise.</li>
398
+ <li>HIP code size grows with the number of inlined variants. Watch register pressure and occupancy.</li>
399
+ </ul>
400
+ </div>
401
+ <div class="card">
402
+ <h3>Scope and maintenance</h3>
403
+ <ul>
404
+ <li>About 3× more kernels (22 vs 7), but each is written once.</li>
405
+ <li>Golden vectors grow per profile: keep about 64k inputs per function per profile, so a few MB total.</li>
406
+ <li>A future UCRT update can add a behaviour group. The survey script reruns in about 2 minutes against Winbindex, so it can become a periodic check.</li>
407
+ <li>An end-to-end game confirmation still needs a Windows player's trace (Steam blocks running outside Steam).</li>
408
+ </ul>
409
+ </div>
410
+ </div>
411
+
412
+ <p class="caption" style="margin-top:28px">Sources: code at <code>e96a2e8</code> (src/math, src/nn, gpu/sim, wasm/gen-sim.py, src/wasm/sim); UCRT survey data in <code>~/altd-wine/versions/</code> (sweep.json, groupcmp_*.txt, exhaustive_*.txt).</p>
413
+ </main>
414
+ </body>
415
+ </html>
github-simulator/source/docs/performance.md ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Performance measurements
2
+
3
+ All timings below describe the stated hardware and inputs. Generated-track benchmarks in this tree provide a repeatable public workload; historical campaign/checkpoint measurements are retained for context and cannot be reproduced from public assets alone.
4
+
5
+ ## Native CPU baseline
6
+
7
+ Measured on an AMD Ryzen 9 7950X, Linux x86_64, rustc 1.94 and LLVM 21.1.8, with local `target-cpu=native`. Each workload had an untimed warmup and seven measured samples. Worker processes used eight distinct physical cores. Desktop activity and a concurrent GPU training process remained active.
8
+
9
+ The fixed bench workloads used 1,024 cars, 3,600 timed ticks, two warmup ticks, a fixed seed/network/model and independent execution. The generated scene used seed 1729, generation zero, slot zero. The random batch used 512 cars, three generations, three tracks per generation, 3,600 ticks per track and eight workers. Random timing includes simulation, turnover and track installation waits. Throughput uses the requested car-tick count; elimination can reduce actual executed work.
10
+
11
+ | Workload | Workers | Median seconds | Requested car-ticks/s |
12
+ | --- | ---: | ---: | ---: |
13
+ | Captured campaign scene | 1 | 7.474565 | 493,193 |
14
+ | Captured campaign scene | 8 | 0.987717 | 3,732,242 |
15
+ | Generated scene | 1 | 7.508362 | 490,973 |
16
+ | Generated scene | 8 | 1.004230 | 3,670,872 |
17
+ | Random training batch | 8 | 4.673350 | 3,549,660 |
18
+
19
+ An identical-source repeat changed the medians by -0.15%, -0.95%, +0.06%, -2.33% and -0.37%, respectively. This measures noise; it is not a speedup claim. Public benchmark recipes use different generated geometry and Xavier weights, so their numbers need their own baseline.
20
+
21
+ ```sh
22
+ cargo build --release --offline
23
+ python3 tools/benchmark.py --population 1024 --ticks 3600 --repeats 7 \
24
+ --threads 1 8 --report target/cpu-benchmark.json
25
+ ```
26
+
27
+ The runner works from any directory, keeps every JSON sample and reports medians. `--scene`, `--spawn-trace`, `--network`, `--model`, `--modes` and `--binary` select another workload. It measures the CLI simulation interval; process launch, loading and the initial population are outside that interval.
28
+
29
+ ## Historical HIP measurements
30
+
31
+ AMD Radeon RX 7900 XTX, gfx1100, HIP 7.1.52802, ROCm clang 20 and Ryzen 9 7950X with eight Rayon workers. Inputs were a captured Rally campaign scene and a frozen 8,192-network checkpoint, repeated for larger populations. Shape was `20,16,16,16,16,12,12,8,5`, with 1,661 double parameters per car and two inference batches. The desktop remained active.
32
+
33
+ Full generation medians after one warmup and three measured runs were 2.094 seconds at 8,192 cars, 5.715 at 32,768 and 40.315 at 262,144. Wall and idle elimination were disabled; the 5,400-tick limit ended at statistics callback 5,406. Timers included packing, transfers, simulation and state import, and excluded reset, hashing, reproduction and checkpoint I/O. A later kernel investigation recorded production wall times of about 1.86, 4.93 and 36.2 seconds with unchanged digests, but did not record the same repeated-median protocol. Treat those later values as diagnostic observations.
34
+
35
+ Separate turnover optimization measurements used three consecutive generations with elimination enabled, one warmup and three timed samples. Complete-generation medians changed from 0.386 to 0.267 seconds at 8,192 cars, 1.154 to 0.790 at 32,768 and 9.663 to 6.336 at 262,144. They include turnover after the third generation and exclude loading, hashing and checkpoint I/O. All 36 before/after generation pairs matched result digests and executed ticks. This workload ended early and must not be compared as a full 5,400-tick run.
36
+
37
+ Use [GPU tooling](../gpu/README.md) for fresh measurements on generated inputs. A GPU performance gate requires an otherwise idle GPU; these historical results do not establish throughput under concurrent training load.
38
+
39
+ ## Historical browser measurements
40
+
41
+ Ryzen 9 7950X, Chromium 153.0.8010.12 on Linux, RX 7900 XTX with RADV 26.2.2. A Formula scene used shape `20,16,5`, two inference batches and no elimination. Four 180-tick generations ran per population; the first was warmup and the median of the next three is shown. Complete time includes evolution and coordinator yields. This was one sweep.
42
+
43
+ | Cars | Serial complete ms | Eight-worker complete ms | WebGPU with eight workers ms |
44
+ | --- | ---: | ---: | ---: |
45
+ | 32 | 12.7 | 4.9 | 365.2 |
46
+ | 256 | 100.4 | 18.3 | 361.5 |
47
+ | 1,024 | 399.5 | 66.1 | 189.0 |
48
+ | 4,096 | 1,657.3 | 263.8 | 647.7 |
49
+
50
+ At 4,096 cars, eight-worker CPU advancement took 228.9 ms versus 1,577.7 ms serial. Module/pool startup was 14.1 ms serial and 94.5 ms with eight workers. Peak observed linear memory in the largest case was 97.6 MiB serial and 113.7 MiB with eight workers, excluding JavaScript workers, GPU allocations and any separate preview module. These inputs are historical; rerun the [public browser harness](../wasm/README.md) to establish results for the generated scenarios and another browser/device.
51
+
52
+ ## Firefox completion waits — 2026-10-06
53
+
54
+ Firefox 157 on the local Linux/RX 7900 XTX system reproduced the 30 ticks/s limit. In the public Formula benchmark, six-tick calls took about 200 ms at both 32 and 1,024 cars. At 32 cars, the 120 measured ticks spent 3,975 ms in advancement while GPU timestamps covered only 192 ms. Setting GPU load to 1 retained the same limit, ruling out the configured idle budget as its cause.
55
+
56
+ The runtime now prompts completion delivery with empty queue submissions every 4 ms while awaiting queued work or mapped readback. It retains the original promises and command order, cancelling the timer after completion, device loss or a submission exception. The earlier workaround was absent from this source tree. The Windows/Proton kernel sources are unchanged by this fix.
57
+
58
+ | Workload | Cars | Before ticks/s | After ticks/s |
59
+ | --- | ---: | ---: | ---: |
60
+ | Formula, fixed six-tick windows | 32 | 30.19 | 688.07 |
61
+ | Formula, fixed six-tick windows | 1,024 | 30.27 | 259.63 |
62
+ | Normally served app, Rally, adaptive windows | 512 | 29.98 | 249.53 |
63
+
64
+ Formula used the public default scenario, shape `20,16,5`, Proton, disabled elimination and 120 warmup ticks, with 120 measured ticks at 32 cars and 60 at 1,024 cars. The benchmark records per-pass GPU timestamps. The app used its Test Loop fixture, default shape `20,16,16,12,8,5`, one inference batch, eight CPU workers, Proton, load 0.85 and disabled elimination. App values are medians of the reported rates during a four-second observation; whole-observation rates were 29.88 and 258.92 ticks/s. The final served run used the rebuilt assets without package or runtime overrides. Compilation and startup are excluded. These are diagnostic before/after observations, not repeated benchmark medians.
65
+
66
+ A separate app comparison completed three generations and 558 ticks with each runtime, using identical rebuilt WASM packages and changing only the JavaScript completion waits. Checkpoint bytes, every population network, terminal car states, summaries and stable generation logs matched exactly. The completed outcome SHA-256 was `7086f0ce5b7f555d7bc2d7a4ef5c2db321d71ebb3c21dd93eaa9324ffa02eedc`. Time through the third turnover changed from 18,688 ms to 1,405 ms; the adaptive pump used 90 calls before and 37 after.
67
+
68
+ Two additional runs through normally served assets completed the same 558 ticks in 2,339 and 2,385 ms, with identical outcomes and stable logs. A prior diagnostic run with package routing reported 1,143 ticks/s; the normal served measurements above provide the conservative result. One initial instrumented run stalled while waiting for its generation outcome. Its detailed phase was not captured, and the stall did not recur in the three subsequent completed comparisons; its cause remains unresolved.
69
+
70
+ Three alternating Chromium 153 comparisons kept the WASM, shader, Win11 profile, 1,024 active cars and 120-tick bulk windows identical, changing only the completion-wait runtime. Median wall time was 355.4 ms before and 355.0 ms after. Background GPU activity was present before browser launches, so these observations do not measure isolated peak throughput.
71
+
72
+ Both serial and threaded production packages were rebuilt. Full Firefox simulation comparisons passed for all three profiles: 184,558 checks, zero mismatches. The Node completion-wait regression suite passes 16 tests against the fixed runtime; the frozen original runtime fails nine of them. See [the browser commands](../wasm/README.md) to repeat the Formula workload or run the regression suite without a GPU.
github-simulator/source/docs/reviews/2026-10-05-native-server-review.md ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Native server implementation review
2
+
3
+ Date: 2026-10-05
4
+
5
+ Two independent reviewers used GPT-6.1-sol at ultra effort. The native comparison starts at `origin/main`, commit `0852de991c6e6e2d9868f74346bb0ac9d3260774`. The companion webapp comparison starts at `aebb7ab` and ends at `e99e65541cb502302a32ac2d1bdc4d53b9aee0a9`, branch `t3code/native-simulator-client`. The webapp repository has no configured remote.
6
+
7
+ The spec is [the approved native server design](../superpowers/specs/2026-10-05-native-server-design.md). Standards sources include `docs/design.md` and the code-review skill's code-smell baseline. The reviewers checked the implementation, then reviewed the fixes below.
8
+
9
+ ## Standards
10
+
11
+ The reviewer found no hard coding-standard violations or actionable code smells. The correctness review found that an ordinary HIP window error could panic and abort the release server. The new Session path returns errors from world creation, simulator creation, world replacement, network upload, state transfers and window execution. Unsupported scenes and networks fall back during initialization or verification.
12
+
13
+ The follow-up review found leaked resources after partial GPU constructor failures. Constructors now retain ownership before copying and release partial allocations before returning null. World cleanup excludes borrowed host pointers; simulator cleanup leaves its borrowed World intact.
14
+
15
+ The final review reports no remaining correctness findings, hard coding-standard violations or actionable code smells.
16
+
17
+ ## Spec
18
+
19
+ The reviewer found that a hidden layer wider than HIP's 16 inference lanes could abort the server rather than fall back. The HIP path now validates its network limits before upload; verification restores the initial runner state before CPU fallback. Real-socket regressions cover a 32-wide hidden layer and a scene without physics shapes.
20
+
21
+ The device-name export was incorrectly mandatory. Older compatible libraries now probe device availability through the legacy math function and report the loaded library path. A real-socket HIP training test passed with a library that lacks the name export.
22
+
23
+ The worker now ignores queued pause and resume commands after a run fails or finishes. A deterministic browser test injects an ordinary native RPC error, checks the saved failed state, and resumes from its checkpoint. Removing the guards makes that test fail.
24
+
25
+ The final review confirms the constructor cleanup and fault-injection coverage, with no remaining high-confidence spec findings.
26
+
27
+ ## Initial validation
28
+
29
+ - Rust debug and release suites: 173 passed and five existing tests ignored in each.
30
+ - Real-socket server suite: ten passed, including CPU parity, binary/JSON checkpoints, invalid requests, Origin/Host checks, HIP claim release and unsupported HIP fallback.
31
+ - Actual RX 7900 XTX Session checks passed for CPU/HIP parity, restart, restore, track replacement, population growth and fallback.
32
+ - Constructor cleanup passed all 38 World and 21 simulator injected failures, plus successful and null destruction. The same harness detects a leak in the previous library. ROCm CI runs it without a GPU.
33
+ - Native and WASM Clippy, Rust 1.93 all-target compilation, formatting and rustdoc passed.
34
+ - Serial and threaded WASM builds passed. CPU reference comparison checked 2,773 values with zero mismatches.
35
+ - Webapp build, TypeScript, 31 unit tests, immutable asset publication and formatting passed.
36
+ - Full browser suite: 45 passed, five conditional tests skipped. All ten native integration cases passed.
37
+ - Hardware WebGPU training, partial-window CPU fallback and contact/reproduction parity checks passed.
38
+
39
+ Remaining findings: Standards 0; Spec 0. Native/WASM last-bit differences remain tracked in [issue #3](https://github.com/Zorro909/driving_sim_rs/issues/3).
40
+
41
+ ## Size limits and platform follow-up
42
+
43
+ The user requested removal of all size constraints for local simulator connections. The server now has no configured WebSocket frame or message ceiling. The companion frontend change is commit `432e7da` on `t3code/native-simulator-client`. Native settings bypass browser network, population, stage, file and checkpoint limits while retaining valid values and shared checkpoint field widths. Unsupported HIP shapes continue on native CPU.
44
+
45
+ Large checkpoints, run metadata and imported tracks use 32 MiB IndexedDB chunks, with replacement and deletion in one transaction. Database version 4 closes older connections before writing the new format. Review found and fixed the unchunked track path, oversized plan edits after browser fallback, and incompatible readers in old tabs. Startup failures with no training history can restart when the local simulator returns.
46
+
47
+ Both reviewers rereviewed these fixes with no remaining findings. The frontend build, TypeScript, formatting, 35 unit tests and immutable asset checks passed. The full browser suite passed 52 tests with five expected skips, including all 13 native cases and four storage cases. Storage regressions round-trip checkpoints, metadata and tracks above 127 MiB and check rollback, replacement, deletion and schema migration.
48
+
49
+ Platform CI exposed x87 precision-dependent large-angle reduction on Windows, a host-specific Xavier checksum on macOS, and ARM default NaN signs. The fixes preserve captured bits rather than relax comparisons. The macOS Xavier oracle compares every parameter and RNG bit with same-host CPython. Session tests retain exact Linux GNU goldens and compare both execution modes with the legacy Runner on every host, including exact checkpoint replay.
50
+
51
+ The final native release suite passed 176 tests with five existing tests ignored. All 35 vehicle tests passed under AArch64 QEMU after reproducing the macOS distance-sensor failure. Debug/release sensor checks and Darwin Clippy passed. Fresh serial/threaded WASM builds passed, with 2,773 CPU reference values and zero mismatches. The rebuilt frontend passed asset checks and all 13 native integration cases again.
github-simulator/source/docs/server.md ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Local simulator server
2
+
3
+ `altd-sim serve` exposes native `Session` simulation over a WebSocket. A client can use Rayon CPU threads or request the AMD HIP backend. The server implements the protocol for Drive Lab's planned native client; the webapp integration is a separate change.
4
+
5
+ The server owns one session per connection. Clients own run storage, checkpoints, logs, stage changes and mutation schedules. Closing the socket drops its session.
6
+
7
+ ## Starting the server
8
+
9
+ The default Cargo features include `server`. CPU use needs no ROCm installation. Build the HIP library separately using [the GPU guide](../gpu/README.md).
10
+
11
+ ```sh
12
+ cargo build --release --offline
13
+ target/release/altd-sim --threads 8 serve
14
+ target/release/altd-sim serve --port 47800 \
15
+ --allow-origin http://127.0.0.1:5173
16
+ ```
17
+
18
+ | Flag | Default and effect |
19
+ | --- | --- |
20
+ | `--port N` | `47800`; binds only `127.0.0.1:N` |
21
+ | `--allow-origin ORIGIN` | `https://drivinglab.jectrum.de`; repeat to allow several origins, replacing the default list |
22
+ | `--threads N` | Global flag; sizes the Rayon pool shared by all CPU sessions, with CPU affinity as the default |
23
+ | `--math-profile PROFILE` | Global flag; the math profile of sessions whose options name none. Without it, the server detects the profile of its own machine |
24
+
25
+ Allowed origins use `http://host[:port]` or `https://host[:port]`, with an ASCII hostname or IP address. Startup normalizes scheme and host case, compresses IPv6 addresses, removes a trailing slash and omits default ports. Paths, credentials, query strings and fragments are rejected. Startup prints the listening URL, allowed origins, CPU thread count, HIP availability and the default math profile.
26
+
27
+ ## Connection checks
28
+
29
+ Connect to `ws://127.0.0.1:47800/v1`, substituting the configured port. The handshake returns HTTP 403 unless all checks pass:
30
+
31
+ - The request path is `/v1`.
32
+ - `Origin` is present and exactly matches an allowed origin. Different schemes or ports do not match; `null` is rejected.
33
+ - `Host` names `127.0.0.1` or `localhost` with the listening port. Other hostnames are rejected to prevent DNS rebinding.
34
+
35
+ There is no LAN binding, TLS or authentication token. These checks restrict browser callers; a local program can supply its own headers. At most 64 connections (including incomplete handshakes) may be active. Excess sockets are closed before spawning a worker. Handshake socket reads/writes time out after 5 seconds; established socket reads/writes after 10 minutes of blocked I/O. Long-running generation computation is not subject to those socket timeouts. Connection-worker spawn failures reject that connection while retaining the accept loop. Requests run in order on a dedicated connection thread, with no per-request timeout. Incoming frames and assembled messages are each limited to 64 MiB, including binary checkpoint headers/payloads. Equal limits allow a browser to send an admissible message in one frame: JavaScript WebSocket.send cannot select continuation framing. Larger messages must be split into protocol-level operations; fragmenting a message does not bypass its assembled limit. A rejected oversized frame/message closes only that connection. A generation may take minutes.
36
+
37
+ ## Protocol 1
38
+
39
+ The first frame is a JSON text greeting:
40
+
41
+ ```json
42
+ {"type":"hello","protocol":1,"version":"1.1.0","threads":8,"carStateStride":14,"carStateFields":["x","y","..."],"hip":{"available":true,"device":"AMD Radeon RX 7900 XTX","platform":"HIP"},"mathProfile":"proton","mathProfiles":["proton","win10-fma3","win11-fma3"]}
43
+ ```
44
+
45
+ Read `carStateStride` and `carStateFields` from the greeting rather than hard-coding them. The example's fields are abbreviated. `platform` is `"HIP"` for the AMD library and `"CUDA"` for the NVIDIA one. When HIP is unavailable, `hip` instead contains `{"available":false,"reason":"..."}`. Availability means the process opened the library and device; a particular session can still fall back to CPU. `mathProfile` is the default math profile and `mathProfiles` lists every profile a session can choose; [math profiles](../math/README.md) documents their runtime variants.
46
+
47
+ Requests use an unsigned integer `id`, an operation name and optional `args`:
48
+
49
+ ```json
50
+ {"id":7,"op":"advance","args":{"ticks":180,"stopWhenInactive":true}}
51
+ ```
52
+
53
+ A successful reply contains the same `id`, its result in `ok` and the current `state`. An error reply contains `id` and `error`, without `state`.
54
+
55
+ ```json
56
+ {"id":7,"ok":180,"state":{"started":true,"generation":0,"tick":180,"population":256,"activeCount":256,"checkpointGeneration":0,"backend":"cpu","backendNote":null,"mathProfile":"proton"}}
57
+ ```
58
+
59
+ `checkpointGeneration` identifies the restorable generation boundary, or is `null` before starting. It need not describe the current tick. `backend` reports what actually runs; `backendNote` explains a CPU fallback. `mathProfile` is the session's math profile. Clients can queue requests and match replies by `id`.
60
+
61
+ ### Operations
62
+
63
+ | Operation | `args` | `ok` |
64
+ | --- | --- | --- |
65
+ | `create` | `scene`, `network`, `model` as JSON strings; optional `options` as a SessionOptions object | `{backend, backendNote, mathProfile}` |
66
+ | `start` | None; seeds training from the `create` network export | `null` |
67
+ | `startWithShape` | `shape` as an array of unsigned 32-bit integers | `null` |
68
+ | `restoreCheckpointBytes` | Binary payload, no arguments required | `null` |
69
+ | `restoreCheckpointJson` | `json` as a checkpoint JSON string | `null` |
70
+ | `advance` | `ticks`, `stopWhenInactive` | Executed ticks |
71
+ | `advanceGeneration` | `timeLimitTicks` | Executed ticks |
72
+ | `nextGeneration` | None | `{preservedCount, rewards}` |
73
+ | `setEvolutionSettings` | `json` as an evolution-settings JSON string; `algorithm` is `"ga"` or `"ars"` (see design notes) | `null` |
74
+ | `replaceTrack` | `scene` as a scene JSON string | `null` |
75
+ | `generationSummary` | None | `{bestIndex, bestScore, lapped, active, lapIndex, lapTime}`, optional `averageScore` and `worstScore` |
76
+ | `networkJson` | `index` | Network JSON string |
77
+ | `carStates` | None | Number of `f64` values, with a binary payload |
78
+ | `checkpointBytes` | None | `null`, with a binary payload |
79
+
80
+ Call `create` once, then start or restore before advancing. `options.backend` accepts `"cpu"`, the default, or `"hip"`. `options.mathProfile` accepts `"proton"`, `"win10-fma3"` or `"win11-fma3"` and defaults to the server's profile; checkpoints restore only into sessions of the profile they were made with. Other SessionOptions fields retain their regular defaults and unknown fields are rejected. A second successful `create` on the same connection is an error. Unknown operations and malformed JSON return errors while leaving the connection usable.
81
+
82
+ `start` uses the export's `weights` and `biases` as the seed, matching `train-scratch --init-network` and the browser simulator. GA breeds mutations of that seed; ARS samples around it as its initial search point. Without weights, `start` draws a Xavier seed from `shape` (or `summary.shape`). Without either weights or a shape, use `startWithShape`. Malformed weighted exports return an error before starting and leave the connection usable.
83
+
84
+ ### Binary frames
85
+
86
+ `restoreCheckpointBytes` requests and `carStates` or `checkpointBytes` replies use a binary WebSocket frame:
87
+
88
+ ```text
89
+ [u32 little-endian JSON byte length][UTF-8 JSON header][raw payload]
90
+ ```
91
+
92
+ The header has the same request or reply shape as a text frame. `carStates` payloads contain little-endian `f64` values, grouped by the greeting's stride and field order. Checkpoint payloads use the shared Session checkpoint format. All other operations use text frames. Invalid binary framing closes the connection.
93
+
94
+ ## HIP fallback and resume
95
+
96
+ Only one session can hold the HIP device per process. Another HIP request runs on CPU with `backendNote: "another session is using the GPU"`. Library/device errors, native broadphase tracks and unsupported path sensors also cause CPU fallback with a reason. HIP requires physics shapes and at least two path points. Networks support up to 12 layers, 32 inputs and 16 neurons in each later layer; wider CPU networks fall back during verification.
97
+
98
+ Before a HIP session's first driving window after a start or restore, it compares 12 ticks against CPU from the same state, then restores that state. A mismatch or verification error drops HIP and runs the requested window on CPU. Track replacement uploads the new world and repeats verification before driving. Read `state.backend` after advancing because it may change from the `create` result.
99
+
100
+ HIP cars and agents stay on the device between windows; an `advance` or `advanceGeneration` reply reads back only the active car count. `generationSummary`, `carStates`, turnover, restores and track replacement read the population back into the CPU runner first; checkpoints are saved from the generation boundary and need no readback. Turnover breeds on CPU to retain the parent records in format-2 checkpoints, then uploads the new networks and cars on the next HIP window. HIP libraries without `altd_gpu_sim_active_count` read the population back after every window. Checkpoints can move between native CPU, HIP and WASM sessions. Native and WASM training have known last-bit differences, so continued training may diverge after switching engines.
101
+
102
+ HIP errors after startup verification return request errors without retrying. The client should stop the run and resume from its last saved checkpoint. A closed connection loses the server's session; reconnect, create a new session and restore a saved checkpoint. Release builds use `panic = "abort"`, so a panic terminates the server and closes every connection.
github-simulator/source/docs/superpowers/specs/2026-10-05-native-server-design.md ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Native simulator server for Drive Lab
2
+
3
+ Date: 2026-10-05
4
+ Status: implemented and reviewed
5
+
6
+ ## Goal
7
+
8
+ Drive Lab (`driving_webapp_public`) trains in the browser on WASM, optionally with the WebGPU raycaster, and that stays the default. Users who also run the native `altd-sim` binary should be able to train faster with it: on native CPU threads without the browser's 8-thread cap and WASM overhead, or on an AMD GPU through the existing HIP simulator.
9
+
10
+ The browser talks to the native binary over a WebSocket on `127.0.0.1`. By default only `https://drivinglab.jectrum.de` may connect; a CLI argument replaces that list.
11
+
12
+ ### Success criteria
13
+
14
+ - With `altd-sim serve` running and the setting enabled, a user can create a run with backend "Native CPU" or "Native GPU (HIP)". It trains, saves checkpoints and logs to IndexedDB, and pauses, resumes and changes stages exactly as WASM runs do.
15
+ - A native run's checkpoint resumes on WASM and the other way round. The format is shared; results are not bit-identical, see "Known limitation".
16
+ - No request reaches the server from an origin that is not allowed, or with a `Host` other than the loopback address and port.
17
+ - When the server is missing, unreachable or crashes, the user gets a clear message and the run falls back or resumes as the existing WebGPU and WASM-trap paths do.
18
+ - A HIP session matches a native CPU session exactly in car state, as the existing HIP checks require, or falls back to CPU with a reason.
19
+
20
+ ### Non-goals
21
+
22
+ - The server does not own runs, storage or the stage, plateau and mutation-schedule logic; those stay in the webapp's coordinator worker.
23
+ - No remote or LAN access, TLS or authentication tokens.
24
+ - No HIP version of the parent-recording turnover (see "Turnover"); that is a later optimization.
25
+ - WASM/native bit-exactness is tracked separately in issue #3.
26
+
27
+ ## Architecture
28
+
29
+ ```
30
+ Page (main thread) Coordinator worker altd-sim serve (127.0.0.1)
31
+ ├─ setting + status probe ──► (hello only, then close) ──────────► ├─ handshake: Origin + Host check
32
+ └─ TrainingWorker ──────────► simulation.worker.ts ├─ one thread per connection
33
+ ├─ training loop (unchanged) └─ one Session per connection
34
+ ├─ Engine interface ├─ backend: cpu (rayon)
35
+ │ ├─ WasmEngine (Simulation, WebGPU) └─ backend: hip (libaltd_gpu.so)
36
+ │ └─ RemoteEngine ── WebSocket ──►
37
+ └─ IndexedDB (runs, logs, checkpoints)
38
+ ```
39
+
40
+ The server works as a remote `Simulation`. The worker keeps its training loop and swaps the local `Simulation` for an object that sends the same calls over the socket.
41
+
42
+ ## Part 1: HIP backend in `Session` (`driving_sim_rs`)
43
+
44
+ ### Native access to Session methods
45
+
46
+ Some `Session` items exist only in WASM builds and must also be compiled for the server:
47
+
48
+ - `options` field (`src/training/session.rs:76`)
49
+ - `set_evolution_settings` (`:313`)
50
+ - `replace_track` (`:653`)
51
+ - the `network` field the WASM build keeps
52
+
53
+ Their cfg changes from "wasm" to "wasm or `server` feature". The server module lives in the library (`src/server/`) so it can use the `pub(crate)` methods directly.
54
+
55
+ ### Backend option
56
+
57
+ `SessionOptions` gains `backend: "cpu" | "hip"` (camelCase JSON, default `"cpu"`). `deny_unknown_fields` stays.
58
+
59
+ - On wasm32, `"hip"` is an error: "the HIP backend is only available in the native simulator".
60
+ - On native, `Session` gains `hip: Option<HipState>`:
61
+
62
+ ```rust
63
+ struct HipState {
64
+ world: GpuWorld<'static>,
65
+ sim: GpuSim<'static>,
66
+ }
67
+ static GPU: OnceLock<Result<Gpu, String>> = OnceLock::new(); // Gpu::open(None): ALTD_GPU_LIB or the default path
68
+ ```
69
+
70
+ The `Gpu` handle is opened once per process and never dropped, so a `Session` can own `'static` GPU handles without self-borrowing.
71
+
72
+ ### When HIP is refused
73
+
74
+ `Session::new` builds `HipState` only when all of these hold. Otherwise it keeps `hip = None` and records a reason that the server reports, and the session runs on CPU.
75
+
76
+ - `GPU` opened successfully.
77
+ - The track does not use the native broadphase (`runner.physics` would be `Some`); `advance_window_gpu` rejects it.
78
+ - `runner.gpu_sim(...)` succeeds. It already rejects path sensors without `track.curve`.
79
+
80
+ Population-dependent capacity is handled on the first window (below), so construction does not need the population.
81
+
82
+ ### Routing
83
+
84
+ | Session call | CPU | HIP |
85
+ |---|---|---|
86
+ | `advance(ticks, stop)` | `runner.advance` | `runner.advance_window_gpu(sim, world, ticks, stop, None)` |
87
+ | `advance_generation(limit)` | `runner.advance_generation` | `runner.advance_generation_gpu(sim, world, limit)` |
88
+ | `next_generation()` | `next_generation_traced` | the same CPU path (see "Turnover") |
89
+ | `replace_track(scene)` | as today | as today, then `GpuWorld::new(gpu, &runner.world)` and `sim.set_world(&world)` |
90
+ | `start*`, `restore_checkpoint*`, `set_network_json` | as today | as today, then `sim.network_tag = None` |
91
+
92
+ Each HIP window reads car and agent state back into `runner.agents`, so `car_states`, `generation_summary`, `network_json`, `metrics`, `active_count` and checkpoints read the runner as they do now.
93
+
94
+ If the population is larger than `GpuSim`'s capacity, `GpuSim` is rebuilt with the new population before the window.
95
+
96
+ ### Turnover
97
+
98
+ `next_generation_gpu` breeds on the GPU but does not produce the parent record (`Lineage`) that format 2 checkpoints store. A HIP session therefore breeds through `next_generation_traced` on the CPU, the same call CPU sessions make. The next window uploads the new networks because `network_tag` no longer matches. Breeding is small next to a generation of driving. A HIP turnover that also records parents can come later if profiling shows it matters.
99
+
100
+ ### Startup check
101
+
102
+ A HIP session checks itself before its first real window, following `GpuSimulation::verify` in `src/wasm/sim/mod.rs:186-235`:
103
+
104
+ 1. Back up `runner.agents`, `tick` and `batch_index`.
105
+ 2. Advance 12 ticks on the CPU and record the canonical state (`canonical_state`, moved from `src/wasm/sim` into shared native and WASM code).
106
+ 3. Restore the backup, run 12 ticks on HIP, and compare canonical states exactly.
107
+ 4. Restore the backup again.
108
+
109
+ On a mismatch or HIP error, the session drops `HipState` and continues on CPU with the reason "HIP verification differs from the CPU simulator" (or the error). It runs once per session, on the first `advance` or `advance_generation` after `start*` or `restore*`.
110
+
111
+ `Session` exposes `backend() -> "cpu" | "hip"` and `backend_note() -> Option<String>`, so hosts can report what actually runs.
112
+
113
+ ### Errors
114
+
115
+ HIP failures after the check, such as a failed window, are returned as `Err(String)` and not retried. The webapp fails the run, and resuming continues from the last checkpoint.
116
+
117
+ ## Part 2: `altd-sim serve`
118
+
119
+ ### Crate layout
120
+
121
+ - Feature `server = ["cli", "dep:tungstenite"]`, added to `default`. `tungstenite` is the synchronous WebSocket crate without TLS features, so there is no async runtime.
122
+ - `src/server/mod.rs`: listener, handshake checks and the per-connection loop.
123
+ - `src/server/protocol.rs`: message types and frame encoding.
124
+ - `src/server/dispatch.rs`: operations to `Session` calls.
125
+ - `src/bin/altd-sim`: a `Serve` subcommand that parses flags and calls `altd_sim::server::run(config)`.
126
+
127
+ ### Flags
128
+
129
+ ```
130
+ altd-sim [--threads N] serve [--port 47800] [--allow-origin ORIGIN]...
131
+ ```
132
+
133
+ - `--port`: TCP port, default `47800`. The server binds `127.0.0.1:<port>` only.
134
+ - `--allow-origin`: repeatable. When given, the list replaces the default `https://drivinglab.jectrum.de`. Values are normalized as `scheme://host[:port]`, and invalid values are rejected at startup. Local development uses `--allow-origin http://127.0.0.1:5173`.
135
+ - `--threads`: the existing global flag; it sizes the rayon pool all CPU sessions share.
136
+
137
+ On startup the server prints the address, allowed origins, thread count and HIP availability.
138
+
139
+ ### Handshake checks
140
+
141
+ `tungstenite::accept_hdr` with a callback that rejects with HTTP 403 and a short plain-text reason unless all hold:
142
+
143
+ - The path is `/v1`.
144
+ - `Origin` is present and equals an allowed origin exactly (scheme, host and port). `null` is rejected.
145
+ - `Host` is `127.0.0.1:<port>` or `localhost:<port>`.
146
+
147
+ ### Greeting
148
+
149
+ Sent as the first text frame after the handshake:
150
+
151
+ ```json
152
+ {"type":"hello","protocol":1,"version":"0.1.0","threads":32,
153
+ "carStateStride":N,"carStateFields":["..."],
154
+ "hip":{"available":true,"device":"..."} | {"available":false,"reason":"..."}}
155
+ ```
156
+
157
+ Checking HIP availability calls `GPU.get_or_init` once per process. `device` comes from the HIP library if it exposes a name; otherwise it is the library path.
158
+
159
+ ### Requests and replies
160
+
161
+ - Request (text frame): `{"id":7,"op":"advance","args":{"ticks":180,"stopWhenInactive":true}}`
162
+ - Success: `{"id":7,"ok":<result>,"state":State}`
163
+ - Failure: `{"id":7,"error":"<text>"}`
164
+ - `State` is `{"started","generation","tick","population","activeCount","checkpointGeneration","backend","backendNote"}`.
165
+
166
+ Requests are handled strictly in order on the connection's thread. Clients may send the next request before the reply arrives and match replies by `id`.
167
+
168
+ ### Binary frames
169
+
170
+ Used by `restoreCheckpointBytes` (request), and by `checkpointBytes` and `carStates` (replies):
171
+
172
+ ```
173
+ [u32 little-endian: JSON length][JSON header][raw payload]
174
+ ```
175
+
176
+ The JSON header has the same shape as a text frame. `carStates` returns little-endian `f64` values. All other messages are text frames.
177
+
178
+ ### Operations
179
+
180
+ | op | args | ok |
181
+ |---|---|---|
182
+ | `create` | `scene`, `network`, `model` (JSON strings), `options` (SessionOptions object, including `backend`) | `{backend, backendNote}` |
183
+ | `startWithShape` | `shape: number[]` | `null` |
184
+ | `restoreCheckpointBytes` | binary payload | `null` |
185
+ | `restoreCheckpointJson` | `json: string` | `null` |
186
+ | `advance` | `ticks`, `stopWhenInactive` | executed ticks |
187
+ | `advanceGeneration` | `timeLimitTicks` | executed ticks |
188
+ | `nextGeneration` | none | `{preservedCount, rewards: number[]}` |
189
+ | `setEvolutionSettings` | `json: string` | `null` |
190
+ | `replaceTrack` | `scene: string` | `null` |
191
+ | `generationSummary` | none | `{bestIndex, bestScore, lapped, active, lapIndex, lapTime}` |
192
+ | `networkJson` | `index` | string |
193
+ | `carStates` | none | binary `f64` payload |
194
+ | `checkpointBytes` | none | binary payload |
195
+
196
+ Rules:
197
+
198
+ - Any op before `create`, or a second `create`, is an error.
199
+ - Only one HIP session may exist per process at a time. A second `create` with `backend: "hip"` falls back to CPU with the note "another session is using the GPU".
200
+ - Unknown ops and malformed JSON are errors and leave the session usable. Malformed binary framing closes the connection.
201
+ - The session is dropped when the socket closes.
202
+
203
+ ### Panics and timeouts
204
+
205
+ The release profile keeps `panic = "abort"`, so a panic ends the server. The client sees the socket close (see "Disconnect" below), and a session that panicked part-way would not be trustworthy anyway. There is no per-request timeout, because `advanceGeneration` can take minutes on large populations.
206
+
207
+ ## Part 3: webapp client (`driving_webapp_public`)
208
+
209
+ ### Setting and status
210
+
211
+ - In `#compute-choices` (`src/views.ts` `computeChoices`, wired in `src/main.ts` `bindCompute`), above the backend radios:
212
+ - a "Use a local altd-sim" checkbox and a port input;
213
+ - help text with the command `altd-sim serve` and the note that the browser may ask once for local network access.
214
+ - The setting is stored in `localStorage` (`drivelab.native` = `{enabled, port}`) and is per machine.
215
+ - When enabled, the page opens a WebSocket to `ws://127.0.0.1:<port>/v1`, reads `hello`, closes it, and shows one of:
216
+ - "Connected: altd-sim 0.1.0 · 32 threads · HIP: <device>"
217
+ - "Not reachable on port N"
218
+ - "Update altd-sim: protocol N is not supported"
219
+
220
+ It probes again when the setting changes and whenever the compute choices are drawn. The page timeout is 3 s.
221
+ - New radios:
222
+ - "Native CPU · N threads": enabled when connected.
223
+ - "Native GPU (HIP) · device": enabled when `hip.available`, otherwise disabled with the server's reason.
224
+ - Both carry the note "Results differ slightly from in-browser training", linking to issue #3.
225
+ - When the selected native backend becomes unavailable while the user is editing a draft, the draft switches to `"cpu"`, as WebGPU does in `main.ts:115`.
226
+
227
+ ### Domain and protocol
228
+
229
+ - `Setup.backend` becomes `"cpu" | "webgpu" | "native-cpu" | "native-hip"`.
230
+ - `validateSetup` accepts the new values; run summaries and the "Computing on" line show "Native CPU, N threads" and "Native GPU (HIP), <device>".
231
+ - `StartCommand` gains `native?: { port: number }`, set by the page from the setting.
232
+ - The `engine` event's `backend` gains `"native-cpu" | "native-hip"` and an optional `device`.
233
+
234
+ ### Engine interface (`src/engine.ts`)
235
+
236
+ ```ts
237
+ interface Engine {
238
+ readonly started: boolean; readonly generation: number; readonly tick: number;
239
+ readonly population: number; readonly activeCount: number;
240
+ readonly checkpointGeneration: number | undefined;
241
+ startWithShape(shape: Uint32Array): Promise<void>;
242
+ restoreCheckpointBytes(bytes: Uint8Array): Promise<void>;
243
+ restoreCheckpointJson(json: string): Promise<void>;
244
+ advance(ticks: number, stopWhenInactive: boolean): Promise<number>;
245
+ advanceGeneration(timeLimitTicks: number): Promise<number>;
246
+ nextGeneration(): Promise<void>;
247
+ setEvolutionSettings(json: string): Promise<void>;
248
+ replaceTrack(sceneJson: string): Promise<void>;
249
+ generationSummary(): Promise<Summary>;
250
+ networkJson(index: number): Promise<string>;
251
+ carStates(): Promise<Float64Array>;
252
+ checkpointBytes(): Promise<Uint8Array>;
253
+ free(): void;
254
+ }
255
+ ```
256
+
257
+ - `WasmEngine` wraps `lib.Simulation` and holds the existing WebGPU logic (`prepareGpu`, `dropGpu`, the GPU-to-CPU fallback in `advance`).
258
+ - `RemoteEngine` opens the socket, checks `hello.protocol === 1`, sends `create`, and updates the cached getters from every reply's `state`. Its getters are synchronous because they return those cached values.
259
+ - `simulation.worker.ts` replaces `sim` and `gpu` with one `engine: Engine` and adds `await` to the calls that become asynchronous. The training loop, storage queue, stage logic and messages are otherwise unchanged.
260
+ - The WASM library still loads for every backend, because `trackScene` and `randomTrackScene` run in the worker.
261
+
262
+ ### Fallback and disconnect
263
+
264
+ - **Server unreachable at `buildSession`, `hello` rejected, or `create` failing:** the run uses `WasmEngine` on CPU and emits an `engine` event with a note, for example "The local altd-sim could not be reached on port 47800, so this run trains in the browser. <reason>".
265
+ - **HIP refused or failing its check:** the server reports `backend: "cpu"` with `backendNote`, and the worker emits `engine` `native-cpu` with that note.
266
+ - **Disconnect during a run:** `RemoteEngine` rejects all pending and future calls with "The native simulator closed the connection. Resuming continues from the last saved checkpoint." The worker treats this like a WASM trap: it sets `trapped`, so `publishRun(true)` skips `checkpointBytes`, marks the run failed, stores it, and reports the error. Resuming goes through the same start path, including the fallback above.
267
+
268
+ ### Content Security Policy
269
+
270
+ Add `ws://127.0.0.1:*` to `connect-src` in `public/_headers` and in `vite.config.ts`. `deploy/deploy.sh` reads `_headers`, and `scripts/serve-test.mjs` reads the same policy. COOP/COEP do not apply to WebSocket connections.
271
+
272
+ ## Known limitation
273
+
274
+ WASM and native CPU are not bit-exact (issue #3). Native CPU and HIP are expected to be exact with each other, and the startup check enforces it. Cross-engine resume works through the shared checkpoint format, but continued training diverges. The UI says so next to the native choices.
275
+
276
+ ## Testing
277
+
278
+ ### `driving_sim_rs`
279
+
280
+ - **Unit:**
281
+ - origin normalization and matching: exact, different port, different scheme, trailing slash, `null`, missing;
282
+ - Host matching;
283
+ - binary frame encode and decode, including truncated frames;
284
+ - `SessionOptions` with `backend` (`"hip"` rejected on wasm by a cfg test).
285
+ - **Integration (`tests/server.rs`):** start the server in-process on port 0 and connect with a `tungstenite` client.
286
+ - A disallowed origin and a wrong Host get 403.
287
+ - `hello` contents.
288
+ - `create → startWithShape → advance → advanceGeneration → nextGeneration → generationSummary → carStates → checkpointBytes → restoreCheckpointBytes` matches a direct `Session` with the same inputs bit for bit.
289
+ - Ops before `create` and a second `create` return errors.
290
+ - A dropped socket frees the session.
291
+ - **HIP (skipped when `Gpu::open` fails):**
292
+ - a CPU and a HIP `Session` agree in canonical state through several `advance` windows, `advanceGeneration` and two turnovers;
293
+ - a checkpoint saved under HIP restores under CPU, and the other way round;
294
+ - fallback reasons for native broadphase and a missing curve;
295
+ - a second concurrent HIP session falls back to CPU.
296
+ - Existing tests: `cargo test`, the WASM parity harness, and a WASM build that confirms the cfg changes do not leak native-only code.
297
+
298
+ ### `driving_webapp_public`
299
+
300
+ - Domain tests: `validateSetup` with the new backends.
301
+ - Playwright (`tests/native.spec.ts`): build `altd-sim` from `SIM_ROOT` and start `altd-sim serve --port <free> --allow-origin http://127.0.0.1:<test port>`.
302
+ - The setting shows "Connected".
303
+ - A two-generation Native CPU run stores progress and a checkpoint, with the correct engine event.
304
+ - Setting enabled with no server: the run falls back to CPU with a note.
305
+ - Killing the server mid-run fails the run with the disconnect message; restarting the server and resuming continues from the checkpoint.
306
+ - CSP permits the socket.
307
+ - The HIP case is skipped unless the server reports HIP available.
308
+
309
+ ## Documentation
310
+
311
+ - `docs/server.md`: purpose, flags, security model, the protocol (greeting, ops, frames) and fallback behavior.
312
+ - `docs/cli.md`: the `serve` subcommand.
313
+ - `docs/gpu.md`: HIP through `Session`, and CPU turnover with network re-upload.
314
+ - Webapp `README.md`: the setting, the CSP change and native testing.
315
+
316
+ ## Delivery order
317
+
318
+ 1. HIP backend in `Session`, with its tests. Independently useful; no server needed.
319
+ 2. `altd-sim serve` and the protocol, with integration tests (CPU, then HIP).
320
+ 3. Webapp client, CSP and Playwright tests.
321
+
322
+ Each phase lands with green tests before the next starts.
github-simulator/source/docs/wasm.md ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # WASM and WebGPU fidelity notes
2
+
3
+ The serial and threaded packages compile the same Session and TrainingRunner code. The threaded package rebuilds Rust's standard library with atomics and uses `wasm-bindgen-rayon`; it requires an initialized worker pool before constructing worlds or invoking Rayon. Both packages preserve the existing arithmetic and RNG algorithms.
4
+
5
+ Three comparisons answer different questions:
6
+
7
+ - Native versus WASM state/reference checks catch portability differences in the exported fields.
8
+ - Serial versus threaded WASM checks cover states, sensors, controls, metrics, rewards, summaries and binary checkpoints, including exchange between the two packages.
9
+ - WebGPU versus WASM checks validate query or full-window device results against the WASM CPU implementation.
10
+
11
+ Expanded native parity is incomplete. On the public generated Formula scenario, serial and threaded WASM matched each other in 253,341 values per mode, including the optional score aggregates, but both differed from native in 2,285 values per mode. Those differences comprised 703 controls, 405 metrics and 1,177 checkpoint bytes. The original captured-scene baseline had 2,532 native differences with the same exact serial/threaded equality. Counts depend on the scenario, build and checkpoint representation. `STRICT_NATIVE=1` makes the browser test fail on any expanded native difference.
12
+
13
+ ## Device arithmetic
14
+
15
+ The WebGPU raycaster keeps simulation on the CPU and casts rays or nearest-wall queries on-device. Explicit float32 rounding boundaries prevent fusion and reassociation. Division corrects a hardware estimate using an exact integer remainder, with an integer-loop fallback; square root and software binary64 division use the same principle. Full `GpuSimulation` also implements binary64 arithmetic in integer WGSL operations and keeps network accumulation ordered.
16
+
17
+ Historical RX 7900 XTX checks with RADV 26.2.2 matched 65,536 rays and 65,536 nearest-wall points per scenario, and millions of in-simulation rays across Formula and three Rally scenes. A newer baseline repeated a smaller 4,000-query corpus and 119,743 in-simulation rays with zero differences. The public replacements contain four distinct generated tracks and native current-code references, not game-oracle captures.
18
+
19
+ WGSL permits device-dependent subnormal and NaN behavior. Raw division fixtures check signed zero, subnormal boundaries, infinities and broad exponent coverage; NaN payloads are treated as equivalent in that arithmetic diagnostic. Simulation comparisons retain exact values and count differences without substituting CPU results. Results on one GPU do not validate every browser adapter.
20
+
21
+ ## Runtime and memory
22
+
23
+ Threaded WASM uses imported shared memory and a 2 GiB growth ceiling. This is a maximum, not an initial allocation. Workers need stack and TLS memory; an application's separately loaded preview module has its own memory. Shared-memory packages require cross-origin isolation and complete generated helper snippets.
24
+
25
+ A host must await GPU windows before mutating the Session. A failed full-GPU window keeps the last committed WASM state so it can continue on CPU. Paused physics and native shared broadphase are unsupported by full WebGPU. The raycaster has one readback per tick; full simulation batches bounded windows, which changes the population size at which device work can pay off.
26
+
27
+ [The WASM guide](../wasm/README.md) contains build commands, initialization and the browser harness. [Performance notes](performance.md) distinguish CPU worker scaling, startup and WebGPU measurements.
github-simulator/source/gpu/README.md ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # GPU backends (AMD HIP, NVIDIA CUDA)
2
+
3
+ The GPU library runs whole driving generations on an AMD GPU (HIP) or an NVIDIA GPU (CUDA, see below). Captured CPU/HIP checks compare simulation values, population parameters, RNG state and checkpoint bytes exactly. Rust owns evolution and checkpoint I/O; both backends can resume the same generation-boundary checkpoints. See [fidelity](../docs/fidelity.md) for the scope of that evidence.
4
+
5
+ ## Build and use
6
+
7
+ Linux release archives include `libaltd_gpu.so` beside `altd-sim`, built with ROCm 7.1 for the targets in `release-targets`. They need a ROCm 7.x runtime that provides `libamdhip64.so.7`, but no compiler. `altd-sim gpu-info` reports the loaded library. To build the library yourself, install ROCm with `hipcc`, then build from the repository root:
8
+
9
+ ```sh
10
+ gpu/build.sh
11
+ cargo build --release --offline
12
+
13
+ target/release/altd-sim --threads 8 train-scratch --gpu \
14
+ --scene assets/scenes/rally_template.json --track-mode random \
15
+ --random-track-settings assets/random_track_settings.json \
16
+ --population 256 --generations 3 --ticks 600 --seed 1729 \
17
+ --eliminate-on-wall --idle-eliminate --out-dir runs/gpu-example
18
+ ```
19
+
20
+ `OUT` overrides the library output directory, default `target/gpu`. `GPU_ARCH` lists one or more architectures separated by commas or spaces, default `gfx1100` for RX 7900 XTX; `rocminfo` reports a GPU's name. `GPU_ARCH="$(cat gpu/release-targets)"` builds the release library for every consumer target.
21
+
22
+ `--gpu` and `gpu-info` load `ALTD_GPU_LIB` when set. Otherwise they try `libaltd_gpu.so` beside the executable, then `target/gpu` of the checkout that built the binary. Load errors list each attempted path.
23
+
24
+ | Targets | GPUs |
25
+ | --- | --- |
26
+ | gfx803, gfx900, gfx902, gfx906, gfx909, gfx90c | Polaris and Vega: RX 470-590, Vega 56/64, Radeon VII, Vega APUs |
27
+ | gfx1010-gfx1013 | RDNA1: RX 5500-5700 XT |
28
+ | gfx1030-gfx1036 | RDNA2: RX 6400-6950 XT, Steam Deck, Radeon 610M/680M |
29
+ | gfx1100-gfx1103, gfx1150-gfx1153 | RDNA3 and RDNA3.5: RX 7600-7900 XTX, Radeon 780M/890M, Strix Halo |
30
+ | gfx1200, gfx1201 | RDNA4: RX 9060/9070 |
31
+
32
+ The ROCm 7.1 runtime officially supports fewer consumer GPUs than this list; the remaining targets are best effort. The captured CPU/HIP equivalence checks have run on gfx1100 only. The script verifies the checked-in double tables against their Rust bit patterns without rewriting tracked files. `python3 gpu/gen_tables.py --output PATH` writes a header explicitly; `--check` validates it.
33
+
34
+ The compiler flags `-ffp-contract=off` and `-fhip-fp32-correctly-rounded-divide-sqrt` preserve separate multiply/add rounding and correctly rounded float32 division/square root. Explicit `fma` calls in Windows kernels still fuse and round once. Keep these flags when changing architecture. The native crate loads the library at runtime, so CPU-only builds do not require ROCm.
35
+
36
+ ## Math profiles
37
+
38
+ Each library contains `proton`, `win10-fma3` and `win11-fma3`; a build selects GPU architectures, while each simulation selects its math profile. `--math-profile win11-fma3` works with `--gpu` on Linux, for example. Without that flag the CLI detects its host profile; resumed scratch runs retain the saved profile. Server sessions use `options.mathProfile`, defaulting to the server's profile. [Math profiles](../math/README.md) documents the shared Windows kernels and the existing musl implementations.
39
+
40
+ ## NVIDIA (CUDA)
41
+
42
+ The same sources build for NVIDIA GPUs with `nvcc`; `sim/compat.h` maps the HIP runtime calls and warp helpers to CUDA. HIP and CUDA use the same library ABI and checkpoint formats: `--gpu`, `gpu-info` and `backend: "hip"` use whichever library is loaded. Math profiles require simulator ABI 10, so rebuild older libraries. Install the CUDA toolkit (13.x tested) and a host compiler (Visual Studio Build Tools on Windows, gcc or clang on Linux), then build from the repository root:
43
+
44
+ ```sh
45
+ gpu/build-cuda.sh # Linux, writes target/gpu/libaltd_gpu.so
46
+ gpu/build-cuda.ps1 # Windows, writes target/gpu/altd_gpu.dll
47
+ ```
48
+
49
+ `OUT` overrides the output directory. `CUDA_ARCH` defaults to `native`, which builds for the GPUs of the build machine. Without a GPU, nvcc 13.4 warns and builds `sm_75` code and PTX instead, which newer GPUs compile at load time. To build for other GPUs, or several, list `sm_` targets separated by commas or spaces; `nvidia-smi --query-gpu=compute_cap --format=csv` reports the number, so 8.6 is `sm_86`. A list also embeds PTX for its newest target, which the driver compiles at load time for a newer GPU. CUDA 13 builds for Turing (`sm_75`, RTX 20 series) and newer only; older GPUs need a CUDA 12 toolkit, which is untested. Both scripts check the double tables with `gpu/gen_tables.py --check` (`python3` on Linux, the `py` launcher or `python` on Windows). The binary loads `ALTD_GPU_LIB`, then the library beside the executable, then `target/gpu`, as on AMD. `altd-sim gpu-info` runs one kernel when a GPU is present, so a library built for another GPU fails there with HIP error 209.
50
+
51
+ nvcc rejects a Visual Studio newer than the toolkit supports. `gpu/build-cuda.ps1 -AllowUnsupportedCompiler` (or `CUDA_ALLOW_UNSUPPORTED_COMPILER=1`) builds anyway with a warning; NVIDIA has not validated that combination, so run the checks below before trusting the library.
52
+
53
+ `-fmad=false` is the CUDA counterpart of `-ffp-contract=off`; keep it, and keep nvcc's default correctly rounded float32 division and square root (no `-use_fast_math`, no `-ftz=true`). NVIDIA arithmetic also replaces every NaN result with its canonical NaN, so the places where the CPU returns a NaN operand or the x86 default NaN call `altd_nan_operand` and `altd_invalid` from `sim/compat.h`, which build those bits explicitly on both vendors.
54
+
55
+ All three math profiles passed primitive and simulation checks on an RTX 3060 Ti (sm_86, Linux, CUDA 13.0); see the [verification report](../math/verification.md). Earlier Proton baseline checks passed on an RTX 4070 Laptop (sm_89, Windows, CUDA 13.4), and on Linux on an RTX 2070 (sm_75, driver 580) and an RTX 3060 (sm_86, driver 595) with `build-cuda.sh` libraries from CUDA 13.4: `native`, and `sm_75,sm_86`. They also passed with a CUDA 13.0 `sm_75` library run through its PTX (`CUDA_FORCE_PTX_JIT=1`). On those three GPUs the baseline `train-scratch --gpu` checkpoints were byte-identical to the CPU's. The driver compiles PTX only from a toolkit no newer than itself, so PTX for newer GPUs needs a driver at least as new as the CUDA toolkit that built the library. Consumer NVIDIA GPUs run float64 at 1/64 of their float32 rate, which bounds the network forward pass. A Windows GPU that drives a display kills kernels that run for more than a couple of seconds; the windowed launches stay well below that in the checks above.
56
+
57
+ ## Execution model
58
+
59
+ GPU driving uses tick-major execution for independent cars. Native shared broadphase and paused windows are unsupported. Generated CLI tracks disable shared broadphase. Track preparation runs on a CPU producer with a bounded queue; `--track-buffer-size` sets its depth, default eight. Population/network buffers stay allocated while each fresh track replaces geometry.
60
+
61
+ ## Validation and measurements
62
+
63
+ ```sh
64
+ cargo build --release --offline --example gpu_check
65
+ target/release/examples/gpu_check --help
66
+ target/release/examples/gpu_check schedules
67
+ ```
68
+
69
+ The default check fixture is a fixed generated Rally track and seeded Xavier networks. `ALTD_GPU_SCENE`, `ALTD_GPU_SPAWN`, `ALTD_GPU_NETWORK`, `ALTD_GPU_MODEL` and `ALTD_GPU_CKPT` select explicit inputs or a saved population. No captured campaign scene is required. `gpu_check --help` lists primitive, query, sensing, inference, physics and generation checks. Exhaustive parts can take minutes.
70
+
71
+ `gpu_check math` compares primitives under all three profiles with the CPU. `ALTD_GPU_MATH` selects the simulation fixture's profile, default `proton`. Run complete simulation checks for each profile on the library's actual GPU:
72
+
73
+ ```sh
74
+ for profile in proton win10-fma3 win11-fma3; do
75
+ ALTD_GPU_MATH="$profile" ALTD_GPU_LIB=target/gpu/libaltd_gpu.so \
76
+ target/release/examples/gpu_check \
77
+ rays sensors infer step stats window schedules reuse novelty turnover
78
+ done
79
+ ```
80
+
81
+ Run benchmarks on an otherwise idle GPU:
82
+
83
+ ```sh
84
+ RAYON_NUM_THREADS=8 python3 gpu/benchmark.py \
85
+ --library target/gpu/libaltd_gpu.so --cars 8192 32768 \
86
+ --samples 5 --warmups 1 --no-elimination --output target/gpu-fixed.json
87
+ RAYON_NUM_THREADS=8 python3 gpu/benchmark.py --training \
88
+ --library target/gpu/libaltd_gpu.so --cars 8192 --samples 5 \
89
+ --output target/gpu-training.json
90
+ ```
91
+
92
+ The runner keeps every sample and validates result digests. `--checkpoint DIR` selects a saved population; omitting it uses the fixed Xavier recipe. `--scene`, `--spawn-trace`, `--network`, `--model` and `--binary` override inputs. Fixed-window timing excludes fixture construction, reset, hashing and reproduction; training timing includes consecutive generation turnover. Elimination can finish early, so inspect executed ticks before comparing results.
93
+
94
+ `gpu/profile.sh` collects phase timings, optional rocprofv3 traces/counters and diagnostic kernel builds. `--only 1,2`, `--sizes "8192 32768"`, `--checkpoint DIR` and `--output target/profile` select the work. Profiling changes execution behavior; final timing should run with profiling disabled. [Performance notes](../docs/performance.md) retain measured hardware results.
95
+
96
+ | Variable | Effect |
97
+ | --- | --- |
98
+ | `ALTD_GPU_LIB` | Runtime shared-library path |
99
+ | `ALTD_GPU_MATH` | Math profile of `gpu_check` simulation fixtures |
100
+ | `ALTD_GPU_GRAPH=0` | Direct launches instead of HIP graph replay |
101
+ | `ALTD_GPU_SPLIT=0` | Fused instead of split physics |
102
+ | `ALTD_GPU_PROFILE=1` | Device phase timings; `host` reports host/wall time only |
103
+ | `ALTD_TRAIN_PROFILE=1` | CPU reproduction, installation and transfer timings |
104
+
105
+ [Implementation notes](../docs/gpu.md) explain batching, graph lifetime, exact reduction order and memory costs. `gpu_engine_scan` is an exhaustive native/GPU trigonometry developer tool; it requires an explicit thread count and output header, and is not part of the regular test suite.
github-simulator/source/gpu/benchmark.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Benchmark identical GPU generations; build examples/gpu_check first."""
3
+ import argparse
4
+ import hashlib
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ import statistics
9
+ import subprocess
10
+
11
+
12
+ def main():
13
+ parser = argparse.ArgumentParser(description=__doc__)
14
+ parser.add_argument("--library", type=Path, required=True)
15
+ parser.add_argument("--cars", type=int, nargs="+", default=[8192, 32768, 262144])
16
+ parser.add_argument("--ticks", type=int, default=5400)
17
+ parser.add_argument("--samples", type=int, default=3)
18
+ parser.add_argument("--warmups", type=int, default=1)
19
+ parser.add_argument("--no-elimination", action="store_true")
20
+ parser.add_argument("--training", action="store_true", help="include turnover across consecutive generations")
21
+ parser.add_argument("--generations", type=int, default=3)
22
+ parser.add_argument("--binary", type=Path, help="gpu_check executable (also supports saved baselines)")
23
+ parser.add_argument("--checkpoint", type=Path, help="saved train-scratch population; otherwise use the fixed generated fixture")
24
+ parser.add_argument("--scene", type=Path, help="explicit scene instead of the generated track")
25
+ parser.add_argument("--spawn-trace", type=Path, help="spawn trace for an explicit scene")
26
+ parser.add_argument("--network", type=Path, help="sensor/control names")
27
+ parser.add_argument("--model", type=Path, help="vehicle sensor model")
28
+ parser.add_argument("--output", type=Path, required=True)
29
+ args = parser.parse_args()
30
+ for key, variable in [("checkpoint", "ALTD_GPU_CKPT"), ("scene", "ALTD_GPU_SCENE"),
31
+ ("spawn_trace", "ALTD_GPU_SPAWN"), ("network", "ALTD_GPU_NETWORK"),
32
+ ("model", "ALTD_GPU_MODEL")]:
33
+ value = getattr(args, key)
34
+ if value is not None:
35
+ os.environ[variable] = str(value.resolve())
36
+ root = Path(__file__).resolve().parent.parent
37
+ library = args.library.resolve()
38
+ binary = args.binary.resolve() if args.binary else root / "target/release/examples/gpu_check"
39
+ if args.samples < 1 or args.warmups < 0 or args.generations < 1:
40
+ parser.error("samples and generations must be positive; warmups must be nonnegative")
41
+ if args.training and args.no_elimination:
42
+ parser.error("the training benchmark uses the fixture's elimination settings")
43
+ report = {
44
+ "benchmark": "training" if args.training else "fixed_generation",
45
+ "binary": str(binary),
46
+ "binary_sha256": hashlib.sha256(binary.read_bytes()).hexdigest(),
47
+ "library": str(library),
48
+ "library_sha256": hashlib.sha256(library.read_bytes()).hexdigest(),
49
+ "requested_ticks": args.ticks,
50
+ "rayon_threads": os.environ.get("RAYON_NUM_THREADS", "default"),
51
+ "graph": os.environ.get("ALTD_GPU_GRAPH", "default"),
52
+ "split": os.environ.get("ALTD_GPU_SPLIT", "default"),
53
+ "profile": os.environ.get("ALTD_GPU_PROFILE", "off"),
54
+ "training_profile": os.environ.get("ALTD_TRAIN_PROFILE", "off"),
55
+ "generations": args.generations if args.training else 1,
56
+ "samples": args.samples,
57
+ "warmups": args.warmups,
58
+ "eliminate": not args.no_elimination,
59
+ "results": [],
60
+ }
61
+ report["fixture_inputs"] = {key: os.environ[key] for key in [
62
+ "ALTD_GPU_CKPT", "ALTD_GPU_SCENE", "ALTD_GPU_SPAWN", "ALTD_GPU_NETWORK", "ALTD_GPU_MODEL"
63
+ ] if key in os.environ}
64
+ if "ALTD_GPU_CKPT" in os.environ:
65
+ checkpoint = Path(os.environ["ALTD_GPU_CKPT"])
66
+ metadata = json.loads((checkpoint / "checkpoint.json").read_text())
67
+ report["checkpoint_sha256"] = hashlib.sha256((checkpoint / metadata["population_file"]).read_bytes()).hexdigest()
68
+ else:
69
+ report["population_recipe"] = {"initialization": "Xavier", "seed": 1, "shape": [20,16,16,16,16,12,12,8,5]}
70
+ for cars in args.cars:
71
+ env = dict(os.environ, ALTD_GPU_LIB=str(library), ALTD_GPU_BENCH_CARS=str(cars),
72
+ ALTD_GPU_BENCH_TICKS=str(args.ticks), ALTD_GPU_BENCH_SAMPLES=str(args.samples),
73
+ ALTD_GPU_BENCH_WARMUPS=str(args.warmups),
74
+ ALTD_GPU_BENCH_GENERATIONS=str(args.generations),
75
+ ALTD_GPU_BENCH_ELIMINATE=str(int(not args.no_elimination)))
76
+ command = [str(binary), "benchtrain" if args.training else "benchfixed"]
77
+ records = []
78
+ for sample in range(args.warmups + args.samples if args.training else 1):
79
+ trial = []
80
+ with subprocess.Popen(command, cwd=root, env=env, stdout=subprocess.PIPE, text=True) as process:
81
+ for line in process.stdout:
82
+ print(line, end="", flush=True)
83
+ if line.startswith("{"):
84
+ record = json.loads(line)
85
+ if args.training:
86
+ record.update(sample=sample, warmup=sample < args.warmups)
87
+ trial.append(record)
88
+ if process.wait():
89
+ raise SystemExit(f"benchmark failed for {cars} cars")
90
+ if args.training and [r["generation"] for r in trial] != list(range(args.generations)):
91
+ raise SystemExit("missing training generations")
92
+ records.extend(trial)
93
+ if args.training:
94
+ generations = []
95
+ for generation in range(args.generations):
96
+ runs = [r for r in records if r["generation"] == generation]
97
+ timed = [r for r in runs if not r["warmup"]]
98
+ if len(timed) != args.samples or len({r["digest"] for r in runs}) != 1:
99
+ raise SystemExit("missing samples or inconsistent training results")
100
+ generations.append({"generation": generation, "digest": runs[0]["digest"], **{
101
+ "median_" + key: statistics.median(r[key] for r in timed)
102
+ for key in ["simulate_seconds", "turnover_seconds", "total_seconds"]}})
103
+ timed = [sum(r["total_seconds"] for r in records if r["sample"] == sample) / args.generations
104
+ for sample in range(args.warmups, args.warmups + args.samples)]
105
+ result = {"cars": cars, "median_seconds": statistics.median(timed), "generations": generations, "runs": records}
106
+ else:
107
+ timed = [r["seconds"] for r in records if not r["warmup"]]
108
+ if len(timed) != args.samples or len({r["digest"] for r in records}) != 1:
109
+ raise SystemExit("missing samples or inconsistent results")
110
+ result = {"cars": cars, "median_seconds": statistics.median(timed), "runs": records}
111
+ report["results"].append(result)
112
+ args.output.parent.mkdir(parents=True, exist_ok=True)
113
+ args.output.write_text(json.dumps(report, indent=2) + "\n")
114
+
115
+
116
+ if __name__ == "__main__":
117
+ main()
github-simulator/source/gpu/build-cuda.ps1 ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Build altd_gpu.dll (the GPU simulator parts) for NVIDIA GPUs into $OUT.
2
+ # CUDA_ARCH is native (the default: the GPUs of this machine; without one nvcc warns and uses
3
+ # sm_75) or one or more sm_ targets separated by commas or spaces. A list also embeds PTX for its
4
+ # newest target, which the driver compiles at load time for newer GPUs.
5
+ # -fmad=false: nvcc otherwise fuses a*b+c into FMA, which rounds once. Keep the default
6
+ # -prec-div=true -prec-sqrt=true -ftz=false and never use -use_fast_math.
7
+ # -AllowUnsupportedCompiler (or CUDA_ALLOW_UNSUPPORTED_COMPILER=1) passes -allow-unsupported-compiler
8
+ # to nvcc for a Visual Studio newer than the CUDA toolkit supports. NVIDIA has not validated that
9
+ # combination; run the CPU/GPU checks in gpu/README.md before trusting the result.
10
+ param(
11
+ [string]$Out = $(if ($env:OUT) { $env:OUT } else { Join-Path $PSScriptRoot "..\target\gpu" }),
12
+ [string]$Arch = $(if ($env:CUDA_ARCH) { $env:CUDA_ARCH } else { "native" }),
13
+ [switch]$AllowUnsupportedCompiler = ($env:CUDA_ALLOW_UNSUPPORTED_COMPILER -eq "1")
14
+ )
15
+ $ErrorActionPreference = "Stop"
16
+ New-Item -ItemType Directory -Force $Out | Out-Null
17
+ $Out = (Resolve-Path $Out).Path
18
+
19
+ # The py launcher first: python3 on Windows is often the Microsoft Store placeholder.
20
+ $tables = Join-Path $PSScriptRoot "gen_tables.py"
21
+ if (Get-Command py -ErrorAction SilentlyContinue) { py -3 $tables --check } else { python $tables --check }
22
+ if ($LASTEXITCODE -ne 0) { throw "gen_tables.py --check failed" }
23
+
24
+ if ($Arch -eq "native") {
25
+ $archFlags = "-arch=native"
26
+ } else {
27
+ $targets = @($Arch -split '[,\s]+' | Where-Object { $_ })
28
+ if (-not $targets) { throw "CUDA_ARCH lists no targets" }
29
+ $bad = @($targets | Where-Object { $_ -notmatch '^sm_\d+$' })
30
+ if ($bad) { throw "CUDA_ARCH target $($bad[0]) is not sm_NN (or the whole value native)" }
31
+ $numbers = @($targets | ForEach-Object { [int]($_ -replace '^sm_', '') })
32
+ $newest = ($numbers | Measure-Object -Maximum).Maximum
33
+ $archFlags = (@($numbers | ForEach-Object { "-gencode=arch=compute_$_,code=sm_$_" }) +
34
+ "-gencode=arch=compute_$newest,code=compute_$newest") -join " "
35
+ }
36
+
37
+ $unsupported = ""
38
+ if ($AllowUnsupportedCompiler) {
39
+ Write-Warning "building with -allow-unsupported-compiler: NVIDIA has not validated this Visual Studio with this CUDA toolkit"
40
+ $unsupported = "-allow-unsupported-compiler"
41
+ }
42
+
43
+ # nvcc needs the MSVC host compiler environment; load it unless cl.exe is already on PATH.
44
+ $src = Join-Path $PSScriptRoot "sim\altd_gpu.hip"
45
+ $dll = Join-Path $Out "altd_gpu.dll"
46
+ $prefix = ""
47
+ if (-not (Get-Command cl -ErrorAction SilentlyContinue)) {
48
+ $vw = Join-Path ([Environment]::GetFolderPath("ProgramFilesX86")) "Microsoft Visual Studio\Installer\vswhere.exe"
49
+ $vs = & $vw -products * -latest -property installationPath
50
+ $prefix = "`"$(Join-Path $vs 'VC\Auxiliary\Build\vcvars64.bat')`" >nul && "
51
+ }
52
+ $cmd = "${prefix}nvcc -x cu -O3 -std=c++20 -fmad=false $unsupported $archFlags -shared -Xcompiler /MD -o `"$dll`" `"$src`""
53
+ cmd /c $cmd
54
+ if ($LASTEXITCODE -ne 0) { throw "nvcc failed" }
55
+ Write-Host "built $dll for $Arch"
github-simulator/source/gpu/build-cuda.sh ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # Build libaltd_gpu.so (the GPU simulator parts) for NVIDIA GPUs into $OUT.
3
+ # CUDA_ARCH is native (the default: the GPUs of this machine; without one nvcc warns and uses
4
+ # sm_75) or one or more sm_ targets separated by commas or spaces. A list also embeds PTX for its
5
+ # newest target, which the driver compiles at load time for newer GPUs.
6
+ # -fmad=false: nvcc otherwise fuses a*b+c into FMA, which rounds once. Keep the default
7
+ # -prec-div=true -prec-sqrt=true -ftz=false and never use -use_fast_math.
8
+ set -euo pipefail
9
+ HERE="$(cd "$(dirname "$0")" && pwd)"
10
+ OUT="${OUT:-$HERE/../target/gpu}"
11
+ mkdir -p "$OUT"
12
+ python3 "$HERE/gen_tables.py" --check
13
+ CUDA_ARCH="${CUDA_ARCH:-native}"
14
+ ARCH_FLAGS=()
15
+ if [ "$CUDA_ARCH" = native ]; then
16
+ ARCH_FLAGS=("-arch=native")
17
+ else
18
+ newest=0
19
+ for arch in ${CUDA_ARCH//,/ }; do
20
+ if ! [[ "$arch" =~ ^sm_[0-9]+$ ]]; then
21
+ echo "CUDA_ARCH target $arch is not sm_NN (or the whole value native)" >&2
22
+ exit 1
23
+ fi
24
+ ARCH_FLAGS+=("-gencode=arch=compute_${arch#sm_},code=$arch")
25
+ if [ "${arch#sm_}" -gt "$newest" ]; then newest="${arch#sm_}"; fi
26
+ done
27
+ if [ "${#ARCH_FLAGS[@]}" -eq 0 ]; then
28
+ echo "CUDA_ARCH lists no targets" >&2
29
+ exit 1
30
+ fi
31
+ ARCH_FLAGS+=("-gencode=arch=compute_$newest,code=compute_$newest")
32
+ fi
33
+ nice -n 19 nvcc -x cu -O3 -std=c++20 -fmad=false "${ARCH_FLAGS[@]}" -shared -Xcompiler -fPIC \
34
+ -o "$OUT/libaltd_gpu.so" "$HERE/sim/altd_gpu.hip"
35
+ echo "built $OUT/libaltd_gpu.so for ${CUDA_ARCH}" >&2
github-simulator/source/gpu/build.sh ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # Build libaltd_gpu.so (the GPU simulator parts) into $OUT.
3
+ # GPU_ARCH lists one or more offload targets separated by commas or spaces.
4
+ # -ffp-contract=off: hipcc otherwise fuses a*b+c into FMA, which rounds once.
5
+ set -euo pipefail
6
+ HERE="$(cd "$(dirname "$0")" && pwd)"
7
+ OUT="${OUT:-$HERE/../target/gpu}"
8
+ mkdir -p "$OUT"
9
+ python3 "$HERE/gen_tables.py" --check
10
+ GPU_ARCH="${GPU_ARCH:-gfx1100}"
11
+ ARCH_FLAGS=()
12
+ for arch in ${GPU_ARCH//,/ }; do
13
+ ARCH_FLAGS+=("--offload-arch=$arch")
14
+ done
15
+ if [ "${#ARCH_FLAGS[@]}" -eq 0 ]; then
16
+ echo "GPU_ARCH lists no targets" >&2
17
+ exit 1
18
+ fi
19
+ nice -n 19 hipcc -O3 -ffp-contract=off "${ARCH_FLAGS[@]}" -fPIC -shared -std=c++20 -fhip-fp32-correctly-rounded-divide-sqrt \
20
+ -o "$OUT/libaltd_gpu.so" "$HERE/sim/altd_gpu.hip" -lamdhip64
21
+ echo "built $OUT/libaltd_gpu.so for ${ARCH_FLAGS[*]#--offload-arch=}" >&2
github-simulator/source/gpu/gen_tables.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Translate src/math/double_math_tables.rs into gpu/sim/double_tables.h.
3
+
4
+ Only bit patterns are copied, so the GPU tables equal the CPU ones exactly.
5
+ """
6
+ import argparse
7
+ from pathlib import Path
8
+ import re
9
+ import sys
10
+
11
+ HERE = Path(__file__).resolve().parent
12
+ source = (HERE.parent / "src/math/double_math_tables.rs").read_text()
13
+ out = [
14
+ "// Generated by gpu/gen_tables.py from src/math/double_math_tables.rs. Do not edit.",
15
+ "#pragma once",
16
+ "#include <cstdint>",
17
+ ]
18
+
19
+
20
+ def c_type(rust):
21
+ dims = []
22
+ while rust.startswith("["):
23
+ inner, count = re.fullmatch(r"\[(.*);(\d+)\]", rust).groups()
24
+ dims.insert(0, count)
25
+ rust = inner
26
+ base = {"f64": "double", "u64": "uint64_t"}[rust]
27
+ return base, "".join(f"[{d}]" for d in reversed(dims))
28
+
29
+
30
+ def c_value(rust, top_level=True):
31
+ """Keep the header layout independent of Rust source formatting."""
32
+ rust = re.sub(r"\s+", "", rust)
33
+ if rust.startswith("["):
34
+ values = []
35
+ start = 1
36
+ depth = 0
37
+ for index, char in enumerate(rust[1:-1], start=1):
38
+ if char in "[(":
39
+ depth += 1
40
+ elif char in "])":
41
+ depth -= 1
42
+ elif char == "," and depth == 0:
43
+ values.append(c_value(rust[start:index], False))
44
+ start = index + 1
45
+ if start < len(rust) - 1:
46
+ values.append(c_value(rust[start:-1], False))
47
+ separator = ",\n" if top_level else ","
48
+ edge = "\n" if top_level else ""
49
+ return "{" + edge + separator.join(values) + edge + "}"
50
+ if match := re.fullmatch(r"f64::from_bits\((0x[0-9a-f]+)\)", rust):
51
+ return f"__builtin_bit_cast(double, (uint64_t){match[1]}ull)"
52
+ if re.fullmatch(r"0x[0-9a-f]+|0", rust):
53
+ return rust + "ull"
54
+ raise ValueError(f"unsupported table value: {rust}")
55
+
56
+
57
+ for name, rust_type, value in re.findall(r"pub(?:\([^)]*\))? const (\w+): ([^=]+?) = (.*?);\n", source, re.S):
58
+ base, dims = c_type(rust_type.replace(" ", ""))
59
+ value = c_value(value)
60
+ out.append(f"__device__ constexpr {base} {name}{dims} = {value};")
61
+
62
+ parser = argparse.ArgumentParser(description=__doc__)
63
+ parser.add_argument("--output", type=Path, default=HERE / "sim/double_tables.h")
64
+ parser.add_argument("--check", action="store_true", help="check the selected header without writing it")
65
+ args = parser.parse_args()
66
+ generated = "\n".join(out) + "\n"
67
+ if args.check:
68
+ if not args.output.is_file() or args.output.read_text() != generated:
69
+ parser.exit(1, f"{args.output} is stale; run python3 gpu/gen_tables.py\n")
70
+ print(f"verified {args.output}", file=sys.stderr)
71
+ else:
72
+ args.output.parent.mkdir(parents=True, exist_ok=True)
73
+ args.output.write_text(generated)
74
+ print(f"wrote {args.output}", file=sys.stderr)
github-simulator/source/gpu/profile.sh ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # Interactive profiling wizard for the GPU simulator. Collects, into one
3
+ # directory, everything needed to see where a generation's time goes:
4
+ #
5
+ # 1 built-in per-phase GPU time (ALTD_GPU_PROFILE=1) and split/fused wall clock
6
+ # 2 per-kernel durations with rocprofv3 (kernel trace + stats)
7
+ # 3 hardware counters of the physics/sensor/forward kernels with rocprofv3
8
+ # 4 step-section cycle counts (a -DALTD_STEP_TIMING library) and kernel resources
9
+ # 5 forward-pass attribution: diagnostic libraries with parts of the network
10
+ # kernel removed (-DALTD_FORWARD_DIAG), and the scalar tanh for an A/B
11
+ #
12
+ # Every step is optional and failures are recorded, not fatal. Run from anywhere:
13
+ # gpu/profile.sh # asks before each level
14
+ # gpu/profile.sh --yes # accept every default without asking
15
+ # gpu/profile.sh --only 1,2 # run only these levels, without asking
16
+ # gpu/profile.sh --sizes "8192 32768" # populations (default: 8192 32768 262144)
17
+ # gpu/profile.sh --checkpoint runs/example # optional saved population
18
+ # gpu/profile.sh --output target/profile # results directory
19
+ #
20
+ set -u
21
+ HERE="$(cd "$(dirname "$0")" && pwd)"
22
+ REPO="$(cd "$HERE/.." && pwd)"
23
+ cd "$REPO"
24
+
25
+ YES=0; ONLY=""; SIZES="8192 32768 262144"; PROFILE_OUT=""
26
+ while [ $# -gt 0 ]; do
27
+ case "$1" in
28
+ --yes|-y) YES=1 ;;
29
+ --only) ONLY="$2"; shift ;;
30
+ --sizes) SIZES="$2"; shift ;;
31
+ --checkpoint) export ALTD_GPU_CKPT="$2"; shift ;;
32
+ --output) PROFILE_OUT="$2"; shift ;;
33
+ -h|--help) sed -n '2,19p' "$0"; exit 0 ;;
34
+ *) echo "unknown option $1" >&2; exit 2 ;;
35
+ esac
36
+ shift
37
+ done
38
+
39
+ OUT="${PROFILE_OUT:-$REPO/prof-$(date +%Y%m%d-%H%M)}"
40
+ mkdir -p "$OUT"
41
+ OUT="$(cd "$OUT" && pwd)"
42
+ LOG="$OUT/README.txt"
43
+ CHECK="target/release/examples/gpu_check"
44
+ LIB="${ALTD_GPU_LIB:-$REPO/target/gpu/libaltd_gpu.so}"
45
+ export RAYON_NUM_THREADS="${RAYON_NUM_THREADS:-8}"
46
+ # Keep the integrated GPU (the Ryzen's gfx1036) out of every run.
47
+ export HIP_VISIBLE_DEVICES="${HIP_VISIBLE_DEVICES:-0}" ROCR_VISIBLE_DEVICES="${ROCR_VISIBLE_DEVICES:-0}"
48
+
49
+ bold() { printf '\033[1m%s\033[0m\n' "$*"; }
50
+ note() { printf '%s\n' "$*" | tee -a "$LOG"; }
51
+ ask() { # ask "question" default(y|n) -> returns 0 for yes
52
+ local q=$1 d=$2 a
53
+ if [ "$YES" = 1 ]; then a=$d; else
54
+ if [ "$d" = y ]; then read -r -p "$q [Y/n] " a </dev/tty; a=${a:-y}; else read -r -p "$q [y/N] " a </dev/tty; a=${a:-n}; fi
55
+ fi
56
+ [[ $a =~ ^[Yy] ]]
57
+ }
58
+ want_level() { # level number: honoured --only, else asks
59
+ local n=$1 title=$2
60
+ if [ -n "$ONLY" ]; then
61
+ [[ ",$ONLY," == *",$n,"* ]] || return 1
62
+ echo; bold "Level $n: $title"; return 0
63
+ fi
64
+ echo; bold "Level $n: $title"; ask "Run level $n?" y
65
+ }
66
+ # run_logged <name> <command...>: runs, times, records the exit code; stdout+stderr to $OUT/<name>.txt
67
+ run_logged() {
68
+ local name=$1; shift
69
+ local start=$SECONDS
70
+ printf ' %-44s' "$name"
71
+ "$@" > "$OUT/$name.txt" 2>&1
72
+ local rc=$?
73
+ local secs=$((SECONDS - start))
74
+ if [ $rc -eq 0 ]; then echo "ok (${secs}s)"; else echo "FAILED rc=$rc (${secs}s), see $name.txt"; fi
75
+ echo "$name: rc=$rc seconds=$secs :: $*" >> "$LOG"
76
+ return $rc
77
+ }
78
+ bench_env() { # bench_env cars eliminate [extra VAR=val ...]
79
+ local cars=$1 elim=$2; shift 2
80
+ echo env ALTD_GPU_BENCH_CARS="$cars" ALTD_GPU_BENCH_ELIMINATE="$elim" ALTD_GPU_BENCH_WARMUPS=0 ALTD_GPU_BENCH_SAMPLES=1 "$@"
81
+ }
82
+
83
+ bold "GPU profiling wizard, output in $OUT"
84
+ {
85
+ echo "altd GPU profiling run $(date -Iseconds)"
86
+ echo "repo: $REPO git: $(git rev-parse --short HEAD 2>/dev/null) $(git status --short 2>/dev/null | wc -l) modified files"
87
+ echo "library: $LIB"; echo "checkpoint: ${ALTD_GPU_CKPT:-generated Xavier population}"; echo "sizes: $SIZES rayon threads: $RAYON_NUM_THREADS"
88
+ echo
89
+ } > "$LOG"
90
+
91
+ # ---- preflight -------------------------------------------------------------
92
+ echo; bold "Preflight"
93
+ missing=0
94
+ for tool in cargo hipcc rocminfo python3; do
95
+ if command -v "$tool" >/dev/null; then echo " $tool: $(command -v "$tool")"; else echo " $tool: MISSING"; missing=1; fi
96
+ done
97
+ if command -v rocprofv3 >/dev/null; then HAVE_ROCPROF=1; echo " rocprofv3: $(command -v rocprofv3)"; else HAVE_ROCPROF=0; echo " rocprofv3: missing (levels 2 and 3 will be skipped)"; fi
98
+ [ $missing = 1 ] && { echo "install the missing tools first"; exit 1; }
99
+ if [ -n "${ALTD_GPU_CKPT:-}" ] && [ ! -f "$ALTD_GPU_CKPT/checkpoint.json" ]; then
100
+ echo " checkpoint: $ALTD_GPU_CKPT/checkpoint.json not found"; exit 1
101
+ fi
102
+ echo " checkpoint: ${ALTD_GPU_CKPT:-generated Xavier population}"
103
+ { rocminfo | grep -E "^\s*(Name|Marketing Name|Compute Unit|Max Clock Freq|Wavefront Size):" ; } > "$OUT/device.txt" 2>&1
104
+ { hipcc --version; echo; rocminfo | grep -m1 -i "ROCk\|version"; [ $HAVE_ROCPROF = 1 ] && rocprofv3 --version; } > "$OUT/toolchain.txt" 2>&1
105
+ grep -m1 "Marketing Name" "$OUT/device.txt" | sed 's/^\s*/ /'
106
+ if [ ! -f "$LIB" ] || [ "$(find gpu/sim math/kernels gpu/build.sh -type f -newer "$LIB" | wc -l)" -gt 0 ]; then
107
+ if ask "GPU library missing or older than its sources or math kernels. Build it now with gpu/build.sh?" y; then run_logged build_library gpu/build.sh || exit 1; fi
108
+ fi
109
+ sha256sum "$LIB" >> "$LOG"
110
+ if [ ! -x "$CHECK" ] || [ "$(find src examples -newer "$CHECK" -name '*.rs' | wc -l)" -gt 0 ]; then
111
+ run_logged build_examples cargo build --release --offline --example gpu_check || exit 1
112
+ fi
113
+ if ask "Run the exactness checks first (gpu_check math novelty turnover step, a few minutes)?" y; then
114
+ run_logged exactness_check "$CHECK" math novelty turnover step
115
+ grep -E "ALL OK|FAILURES|mismatch" "$OUT/exactness_check.txt" | tail -3 | sed 's/^/ /'
116
+ fi
117
+ echo " Close games and other GPU load now; the step kernels are latency sensitive."
118
+ ask "Continue?" y || exit 0
119
+
120
+ # ---- level 1: built-in phases ---------------------------------------------
121
+ if want_level 1 "built-in per-phase GPU time and split/fused wall clock"; then
122
+ note "level 1: ALTD_GPU_PROFILE=1 prints GPU seconds per phase and per 600 ticks; graphs are disabled in this mode"
123
+ for cars in $SIZES; do for elim in 0 1; do
124
+ run_logged "phases_${cars}_elim${elim}" $(bench_env "$cars" "$elim" ALTD_GPU_PROFILE=1) "$CHECK" benchfixed
125
+ grep -h "altd_gpu window:" "$OUT/phases_${cars}_elim${elim}.txt" | head -2 | sed 's/^/ /'
126
+ done; done
127
+ for cars in $SIZES; do [ "$cars" -le 32768 ] || continue
128
+ run_logged "phases_${cars}_elim1_fused" $(bench_env "$cars" 1 ALTD_GPU_PROFILE=1 ALTD_GPU_SPLIT=0) "$CHECK" benchfixed
129
+ done
130
+ note "level 1b: production-mode wall clock, split vs fused step (graph on, no profiling)"
131
+ : > "$OUT/split_vs_fused.jsonl"
132
+ for split in 1 0; do for cars in $SIZES; do [ "$cars" -le 32768 ] || continue; for elim in 0 1; do
133
+ run_logged "wall_split${split}_${cars}_elim${elim}" env ALTD_GPU_SPLIT=$split ALTD_GPU_BENCH_CARS=$cars ALTD_GPU_BENCH_ELIMINATE=$elim ALTD_GPU_BENCH_WARMUPS=1 ALTD_GPU_BENCH_SAMPLES=2 "$CHECK" benchfixed
134
+ grep '^{' "$OUT/wall_split${split}_${cars}_elim${elim}.txt" | sed "s/^{/{\"split\":$split,/" >> "$OUT/split_vs_fused.jsonl"
135
+ done; done; done
136
+ fi
137
+
138
+ # ---- level 2: rocprofv3 kernel trace ---------------------------------------
139
+ if [ $HAVE_ROCPROF = 1 ] && want_level 2 "per-kernel durations with rocprofv3 (kernel trace + stats)"; then
140
+ note "level 2: rocprofv3 --kernel-trace --stats per run; graph mode unless kernels do not appear individually"
141
+ for cars in $SIZES; do for elim in 0 1; do
142
+ dir="$OUT/trace_${cars}_elim${elim}"
143
+ run_logged "trace_${cars}_elim${elim}" $(bench_env "$cars" "$elim") rocprofv3 --kernel-trace --stats --output-format csv -d "$dir" -o run -- "$CHECK" benchfixed
144
+ stats=$(find "$dir" -name '*kernel_stats.csv' 2>/dev/null | head -1)
145
+ if [ -z "$stats" ] || ! grep -q "w_step\|step_kernel" "$stats"; then
146
+ note " graph launches were not traced as kernels; repeating with ALTD_GPU_GRAPH=0"
147
+ run_logged "trace_${cars}_elim${elim}_direct" $(bench_env "$cars" "$elim" ALTD_GPU_GRAPH=0) rocprofv3 --kernel-trace --stats --output-format csv -d "${dir}_direct" -o run -- "$CHECK" benchfixed
148
+ stats=$(find "${dir}_direct" -name '*kernel_stats.csv' 2>/dev/null | head -1)
149
+ fi
150
+ [ -n "$stats" ] && { echo " top kernels by total time:"; sort -t, -k3 -g -r "$stats" 2>/dev/null | head -6 | cut -d, -f1-4 | sed 's/^/ /'; }
151
+ done; done
152
+ fi
153
+
154
+ # ---- level 3: hardware counters -------------------------------------------
155
+ if [ $HAVE_ROCPROF = 1 ] && want_level 3 "hardware counters of the step, sensor and forward kernels"; then
156
+ note "level 3: rocprofv3 --pmc passes over 300 ticks with direct launches"
157
+ rocprofv3 --list-avail > "$OUT/counters_available.txt" 2>&1
158
+ # Candidate passes; names absent from this GPU's list are dropped, empty passes skipped.
159
+ passes=(
160
+ "SQ_WAVES SQ_BUSY_CYCLES SQ_WAVE_CYCLES GRBM_GUI_ACTIVE"
161
+ "SQ_INSTS_VALU SQ_INSTS_SALU SQ_INSTS_LDS SQ_INSTS_SMEM"
162
+ "SQ_INSTS_VMEM_RD SQ_INSTS_VMEM_WR SQ_INST_LEVEL_VMEM SQ_WAIT_INST_ANY"
163
+ "SQ_ACTIVE_INST_VALU SQ_ACTIVE_INST_VMEM SQ_ACTIVE_INST_LDS SQ_INST_LEVEL_LDS"
164
+ "GL2C_HIT GL2C_MISS TCP_TOTAL_CACHE_ACCESSES TCP_TCC_READ_REQ"
165
+ "SQ_LEVEL_WAVES SQ_INSTS_BRANCH SQ_INSTS_FLAT SQ_INST_CYCLES_VMEM_RD"
166
+ "SQ_INSTS_VALU_TRANS_F64 SQ_INSTS_VALU_FMA_F64 SQ_INSTS_VALU_ADD_F64 SQ_INSTS_VALU_MUL_F64"
167
+ )
168
+ : > "$OUT/pmc.txt"; dropped=""
169
+ for pass in "${passes[@]}"; do
170
+ keep=""
171
+ for c in $pass; do if grep -qw "$c" "$OUT/counters_available.txt"; then keep="$keep $c"; else dropped="$dropped $c"; fi; done
172
+ [ -n "$keep" ] && echo "pmc:$keep" >> "$OUT/pmc.txt"
173
+ done
174
+ note " counter passes:"; sed 's/^/ /' "$OUT/pmc.txt" | tee -a "$LOG"
175
+ [ -n "$dropped" ] && note " not available on this GPU, dropped:$dropped"
176
+ for cars in $SIZES; do [ "$cars" = 32768 ] && continue
177
+ run_logged "counters_${cars}" $(bench_env "$cars" 0 ALTD_GPU_GRAPH=0 ALTD_GPU_BENCH_TICKS=300) rocprofv3 -i "$OUT/pmc.txt" --kernel-include-regex "w_step|sat_kernel|w_sensor|w_forward|w_stats|step_kernel" --output-format csv -d "$OUT/counters_${cars}" -o run -- "$CHECK" benchfixed
178
+ done
179
+ fi
180
+
181
+ # ---- level 4: step sections and kernel resources ---------------------------
182
+ if want_level 4 "step-section cycle counts (timing build) and kernel resource usage"; then
183
+ note "level 4: a second library built with -DALTD_STEP_TIMING (atomics slow the step; only the ratios matter)"
184
+ FLAGS=(-O3 -ffp-contract=off --offload-arch="${GPU_ARCH:-gfx1100}" -fPIC -shared -std=c++20 -fhip-fp32-correctly-rounded-divide-sqrt)
185
+ TLIB="$REPO/target/gpu/libaltd_gpu_timing.so"
186
+ python3 gpu/gen_tables.py --check || exit 1
187
+ if run_logged build_timing_library hipcc "${FLAGS[@]}" -DALTD_STEP_TIMING -o "$TLIB" gpu/sim/altd_gpu.hip -lamdhip64; then
188
+ : > "$OUT/step_sections.txt"
189
+ for cars in $SIZES; do [ "$cars" = 32768 ] && continue; for elim in 0 1; do
190
+ run_logged "sections_${cars}_elim${elim}" $(bench_env "$cars" "$elim" ALTD_GPU_LIB="$TLIB" ALTD_GPU_PROFILE=1) "$CHECK" benchfixed
191
+ { echo "== $cars cars, eliminate=$elim"; grep -E "step sections|altd_gpu window:" "$OUT/sections_${cars}_elim${elim}.txt"; } >> "$OUT/step_sections.txt"
192
+ done; done
193
+ fi
194
+ note " kernel resource usage (VGPRs, SGPRs, scratch, LDS, occupancy) of the production flags"
195
+ run_logged kernel_resources hipcc "${FLAGS[@]}" -Rpass-analysis=kernel-resource-usage -o "$OUT/.tmp_resources.so" gpu/sim/altd_gpu.hip -lamdhip64
196
+ rm -f "$OUT/.tmp_resources.so"
197
+ fi
198
+
199
+ # ---- level 5: forward-pass attribution -------------------------------------
200
+ if want_level 5 "forward-pass attribution (diagnostic builds; their results are wrong by design)"; then
201
+ note "level 5: forward seconds of a 32K full run per variant. diag1 = no activation, diag2 = no weight traffic, diag3 = activation only, diag4 = layer loop only, diag5 = diag4 without layer barriers, branchy = scalar tanh, cumode = -mcumode build"
202
+ FLAGS=(-O3 -ffp-contract=off --offload-arch="${GPU_ARCH:-gfx1100}" -fPIC -shared -std=c++20 -fhip-fp32-correctly-rounded-divide-sqrt)
203
+ python3 gpu/gen_tables.py --check || exit 1
204
+ : > "$OUT/forward_attribution.txt"
205
+ for variant in "current:" "cumode:-mcumode" "branchy:-DALTD_TANH_BRANCHY" "diag1:-DALTD_FORWARD_DIAG=1" "diag2:-DALTD_FORWARD_DIAG=2" "diag3:-DALTD_FORWARD_DIAG=3" "diag4:-DALTD_FORWARD_DIAG=4" "diag5:-DALTD_FORWARD_DIAG=5"; do
206
+ name=${variant%%:*}; define=${variant#*:}
207
+ DLIB="$REPO/target/gpu/libaltd_gpu_$name.so"
208
+ if [ "$name" = current ]; then DLIB="$LIB"; else
209
+ run_logged "build_$name" hipcc "${FLAGS[@]}" $define -o "$DLIB" gpu/sim/altd_gpu.hip -lamdhip64 || continue
210
+ fi
211
+ run_logged "forward_$name" $(bench_env 32768 0 ALTD_GPU_LIB="$DLIB" ALTD_GPU_PROFILE=1) "$CHECK" benchfixed
212
+ line=$(grep -h "altd_gpu window:" "$OUT/forward_$name.txt" | head -1)
213
+ echo "$name: $line" >> "$OUT/forward_attribution.txt"; echo " $name: ${line#*cars: }"
214
+ done
215
+ fi
216
+
217
+ # ---- pack -------------------------------------------------------------------
218
+ echo; bold "Done"
219
+ TAR="$OUT.tar.gz"
220
+ tar czf "$TAR" -C "$(dirname "$OUT")" "$(basename "$OUT")"
221
+ echo " results: $OUT"
222
+ echo " archive: $TAR ($(du -h "$TAR" | cut -f1)), send this file"
223
+ grep -c "rc=0" "$LOG" | sed 's/^/ steps succeeded: /'
224
+ grep "rc=[1-9]" "$LOG" | sed 's/^/ failed: /'
225
+ exit 0
github-simulator/source/gpu/release-targets ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gfx803
2
+ gfx900
3
+ gfx902
4
+ gfx906
5
+ gfx909
6
+ gfx90c
7
+ gfx1010
8
+ gfx1011
9
+ gfx1012
10
+ gfx1013
11
+ gfx1030
12
+ gfx1031
13
+ gfx1032
14
+ gfx1033
15
+ gfx1034
16
+ gfx1035
17
+ gfx1036
18
+ gfx1100
19
+ gfx1101
20
+ gfx1102
21
+ gfx1103
22
+ gfx1150
23
+ gfx1151
24
+ gfx1152
25
+ gfx1153
26
+ gfx1200
27
+ gfx1201
github-simulator/source/gpu/sim/altd_gpu.hip ADDED
@@ -0,0 +1,1384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include <chrono>
2
+ #include <vector>
3
+ #include <cstdlib>
4
+ #include <cstring>
5
+ #include <ctime>
6
+ #include <new>
7
+ // libaltd_gpu.so: GPU parts of the fidelity simulator behind a C ABI.
8
+ // Rust loads it with dlopen (src/gpu/hip.rs); examples/gpu_check/main.rs compares
9
+ // every part bit for bit with the CPU code.
10
+ #include "compat.h"
11
+ #include <cstdint>
12
+ #include <cstdio>
13
+ #include <cstddef>
14
+ #include "math.h"
15
+ #include "rays.h"
16
+ #include "sensors.h"
17
+ #include "step.h"
18
+ #include "stats.h"
19
+ #ifdef _WIN32
20
+ // For GetThreadTimes. After the other headers: windows.h defines `near` and `far`, which the
21
+ // simulator headers use as variable names.
22
+ #ifndef NOMINMAX
23
+ #define NOMINMAX
24
+ #endif
25
+ #define WIN32_LEAN_AND_MEAN
26
+ #include <windows.h>
27
+ #undef near
28
+ #undef far
29
+ #endif
30
+
31
+ using namespace altd;
32
+
33
+ #define CHECK(call) do { hipError_t e_ = (call); if (e_ != hipSuccess) { \
34
+ fprintf(stderr, "altd_gpu %s:%d %s\n", __FILE__, __LINE__, hipGetErrorString(e_)); return (int)e_; } } while (0)
35
+
36
+ namespace {
37
+
38
+ enum MathOp : int {
39
+ OP_NATIVE_SIN = 0, // f32 -> f32
40
+ OP_NATIVE_COS = 1, // f32 -> f32
41
+ OP_NATIVE_ATAN2 = 2, // (f32 y, f32 x) -> f32
42
+ OP_MANAGED_SINCOS = 3, // f32 -> (f32, f32)
43
+ OP_DEXP = 4, // f64 -> f64
44
+ OP_DPOW = 5, // (f64, f64) -> f64
45
+ OP_ENGINE_SINCOS = 6, // f32 -> (f32, f32), with exceptions
46
+ OP_ENGINE_RAW = 7, // f32 -> (f32, f32), without exceptions
47
+ OP_GAME_TANH = 8, // f64 -> f64
48
+ };
49
+ // altd_gpu_math's op carries a math profile above these bits; it selects the
50
+ // variant of the sin, cos, atan2, exp, pow and tanh ops.
51
+ constexpr int OP_MASK = 0xff, OP_PROFILE_SHIFT = 8;
52
+
53
+ __global__ void math_kernel(int op, int n, const void* in, void* out, uint32_t* err) {
54
+ int i = blockIdx.x * blockDim.x + threadIdx.x;
55
+ if (i >= n) return;
56
+ uint32_t e = 0, math = (uint32_t)op >> OP_PROFILE_SHIFT;
57
+ const float* fi = (const float*)in;
58
+ const double* di = (const double*)in;
59
+ float* fo = (float*)out;
60
+ double* dout = (double*)out;
61
+ switch (op & OP_MASK) {
62
+ case OP_NATIVE_SIN: fo[i] = profile_sin(math, fi[i], e); break;
63
+ case OP_NATIVE_COS: fo[i] = profile_cos(math, fi[i], e); break;
64
+ case OP_NATIVE_ATAN2: fo[i] = profile_atan2(math, fi[2 * i], fi[2 * i + 1]); break;
65
+ case OP_MANAGED_SINCOS: managed_sin_cos(fi[i], fo[2 * i], fo[2 * i + 1], e); break;
66
+ case OP_DEXP: dout[i] = profile_exp(math, di[i]); break;
67
+ case OP_DPOW: dout[i] = profile_pow(math, di[2 * i], di[2 * i + 1]); break;
68
+ case OP_ENGINE_SINCOS: engine_sin_cos(fi[i], fo[2 * i], fo[2 * i + 1], e); break;
69
+ case OP_ENGINE_RAW: engine_sin_cos_raw(fi[i], fo[2 * i], fo[2 * i + 1]); break;
70
+ case OP_GAME_TANH: dout[i] = profile_tanh(math, di[i]); break;
71
+ }
72
+ err[i] = e;
73
+ }
74
+
75
+ // Engine sin/cos of the float with bits `first + i` (wrapping), for the
76
+ // exhaustive x87 scan. Output: interleaved sine and cosine bits.
77
+ __global__ void engine_scan_kernel(uint32_t first, uint32_t n, uint32_t* out) {
78
+ uint32_t i = blockIdx.x * blockDim.x + threadIdx.x;
79
+ if (i >= n) return;
80
+ float s, c;
81
+ engine_sin_cos_raw(ffrom(first + i), s, c);
82
+ out[2 * (size_t)i] = fbits(s);
83
+ out[2 * (size_t)i + 1] = fbits(c);
84
+ }
85
+
86
+ size_t in_size(int op) {
87
+ switch (op & OP_MASK) {
88
+ case OP_NATIVE_ATAN2: return 8;
89
+ case OP_DEXP: case OP_GAME_TANH: return 8;
90
+ case OP_DPOW: return 16;
91
+ default: return 4;
92
+ }
93
+ }
94
+ size_t out_size(int op) {
95
+ switch (op & OP_MASK) {
96
+ case OP_MANAGED_SINCOS: case OP_ENGINE_SINCOS: case OP_ENGINE_RAW: return 8;
97
+ case OP_DEXP: case OP_DPOW: case OP_GAME_TANH: return 8;
98
+ default: return 4;
99
+ }
100
+ }
101
+
102
+ // Query kinds of altd_gpu_query.
103
+ enum QueryOp : int {
104
+ Q_RAYCAST = 0, // in (sx, sy, ex, ey) -> out (hx, hy, hit, 0)
105
+ Q_CLOSEST = 1, // in (px, py, -, -) -> out (cx, cy, 1, 0)
106
+ };
107
+
108
+ __global__ void query_kernel(World w, int op, int n, const float4* in, float4* out) {
109
+ int i = blockIdx.x * blockDim.x + threadIdx.x;
110
+ if (i >= n) return;
111
+ float4 q = in[i];
112
+ float2 r = make_float2(0.0f, 0.0f);
113
+ bool ok = true;
114
+ if (op == Q_RAYCAST) ok = raycast(w, make_float2(q.x, q.y), make_float2(q.z, q.w), r);
115
+ else r = closest_wall_point(w, make_float2(q.x, q.y));
116
+ out[i] = make_float4(r.x, r.y, ok ? 1.0f : 0.0f, 0.0f);
117
+ }
118
+
119
+ template <typename T>
120
+ int upload(const T* host, size_t count, const T*& device) {
121
+ T* d = nullptr;
122
+ device = nullptr;
123
+ if (count) {
124
+ CHECK(hipMalloc(&d, sizeof(T) * count));
125
+ // The owner must retain this allocation if copying fails.
126
+ device = d;
127
+ CHECK(hipMemcpy(d, host, sizeof(T) * count, hipMemcpyHostToDevice));
128
+ }
129
+ return 0;
130
+ }
131
+
132
+ int upload_grid(const NearGrid& host, NearGrid& device) {
133
+ size_t cells = host.start ? (size_t)host.nx * host.ny : 0;
134
+ size_t items = cells ? host.start[cells] : 0;
135
+ return upload(host.start, cells ? cells + 1 : 0, device.start) || upload(host.items, items, device.items);
136
+ }
137
+
138
+ // ---- simulation ----
139
+
140
+ // Network layer widths, and for each control (Agent::controls order:
141
+ // acceleration, steering, brake, handbrake, boost) the output neuron that
142
+ // sets it: the last one with that name, or -1 for 0.0.
143
+ struct NetShape {
144
+ uint32_t layers, width[MAX_LAYERS];
145
+ int32_t control_src[5];
146
+ };
147
+
148
+ } // namespace
149
+
150
+ // Sim and WindowArgs stay outside the anonymous namespace: g++ (nvcc's host compiler) gives the
151
+ // extern "C" functions that take them internal linkage otherwise, and leaves them out of the library.
152
+ struct WindowArgs {
153
+ uint64_t start_tick, ticks;
154
+ uint32_t start_batch, batches_per_tick, stats_phase, stop_when_inactive;
155
+ uint32_t eliminate_on_wall, eliminate_when_idle, has_time_limit, pad;
156
+ double time_limit;
157
+ };
158
+
159
+ namespace {
160
+
161
+ struct WindowState {
162
+ uint32_t live_count[2][8], step_count[2];
163
+ uint32_t stopped;
164
+ uint32_t items; // SAT items of the split step (cleared by its end kernel)
165
+ uint64_t executed; // the stopping tick's k
166
+ uint64_t base; // k before the current period; kernels add their offset
167
+ };
168
+
169
+ uint32_t live_total(const WindowState& st, uint32_t p) {
170
+ uint32_t count = 0;
171
+ for (uint32_t b = 0; b < 8; b++) count += st.live_count[p][b];
172
+ return count;
173
+ }
174
+
175
+ } // namespace
176
+
177
+ struct Sim {
178
+ World world; // device pointers, owned by the world handle
179
+ VehicleDesc vehicle;
180
+ Sensors sensors;
181
+ uint32_t capacity, n;
182
+ Car* cars;
183
+ Agent* agents;
184
+ float* here; // sensor path offset per car
185
+ double* inputs; // n * sensors.count, car-major
186
+ NetShape shape;
187
+ size_t stride; // parameters per network
188
+ double* params; // capacity * stride, one network per car
189
+ uint32_t network_count;
190
+ double* mean;
191
+ double* novelty;
192
+ // altd_gpu_sim_window: work lists, settling, device state, stop polling.
193
+ uint32_t* live[2];
194
+ uint32_t* steps[2];
195
+ uint32_t* settled_at;
196
+ Car* shadow;
197
+ WindowState* state;
198
+ // The split step: carried state per car, SAT items (car * MAX_PAIRS + pair) and their pushes.
199
+ StepCarry* carry;
200
+ uint32_t* items;
201
+ Vec2* pushes;
202
+ uint32_t* host_stopped; // pinned
203
+ uint32_t* active; // altd_gpu_sim_active_count's result
204
+ hipEvent_t chunk_done[4];
205
+ hipStream_t stream; // blocking, so ordered with the null stream
206
+ hipGraphExec_t graph_exec;
207
+ WindowArgs graph_args;
208
+ uint32_t graph_n;
209
+ };
210
+
211
+ namespace {
212
+
213
+ // Kernel arguments captured by the period graph. The requested window length
214
+ // only affects the host's replay count and is not part of the captured graph.
215
+ bool same_schedule(const WindowArgs& a, const WindowArgs& b) {
216
+ return a.start_tick == b.start_tick && a.start_batch == b.start_batch
217
+ && a.batches_per_tick == b.batches_per_tick && a.stats_phase == b.stats_phase
218
+ && a.stop_when_inactive == b.stop_when_inactive && a.eliminate_on_wall == b.eliminate_on_wall
219
+ && a.eliminate_when_idle == b.eliminate_when_idle && a.has_time_limit == b.has_time_limit
220
+ && a.time_limit == b.time_limit;
221
+ }
222
+
223
+ // Pass 1 of the sensor read: the path offset (a nearest-segment search) per reading car.
224
+ __global__ void path_offset_kernel(World w, ReadMask m, Car* cars, uint32_t n, float* here) {
225
+ uint32_t i = blockIdx.x * blockDim.x + threadIdx.x;
226
+ if (i >= n || !reads(m, cars[i], i)) return;
227
+ here[i] = sensor_path_offset(w, cars[i]);
228
+ }
229
+
230
+ // Pass 2: one thread per (sensor, car), sensor-major so a wave reads one sensor kind.
231
+ __global__ void sensor_kernel(World w, VehicleDesc v, Sensors s, ReadMask m, Car* cars, uint32_t n,
232
+ const float* here, double* inputs) {
233
+ uint32_t t = blockIdx.x * blockDim.x + threadIdx.x;
234
+ uint32_t k = t / n, i = t % n;
235
+ if (k >= s.count || !reads(m, cars[i], i)) return;
236
+ uint32_t err = 0;
237
+ inputs[(size_t)i * s.count + k] = sensor_value(w, v, cars[i], s.s[k], s.needs_path ? here[i] : 0.0f, err);
238
+ if (err) atomicOr(&cars[i].error, err);
239
+ }
240
+
241
+ // `Network::forward_into` for the reading cars, then Controls(**outputs).
242
+ // A block holds CARS_PER_BLOCK cars. Within a layer of `cols` outputs, thread
243
+ // t computes output t % cols of the block's car t / cols, so the block's
244
+ // CARS_PER_BLOCK * cols outputs occupy the first threads and the waves past
245
+ // them skip the layer entirely. With one car per LANES threads, a 12-, 8- or
246
+ // 5-wide layer would leave 4, 8 or 11 idle lanes in every wave, and idle
247
+ // lanes still cost the double-precision issue slots that bound this kernel.
248
+ // Each output adds its products in input order, then the bias, as the CPU does.
249
+ constexpr int LANES = 16;
250
+ constexpr int CARS_PER_BLOCK = 16;
251
+ // ALTD_FORWARD_DIAG (timing builds only, results are wrong): 1 skips the
252
+ // activation, 2 reads one weight row for every input (no weight traffic),
253
+ // 3 skips the products and applies the activation to an input, 4 skips both
254
+ // (the layer loop's own cost), 5 additionally drops the per-layer barriers.
255
+ #ifndef ALTD_FORWARD_DIAG
256
+ #define ALTD_FORWARD_DIAG 0
257
+ #endif
258
+ #if ALTD_FORWARD_DIAG == 5
259
+ #define FORWARD_LAYER_BARRIER() ((void)0)
260
+ #else
261
+ #define FORWARD_LAYER_BARRIER() __syncthreads()
262
+ #endif
263
+ // Weight rows are loaded ROW_CHUNK at a time, one chunk ahead of the products
264
+ // that use them, and a layer's first chunk and bias during the previous
265
+ // layer's activation. Every row's weights are a fresh cache line streamed
266
+ // from memory, so without the lookahead each row waits for its own load.
267
+ constexpr uint32_t ROW_CHUNK = 8;
268
+
269
+ // Rows first .. first + ROW_CHUNK - 1 of output j (zero past `rows` or when
270
+ // not `active`; nothing is read there).
271
+ __device__ inline void load_rows(double (&w)[ROW_CHUNK], const double* __restrict__ matrix, uint32_t rows, uint32_t cols, uint32_t j,
272
+ uint32_t first, bool active) {
273
+ #pragma unroll
274
+ for (uint32_t u = 0; u < ROW_CHUNK; u++) {
275
+ bool have = active && first + u < rows;
276
+ #if ALTD_FORWARD_DIAG == 2
277
+ w[u] = have ? matrix[j] : 0.0;
278
+ #else
279
+ w[u] = have ? matrix[(size_t)(first + u) * cols + j] : 0.0;
280
+ #endif
281
+ }
282
+ }
283
+
284
+ // Every thread of the block must call it (barriers); `car` and `live` are the
285
+ // caller's slot (threadIdx.x / LANES).
286
+ __device__ inline void forward_car(uint32_t math, const NetShape& shape, const double* __restrict__ params, size_t stride, bool live,
287
+ uint32_t car, const double* __restrict__ inputs, Agent* agents) {
288
+ // Two activation buffers: a layer reads one and writes the other, so one
289
+ // barrier per layer orders the writes before the next layer's reads (the
290
+ // previous barrier already ordered this layer's reads before those writes).
291
+ // Row stride of MAX_WIDTH + 1 doubles: the cars of one wave read the same
292
+ // column at the same time, and rows 256 bytes apart would share LDS banks.
293
+ __shared__ double buffers[2][CARS_PER_BLOCK][MAX_WIDTH + 1];
294
+ __shared__ uint32_t slot_car[CARS_PER_BLOCK];
295
+ __shared__ uint32_t slot_live[CARS_PER_BLOCK];
296
+ uint32_t t = threadIdx.x, slot = t / LANES, lane = t % LANES;
297
+ uint32_t n_in = shape.width[0];
298
+ if (lane == 0) {
299
+ slot_car[slot] = car;
300
+ slot_live[slot] = live;
301
+ }
302
+ for (uint32_t i = lane; live && i < n_in; i += LANES) buffers[0][slot][i] = inputs[(size_t)car * n_in + i];
303
+ __syncthreads();
304
+ uint32_t current = 0; // the buffer holding this layer's inputs
305
+ // This thread's assignment for the current layer: car slot s, output j.
306
+ uint32_t rows = shape.width[0], cols = shape.width[1];
307
+ uint32_t s = t / cols, j = t % cols;
308
+ bool active = t < CARS_PER_BLOCK * cols && slot_live[s];
309
+ const double* matrix = active ? params + (size_t)slot_car[s] * stride : params;
310
+ double w[ROW_CHUNK];
311
+ load_rows(w, matrix, rows, cols, j, 0, active);
312
+ size_t position = 0;
313
+ for (uint32_t layer = 0; layer + 1 < shape.layers; layer++) {
314
+ double (&values)[CARS_PER_BLOCK][MAX_WIDTH + 1] = buffers[current];
315
+ double acc = 0.0, bias = 0.0, out = 0.0;
316
+ if (active) {
317
+ bias = matrix[(size_t)rows * cols + j];
318
+ #if ALTD_FORWARD_DIAG == 3 || ALTD_FORWARD_DIAG >= 4
319
+ acc = values[s][j < rows ? j : 0];
320
+ #else
321
+ for (uint32_t i = 0; i < rows; i += ROW_CHUNK) {
322
+ double next[ROW_CHUNK];
323
+ load_rows(next, matrix, rows, cols, j, i + ROW_CHUNK, true);
324
+ #pragma unroll
325
+ for (uint32_t u = 0; u < ROW_CHUNK; u++)
326
+ if (i + u < rows) acc += values[s][i + u] * w[u];
327
+ #pragma unroll
328
+ for (uint32_t u = 0; u < ROW_CHUNK; u++) w[u] = next[u];
329
+ }
330
+ #endif
331
+ }
332
+ // The next layer's assignment and first rows, issued before this
333
+ // layer's activation so their latency overlaps it.
334
+ bool has_next = layer + 2 < shape.layers;
335
+ uint32_t rows_n = cols, cols_n = has_next ? shape.width[layer + 2] : 1;
336
+ uint32_t s_n = t / cols_n, j_n = t % cols_n;
337
+ bool active_n = has_next && t < CARS_PER_BLOCK * cols_n && slot_live[s_n];
338
+ size_t position_n = position + (size_t)(rows + 1) * cols;
339
+ const double* matrix_n = active_n ? params + (size_t)slot_car[s_n] * stride + position_n : params;
340
+ load_rows(w, matrix_n, rows_n, cols_n, j_n, 0, active_n);
341
+ if (active) {
342
+ #if ALTD_FORWARD_DIAG == 1 || ALTD_FORWARD_DIAG >= 4
343
+ out = acc + bias;
344
+ #else
345
+ out = profile_tanh(math, acc + bias);
346
+ #endif
347
+ }
348
+ if (active) buffers[current ^ 1][s][j] = out;
349
+ FORWARD_LAYER_BARRIER();
350
+ current ^= 1;
351
+ rows = rows_n; cols = cols_n; s = s_n; j = j_n; active = active_n; matrix = matrix_n; position = position_n;
352
+ }
353
+ if (live && lane < 5) {
354
+ int32_t src = shape.control_src[lane];
355
+ agents[car].controls[lane] = src >= 0 ? buffers[current][slot][src] : 0.0;
356
+ }
357
+ }
358
+
359
+ __global__ void __launch_bounds__(LANES * CARS_PER_BLOCK)
360
+ forward_kernel(uint32_t math, NetShape shape, const double* __restrict__ params, size_t stride, ReadMask m, const Car* cars,
361
+ uint32_t n, const double* __restrict__ inputs, Agent* agents) {
362
+ uint32_t car = blockIdx.x * CARS_PER_BLOCK + threadIdx.x / LANES;
363
+ bool live = car < n && reads(m, cars[car], car);
364
+ forward_car(math, shape, params, stride, live, car, inputs, agents);
365
+ }
366
+
367
+ // The agent's side of a step of car i: a reported wall contact sets
368
+ // AGENT_PENDING_CONTACT; a car deactivated by the step records `tick`
369
+ // (Schedule::drive_agent).
370
+ __device__ inline void record(const Car& car, Agent& agent, bool was_active, bool report, uint64_t tick) {
371
+ uint32_t flags = agent.flags | (report ? AGENT_PENDING_CONTACT : 0u);
372
+ if (was_active && !(car.flags & CAR_ACTIVE)) {
373
+ agent.deactivated_at = tick;
374
+ flags |= AGENT_HAS_DEACTIVATED;
375
+ }
376
+ agent.flags = flags;
377
+ }
378
+
379
+ // `Car::step` (drive) or `Car::passive_step` of car i with the agent's controls.
380
+ __device__ inline void step_car(const World& w, const VehicleDesc& v, Car& car, Agent& agent, bool drive, bool eliminate_on_wall,
381
+ uint64_t tick) {
382
+ bool was_active = car.flags & CAR_ACTIVE;
383
+ bool report = car_step(w, v, car, agent.controls, drive, eliminate_on_wall);
384
+ record(car, agent, was_active, report, tick);
385
+ }
386
+
387
+ constexpr uint32_t STEP_THREADS = 64;
388
+
389
+ // Bound all physics kernels to their actual block size so the solver
390
+ // contact-index array reserves LDS for STEP_THREADS threads, rather than 1024.
391
+ __global__ void __launch_bounds__(STEP_THREADS) step_kernel(World w, VehicleDesc v, Car* cars, Agent* agents, uint32_t n, uint32_t drive, uint32_t eliminate_on_wall,
392
+ uint64_t tick) {
393
+ uint32_t i = blockIdx.x * blockDim.x + threadIdx.x;
394
+ if (i >= n) return;
395
+ step_car(w, v, cars[i], agents[i], drive != 0, eliminate_on_wall != 0, tick);
396
+ }
397
+
398
+ // ---- the split step ----
399
+ //
400
+ // `step_car` in three kernels, so the SAT tests of all broad pairs run side
401
+ // by side instead of in a loop per car: `step_begin` per car reserves one
402
+ // SAT item per broad pair (consecutive from the carry's base), the SAT kernel
403
+ // runs one item per thread, and `step_end` per car reads the pushes. The
404
+ // work per SAT thread varies only with its pair's axes, not with the pair
405
+ // counts of the other cars of the wave.
406
+
407
+ // Reserves `count` consecutive slots of `*total` per lane, one atomic per
408
+ // wave; returns the lane's first slot. Every lane must call it.
409
+ __device__ inline uint32_t reserve(uint32_t count, uint32_t* total) {
410
+ uint32_t lane = altd_lane_id(), prefix = count; // inclusive scan over the wave
411
+ for (uint32_t o = 1; o < (uint32_t)warpSize; o <<= 1) {
412
+ uint32_t x = altd_shfl_up(prefix, o);
413
+ prefix += lane >= o ? x : 0u;
414
+ }
415
+ uint32_t sum = altd_shfl(prefix, warpSize - 1), base = 0;
416
+ if (lane == 0 && sum) base = atomicAdd(total, sum);
417
+ return altd_shfl(base, 0) + prefix - count;
418
+ }
419
+
420
+ // Every lane must call it.
421
+ __device__ inline void begin_car(const World& w, const VehicleDesc& v, Car* cars, Agent* agents, bool valid, uint32_t i, bool drive,
422
+ StepCarry* carry, uint32_t* items, uint32_t* item_count) {
423
+ StepCarry k{};
424
+ uint32_t count = 0;
425
+ if (valid) {
426
+ k = step_begin(w, v, cars[i], agents[i].controls, drive);
427
+ count = cars[i].pair_count;
428
+ }
429
+ uint32_t base = reserve(count, item_count);
430
+ if (!valid) return;
431
+ k.base = base;
432
+ carry[i] = k;
433
+ for (uint32_t c = 0; c < count; c++) items[base + c] = i * MAX_PAIRS + c;
434
+ }
435
+
436
+ // The SAT items, grid-stride (the grid is sized for the cars, not the items).
437
+ __global__ void __launch_bounds__(STEP_THREADS) sat_kernel(World w, VehicleDesc v, Car* cars, const uint32_t* items, const WindowState* st, Vec2* pushes) {
438
+ uint32_t total = st->items;
439
+ for (uint32_t t = blockIdx.x * blockDim.x + threadIdx.x; t < total; t += gridDim.x * blockDim.x) {
440
+ uint32_t item = items[t];
441
+ pushes[t] = sat_pair(w, v, cars[item / MAX_PAIRS], item % MAX_PAIRS);
442
+ }
443
+ }
444
+
445
+ __device__ inline bool end_car(const World& w, const VehicleDesc& v, Car& car, Agent& agent, const StepCarry& k, const Vec2* pushes,
446
+ bool drive, bool eliminate_on_wall, uint64_t tick) {
447
+ bool report = step_end(w, v, car, k, [&](uint32_t ci) { return pushes[k.base + ci]; }, drive, eliminate_on_wall);
448
+ record(car, agent, k.was_active, report, tick);
449
+ return report;
450
+ }
451
+
452
+ __global__ void __launch_bounds__(STEP_THREADS) step_begin_kernel(World w, VehicleDesc v, Car* cars, Agent* agents, uint32_t n, uint32_t drive, StepCarry* carry,
453
+ uint32_t* items, WindowState* st) {
454
+ uint32_t i = blockIdx.x * blockDim.x + threadIdx.x;
455
+ begin_car(w, v, cars, agents, i < n, i, drive != 0, carry, items, &st->items);
456
+ }
457
+
458
+ __global__ void __launch_bounds__(STEP_THREADS) step_end_kernel(World w, VehicleDesc v, Car* cars, Agent* agents, uint32_t n, uint32_t drive, uint32_t eliminate_on_wall,
459
+ uint64_t tick, const StepCarry* carry, const Vec2* pushes, WindowState* st) {
460
+ uint32_t i = blockIdx.x * blockDim.x + threadIdx.x;
461
+ if (i == 0) st->items = 0; // the SAT kernel is done with them
462
+ if (i >= n) return;
463
+ end_car(w, v, cars[i], agents[i], carry[i], pushes, drive != 0, eliminate_on_wall != 0, tick);
464
+ }
465
+
466
+ // Counts the cars with CAR_ACTIVE, one atomic per wave.
467
+ __global__ void active_count_kernel(const Car* cars, uint32_t n, uint32_t* count) {
468
+ uint32_t i = blockIdx.x * blockDim.x + threadIdx.x;
469
+ uint64_t mask = altd_ballot(i < n && (cars[i].flags & CAR_ACTIVE));
470
+ if (mask && altd_lane_id() == (uint32_t)__ffsll((unsigned long long)mask) - 1) atomicAdd(count, (uint32_t)__popcll(mask));
471
+ }
472
+
473
+ uint32_t blocks(size_t n, uint32_t size = 256) { return (uint32_t)((n + size - 1) / size); }
474
+
475
+ // ALTD_GPU_SPLIT=0: the step in one kernel (car_step) instead of the split step.
476
+ bool split_step() {
477
+ static const char* env = getenv("ALTD_GPU_SPLIT");
478
+ static const bool split = !(env && strcmp(env, "0") == 0);
479
+ return split;
480
+ }
481
+
482
+ // Host clocks of ALTD_GPU_PROFILE, in seconds. A negative thread CPU time is not printed.
483
+ double wall_seconds() { return std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count(); }
484
+ double thread_cpu_seconds() {
485
+ #ifdef _WIN32
486
+ FILETIME created, exited, kernel, user; // 100 ns units
487
+ if (!GetThreadTimes(GetCurrentThread(), &created, &exited, &kernel, &user)) return -1.0;
488
+ ULARGE_INTEGER k, u;
489
+ k.LowPart = kernel.dwLowDateTime, k.HighPart = kernel.dwHighDateTime;
490
+ u.LowPart = user.dwLowDateTime, u.HighPart = user.dwHighDateTime;
491
+ return 1e-7 * (double)(k.QuadPart + u.QuadPart);
492
+ #else
493
+ timespec t;
494
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &t);
495
+ return (double)t.tv_sec + 1e-9 * (double)t.tv_nsec;
496
+ #endif
497
+ }
498
+
499
+ // The step kernels need ~300 MB of scratch at full occupancy, over the HSA
500
+ // runtime's single-dispatch limit, so by default every launch allocates and
501
+ // frees it (0.2-1.7 ms per launch). Keeping the scratch avoids that. Runs at
502
+ // load, before the first HIP call initialises HSA; an explicit setting wins.
503
+ #if !defined(__CUDACC__)
504
+ __attribute__((constructor)) void keep_scratch() { setenv("HSA_NO_SCRATCH_RECLAIM", "1", 0); }
505
+ #endif
506
+
507
+ } // namespace
508
+
509
+ extern "C" {
510
+
511
+ ALTD_API void altd_gpu_world_free(World* w);
512
+ ALTD_API void altd_gpu_sim_free(Sim* s);
513
+
514
+ // Copies the track data to the device; returns an opaque handle or null.
515
+ // `desc` holds host pointers and has the layout of World.
516
+ ALTD_API World* altd_gpu_world_create(const World* desc) {
517
+ if (desc->ray_depth > (uint32_t)MAX_RAY_DEPTH) {
518
+ fprintf(stderr, "altd_gpu: BSP depth %u exceeds MAX_RAY_DEPTH %d\n", desc->ray_depth, MAX_RAY_DEPTH);
519
+ return nullptr;
520
+ }
521
+ World* w = new (std::nothrow) World(*desc);
522
+ if (!w) return nullptr;
523
+ // desc contains borrowed host pointers. Cleanup may only free pointers
524
+ // assigned by successful device allocations, including a failed copy.
525
+ w->ray_nodes = nullptr;
526
+ w->ray_walls = nullptr;
527
+ w->surfaces = nullptr;
528
+ w->tiles = nullptr;
529
+ w->path_segments = nullptr;
530
+ w->path_offsets = nullptr;
531
+ w->path_grid.start = nullptr;
532
+ w->path_grid.items = nullptr;
533
+ w->curve_position = nullptr;
534
+ w->curve_offsets = nullptr;
535
+ w->curve_forwards = nullptr;
536
+ w->curve_grid.start = nullptr;
537
+ w->curve_grid.items = nullptr;
538
+ w->shapes = nullptr;
539
+ w->shape_local = nullptr;
540
+ w->shape_normals = nullptr;
541
+ w->shape_grid.start = nullptr;
542
+ w->shape_grid.items = nullptr;
543
+ w->path_xy = nullptr;
544
+ int bad = upload(desc->ray_nodes, desc->ray_node_count, w->ray_nodes)
545
+ || upload(desc->ray_walls, desc->ray_wall_count, w->ray_walls)
546
+ || upload(desc->surfaces, desc->surface_count, w->surfaces)
547
+ || upload(desc->tiles, (size_t)(desc->tile_nx * desc->tile_ny), w->tiles)
548
+ || upload(desc->path_segments, desc->path_points ? desc->path_points - 1 : 0, w->path_segments)
549
+ || upload(desc->path_offsets, desc->path_points, w->path_offsets)
550
+ || upload_grid(desc->path_grid, w->path_grid)
551
+ || upload(desc->curve_position, desc->curve_points, w->curve_position)
552
+ || upload(desc->curve_offsets, desc->curve_points, w->curve_offsets)
553
+ || upload(desc->curve_forwards, desc->curve_points, w->curve_forwards)
554
+ || upload_grid(desc->curve_grid, w->curve_grid)
555
+ || upload(desc->shapes, desc->shape_count, w->shapes)
556
+ || upload(desc->shape_local, desc->shape_point_count, w->shape_local)
557
+ || upload(desc->shape_normals, desc->shape_point_count, w->shape_normals)
558
+ || upload_grid(desc->shape_grid, w->shape_grid)
559
+ || upload(desc->path_xy, (size_t)desc->path_points * 2, w->path_xy);
560
+ if (bad) {
561
+ fprintf(stderr, "altd_gpu: world upload failed\n");
562
+ altd_gpu_world_free(w);
563
+ return nullptr;
564
+ }
565
+ return w;
566
+ }
567
+
568
+ ALTD_API void altd_gpu_world_free(World* w) {
569
+ if (!w) return;
570
+ for (const void* p : {(const void*)w->ray_nodes, (const void*)w->ray_walls, (const void*)w->surfaces,
571
+ (const void*)w->tiles, (const void*)w->path_segments, (const void*)w->path_offsets,
572
+ (const void*)w->path_grid.start, (const void*)w->path_grid.items,
573
+ (const void*)w->curve_position, (const void*)w->curve_offsets, (const void*)w->curve_forwards,
574
+ (const void*)w->curve_grid.start, (const void*)w->curve_grid.items,
575
+ (const void*)w->shapes, (const void*)w->shape_local, (const void*)w->shape_normals,
576
+ (const void*)w->shape_grid.start, (const void*)w->shape_grid.items, (const void*)w->path_xy})
577
+ if (p) (void)hipFree((void*)p);
578
+ delete w;
579
+ }
580
+
581
+ // Runs `n` BSP queries (see QueryOp) on host arrays.
582
+ ALTD_API int altd_gpu_query(const World* w, int op, int n, const float4* in, float4* out) {
583
+ float4 *d_in, *d_out;
584
+ CHECK(hipMalloc(&d_in, sizeof(float4) * n));
585
+ CHECK(hipMalloc(&d_out, sizeof(float4) * n));
586
+ CHECK(hipMemcpy(d_in, in, sizeof(float4) * n, hipMemcpyHostToDevice));
587
+ query_kernel<<<(n + 255) / 256, 256>>>(*w, op, n, d_in, d_out);
588
+ CHECK(hipGetLastError());
589
+ CHECK(hipMemcpy(out, d_out, sizeof(float4) * n, hipMemcpyDeviceToHost));
590
+ CHECK(hipFree(d_in));
591
+ CHECK(hipFree(d_out));
592
+ return 0;
593
+ }
594
+
595
+
596
+ // Struct sizes and a few offsets, for the Rust layout check.
597
+ ALTD_API int altd_gpu_layout(uint64_t* out, int n) {
598
+ uint64_t v[] = {sizeof(World), sizeof(Car), sizeof(Contact), sizeof(Agent), sizeof(VehicleDesc), sizeof(SensorDesc),
599
+ sizeof(WheelDesc), sizeof(ShapeDev), sizeof(TileCell), sizeof(PathSeg), sizeof(NearGrid),
600
+ offsetof(World, tiles), offsetof(World, curve_grid), offsetof(World, shape_grid),
601
+ offsetof(Car, tick), offsetof(Car, pairs), offsetof(Car, contacts), offsetof(Agent, recent),
602
+ offsetof(VehicleDesc, steering_speed), offsetof(VehicleDesc, gravity), offsetof(World, math_profile)};
603
+ int count = (int)(sizeof(v) / sizeof(v[0]));
604
+ for (int i = 0; i < n && i < count; i++) out[i] = v[i];
605
+ return count;
606
+ }
607
+
608
+ ALTD_API Sim* altd_gpu_sim_create(const World* world, const VehicleDesc* vehicle, const SensorDesc* sensors, uint32_t sensor_count,
609
+ uint32_t capacity) {
610
+ if (sensor_count > (uint32_t)MAX_SENSORS || vehicle->wheel_count > (uint32_t)MAX_WHEELS) return nullptr;
611
+ Sim* s = new (std::nothrow) Sim{};
612
+ if (!s) return nullptr;
613
+ s->world = *world;
614
+ s->vehicle = *vehicle;
615
+ s->sensors.count = sensor_count;
616
+ for (uint32_t k = 0; k < sensor_count; k++) {
617
+ s->sensors.s[k] = sensors[k];
618
+ s->sensors.needs_path |= sensors[k].kind == S_CORRECT_DIRECTION || sensors[k].kind == S_TRACK_CURVATURE;
619
+ }
620
+ s->capacity = capacity;
621
+ if (hipMalloc(&s->cars, sizeof(Car) * capacity) || hipMalloc(&s->agents, sizeof(Agent) * capacity)
622
+ || hipMalloc(&s->here, sizeof(float) * capacity) || hipMalloc(&s->inputs, sizeof(double) * capacity * MAX_SENSORS)
623
+ || hipMalloc(&s->live[0], sizeof(uint32_t) * ((capacity + 7) / 8) * 8) || hipMalloc(&s->live[1], sizeof(uint32_t) * ((capacity + 7) / 8) * 8)
624
+ || hipMalloc(&s->steps[0], sizeof(uint32_t) * capacity) || hipMalloc(&s->steps[1], sizeof(uint32_t) * capacity)
625
+ || hipMalloc(&s->settled_at, sizeof(uint32_t) * capacity) || hipMalloc(&s->shadow, sizeof(Car) * capacity)
626
+ || hipMalloc(&s->carry, sizeof(StepCarry) * capacity) || hipMalloc(&s->items, sizeof(uint32_t) * capacity * MAX_PAIRS)
627
+ || hipMalloc(&s->pushes, sizeof(Vec2) * capacity * MAX_PAIRS)
628
+ || hipMalloc(&s->novelty, sizeof(double) * capacity)
629
+ || hipMalloc(&s->state, sizeof(WindowState)) || hipHostMalloc(&s->host_stopped, sizeof(uint32_t) * 4)
630
+ || hipMalloc(&s->active, sizeof(uint32_t))
631
+ || hipEventCreateWithFlags(&s->chunk_done[0], hipEventDisableTiming) || hipEventCreateWithFlags(&s->chunk_done[1], hipEventDisableTiming)
632
+ || hipEventCreateWithFlags(&s->chunk_done[2], hipEventDisableTiming) || hipEventCreateWithFlags(&s->chunk_done[3], hipEventDisableTiming)
633
+ || hipStreamCreate(&s->stream)) {
634
+ fprintf(stderr, "altd_gpu: sim allocation failed\n");
635
+ altd_gpu_sim_free(s);
636
+ return nullptr;
637
+ }
638
+ return s;
639
+ }
640
+
641
+ struct RefreshedSensors { Sensors sensors; uint32_t error; };
642
+
643
+ // Ray endpoints belong to the world's math profile. Recompute once per profile
644
+ // switch, retaining the precomputed endpoints in the per-car sensing kernels.
645
+ __global__ void refresh_ray_offsets(Sensors sensors, uint32_t math, RefreshedSensors* out) {
646
+ uint32_t error = 0;
647
+ for (uint32_t i = 0; i < sensors.count; ++i) {
648
+ SensorDesc& sensor = sensors.s[i];
649
+ if (sensor.kind != S_RAYCAST) continue;
650
+ float angle = (sensor.a - 90.0f) * (PI_F / 180.0f);
651
+ sensor.offset = Vec2{profile_cos(math, angle, error) * sensor.b,
652
+ profile_sin(math, angle, error) * sensor.b};
653
+ }
654
+ out->sensors = sensors;
655
+ out->error = error;
656
+ }
657
+
658
+ // Graph kernels capture world pointers by value. Invalidate the graph before
659
+ // releasing the old track, while retaining all population allocations.
660
+ ALTD_API int altd_gpu_sim_set_world(Sim* s, const World* world) {
661
+ CHECK(hipStreamSynchronize(s->stream));
662
+ Sensors sensors = s->sensors;
663
+ if (s->world.math_profile != world->math_profile) {
664
+ struct DeviceBuffer {
665
+ RefreshedSensors* value = nullptr;
666
+ ~DeviceBuffer() { if (value) (void)hipFree(value); }
667
+ } buffer;
668
+ CHECK(hipMalloc(&buffer.value, sizeof(RefreshedSensors)));
669
+ refresh_ray_offsets<<<1, 1, 0, s->stream>>>(sensors, world->math_profile, buffer.value);
670
+ CHECK(hipGetLastError());
671
+ CHECK(hipStreamSynchronize(s->stream));
672
+ RefreshedSensors refreshed{};
673
+ CHECK(hipMemcpy(&refreshed, buffer.value, sizeof(refreshed), hipMemcpyDeviceToHost));
674
+ if (refreshed.error) return -static_cast<int>(refreshed.error);
675
+ sensors = refreshed.sensors;
676
+ }
677
+ if (s->graph_exec) {
678
+ CHECK(hipGraphExecDestroy(s->graph_exec));
679
+ s->graph_exec = nullptr;
680
+ }
681
+ s->sensors = sensors;
682
+ s->world = *world;
683
+ return 0;
684
+ }
685
+
686
+ ALTD_API void altd_gpu_sim_free(Sim* s) {
687
+ if (!s) return;
688
+ if (s->graph_exec) (void)hipGraphExecDestroy(s->graph_exec);
689
+ for (void* p : {(void*)s->cars, (void*)s->agents, (void*)s->here, (void*)s->inputs, (void*)s->params, (void*)s->live[0],
690
+ (void*)s->live[1], (void*)s->steps[0], (void*)s->steps[1], (void*)s->settled_at, (void*)s->shadow, (void*)s->state,
691
+ (void*)s->carry, (void*)s->items, (void*)s->pushes, (void*)s->mean, (void*)s->novelty, (void*)s->active})
692
+ if (p) (void)hipFree(p);
693
+ if (s->host_stopped) (void)hipHostFree(s->host_stopped);
694
+ for (hipEvent_t e : s->chunk_done)
695
+ if (e) (void)hipEventDestroy(e);
696
+ if (s->stream) (void)hipStreamDestroy(s->stream);
697
+ delete s;
698
+ }
699
+
700
+ // Uploads `n` cars and (if not null) agents.
701
+ ALTD_API int altd_gpu_sim_upload(Sim* s, uint32_t n, const Car* cars, const Agent* agents) {
702
+ if (n > s->capacity) return -1;
703
+ s->n = n;
704
+ CHECK(hipMemcpy(s->cars, cars, sizeof(Car) * n, hipMemcpyHostToDevice));
705
+ if (agents) CHECK(hipMemcpy(s->agents, agents, sizeof(Agent) * n, hipMemcpyHostToDevice));
706
+ return 0;
707
+ }
708
+
709
+ ALTD_API int altd_gpu_sim_upload_agents(Sim* s, uint32_t n, const Agent* agents) {
710
+ if (n != s->n) return -1;
711
+ CHECK(hipMemcpy(s->agents, agents, sizeof(Agent) * n, hipMemcpyHostToDevice));
712
+ return 0;
713
+ }
714
+
715
+ // The number of uploaded cars that are active, without reading them back.
716
+ ALTD_API int altd_gpu_sim_active_count(Sim* s, uint32_t* out) {
717
+ CHECK(hipMemsetAsync(s->active, 0, sizeof(uint32_t), s->stream));
718
+ if (s->n) active_count_kernel<<<blocks(s->n), 256, 0, s->stream>>>(s->cars, s->n, s->active);
719
+ CHECK(hipGetLastError());
720
+ CHECK(hipMemcpyAsync(out, s->active, sizeof(uint32_t), hipMemcpyDeviceToHost, s->stream));
721
+ CHECK(hipStreamSynchronize(s->stream));
722
+ return 0;
723
+ }
724
+
725
+ ALTD_API int altd_gpu_sim_download(Sim* s, Car* cars, Agent* agents) {
726
+ CHECK(hipDeviceSynchronize());
727
+ if (cars) CHECK(hipMemcpy(cars, s->cars, sizeof(Car) * s->n, hipMemcpyDeviceToHost));
728
+ if (agents) CHECK(hipMemcpy(agents, s->agents, sizeof(Agent) * s->n, hipMemcpyDeviceToHost));
729
+ return 0;
730
+ }
731
+
732
+ // Reads the sensors of the cars selected by `mask`; if `inputs` is not null,
733
+ // copies all n * sensor_count values back (unread cars keep stale values).
734
+ ALTD_API int altd_gpu_sim_sensors(Sim* s, ReadMask mask, double* inputs) {
735
+ if (s->n == 0) return 0;
736
+ if (s->sensors.needs_path) path_offset_kernel<<<blocks(s->n, 64), 64>>>(s->world, mask, s->cars, s->n, s->here);
737
+ sensor_kernel<<<blocks((size_t)s->n * s->sensors.count, 64), 64>>>(s->world, s->vehicle, s->sensors, mask, s->cars, s->n,
738
+ s->here, s->inputs);
739
+ CHECK(hipGetLastError());
740
+ if (inputs)
741
+ CHECK(hipMemcpy(inputs, s->inputs, sizeof(double) * s->n * s->sensors.count, hipMemcpyDeviceToHost));
742
+ return 0;
743
+ }
744
+
745
+ // Installs one network per car: `layers` widths, then n * parameter_count(widths)
746
+ // f64 parameters (Network::params, car-major). `control_src` as in NetShape.
747
+ ALTD_API int altd_gpu_sim_networks(Sim* s, uint32_t layers, const uint32_t* width, uint32_t n, const double* params,
748
+ const int32_t* control_src) {
749
+ if (layers < 2 || layers > (uint32_t)MAX_LAYERS || n > s->capacity || width[0] != s->sensors.count) return -1;
750
+ NetShape shape{};
751
+ shape.layers = layers;
752
+ size_t stride = 0;
753
+ for (uint32_t l = 0; l < layers; l++) {
754
+ if (width[l] == 0 || width[l] > (uint32_t)MAX_WIDTH || (l > 0 && width[l] > (uint32_t)LANES)) return -1;
755
+ shape.width[l] = width[l];
756
+ if (l > 0) stride += (size_t)(width[l - 1] + 1) * width[l];
757
+ }
758
+ for (int c = 0; c < 5; c++) {
759
+ if (control_src[c] >= (int32_t)width[layers - 1]) return -1;
760
+ shape.control_src[c] = control_src[c];
761
+ }
762
+ // The graph embeds the network shape and parameter pointer. Parameter
763
+ // contents may change in place without invalidating it.
764
+ if (s->graph_exec && memcmp(&shape, &s->shape, sizeof shape) != 0) {
765
+ CHECK(hipGraphExecDestroy(s->graph_exec));
766
+ s->graph_exec = nullptr;
767
+ }
768
+ if (stride != s->stride || !s->params) {
769
+ if (s->params) CHECK(hipFree(s->params));
770
+ if (s->mean) CHECK(hipFree(s->mean));
771
+ s->params = nullptr;
772
+ s->mean = nullptr;
773
+ CHECK(hipMalloc(&s->params, sizeof(double) * stride * s->capacity));
774
+ CHECK(hipMalloc(&s->mean, sizeof(double) * stride));
775
+ }
776
+ s->shape = shape;
777
+ s->stride = stride;
778
+ CHECK(hipMemcpy(s->params, params, sizeof(double) * stride * n, hipMemcpyHostToDevice));
779
+ s->network_count = n;
780
+ return 0;
781
+ }
782
+
783
+ // One thread per parameter preserves the CPU's left-to-right population sum.
784
+ // Only `stride` such threads exist (a few dozen waves), so with one load in
785
+ // flight per add the population's 8 bytes x n x stride stream far below the
786
+ // memory bandwidth. Each block therefore stages MEAN_ROWS networks' slice of
787
+ // its MEAN_COLS parameters in LDS with all MEAN_THREADS threads loading
788
+ // (coalesced rows, many loads in flight), then the MEAN_COLS summing threads
789
+ // add the staged rows in ascending network order, exactly as before.
790
+ constexpr uint32_t MEAN_COLS = 64, MEAN_ROWS = 64, MEAN_THREADS = 256;
791
+
792
+ __global__ void __launch_bounds__(MEAN_THREADS)
793
+ population_mean_kernel(const double* params, size_t stride, uint32_t n, double* mean) {
794
+ __shared__ double tile[MEAN_ROWS][MEAN_COLS + 1];
795
+ uint32_t t = threadIdx.x;
796
+ size_t col0 = (size_t)blockIdx.x * MEAN_COLS;
797
+ bool sums = t < MEAN_COLS && col0 + t < stride;
798
+ double sum = 0.0;
799
+ for (uint32_t row0 = 0; row0 < n; row0 += MEAN_ROWS) {
800
+ uint32_t rows = min(MEAN_ROWS, n - row0);
801
+ for (uint32_t e = t; e < rows * MEAN_COLS; e += MEAN_THREADS) {
802
+ uint32_t r = e / MEAN_COLS, c = e % MEAN_COLS;
803
+ if (col0 + c < stride) tile[r][c] = params[(size_t)(row0 + r) * stride + col0 + c];
804
+ }
805
+ __syncthreads();
806
+ if (sums)
807
+ for (uint32_t r = 0; r < rows; r++) sum += tile[r][t];
808
+ __syncthreads();
809
+ }
810
+ if (sums) mean[col0 + t] = sum / (double)n;
811
+ }
812
+
813
+ // Parallelize the expensive pow calls, then add their results in the exact
814
+ // parameter order used by TrainingRunner::install. pow(x, 2) is the game's
815
+ // routine and must not be replaced by x*x or a parallel sum.
816
+ __global__ void __launch_bounds__(64)
817
+ population_novelty_kernel(uint32_t math, const double* params, size_t stride, const double* mean, double* novelty) {
818
+ __shared__ double terms[64];
819
+ size_t car = blockIdx.x;
820
+ uint32_t lane = threadIdx.x;
821
+ double sum = 0.0;
822
+ for (size_t base = 0; base < stride; base += 64) {
823
+ size_t j = base + lane;
824
+ if (j < stride) terms[lane] = profile_pow(math, params[car * stride + j] - mean[j], 2.0);
825
+ __syncthreads();
826
+ if (lane == 0)
827
+ for (uint32_t k = 0; k < 64 && base + k < stride; k++) sum += terms[k];
828
+ __syncthreads();
829
+ }
830
+ if (lane == 0) novelty[car] = sqrt(sum);
831
+ }
832
+
833
+ ALTD_API int altd_gpu_sim_novelty(Sim* s, uint32_t n, double* novelty) {
834
+ if (n != s->network_count || !s->params) return -1;
835
+ if (n == 0) return 0;
836
+ population_mean_kernel<<<blocks(s->stride, MEAN_COLS), MEAN_THREADS, 0, s->stream>>>(s->params, s->stride, n, s->mean);
837
+ population_novelty_kernel<<<n, 64, 0, s->stream>>>(s->world.math_profile, s->params, s->stride, s->mean, s->novelty);
838
+ CHECK(hipGetLastError());
839
+ CHECK(hipMemcpyAsync(novelty, s->novelty, sizeof(double) * n, hipMemcpyDeviceToHost, s->stream));
840
+ CHECK(hipStreamSynchronize(s->stream));
841
+ return 0;
842
+ }
843
+
844
+ // Sensors and network for the cars in `mask`: sets their Agent::controls.
845
+ ALTD_API int altd_gpu_sim_infer(Sim* s, ReadMask mask) {
846
+ if (!s->params) return -1;
847
+ if (s->n == 0) return 0;
848
+ int status = altd_gpu_sim_sensors(s, mask, nullptr);
849
+ if (status) return status;
850
+ forward_kernel<<<blocks(s->n, CARS_PER_BLOCK), LANES * CARS_PER_BLOCK>>>(s->world.math_profile, s->shape, s->params, s->stride, mask, s->cars, s->n,
851
+ s->inputs, s->agents);
852
+ CHECK(hipGetLastError());
853
+ return 0;
854
+ }
855
+
856
+ // Counts the cars still active afterwards into `active` if not null.
857
+ __global__ void stats_kernel(World w, Car* cars, Agent* agents, uint32_t n, uint64_t tick, uint64_t stats_tick,
858
+ uint32_t eliminate_when_idle, uint32_t* active) {
859
+ uint32_t i = blockIdx.x * blockDim.x + threadIdx.x;
860
+ if (i >= n) return;
861
+ uint32_t err = 0;
862
+ update_stats(w, cars[i], agents[i], tick, stats_tick, eliminate_when_idle != 0, err);
863
+ cars[i].error |= err;
864
+ if (active && (cars[i].flags & CAR_ACTIVE)) atomicAdd(active, 1u);
865
+ }
866
+
867
+ // One physics tick for all cars (see step_kernel). Needs physics shapes
868
+ // (the shapeless resolve_walls path is not ported).
869
+ ALTD_API int altd_gpu_sim_step(Sim* s, uint32_t drive, uint32_t eliminate_on_wall, uint64_t tick) {
870
+ if (s->world.shape_count == 0) return -2;
871
+ if (s->n == 0) return 0;
872
+ if (!split_step()) {
873
+ step_kernel<<<blocks(s->n, STEP_THREADS), STEP_THREADS>>>(s->world, s->vehicle, s->cars, s->agents, s->n, drive, eliminate_on_wall, tick);
874
+ CHECK(hipGetLastError());
875
+ return 0;
876
+ }
877
+ CHECK(hipMemset(s->state, 0, sizeof(WindowState)));
878
+ step_begin_kernel<<<blocks(s->n, STEP_THREADS), STEP_THREADS>>>(s->world, s->vehicle, s->cars, s->agents, s->n, drive, s->carry, s->items, s->state);
879
+ sat_kernel<<<blocks(s->n, STEP_THREADS), STEP_THREADS>>>(s->world, s->vehicle, s->cars, s->items, s->state, s->pushes);
880
+ step_end_kernel<<<blocks(s->n, STEP_THREADS), STEP_THREADS>>>(s->world, s->vehicle, s->cars, s->agents, s->n, drive, eliminate_on_wall, tick, s->carry,
881
+ s->pushes, s->state);
882
+ CHECK(hipGetLastError());
883
+ return 0;
884
+ }
885
+
886
+ // Schedule::update_stats for all cars on a statistics tick.
887
+ ALTD_API int altd_gpu_sim_stats(Sim* s, uint64_t tick, uint64_t stats_tick, uint32_t eliminate_when_idle) {
888
+ if (s->world.path_points < 2) return -2; // Track::closest_path needs a path
889
+ if (s->n == 0) return 0;
890
+ stats_kernel<<<blocks(s->n, 64), 64>>>(s->world, s->cars, s->agents, s->n, tick, stats_tick, eliminate_when_idle, nullptr);
891
+ CHECK(hipGetLastError());
892
+ return 0;
893
+ }
894
+
895
+ // ---- whole windows ----
896
+ //
897
+ // TrainingRunner::advance_window (not paused, no native broadphase), in the
898
+ // tick-major order: per tick the statistics (on statistics ticks, followed by
899
+ // the stop test), then the batch's sensors + forward, then the physics step.
900
+ //
901
+ // The host only enqueues: the stop test runs on the device and every later
902
+ // kernel of the window returns at once. Kernels walk compact work lists,
903
+ // double-buffered and rebuilt on statistics ticks:
904
+ // - live: the cars that may read sensors or change statistics (active, or
905
+ // with a pending contact at the window start); after each statistics
906
+ // update exactly the active cars. Cars deactivated by a wall stay listed
907
+ // until the next rebuild and are filtered by their active flag. Each live
908
+ // list has eight segments, retaining the cars' original inference batches.
909
+ // - step: the cars still stepped. An inactive car whose step leaves every
910
+ // field but `tick` bitwise unchanged is settled: `car_step` is a pure
911
+ // function of that state for inactive cars (controls, agent and tick are
912
+ // not read), so all its later steps repeat it. Settled cars leave the list
913
+ // and get the skipped `tick` increments when the window ends.
914
+ constexpr uint32_t NOT_SETTLED = 0xffffffffu;
915
+
916
+ // Appends `value` where `take`, one atomic per wave; every lane must call it.
917
+ __device__ inline void append(bool take, uint32_t value, uint32_t* list, uint32_t* count) {
918
+ uint64_t mask = altd_ballot(take);
919
+ if (!mask) return;
920
+ uint32_t lane = altd_lane_id();
921
+ uint32_t leader = (uint32_t)__ffsll((unsigned long long)mask) - 1;
922
+ uint32_t base = 0;
923
+ if (lane == leader) base = atomicAdd(count, (uint32_t)__popcll(mask));
924
+ base = altd_shfl(base, leader);
925
+ if (take) list[base + (uint32_t)__popcll(mask & ((1ull << lane) - 1))] = value;
926
+ }
927
+
928
+ // The Car state `car_step` reads: everything but `tick`, and the used pairs and contacts.
929
+ __device__ inline void snapshot(const Car& c, Car& s) {
930
+ altd_memcpy(&s, &c, offsetof(Car, pairs));
931
+ altd_memcpy(s.pairs, c.pairs, sizeof(uint32_t) * c.pair_count);
932
+ altd_memcpy(s.axes, c.axes, sizeof(Vec2) * c.pair_count);
933
+ altd_memcpy(s.contacts, c.contacts, sizeof(Contact) * c.contact_count);
934
+ }
935
+
936
+ __device__ inline bool same_words(const void* a, const void* b, size_t bytes) {
937
+ uint32_t differ = 0;
938
+ for (size_t k = 0; k < bytes; k += 4) {
939
+ uint32_t x, y;
940
+ altd_memcpy(&x, (const char*)a + k, 4);
941
+ altd_memcpy(&y, (const char*)b + k, 4);
942
+ differ |= x ^ y;
943
+ }
944
+ return differ == 0;
945
+ }
946
+
947
+ __device__ inline bool same_state(const Car& s, const Car& c) {
948
+ if (s.pair_count != c.pair_count || s.contact_count != c.contact_count) return false;
949
+ return same_words(&s, &c, offsetof(Car, tick))
950
+ && same_words((const char*)&s + offsetof(Car, tick) + 8, (const char*)&c + offsetof(Car, tick) + 8,
951
+ offsetof(Car, pairs) - offsetof(Car, tick) - 8)
952
+ && same_words(s.pairs, c.pairs, sizeof(uint32_t) * c.pair_count)
953
+ && same_words(s.axes, c.axes, sizeof(Vec2) * c.pair_count)
954
+ && same_words(s.contacts, c.contacts, sizeof(Contact) * c.contact_count);
955
+ }
956
+
957
+ // Clears HAS_DEACTIVATED and builds the first live and step lists (index 0).
958
+ __global__ void w_start_kernel(Car* cars, Agent* agents, uint32_t n, uint32_t* live, uint32_t* steps, uint32_t* settled_at,
959
+ WindowState* st) {
960
+ uint32_t batch_size = (n + 7) / 8, batch = blockIdx.y;
961
+ uint32_t j = blockIdx.x * blockDim.x + threadIdx.x;
962
+ uint32_t i = batch * batch_size + j;
963
+ bool valid = j < batch_size && i < n, listed = false;
964
+ if (valid) {
965
+ uint32_t flags = agents[i].flags & ~AGENT_HAS_DEACTIVATED;
966
+ agents[i].flags = flags;
967
+ settled_at[i] = NOT_SETTLED;
968
+ listed = (cars[i].flags & CAR_ACTIVE) || (flags & AGENT_PENDING_CONTACT);
969
+ }
970
+ append(listed, i, live + batch * batch_size, &st->live_count[0][batch]);
971
+ append(valid, i, steps, &st->step_count[0]);
972
+ }
973
+
974
+ // Statistics of the live cars in list p; the still active ones form list 1 - p.
975
+ __global__ void w_stats_kernel(World w, Car* cars, Agent* agents, const uint32_t* in, uint32_t* out, WindowState* st, uint32_t p, uint32_t batch_size,
976
+ uint64_t start_tick, uint32_t off, uint64_t first, uint32_t eliminate_when_idle) {
977
+ uint32_t j = blockIdx.x * blockDim.x + threadIdx.x;
978
+ uint32_t batch = blockIdx.y;
979
+ bool valid = !st->stopped && j < st->live_count[p][batch], keep = false;
980
+ uint32_t i = valid ? in[batch * batch_size + j] : 0;
981
+ if (valid) {
982
+ uint32_t err = 0;
983
+ uint64_t tick = start_tick + st->base + off;
984
+ update_stats(w, cars[i], agents[i], tick, tick - first, eliminate_when_idle != 0, err);
985
+ cars[i].error |= err;
986
+ keep = cars[i].flags & CAR_ACTIVE;
987
+ }
988
+ append(keep, i, out + batch * batch_size, &st->live_count[1 - p][batch]);
989
+ }
990
+
991
+ // The stop test after the statistics of tick k (live list 1 - p holds the
992
+ // active cars); clears the counts the next rebuilds append to.
993
+ __global__ void w_stop_kernel(WindowState* st, uint32_t p, uint64_t start_tick, uint32_t off, uint64_t first,
994
+ uint32_t has_time_limit, double time_limit, uint32_t stop_when_inactive) {
995
+ if (st->stopped) return;
996
+ uint64_t k = st->base + off;
997
+ bool time_stop = has_time_limit && (double)((start_tick + k - first) / 6) * 0.1 >= time_limit;
998
+ uint32_t live = 0;
999
+ for (uint32_t b = 0; b < 8; b++) live += st->live_count[1 - p][b];
1000
+ if (time_stop || (stop_when_inactive && live == 0)) {
1001
+ st->stopped = 1;
1002
+ st->executed = k;
1003
+ return;
1004
+ }
1005
+ for (uint32_t b = 0; b < 8; b++) st->live_count[p][b] = 0;
1006
+ st->step_count[1 - p] = 0;
1007
+ }
1008
+
1009
+ // The live lists retain original batch identity. Each y slice handles one
1010
+ // scheduled batch; z slices of the sensor grid handle individual sensors.
1011
+ __global__ void w_path_offset_kernel(World w, ReadMask m, Car* cars, const uint32_t* list, const WindowState* st, uint32_t p,
1012
+ float* here) {
1013
+ uint32_t batch = (m.batch_index + blockIdx.y) % 8;
1014
+ uint32_t j = blockIdx.x * blockDim.x + threadIdx.x;
1015
+ if (st->stopped || j >= st->live_count[p][batch]) return;
1016
+ uint32_t i = list[batch * m.batch_size + j];
1017
+ if (!(cars[i].flags & CAR_ACTIVE)) return;
1018
+ here[i] = sensor_path_offset(w, cars[i]);
1019
+ }
1020
+
1021
+ __global__ void w_sensor_kernel(World w, VehicleDesc v, Sensors s, ReadMask m, Car* cars, const uint32_t* list,
1022
+ const WindowState* st, uint32_t p, const float* here, double* inputs) {
1023
+ uint32_t batch = (m.batch_index + blockIdx.y) % 8;
1024
+ uint32_t j = blockIdx.x * blockDim.x + threadIdx.x, k = blockIdx.z;
1025
+ if (st->stopped || j >= st->live_count[p][batch]) return;
1026
+ uint32_t i = list[batch * m.batch_size + j];
1027
+ if (!(cars[i].flags & CAR_ACTIVE)) return;
1028
+ uint32_t err = 0;
1029
+ inputs[(size_t)i * s.count + k] = sensor_value(w, v, cars[i], s.s[k], s.needs_path ? here[i] : 0.0f, err);
1030
+ if (err) atomicOr(&cars[i].error, err);
1031
+ }
1032
+
1033
+ __global__ void __launch_bounds__(LANES * CARS_PER_BLOCK)
1034
+ w_forward_kernel(uint32_t math, NetShape shape, const double* __restrict__ params, size_t stride, ReadMask m, const Car* cars,
1035
+ const uint32_t* list, const WindowState* st, uint32_t p, const double* __restrict__ inputs, Agent* agents) {
1036
+ uint32_t batch = (m.batch_index + blockIdx.y) % 8;
1037
+ uint32_t count = st->live_count[p][batch];
1038
+ if (st->stopped || blockIdx.x * CARS_PER_BLOCK >= count) return; // uniform per block
1039
+ uint32_t j = blockIdx.x * CARS_PER_BLOCK + threadIdx.x / LANES;
1040
+ uint32_t car = j < count ? list[batch * m.batch_size + j] : 0;
1041
+ bool live = j < count && (cars[car].flags & CAR_ACTIVE);
1042
+ forward_car(math, shape, params, stride, live, car, inputs, agents);
1043
+ }
1044
+
1045
+ // Steps the cars of step list q. With `out`, inactive cars are checked for
1046
+ // settling and the others form step list 1 - q.
1047
+ __global__ void __launch_bounds__(STEP_THREADS) w_step_kernel(World w, VehicleDesc v, Car* cars, Agent* agents, const uint32_t* in, uint32_t* out, WindowState* st,
1048
+ uint32_t q, uint32_t* settled_at, Car* shadow, uint32_t eliminate_on_wall, uint64_t start_tick,
1049
+ uint32_t off) {
1050
+ uint32_t j = blockIdx.x * blockDim.x + threadIdx.x;
1051
+ bool valid = !st->stopped && j < st->step_count[q], keep = valid;
1052
+ uint32_t i = valid ? in[j] : 0;
1053
+ if (valid) {
1054
+ uint64_t k = st->base + off; // the step index: one step per tick
1055
+ uint64_t tick = start_tick + k;
1056
+ uint32_t step_index = (uint32_t)k;
1057
+ Car& car = cars[i];
1058
+ bool check = out && !(car.flags & CAR_ACTIVE);
1059
+ if (check) snapshot(car, shadow[i]);
1060
+ step_car(w, v, car, agents[i], true, eliminate_on_wall != 0, tick);
1061
+ if (check && same_state(shadow[i], car)) {
1062
+ settled_at[i] = step_index;
1063
+ keep = false;
1064
+ }
1065
+ }
1066
+ if (out) append(keep, i, out, &st->step_count[1 - q]);
1067
+ }
1068
+
1069
+ // w_step_kernel split (see sat_kernel): begin, then the SAT items, then end.
1070
+ __global__ void __launch_bounds__(STEP_THREADS) w_step_begin_kernel(World w, VehicleDesc v, Car* cars, Agent* agents, const uint32_t* in, WindowState* st, uint32_t q,
1071
+ uint32_t settle, Car* shadow, StepCarry* carry, uint32_t* items) {
1072
+ uint32_t j = blockIdx.x * blockDim.x + threadIdx.x;
1073
+ if (st->stopped) return; // uniform
1074
+ bool valid = j < st->step_count[q];
1075
+ uint32_t i = valid ? in[j] : 0;
1076
+ if (valid && settle && !(cars[i].flags & CAR_ACTIVE)) snapshot(cars[i], shadow[i]);
1077
+ begin_car(w, v, cars, agents, valid, i, true, carry, items, &st->items);
1078
+ }
1079
+
1080
+ __global__ void __launch_bounds__(STEP_THREADS) w_step_end_kernel(World w, VehicleDesc v, Car* cars, Agent* agents, const uint32_t* in, uint32_t* out, WindowState* st,
1081
+ uint32_t q, uint32_t* settled_at, const Car* shadow, uint32_t eliminate_on_wall, uint64_t start_tick,
1082
+ uint32_t off, const StepCarry* carry, const Vec2* pushes) {
1083
+ uint32_t j = blockIdx.x * blockDim.x + threadIdx.x;
1084
+ if (j == 0) st->items = 0; // the SAT kernel is done with them
1085
+ bool valid = !st->stopped && j < st->step_count[q], keep = valid;
1086
+ uint32_t i = valid ? in[j] : 0;
1087
+ if (valid) {
1088
+ uint64_t k = st->base + off;
1089
+ const StepCarry& c = carry[i];
1090
+ bool check = out && !c.was_active;
1091
+ end_car(w, v, cars[i], agents[i], c, pushes, true, eliminate_on_wall != 0, start_tick + k);
1092
+ if (check && same_state(shadow[i], cars[i])) {
1093
+ settled_at[i] = (uint32_t)k;
1094
+ keep = false;
1095
+ }
1096
+ }
1097
+ if (out) append(keep, i, out, &st->step_count[1 - q]);
1098
+ }
1099
+
1100
+ __global__ void w_advance_kernel(WindowState* st, uint32_t by) { st->base += by; }
1101
+
1102
+ // Adds the skipped steps to the settled cars' tick.
1103
+ __global__ void w_finish_kernel(Car* cars, uint32_t n, const uint32_t* settled_at, const WindowState* st, uint64_t ticks) {
1104
+ uint32_t i = blockIdx.x * blockDim.x + threadIdx.x;
1105
+ if (i >= n || settled_at[i] == NOT_SETTLED) return;
1106
+ uint64_t steps = st->stopped ? st->executed - 1 : ticks;
1107
+ cars[i].tick += steps - settled_at[i];
1108
+ }
1109
+
1110
+ ALTD_API int altd_gpu_sim_window(Sim* s, const WindowArgs* a, uint64_t* executed, uint32_t* transition_without_drive) {
1111
+ *executed = 0;
1112
+ *transition_without_drive = 0;
1113
+ if (a->ticks == 0) return 0;
1114
+ if (!s->params || s->world.shape_count == 0 || s->world.path_points < 2) return -2;
1115
+ uint32_t n = s->n;
1116
+ hipStream_t stream = s->stream;
1117
+ // ALTD_GPU_PROFILE=1: GPU time per phase (event pairs around direct launches).
1118
+ // ALTD_GPU_PROFILE=host: only the host thread's CPU time and the wall time.
1119
+ // ALTD_GPU_GRAPH=0: direct launches instead of the period graph.
1120
+ static const char* profile_env = getenv("ALTD_GPU_PROFILE");
1121
+ static const bool profile = profile_env && strcmp(profile_env, "host") != 0;
1122
+ static const char* graph_env = getenv("ALTD_GPU_GRAPH");
1123
+ static const bool use_graph = !profile && !(graph_env && strcmp(graph_env, "0") == 0);
1124
+ const double cpu0 = profile_env ? thread_cpu_seconds() : 0, wall0 = profile_env ? wall_seconds() : 0;
1125
+ double spent[4] = {0, 0, 0, 0}; // stats, sensors, forward, step
1126
+ struct Timed { int slot; uint64_t k; hipEvent_t start, stop; };
1127
+ std::vector<Timed> timed;
1128
+ uint64_t enqueued = 0; // ticks enqueued so far (profile timeline)
1129
+ auto phase = [&](int slot, auto&& launch) -> int {
1130
+ if (!profile) { launch(); return 0; }
1131
+ Timed t{slot, enqueued, nullptr, nullptr};
1132
+ CHECK(hipEventCreate(&t.start));
1133
+ CHECK(hipEventCreate(&t.stop));
1134
+ CHECK(hipEventRecord(t.start, stream));
1135
+ launch();
1136
+ CHECK(hipEventRecord(t.stop, stream));
1137
+ timed.push_back(t);
1138
+ return 0;
1139
+ };
1140
+ WindowState* st = s->state;
1141
+ CHECK(hipMemsetAsync(st, 0, sizeof(WindowState), stream));
1142
+ if (n) w_start_kernel<<<dim3(blocks((n + 7) / 8, 256), 8), 256, 0, stream>>>(s->cars, s->agents, n, s->live[0], s->steps[0], s->settled_at, st);
1143
+ uint64_t first = a->stats_phase == 0 ? 6 : a->stats_phase;
1144
+ // The time limit stops the window at a known tick; the host runs up to it.
1145
+ uint64_t limit = a->ticks;
1146
+ if (a->has_time_limit)
1147
+ for (uint64_t k = 1; k <= a->ticks; k++) {
1148
+ uint64_t tick = a->start_tick + k;
1149
+ if (tick % 6 == a->stats_phase && (double)((tick - first) / 6) * 0.1 >= a->time_limit) {
1150
+ limit = k;
1151
+ break;
1152
+ }
1153
+ }
1154
+ // Tick k = base + off of a period of PERIOD ticks: the statistics ticks,
1155
+ // read batches and list parity depend only on off, so a period is one graph.
1156
+ constexpr uint32_t PERIOD = 24; // lcm of the statistics (6) and batch (8) cycles
1157
+ static_assert(PERIOD % 6 == 0 && PERIOD % 8 == 0, "period");
1158
+ auto enqueue_tick = [&](uint32_t off, uint32_t& p) {
1159
+ enqueued++;
1160
+ if (profile && enqueued % 600 == 1) { // list sizes along the window
1161
+ WindowState now;
1162
+ (void)hipMemcpyAsync(&now, st, sizeof now, hipMemcpyDeviceToHost, stream); // no CHECK: this lambda returns void
1163
+ (void)hipStreamSynchronize(stream);
1164
+ fprintf(stderr, " tick %5llu: live %u/%u, steps %u/%u\n", (unsigned long long)enqueued, live_total(now, 0), live_total(now, 1),
1165
+ now.step_count[0], now.step_count[1]);
1166
+ }
1167
+ uint64_t tick_mod = a->start_tick + off;
1168
+ uint32_t batch_index = (uint32_t)((a->start_batch + ((off - 1) % 8) * a->batches_per_tick) % 8);
1169
+ bool stats = tick_mod % 6 == a->stats_phase;
1170
+ if (stats) {
1171
+ if (n) phase(0, [&] {
1172
+ w_stats_kernel<<<dim3(blocks((n + 7) / 8, 64), 8), 64, 0, stream>>>(s->world, s->cars, s->agents, s->live[p], s->live[1 - p], st, p, (n + 7) / 8,
1173
+ a->start_tick, off, first, a->eliminate_when_idle);
1174
+ w_stop_kernel<<<1, 1, 0, stream>>>(st, p, a->start_tick, off, first, a->has_time_limit, a->time_limit,
1175
+ a->stop_when_inactive);
1176
+ });
1177
+ else w_stop_kernel<<<1, 1, 0, stream>>>(st, p, a->start_tick, off, first, a->has_time_limit, a->time_limit,
1178
+ a->stop_when_inactive);
1179
+ p = 1 - p;
1180
+ }
1181
+ if (n) {
1182
+ ReadMask mask{(uint32_t)((n + 7) / 8), batch_index, a->batches_per_tick, 1};
1183
+ phase(1, [&] {
1184
+ if (s->sensors.needs_path)
1185
+ w_path_offset_kernel<<<dim3(blocks(mask.batch_size, 64), mask.batches_per_tick), 64, 0, stream>>>(s->world, mask, s->cars, s->live[p], st, p, s->here);
1186
+ w_sensor_kernel<<<dim3(blocks(mask.batch_size, 64), mask.batches_per_tick, s->sensors.count), 64, 0, stream>>>(s->world, s->vehicle, s->sensors, mask,
1187
+ s->cars, s->live[p], st, p, s->here, s->inputs);
1188
+ });
1189
+ phase(2, [&] {
1190
+ w_forward_kernel<<<dim3(blocks(mask.batch_size, CARS_PER_BLOCK), mask.batches_per_tick), LANES * CARS_PER_BLOCK, 0, stream>>>(
1191
+ s->world.math_profile, s->shape, s->params, s->stride, mask, s->cars, s->live[p], st, p, s->inputs, s->agents);
1192
+ });
1193
+ phase(3, [&] {
1194
+ // On statistics ticks list 1 - p is read and list p rebuilt.
1195
+ uint32_t q = stats ? 1 - p : p;
1196
+ uint32_t* out = stats ? s->steps[p] : nullptr;
1197
+ if (!split_step()) {
1198
+ w_step_kernel<<<blocks(n, STEP_THREADS), STEP_THREADS, 0, stream>>>(s->world, s->vehicle, s->cars, s->agents, s->steps[q], out, st, q,
1199
+ s->settled_at, s->shadow, a->eliminate_on_wall, a->start_tick, off);
1200
+ return;
1201
+ }
1202
+ w_step_begin_kernel<<<blocks(n, STEP_THREADS), STEP_THREADS, 0, stream>>>(s->world, s->vehicle, s->cars, s->agents, s->steps[q], st, q,
1203
+ out != nullptr, s->shadow, s->carry, s->items);
1204
+ sat_kernel<<<blocks(n, STEP_THREADS), STEP_THREADS, 0, stream>>>(s->world, s->vehicle, s->cars, s->items, st, s->pushes);
1205
+ w_step_end_kernel<<<blocks(n, STEP_THREADS), STEP_THREADS, 0, stream>>>(s->world, s->vehicle, s->cars, s->agents, s->steps[q], out, st, q,
1206
+ s->settled_at, s->shadow, a->eliminate_on_wall, a->start_tick, off,
1207
+ s->carry, s->pushes);
1208
+ });
1209
+ }
1210
+ };
1211
+ auto enqueue_period = [&] {
1212
+ uint32_t p = 0;
1213
+ for (uint32_t off = 1; off <= PERIOD; off++) enqueue_tick(off, p);
1214
+ w_advance_kernel<<<1, 1, 0, stream>>>(st, PERIOD);
1215
+ };
1216
+ uint64_t periods = limit / PERIOD, rest = limit % PERIOD;
1217
+ hipGraphExec_t exec = nullptr;
1218
+ if (use_graph && periods > 1) {
1219
+ if (s->graph_exec && (s->graph_n != n || !same_schedule(s->graph_args, *a))) {
1220
+ CHECK(hipGraphExecDestroy(s->graph_exec));
1221
+ s->graph_exec = nullptr;
1222
+ }
1223
+ if (!s->graph_exec) {
1224
+ hipGraph_t graph;
1225
+ CHECK(hipStreamBeginCapture(stream, hipStreamCaptureModeThreadLocal));
1226
+ enqueue_period();
1227
+ CHECK(hipStreamEndCapture(stream, &graph));
1228
+ CHECK(hipGraphInstantiate(&s->graph_exec, graph, nullptr, nullptr, 0));
1229
+ CHECK(hipGraphDestroy(graph));
1230
+ s->graph_n = n;
1231
+ s->graph_args = *a;
1232
+ }
1233
+ exec = s->graph_exec;
1234
+ }
1235
+ // Polls the device stop flag every POLL periods, keeping DEPTH polls queued.
1236
+ constexpr uint64_t POLL = 2;
1237
+ constexpr int DEPTH = 3;
1238
+ uint32_t* seen = s->host_stopped; // pinned, one slot per queued poll
1239
+ for (int c = 0; c < DEPTH; c++) seen[c] = 0;
1240
+ int polls = 0;
1241
+ bool host_stop = false;
1242
+ double launch_wall = 0, wait_wall = 0; // host profile: time in launches and in polls
1243
+ for (uint64_t m = 0; m < periods && !host_stop; m++) {
1244
+ double t0 = profile_env ? wall_seconds() : 0;
1245
+ if (exec) CHECK(hipGraphLaunch(exec, stream));
1246
+ else enqueue_period();
1247
+ if (profile_env) launch_wall += wall_seconds() - t0;
1248
+ CHECK(hipGetLastError());
1249
+ if (a->stop_when_inactive && (m + 1) % POLL == 0) {
1250
+ int slot = polls % DEPTH;
1251
+ CHECK(hipMemcpyAsync(&seen[slot], &st->stopped, sizeof(uint32_t), hipMemcpyDeviceToHost, stream));
1252
+ CHECK(hipEventRecord(s->chunk_done[slot], stream));
1253
+ polls++;
1254
+ if (polls >= DEPTH) { // wait for the oldest queued poll
1255
+ int oldest = polls % DEPTH;
1256
+ double t1 = profile_env ? wall_seconds() : 0;
1257
+ CHECK(hipEventSynchronize(s->chunk_done[oldest]));
1258
+ if (profile_env) wait_wall += wall_seconds() - t1;
1259
+ host_stop = seen[oldest] != 0;
1260
+ }
1261
+ }
1262
+ }
1263
+ if (!host_stop) {
1264
+ uint32_t p = 0;
1265
+ for (uint32_t off = 1; off <= rest; off++) enqueue_tick(off, p);
1266
+ }
1267
+ if (n) w_finish_kernel<<<blocks(n, 256), 256, 0, stream>>>(s->cars, n, s->settled_at, st, a->ticks);
1268
+ CHECK(hipGetLastError());
1269
+ WindowState result;
1270
+ CHECK(hipMemcpyAsync(&result, st, sizeof result, hipMemcpyDeviceToHost, stream));
1271
+ CHECK(hipStreamSynchronize(stream));
1272
+ *executed = result.stopped ? result.executed : limit;
1273
+ *transition_without_drive = result.stopped;
1274
+ constexpr uint64_t SPAN = 600; // profile timeline bucket, ticks
1275
+ std::vector<double> line(4 * (limit / SPAN + 1), 0.0);
1276
+ for (const Timed& t : timed) {
1277
+ float ms = 0.0f;
1278
+ CHECK(hipEventElapsedTime(&ms, t.start, t.stop));
1279
+ spent[t.slot] += ms * 1e-3;
1280
+ line[4 * ((t.k - 1) / SPAN) + t.slot] += ms * 1e-3;
1281
+ CHECK(hipEventDestroy(t.start));
1282
+ CHECK(hipEventDestroy(t.stop));
1283
+ }
1284
+ if (profile)
1285
+ fprintf(stderr, "altd_gpu window: %llu ticks, %u cars: stats %.3f s, sensors %.3f s, forward %.3f s, step %.3f s\n",
1286
+ (unsigned long long)*executed, n, spent[0], spent[1], spent[2], spent[3]);
1287
+ if (profile)
1288
+ for (size_t b = 0; 4 * b < line.size(); b++)
1289
+ fprintf(stderr, " ticks %5zu..: stats %.3f sensors %.3f forward %.3f step %.3f\n", b * SPAN + 1, line[4 * b], line[4 * b + 1],
1290
+ line[4 * b + 2], line[4 * b + 3]);
1291
+ #ifdef ALTD_STEP_TIMING
1292
+ {
1293
+ unsigned long long c[16];
1294
+ int rate_khz = 0;
1295
+ CHECK(hipMemcpyFromSymbol(c, HIP_SYMBOL(g_step_cycles), sizeof c));
1296
+ CHECK(altd_wall_clock_khz(&rate_khz));
1297
+ const char* names[6] = {"drive", "validate", "contacts+merge", "active", "solver", "finish"};
1298
+ double us = 1e3 / (double)rate_khz, steps = (double)(c[8] ? c[8] : 1);
1299
+ fprintf(stderr, "altd_gpu step sections (%llu car steps, %llu solving, %llu pair updates, %llu full scans), us per car step:",
1300
+ c[8], c[9], c[7], c[6]);
1301
+ for (int k = 0; k < 6; k++) fprintf(stderr, " %s %.2f", names[k], (double)c[k] * us / steps);
1302
+ fprintf(stderr, " (pairs %.2f, merge %.2f)", (double)c[10] * us / steps, (double)c[11] * us / steps);
1303
+ fprintf(stderr, "; SAT pairs %.2f per step, axes %.2f per pair, separated %.2f, wall points %.2f",
1304
+ (double)c[12] / steps, (double)c[13] / (double)(c[12] ? c[12] : 1), (double)c[14] / (double)(c[12] ? c[12] : 1),
1305
+ (double)c[15] / (double)(c[12] ? c[12] : 1));
1306
+ fprintf(stderr, "\n");
1307
+ unsigned long long zero[16] = {};
1308
+ CHECK(hipMemcpyToSymbol(HIP_SYMBOL(g_step_cycles), zero, sizeof zero));
1309
+ }
1310
+ #endif
1311
+ if (profile_env) {
1312
+ double cpu = cpu0 >= 0 ? thread_cpu_seconds() - cpu0 : -1.0, wall = wall_seconds() - wall0;
1313
+ if (cpu >= 0) fprintf(stderr, "altd_gpu window: host thread cpu %.3f s of %.3f s wall", cpu, wall);
1314
+ else fprintf(stderr, "altd_gpu window: %.3f s wall", wall);
1315
+ fprintf(stderr, " (%s): launches %.3f s, polls %.3f s\n", exec ? "graph" : "direct", launch_wall, wait_wall);
1316
+ }
1317
+ return 0;
1318
+ }
1319
+
1320
+ ALTD_API int altd_gpu_version() { return 10; }
1321
+
1322
+ // The runtime the library was built for: "HIP" (AMD) or "CUDA" (NVIDIA).
1323
+ ALTD_API const char* altd_gpu_platform() { return ALTD_PLATFORM; }
1324
+
1325
+ // Writes the current device's name, NUL-terminated and truncated to `len`
1326
+ // bytes; returns the HIP error, so it also tells whether a device is usable.
1327
+ ALTD_API int altd_gpu_device_name(char* out, int len) {
1328
+ if (len <= 0) return (int)hipErrorInvalidValue;
1329
+ int device = 0;
1330
+ hipError_t e = hipGetDevice(&device);
1331
+ if (e != hipSuccess) return (int)e;
1332
+ hipDeviceProp_t prop;
1333
+ e = hipGetDeviceProperties(&prop, device);
1334
+ if (e != hipSuccess) return (int)e;
1335
+ snprintf(out, (size_t)len, "%s", prop.name);
1336
+ return 0;
1337
+ }
1338
+
1339
+ // Whether the library has kernels for the current device, without launching one: ROCm 7.1 crashes
1340
+ // in the launch instead of returning an error when the library was built for another GPU_ARCH.
1341
+ // Returns the HIP error, hipErrorNoBinaryForGpu when the kernels are missing.
1342
+ ALTD_API int altd_gpu_probe() {
1343
+ hipFuncAttributes attributes;
1344
+ hipError_t e = hipFuncGetAttributes(&attributes, (const void*)math_kernel);
1345
+ #if !defined(__CUDACC__)
1346
+ if (e == hipErrorInvalidKernelFile) return (int)hipErrorNoBinaryForGpu; // HIP's answer for a missing code object
1347
+ #endif
1348
+ return (int)e;
1349
+ }
1350
+
1351
+ // Runs one math primitive over `n` host inputs; `err` gets the per-element error bits.
1352
+ ALTD_API int altd_gpu_math(int op, int n, const void* in, void* out, uint32_t* err) {
1353
+ void *d_in, *d_out;
1354
+ uint32_t* d_err;
1355
+ CHECK(hipMalloc(&d_in, in_size(op) * n));
1356
+ CHECK(hipMalloc(&d_out, out_size(op) * n));
1357
+ CHECK(hipMalloc(&d_err, 4 * (size_t)n));
1358
+ CHECK(hipMemcpy(d_in, in, in_size(op) * n, hipMemcpyHostToDevice));
1359
+ math_kernel<<<(n + 255) / 256, 256>>>(op, n, d_in, d_out, d_err);
1360
+ CHECK(hipGetLastError());
1361
+ CHECK(hipMemcpy(out, d_out, out_size(op) * n, hipMemcpyDeviceToHost));
1362
+ CHECK(hipMemcpy(err, d_err, 4 * (size_t)n, hipMemcpyDeviceToHost));
1363
+ CHECK(hipFree(d_in));
1364
+ CHECK(hipFree(d_out));
1365
+ CHECK(hipFree(d_err));
1366
+ return 0;
1367
+ }
1368
+
1369
+ // Raw engine sin/cos for floats with bits first .. first + n - 1.
1370
+ ALTD_API int altd_gpu_engine_scan(uint32_t first, uint32_t n, uint32_t* out) {
1371
+ static uint32_t* d_out = nullptr;
1372
+ static uint32_t capacity = 0;
1373
+ if (n > capacity) {
1374
+ if (d_out) CHECK(hipFree(d_out));
1375
+ CHECK(hipMalloc(&d_out, 8 * (size_t)n));
1376
+ capacity = n;
1377
+ }
1378
+ engine_scan_kernel<<<(n + 255) / 256, 256>>>(first, n, d_out);
1379
+ CHECK(hipGetLastError());
1380
+ CHECK(hipMemcpy(out, d_out, 8 * (size_t)n, hipMemcpyDeviceToHost));
1381
+ return 0;
1382
+ }
1383
+
1384
+ } // extern "C"
github-simulator/source/gpu/sim/compat.h ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // One source for AMD (hipcc) and NVIDIA (nvcc -x cu). Under HIP this header only includes the
2
+ // runtime and forwards the warp helpers to the HIP builtins; under CUDA it maps the hip* runtime
3
+ // names the simulator uses to their cuda* equivalents.
4
+ //
5
+ // ALTD_PLATFORM names the compiler the library was built with ("HIP" or "CUDA").
6
+ //
7
+ // Warp helpers: kernels call altd_lane_id/altd_ballot/altd_shfl*. On AMD a wave is warpSize
8
+ // (32 or 64) lanes and __ballot returns a 64-bit mask. On NVIDIA a warp is 32 lanes and the
9
+ // *_sync forms with a full mask are required; callers already have every lane of the wave call them.
10
+ #pragma once
11
+
12
+ // Marks the C ABI functions of the library. A Windows DLL exports nothing unless told to.
13
+ #if defined(_WIN32)
14
+ #define ALTD_API __declspec(dllexport)
15
+ #else
16
+ #define ALTD_API
17
+ #endif
18
+
19
+ #if defined(__CUDACC__)
20
+
21
+ #include <cuda_runtime.h>
22
+ #include <cstdint>
23
+ #include <cstring>
24
+
25
+ #define ALTD_PLATFORM "CUDA"
26
+
27
+ #define hipError_t cudaError_t
28
+ #define hipSuccess cudaSuccess
29
+ #define hipErrorInvalidValue cudaErrorInvalidValue
30
+ #define hipGetErrorString cudaGetErrorString
31
+ #define hipGetLastError cudaGetLastError
32
+ #define hipGetDevice cudaGetDevice
33
+ #define hipDeviceSynchronize cudaDeviceSynchronize
34
+ #define hipDeviceProp_t cudaDeviceProp
35
+ #define hipFuncAttributes cudaFuncAttributes
36
+ #define hipFuncGetAttributes cudaFuncGetAttributes
37
+ #define hipGetDeviceProperties cudaGetDeviceProperties
38
+
39
+ #define hipMalloc cudaMalloc
40
+ #define hipFree cudaFree
41
+ #define hipHostMalloc(ptr, size) cudaMallocHost(ptr, size)
42
+ #define hipHostFree cudaFreeHost
43
+ #define hipMemcpy cudaMemcpy
44
+ #define hipMemcpyAsync cudaMemcpyAsync
45
+ #define hipMemcpyHostToDevice cudaMemcpyHostToDevice
46
+ #define hipMemcpyDeviceToHost cudaMemcpyDeviceToHost
47
+ #define hipMemset cudaMemset
48
+ #define hipMemsetAsync cudaMemsetAsync
49
+ #define hipMemcpyToSymbol cudaMemcpyToSymbol
50
+ #define hipMemcpyFromSymbol cudaMemcpyFromSymbol
51
+ #define HIP_SYMBOL(symbol) (symbol)
52
+
53
+ #define hipStream_t cudaStream_t
54
+ #define hipStreamCreate cudaStreamCreate
55
+ #define hipStreamDestroy cudaStreamDestroy
56
+ #define hipStreamSynchronize cudaStreamSynchronize
57
+ #define hipStreamBeginCapture cudaStreamBeginCapture
58
+ #define hipStreamEndCapture cudaStreamEndCapture
59
+ #define hipStreamCaptureModeThreadLocal cudaStreamCaptureModeThreadLocal
60
+
61
+ #define hipEvent_t cudaEvent_t
62
+ #define hipEventCreate cudaEventCreate
63
+ #define hipEventCreateWithFlags cudaEventCreateWithFlags
64
+ #define hipEventDisableTiming cudaEventDisableTiming
65
+ #define hipEventRecord cudaEventRecord
66
+ #define hipEventSynchronize cudaEventSynchronize
67
+ #define hipEventElapsedTime cudaEventElapsedTime
68
+ #define hipEventDestroy cudaEventDestroy
69
+
70
+ #define hipGraph_t cudaGraph_t
71
+ #define hipGraphExec_t cudaGraphExec_t
72
+ #define hipGraphLaunch cudaGraphLaunch
73
+ #define hipGraphDestroy cudaGraphDestroy
74
+ #define hipGraphExecDestroy cudaGraphExecDestroy
75
+ // HIP's (exec, graph, error node, log buffer, buffer size) form; CUDA 12+ takes flags instead.
76
+ static inline cudaError_t hipGraphInstantiate(cudaGraphExec_t* exec, cudaGraph_t graph, void*, void*, size_t) {
77
+ return cudaGraphInstantiate(exec, graph, 0ull);
78
+ }
79
+
80
+ // ALTD_STEP_TIMING: clock64() counts SM cycles at a varying clock, so the step sections read
81
+ // the nanosecond %globaltimer instead (updated about once a microsecond, so only sums are useful).
82
+ __device__ inline uint64_t wall_clock64() {
83
+ uint64_t t;
84
+ asm volatile("mov.u64 %0, %%globaltimer;" : "=l"(t));
85
+ return t;
86
+ }
87
+ inline cudaError_t altd_wall_clock_khz(int* khz) {
88
+ *khz = 1000000;
89
+ return cudaSuccess;
90
+ }
91
+
92
+ // nvcc's front end does not know clang's __builtin_memcpy.
93
+ __device__ inline void altd_memcpy(void* dst, const void* src, size_t bytes) { memcpy(dst, src, bytes); }
94
+
95
+ __device__ inline uint32_t altd_lane_id() {
96
+ uint32_t lane;
97
+ asm volatile("mov.u32 %0, %%laneid;" : "=r"(lane));
98
+ return lane;
99
+ }
100
+ __device__ inline uint64_t altd_ballot(bool predicate) { return (uint64_t)__ballot_sync(0xffffffffu, predicate); }
101
+ __device__ inline uint32_t altd_shfl(uint32_t value, uint32_t lane) { return __shfl_sync(0xffffffffu, value, lane); }
102
+ __device__ inline uint32_t altd_shfl_up(uint32_t value, uint32_t delta) { return __shfl_up_sync(0xffffffffu, value, delta); }
103
+
104
+ #else
105
+
106
+ #include <hip/hip_runtime.h>
107
+ #include <cstdint>
108
+
109
+ #define ALTD_PLATFORM "HIP"
110
+
111
+ inline hipError_t altd_wall_clock_khz(int* khz) { return hipDeviceGetAttribute(khz, hipDeviceAttributeWallClockRate, 0); }
112
+
113
+ // HIP's device memcpy is a byte loop; the builtin lets clang copy whole words.
114
+ __device__ inline void altd_memcpy(void* dst, const void* src, size_t bytes) { __builtin_memcpy(dst, src, bytes); }
115
+
116
+ __device__ inline uint32_t altd_lane_id() { return __lane_id(); }
117
+ __device__ inline uint64_t altd_ballot(bool predicate) { return __ballot(predicate); }
118
+ __device__ inline uint32_t altd_shfl(uint32_t value, uint32_t lane) { return (uint32_t)__shfl((int)value, (int)lane); }
119
+ __device__ inline uint32_t altd_shfl_up(uint32_t value, uint32_t delta) { return (uint32_t)__shfl_up((int)value, delta); }
120
+
121
+ #endif
122
+
123
+ // NaN results with the CPU's bits. x86 SSE returns the first NaN operand with its quiet bit set,
124
+ // and the default NaN 0xffc00000 (0xfff8000000000000) for an invalid operation such as inf - inf.
125
+ // NVIDIA arithmetic instead returns its canonical NaN, and which NaN plain `a + b` returns on AMD
126
+ // depends on the operand order the compiler picks, so both vendors build the bits explicitly.
127
+ // altd_nan_operand(a, b): the result of an add, multiply or divide when a or b is a NaN.
128
+ // altd_invalid(x): the result of x - x for a NaN or infinite x.
129
+ __device__ inline float altd_nan_operand(float a, float b) {
130
+ return __uint_as_float(__float_as_uint(isnan(a) ? a : b) | 0x00400000u);
131
+ }
132
+ __device__ inline double altd_nan_operand(double a, double b) {
133
+ return __longlong_as_double(__double_as_longlong(isnan(a) ? a : b) | 0x0008000000000000ll);
134
+ }
135
+ __device__ inline float altd_invalid(float x) { return isnan(x) ? altd_nan_operand(x, x) : __uint_as_float(0xffc00000u); }
github-simulator/source/gpu/sim/double_tables.h ADDED
@@ -0,0 +1,691 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Generated by gpu/gen_tables.py from src/math/double_math_tables.rs. Do not edit.
2
+ #pragma once
3
+ #include <cstdint>
4
+ __device__ constexpr double INV_LN2_N = __builtin_bit_cast(double, (uint64_t)0x40671547652b82feull);
5
+ __device__ constexpr double NEG_LN2_HI_N = __builtin_bit_cast(double, (uint64_t)0xbf762e42fefa0000ull);
6
+ __device__ constexpr double NEG_LN2_LO_N = __builtin_bit_cast(double, (uint64_t)0xbd0cf79abc9e3b3aull);
7
+ __device__ constexpr double EXP_POLY[4] = {
8
+ __builtin_bit_cast(double, (uint64_t)0x3fdffffffffffdbdull),
9
+ __builtin_bit_cast(double, (uint64_t)0x3fc555555555543cull),
10
+ __builtin_bit_cast(double, (uint64_t)0x3fa55555cf172b91ull),
11
+ __builtin_bit_cast(double, (uint64_t)0x3f81111167a4d017ull)
12
+ };
13
+ __device__ constexpr uint64_t EXP_TAB[256] = {
14
+ 0x0ull,
15
+ 0x3ff0000000000000ull,
16
+ 0x3c9b3b4f1a88bf6eull,
17
+ 0x3feff63da9fb3335ull,
18
+ 0xbc7160139cd8dc5dull,
19
+ 0x3fefec9a3e778061ull,
20
+ 0xbc905e7a108766d1ull,
21
+ 0x3fefe315e86e7f85ull,
22
+ 0x3c8cd2523567f613ull,
23
+ 0x3fefd9b0d3158574ull,
24
+ 0xbc8bce8023f98efaull,
25
+ 0x3fefd06b29ddf6deull,
26
+ 0x3c60f74e61e6c861ull,
27
+ 0x3fefc74518759bc8ull,
28
+ 0x3c90a3e45b33d399ull,
29
+ 0x3fefbe3ecac6f383ull,
30
+ 0x3c979aa65d837b6dull,
31
+ 0x3fefb5586cf9890full,
32
+ 0x3c8eb51a92fdeffcull,
33
+ 0x3fefac922b7247f7ull,
34
+ 0x3c3ebe3d702f9cd1ull,
35
+ 0x3fefa3ec32d3d1a2ull,
36
+ 0xbc6a033489906e0bull,
37
+ 0x3fef9b66affed31bull,
38
+ 0xbc9556522a2fbd0eull,
39
+ 0x3fef9301d0125b51ull,
40
+ 0xbc5080ef8c4eea55ull,
41
+ 0x3fef8abdc06c31ccull,
42
+ 0xbc91c923b9d5f416ull,
43
+ 0x3fef829aaea92de0ull,
44
+ 0x3c80d3e3e95c55afull,
45
+ 0x3fef7a98c8a58e51ull,
46
+ 0xbc801b15eaa59348ull,
47
+ 0x3fef72b83c7d517bull,
48
+ 0xbc8f1ff055de323dull,
49
+ 0x3fef6af9388c8deaull,
50
+ 0x3c8b898c3f1353bfull,
51
+ 0x3fef635beb6fcb75ull,
52
+ 0xbc96d99c7611eb26ull,
53
+ 0x3fef5be084045cd4ull,
54
+ 0x3c9aecf73e3a2f60ull,
55
+ 0x3fef54873168b9aaull,
56
+ 0xbc8fe782cb86389dull,
57
+ 0x3fef4d5022fcd91dull,
58
+ 0x3c8a6f4144a6c38dull,
59
+ 0x3fef463b88628cd6ull,
60
+ 0x3c807a05b0e4047dull,
61
+ 0x3fef3f49917ddc96ull,
62
+ 0x3c968efde3a8a894ull,
63
+ 0x3fef387a6e756238ull,
64
+ 0x3c875e18f274487dull,
65
+ 0x3fef31ce4fb2a63full,
66
+ 0x3c80472b981fe7f2ull,
67
+ 0x3fef2b4565e27cddull,
68
+ 0xbc96b87b3f71085eull,
69
+ 0x3fef24dfe1f56381ull,
70
+ 0x3c82f7e16d09ab31ull,
71
+ 0x3fef1e9df51fdee1ull,
72
+ 0xbc3d219b1a6fbffaull,
73
+ 0x3fef187fd0dad990ull,
74
+ 0x3c8b3782720c0ab4ull,
75
+ 0x3fef1285a6e4030bull,
76
+ 0x3c6e149289cecb8full,
77
+ 0x3fef0cafa93e2f56ull,
78
+ 0x3c834d754db0abb6ull,
79
+ 0x3fef06fe0a31b715ull,
80
+ 0x3c864201e2ac744cull,
81
+ 0x3fef0170fc4cd831ull,
82
+ 0x3c8fdd395dd3f84aull,
83
+ 0x3feefc08b26416ffull,
84
+ 0xbc86a3803b8e5b04ull,
85
+ 0x3feef6c55f929ff1ull,
86
+ 0xbc924aedcc4b5068ull,
87
+ 0x3feef1a7373aa9cbull,
88
+ 0xbc9907f81b512d8eull,
89
+ 0x3feeecae6d05d866ull,
90
+ 0xbc71d1e83e9436d2ull,
91
+ 0x3feee7db34e59ff7ull,
92
+ 0xbc991919b3ce1b15ull,
93
+ 0x3feee32dc313a8e5ull,
94
+ 0x3c859f48a72a4c6dull,
95
+ 0x3feedea64c123422ull,
96
+ 0xbc9312607a28698aull,
97
+ 0x3feeda4504ac801cull,
98
+ 0xbc58a78f4817895bull,
99
+ 0x3feed60a21f72e2aull,
100
+ 0xbc7c2c9b67499a1bull,
101
+ 0x3feed1f5d950a897ull,
102
+ 0x3c4363ed60c2ac11ull,
103
+ 0x3feece086061892dull,
104
+ 0x3c9666093b0664efull,
105
+ 0x3feeca41ed1d0057ull,
106
+ 0x3c6ecce1daa10379ull,
107
+ 0x3feec6a2b5c13cd0ull,
108
+ 0x3c93ff8e3f0f1230ull,
109
+ 0x3feec32af0d7d3deull,
110
+ 0x3c7690cebb7aafb0ull,
111
+ 0x3feebfdad5362a27ull,
112
+ 0x3c931dbdeb54e077ull,
113
+ 0x3feebcb299fddd0dull,
114
+ 0xbc8f94340071a38eull,
115
+ 0x3feeb9b2769d2ca7ull,
116
+ 0xbc87deccdc93a349ull,
117
+ 0x3feeb6daa2cf6642ull,
118
+ 0xbc78dec6bd0f385full,
119
+ 0x3feeb42b569d4f82ull,
120
+ 0xbc861246ec7b5cf6ull,
121
+ 0x3feeb1a4ca5d920full,
122
+ 0x3c93350518fdd78eull,
123
+ 0x3feeaf4736b527daull,
124
+ 0x3c7b98b72f8a9b05ull,
125
+ 0x3feead12d497c7fdull,
126
+ 0x3c9063e1e21c5409ull,
127
+ 0x3feeab07dd485429ull,
128
+ 0x3c34c7855019c6eaull,
129
+ 0x3feea9268a5946b7ull,
130
+ 0x3c9432e62b64c035ull,
131
+ 0x3feea76f15ad2148ull,
132
+ 0xbc8ce44a6199769full,
133
+ 0x3feea5e1b976dc09ull,
134
+ 0xbc8c33c53bef4da8ull,
135
+ 0x3feea47eb03a5585ull,
136
+ 0xbc845378892be9aeull,
137
+ 0x3feea34634ccc320ull,
138
+ 0xbc93cedd78565858ull,
139
+ 0x3feea23882552225ull,
140
+ 0x3c5710aa807e1964ull,
141
+ 0x3feea155d44ca973ull,
142
+ 0xbc93b3efbf5e2228ull,
143
+ 0x3feea09e667f3bcdull,
144
+ 0xbc6a12ad8734b982ull,
145
+ 0x3feea012750bdabfull,
146
+ 0xbc6367efb86da9eeull,
147
+ 0x3fee9fb23c651a2full,
148
+ 0xbc80dc3d54e08851ull,
149
+ 0x3fee9f7df9519484ull,
150
+ 0xbc781f647e5a3ecfull,
151
+ 0x3fee9f75e8ec5f74ull,
152
+ 0xbc86ee4ac08b7db0ull,
153
+ 0x3fee9f9a48a58174ull,
154
+ 0xbc8619321e55e68aull,
155
+ 0x3fee9feb564267c9ull,
156
+ 0x3c909ccb5e09d4d3ull,
157
+ 0x3feea0694fde5d3full,
158
+ 0xbc7b32dcb94da51dull,
159
+ 0x3feea11473eb0187ull,
160
+ 0x3c94ecfd5467c06bull,
161
+ 0x3feea1ed0130c132ull,
162
+ 0x3c65ebe1abd66c55ull,
163
+ 0x3feea2f336cf4e62ull,
164
+ 0xbc88a1c52fb3cf42ull,
165
+ 0x3feea427543e1a12ull,
166
+ 0xbc9369b6f13b3734ull,
167
+ 0x3feea589994cce13ull,
168
+ 0xbc805e843a19ff1eull,
169
+ 0x3feea71a4623c7adull,
170
+ 0xbc94d450d872576eull,
171
+ 0x3feea8d99b4492edull,
172
+ 0x3c90ad675b0e8a00ull,
173
+ 0x3feeaac7d98a6699ull,
174
+ 0x3c8db72fc1f0eab4ull,
175
+ 0x3feeace5422aa0dbull,
176
+ 0xbc65b6609cc5e7ffull,
177
+ 0x3feeaf3216b5448cull,
178
+ 0x3c7bf68359f35f44ull,
179
+ 0x3feeb1ae99157736ull,
180
+ 0xbc93091fa71e3d83ull,
181
+ 0x3feeb45b0b91ffc6ull,
182
+ 0xbc5da9b88b6c1e29ull,
183
+ 0x3feeb737b0cdc5e5ull,
184
+ 0xbc6c23f97c90b959ull,
185
+ 0x3feeba44cbc8520full,
186
+ 0xbc92434322f4f9aaull,
187
+ 0x3feebd829fde4e50ull,
188
+ 0xbc85ca6cd7668e4bull,
189
+ 0x3feec0f170ca07baull,
190
+ 0x3c71affc2b91ce27ull,
191
+ 0x3feec49182a3f090ull,
192
+ 0x3c6dd235e10a73bbull,
193
+ 0x3feec86319e32323ull,
194
+ 0xbc87c50422622263ull,
195
+ 0x3feecc667b5de565ull,
196
+ 0x3c8b1c86e3e231d5ull,
197
+ 0x3feed09bec4a2d33ull,
198
+ 0xbc91bbd1d3bcbb15ull,
199
+ 0x3feed503b23e255dull,
200
+ 0x3c90cc319cee31d2ull,
201
+ 0x3feed99e1330b358ull,
202
+ 0x3c8469846e735ab3ull,
203
+ 0x3feede6b5579fdbfull,
204
+ 0xbc82dfcd978e9db4ull,
205
+ 0x3feee36bbfd3f37aull,
206
+ 0x3c8c1a7792cb3387ull,
207
+ 0x3feee89f995ad3adull,
208
+ 0xbc907b8f4ad1d9faull,
209
+ 0x3feeee07298db666ull,
210
+ 0xbc55c3d956dcaebaull,
211
+ 0x3feef3a2b84f15fbull,
212
+ 0xbc90a40e3da6f640ull,
213
+ 0x3feef9728de5593aull,
214
+ 0xbc68d6f438ad9334ull,
215
+ 0x3feeff76f2fb5e47ull,
216
+ 0xbc91eee26b588a35ull,
217
+ 0x3fef05b030a1064aull,
218
+ 0x3c74ffd70a5fddcdull,
219
+ 0x3fef0c1e904bc1d2ull,
220
+ 0xbc91bdfbfa9298acull,
221
+ 0x3fef12c25bd71e09ull,
222
+ 0x3c736eae30af0cb3ull,
223
+ 0x3fef199bdd85529cull,
224
+ 0x3c8ee3325c9ffd94ull,
225
+ 0x3fef20ab5fffd07aull,
226
+ 0x3c84e08fd10959acull,
227
+ 0x3fef27f12e57d14bull,
228
+ 0x3c63cdaf384e1a67ull,
229
+ 0x3fef2f6d9406e7b5ull,
230
+ 0x3c676b2c6c921968ull,
231
+ 0x3fef3720dcef9069ull,
232
+ 0xbc808a1883ccb5d2ull,
233
+ 0x3fef3f0b555dc3faull,
234
+ 0xbc8fad5d3ffffa6full,
235
+ 0x3fef472d4a07897cull,
236
+ 0xbc900dae3875a949ull,
237
+ 0x3fef4f87080d89f2ull,
238
+ 0x3c74a385a63d07a7ull,
239
+ 0x3fef5818dcfba487ull,
240
+ 0xbc82919e2040220full,
241
+ 0x3fef60e316c98398ull,
242
+ 0x3c8e5a50d5c192acull,
243
+ 0x3fef69e603db3285ull,
244
+ 0x3c843a59ac016b4bull,
245
+ 0x3fef7321f301b460ull,
246
+ 0xbc82d52107b43e1full,
247
+ 0x3fef7c97337b9b5full,
248
+ 0xbc892ab93b470dc9ull,
249
+ 0x3fef864614f5a129ull,
250
+ 0x3c74b604603a88d3ull,
251
+ 0x3fef902ee78b3ff6ull,
252
+ 0x3c83c5ec519d7271ull,
253
+ 0x3fef9a51fbc74c83ull,
254
+ 0xbc8ff7128fd391f0ull,
255
+ 0x3fefa4afa2a490daull,
256
+ 0xbc8dae98e223747dull,
257
+ 0x3fefaf482d8e67f1ull,
258
+ 0x3c8ec3bc41aa2008ull,
259
+ 0x3fefba1bee615a27ull,
260
+ 0x3c842b94c3a9eb32ull,
261
+ 0x3fefc52b376bba97ull,
262
+ 0x3c8a64a931d185eeull,
263
+ 0x3fefd0765b6e4540ull,
264
+ 0xbc8e37bae43be3edull,
265
+ 0x3fefdbfdad9cbe14ull,
266
+ 0x3c77893b4d91cd9dull,
267
+ 0x3fefe7c1819e90d8ull,
268
+ 0x3c5305c14160cc89ull,
269
+ 0x3feff3c22b8f71f1ull
270
+ };
271
+ __device__ constexpr double LN2_HI = __builtin_bit_cast(double, (uint64_t)0x3fe62e42fefa3800ull);
272
+ __device__ constexpr double LN2_LO = __builtin_bit_cast(double, (uint64_t)0x3d2ef35793c76730ull);
273
+ __device__ constexpr double POW_POLY[7] = {
274
+ __builtin_bit_cast(double, (uint64_t)0xbfe0000000000000ull),
275
+ __builtin_bit_cast(double, (uint64_t)0xbfe5555555555560ull),
276
+ __builtin_bit_cast(double, (uint64_t)0x3fe0000000000006ull),
277
+ __builtin_bit_cast(double, (uint64_t)0x3fe999999959554eull),
278
+ __builtin_bit_cast(double, (uint64_t)0xbfe555555529a47aull),
279
+ __builtin_bit_cast(double, (uint64_t)0xbff2495b9b4845e9ull),
280
+ __builtin_bit_cast(double, (uint64_t)0x3ff0002b8b263fc3ull)
281
+ };
282
+ __device__ constexpr double POW_TAB[128][3] = {
283
+ {__builtin_bit_cast(double, (uint64_t)0x3ff6a00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd62c82f2b9c800ull),__builtin_bit_cast(double, (uint64_t)0x3cfab42428375680ull)},
284
+ {__builtin_bit_cast(double, (uint64_t)0x3ff6800000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd5d1bdbf580800ull),__builtin_bit_cast(double, (uint64_t)0xbd1ca508d8e0f720ull)},
285
+ {__builtin_bit_cast(double, (uint64_t)0x3ff6600000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd5767717455800ull),__builtin_bit_cast(double, (uint64_t)0xbd2362a4d5b6506dull)},
286
+ {__builtin_bit_cast(double, (uint64_t)0x3ff6400000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd51aad872df800ull),__builtin_bit_cast(double, (uint64_t)0xbce684e49eb067d5ull)},
287
+ {__builtin_bit_cast(double, (uint64_t)0x3ff6200000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd4be5f95777800ull),__builtin_bit_cast(double, (uint64_t)0xbd041b6993293ee0ull)},
288
+ {__builtin_bit_cast(double, (uint64_t)0x3ff6000000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd4618bc21c6000ull),__builtin_bit_cast(double, (uint64_t)0x3d13d82f484c84ccull)},
289
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5e00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd404308686a800ull),__builtin_bit_cast(double, (uint64_t)0x3cdc42f3ed820b3aull)},
290
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5c00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd3a64c55694800ull),__builtin_bit_cast(double, (uint64_t)0x3d20b1c686519460ull)},
291
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5a00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd347dd9a988000ull),__builtin_bit_cast(double, (uint64_t)0x3d25594dd4c58092ull)},
292
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5800000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd2e8e2bae12000ull),__builtin_bit_cast(double, (uint64_t)0x3d267b1e99b72bd8ull)},
293
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5600000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd2895a13de8800ull),__builtin_bit_cast(double, (uint64_t)0x3d15ca14b6cfb03full)},
294
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5600000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd2895a13de8800ull),__builtin_bit_cast(double, (uint64_t)0x3d15ca14b6cfb03full)},
295
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5400000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd22941fbcf7800ull),__builtin_bit_cast(double, (uint64_t)0xbd165a242853da76ull)},
296
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5200000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd1c898c1699800ull),__builtin_bit_cast(double, (uint64_t)0xbd1fafbc68e75404ull)},
297
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5000000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd1675cababa800ull),__builtin_bit_cast(double, (uint64_t)0x3d1f1fc63382a8f0ull)},
298
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4e00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd1058bf9ae4800ull),__builtin_bit_cast(double, (uint64_t)0xbd26a8c4fd055a66ull)},
299
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4c00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd0a324e2739000ull),__builtin_bit_cast(double, (uint64_t)0xbd0c6bee7ef4030eull)},
300
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4a00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd0402594b4d000ull),__builtin_bit_cast(double, (uint64_t)0xbcf036b89ef42d7full)},
301
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4a00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfd0402594b4d000ull),__builtin_bit_cast(double, (uint64_t)0xbcf036b89ef42d7full)},
302
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4800000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfcfb9186d5e4000ull),__builtin_bit_cast(double, (uint64_t)0x3d0d572aab993c87ull)},
303
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4600000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfcef0adcbdc6000ull),__builtin_bit_cast(double, (uint64_t)0x3d2b26b79c86af24ull)},
304
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4400000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfce27076e2af000ull),__builtin_bit_cast(double, (uint64_t)0xbd172f4f543fff10ull)},
305
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4200000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfcd5c216b4fc000ull),__builtin_bit_cast(double, (uint64_t)0x3d21ba91bbca681bull)},
306
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4000000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfcc8ff7c79aa000ull),__builtin_bit_cast(double, (uint64_t)0x3d27794f689f8434ull)},
307
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4000000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfcc8ff7c79aa000ull),__builtin_bit_cast(double, (uint64_t)0x3d27794f689f8434ull)},
308
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3e00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfcbc286742d9000ull),__builtin_bit_cast(double, (uint64_t)0x3d194eb0318bb78full)},
309
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3c00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfcaf3c94e80c000ull),__builtin_bit_cast(double, (uint64_t)0x3cba4e633fcd9066ull)},
310
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3a00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfca23bc1fe2b000ull),__builtin_bit_cast(double, (uint64_t)0xbd258c64dc46c1eaull)},
311
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3a00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfca23bc1fe2b000ull),__builtin_bit_cast(double, (uint64_t)0xbd258c64dc46c1eaull)},
312
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3800000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc9525a9cf45000ull),__builtin_bit_cast(double, (uint64_t)0xbd2ad1d904c1d4e3ull)},
313
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3600000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc87fa06520d000ull),__builtin_bit_cast(double, (uint64_t)0x3d2bbdbf7fdbfa09ull)},
314
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3400000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc7ab890210e000ull),__builtin_bit_cast(double, (uint64_t)0x3d2bdb9072534a58ull)},
315
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3400000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc7ab890210e000ull),__builtin_bit_cast(double, (uint64_t)0x3d2bdb9072534a58ull)},
316
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3200000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc6d60fe719d000ull),__builtin_bit_cast(double, (uint64_t)0xbd10e46aa3b2e266ull)},
317
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3000000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc5ff3070a79000ull),__builtin_bit_cast(double, (uint64_t)0xbd1e9e439f105039ull)},
318
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3000000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc5ff3070a79000ull),__builtin_bit_cast(double, (uint64_t)0xbd1e9e439f105039ull)},
319
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2e00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc526e5e3a1b000ull),__builtin_bit_cast(double, (uint64_t)0xbd20de8b90075b8full)},
320
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2c00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc44d2b6ccb8000ull),__builtin_bit_cast(double, (uint64_t)0x3d170cc16135783cull)},
321
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2c00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc44d2b6ccb8000ull),__builtin_bit_cast(double, (uint64_t)0x3d170cc16135783cull)},
322
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2a00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc371fc201e9000ull),__builtin_bit_cast(double, (uint64_t)0x3cf178864d27543aull)},
323
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2800000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc29552f81ff000ull),__builtin_bit_cast(double, (uint64_t)0xbd248d301771c408ull)},
324
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2600000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc1b72ad52f6000ull),__builtin_bit_cast(double, (uint64_t)0xbd2e80a41811a396ull)},
325
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2600000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc1b72ad52f6000ull),__builtin_bit_cast(double, (uint64_t)0xbd2e80a41811a396ull)},
326
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2400000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc0d77e7cd09000ull),__builtin_bit_cast(double, (uint64_t)0x3d0a699688e85bf4ull)},
327
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2400000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfc0d77e7cd09000ull),__builtin_bit_cast(double, (uint64_t)0x3d0a699688e85bf4ull)},
328
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2200000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfbfec9131dbe000ull),__builtin_bit_cast(double, (uint64_t)0xbd2575545ca333f2ull)},
329
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2000000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfbe27076e2b0000ull),__builtin_bit_cast(double, (uint64_t)0x3d2a342c2af0003cull)},
330
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2000000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfbe27076e2b0000ull),__builtin_bit_cast(double, (uint64_t)0x3d2a342c2af0003cull)},
331
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1e00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfbc5e548f5bc000ull),__builtin_bit_cast(double, (uint64_t)0xbd1d0c57585fbe06ull)},
332
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1c00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfba926d3a4ae000ull),__builtin_bit_cast(double, (uint64_t)0x3d253935e85baac8ull)},
333
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1c00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfba926d3a4ae000ull),__builtin_bit_cast(double, (uint64_t)0x3d253935e85baac8ull)},
334
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1a00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfb8c345d631a000ull),__builtin_bit_cast(double, (uint64_t)0x3d137c294d2f5668ull)},
335
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1a00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfb8c345d631a000ull),__builtin_bit_cast(double, (uint64_t)0x3d137c294d2f5668ull)},
336
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1800000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfb6f0d28ae56000ull),__builtin_bit_cast(double, (uint64_t)0xbd269737c93373daull)},
337
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1600000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfb51b073f062000ull),__builtin_bit_cast(double, (uint64_t)0x3d1f025b61c65e57ull)},
338
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1600000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfb51b073f062000ull),__builtin_bit_cast(double, (uint64_t)0x3d1f025b61c65e57ull)},
339
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1400000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfb341d7961be000ull),__builtin_bit_cast(double, (uint64_t)0x3d2c5edaccf913dfull)},
340
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1400000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfb341d7961be000ull),__builtin_bit_cast(double, (uint64_t)0x3d2c5edaccf913dfull)},
341
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1200000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfb16536eea38000ull),__builtin_bit_cast(double, (uint64_t)0x3d147c5e768fa309ull)},
342
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1000000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfaf0a30c0118000ull),__builtin_bit_cast(double, (uint64_t)0x3d2d599e83368e91ull)},
343
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1000000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfaf0a30c0118000ull),__builtin_bit_cast(double, (uint64_t)0x3d2d599e83368e91ull)},
344
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0e00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfab42dd71198000ull),__builtin_bit_cast(double, (uint64_t)0x3d1c827ae5d6704cull)},
345
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0e00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfab42dd71198000ull),__builtin_bit_cast(double, (uint64_t)0x3d1c827ae5d6704cull)},
346
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0c00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfa77458f632c000ull),__builtin_bit_cast(double, (uint64_t)0xbd2cfc4634f2a1eeull)},
347
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0c00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfa77458f632c000ull),__builtin_bit_cast(double, (uint64_t)0xbd2cfc4634f2a1eeull)},
348
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0a00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfa39e87b9fec000ull),__builtin_bit_cast(double, (uint64_t)0x3cf502b7f526feaaull)},
349
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0a00000000000ull),__builtin_bit_cast(double, (uint64_t)0xbfa39e87b9fec000ull),__builtin_bit_cast(double, (uint64_t)0x3cf502b7f526feaaull)},
350
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0800000000000ull),__builtin_bit_cast(double, (uint64_t)0xbf9f829b0e780000ull),__builtin_bit_cast(double, (uint64_t)0xbd2980267c7e09e4ull)},
351
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0800000000000ull),__builtin_bit_cast(double, (uint64_t)0xbf9f829b0e780000ull),__builtin_bit_cast(double, (uint64_t)0xbd2980267c7e09e4ull)},
352
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0600000000000ull),__builtin_bit_cast(double, (uint64_t)0xbf97b91b07d58000ull),__builtin_bit_cast(double, (uint64_t)0xbd288d5493faa639ull)},
353
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0400000000000ull),__builtin_bit_cast(double, (uint64_t)0xbf8fc0a8b0fc0000ull),__builtin_bit_cast(double, (uint64_t)0xbcdf1e7cf6d3a69cull)},
354
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0400000000000ull),__builtin_bit_cast(double, (uint64_t)0xbf8fc0a8b0fc0000ull),__builtin_bit_cast(double, (uint64_t)0xbcdf1e7cf6d3a69cull)},
355
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0200000000000ull),__builtin_bit_cast(double, (uint64_t)0xbf7fe02a6b100000ull),__builtin_bit_cast(double, (uint64_t)0xbd19e23f0dda40e4ull)},
356
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0200000000000ull),__builtin_bit_cast(double, (uint64_t)0xbf7fe02a6b100000ull),__builtin_bit_cast(double, (uint64_t)0xbd19e23f0dda40e4ull)},
357
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0000000000000ull),__builtin_bit_cast(double, (uint64_t)0x0000000000000000ull),__builtin_bit_cast(double, (uint64_t)0x0000000000000000ull)},
358
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0000000000000ull),__builtin_bit_cast(double, (uint64_t)0x0000000000000000ull),__builtin_bit_cast(double, (uint64_t)0x0000000000000000ull)},
359
+ {__builtin_bit_cast(double, (uint64_t)0x3fefc00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3f80101575890000ull),__builtin_bit_cast(double, (uint64_t)0xbd10c76b999d2be8ull)},
360
+ {__builtin_bit_cast(double, (uint64_t)0x3fef800000000000ull),__builtin_bit_cast(double, (uint64_t)0x3f90205658938000ull),__builtin_bit_cast(double, (uint64_t)0xbd23dc5b06e2f7d2ull)},
361
+ {__builtin_bit_cast(double, (uint64_t)0x3fef400000000000ull),__builtin_bit_cast(double, (uint64_t)0x3f98492528c90000ull),__builtin_bit_cast(double, (uint64_t)0xbd2aa0ba325a0c34ull)},
362
+ {__builtin_bit_cast(double, (uint64_t)0x3fef000000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fa0415d89e74000ull),__builtin_bit_cast(double, (uint64_t)0x3d0111c05cf1d753ull)},
363
+ {__builtin_bit_cast(double, (uint64_t)0x3feec00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fa466aed42e0000ull),__builtin_bit_cast(double, (uint64_t)0xbd2c167375bdfd28ull)},
364
+ {__builtin_bit_cast(double, (uint64_t)0x3fee800000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fa894aa149fc000ull),__builtin_bit_cast(double, (uint64_t)0xbd197995d05a267dull)},
365
+ {__builtin_bit_cast(double, (uint64_t)0x3fee400000000000ull),__builtin_bit_cast(double, (uint64_t)0x3faccb73cdddc000ull),__builtin_bit_cast(double, (uint64_t)0xbd1a68f247d82807ull)},
366
+ {__builtin_bit_cast(double, (uint64_t)0x3fee200000000000ull),__builtin_bit_cast(double, (uint64_t)0x3faeea31c006c000ull),__builtin_bit_cast(double, (uint64_t)0xbd0e113e4fc93b7bull)},
367
+ {__builtin_bit_cast(double, (uint64_t)0x3fede00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fb1973bd1466000ull),__builtin_bit_cast(double, (uint64_t)0xbd25325d560d9e9bull)},
368
+ {__builtin_bit_cast(double, (uint64_t)0x3feda00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fb3bdf5a7d1e000ull),__builtin_bit_cast(double, (uint64_t)0x3d2cc85ea5db4ed7ull)},
369
+ {__builtin_bit_cast(double, (uint64_t)0x3fed600000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fb5e95a4d97a000ull),__builtin_bit_cast(double, (uint64_t)0xbd2c69063c5d1d1eull)},
370
+ {__builtin_bit_cast(double, (uint64_t)0x3fed400000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fb700d30aeac000ull),__builtin_bit_cast(double, (uint64_t)0x3cec1e8da99ded32ull)},
371
+ {__builtin_bit_cast(double, (uint64_t)0x3fed000000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fb9335e5d594000ull),__builtin_bit_cast(double, (uint64_t)0x3d23115c3abd47daull)},
372
+ {__builtin_bit_cast(double, (uint64_t)0x3fecc00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fbb6ac88dad6000ull),__builtin_bit_cast(double, (uint64_t)0xbd1390802bf768e5ull)},
373
+ {__builtin_bit_cast(double, (uint64_t)0x3feca00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fbc885801bc4000ull),__builtin_bit_cast(double, (uint64_t)0x3d2646d1c65aacd3ull)},
374
+ {__builtin_bit_cast(double, (uint64_t)0x3fec600000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fbec739830a2000ull),__builtin_bit_cast(double, (uint64_t)0xbd2dc068afe645e0ull)},
375
+ {__builtin_bit_cast(double, (uint64_t)0x3fec400000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fbfe89139dbe000ull),__builtin_bit_cast(double, (uint64_t)0xbd2534d64fa10afdull)},
376
+ {__builtin_bit_cast(double, (uint64_t)0x3fec000000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fc1178e8227e000ull),__builtin_bit_cast(double, (uint64_t)0x3d21ef78ce2d07f2ull)},
377
+ {__builtin_bit_cast(double, (uint64_t)0x3febe00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fc1aa2b7e23f000ull),__builtin_bit_cast(double, (uint64_t)0x3d2ca78e44389934ull)},
378
+ {__builtin_bit_cast(double, (uint64_t)0x3feba00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fc2d1610c868000ull),__builtin_bit_cast(double, (uint64_t)0x3d039d6ccb81b4a1ull)},
379
+ {__builtin_bit_cast(double, (uint64_t)0x3feb800000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fc365fcb0159000ull),__builtin_bit_cast(double, (uint64_t)0x3cc62fa8234b7289ull)},
380
+ {__builtin_bit_cast(double, (uint64_t)0x3feb400000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fc4913d8333b000ull),__builtin_bit_cast(double, (uint64_t)0x3d25837954fdb678ull)},
381
+ {__builtin_bit_cast(double, (uint64_t)0x3feb200000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fc527e5e4a1b000ull),__builtin_bit_cast(double, (uint64_t)0x3d2633e8e5697dc7ull)},
382
+ {__builtin_bit_cast(double, (uint64_t)0x3feae00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fc6574ebe8c1000ull),__builtin_bit_cast(double, (uint64_t)0x3d19cf8b2c3c2e78ull)},
383
+ {__builtin_bit_cast(double, (uint64_t)0x3feac00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fc6f0128b757000ull),__builtin_bit_cast(double, (uint64_t)0xbd25118de59c21e1ull)},
384
+ {__builtin_bit_cast(double, (uint64_t)0x3feaa00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fc7898d85445000ull),__builtin_bit_cast(double, (uint64_t)0xbd1c661070914305ull)},
385
+ {__builtin_bit_cast(double, (uint64_t)0x3fea600000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fc8beafeb390000ull),__builtin_bit_cast(double, (uint64_t)0xbd073d54aae92cd1ull)},
386
+ {__builtin_bit_cast(double, (uint64_t)0x3fea400000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fc95a5adcf70000ull),__builtin_bit_cast(double, (uint64_t)0x3d07f22858a0ff6full)},
387
+ {__builtin_bit_cast(double, (uint64_t)0x3fea000000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fca93ed3c8ae000ull),__builtin_bit_cast(double, (uint64_t)0xbd28724350562169ull)},
388
+ {__builtin_bit_cast(double, (uint64_t)0x3fe9e00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fcb31d8575bd000ull),__builtin_bit_cast(double, (uint64_t)0xbd0c358d4eace1aaull)},
389
+ {__builtin_bit_cast(double, (uint64_t)0x3fe9c00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fcbd087383be000ull),__builtin_bit_cast(double, (uint64_t)0xbd2d4bc4595412b6ull)},
390
+ {__builtin_bit_cast(double, (uint64_t)0x3fe9a00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fcc6ffbc6f01000ull),__builtin_bit_cast(double, (uint64_t)0xbcf1ec72c5962bd2ull)},
391
+ {__builtin_bit_cast(double, (uint64_t)0x3fe9600000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fcdb13db0d49000ull),__builtin_bit_cast(double, (uint64_t)0xbd2aff2af715b035ull)},
392
+ {__builtin_bit_cast(double, (uint64_t)0x3fe9400000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fce530effe71000ull),__builtin_bit_cast(double, (uint64_t)0x3cc212276041f430ull)},
393
+ {__builtin_bit_cast(double, (uint64_t)0x3fe9200000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fcef5ade4dd0000ull),__builtin_bit_cast(double, (uint64_t)0xbcca211565bb8e11ull)},
394
+ {__builtin_bit_cast(double, (uint64_t)0x3fe9000000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fcf991c6cb3b000ull),__builtin_bit_cast(double, (uint64_t)0x3d1bcbecca0cdf30ull)},
395
+ {__builtin_bit_cast(double, (uint64_t)0x3fe8c00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd07138604d5800ull),__builtin_bit_cast(double, (uint64_t)0x3cf89cdb16ed4e91ull)},
396
+ {__builtin_bit_cast(double, (uint64_t)0x3fe8a00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd0c42d67616000ull),__builtin_bit_cast(double, (uint64_t)0x3d27188b163ceae9ull)},
397
+ {__builtin_bit_cast(double, (uint64_t)0x3fe8800000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd1178e8227e800ull),__builtin_bit_cast(double, (uint64_t)0xbd2c210e63a5f01cull)},
398
+ {__builtin_bit_cast(double, (uint64_t)0x3fe8600000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd16b5ccbacf800ull),__builtin_bit_cast(double, (uint64_t)0x3d2b9acdf7a51681ull)},
399
+ {__builtin_bit_cast(double, (uint64_t)0x3fe8400000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd1bf99635a6800ull),__builtin_bit_cast(double, (uint64_t)0x3d2ca6ed5147bdb7ull)},
400
+ {__builtin_bit_cast(double, (uint64_t)0x3fe8200000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd214456d0eb800ull),__builtin_bit_cast(double, (uint64_t)0x3d0a87deba46baeaull)},
401
+ {__builtin_bit_cast(double, (uint64_t)0x3fe7e00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd2bef07cdc9000ull),__builtin_bit_cast(double, (uint64_t)0x3d2a9cfa4a5004f4ull)},
402
+ {__builtin_bit_cast(double, (uint64_t)0x3fe7c00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd314f1e1d36000ull),__builtin_bit_cast(double, (uint64_t)0xbd28e27ad3213cb8ull)},
403
+ {__builtin_bit_cast(double, (uint64_t)0x3fe7a00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd36b6776be1000ull),__builtin_bit_cast(double, (uint64_t)0x3d116ecdb0f177c8ull)},
404
+ {__builtin_bit_cast(double, (uint64_t)0x3fe7800000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd3c25277333000ull),__builtin_bit_cast(double, (uint64_t)0x3d183b54b606bd5cull)},
405
+ {__builtin_bit_cast(double, (uint64_t)0x3fe7600000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd419b423d5e800ull),__builtin_bit_cast(double, (uint64_t)0x3d08e436ec90e09dull)},
406
+ {__builtin_bit_cast(double, (uint64_t)0x3fe7400000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd4718dc271c800ull),__builtin_bit_cast(double, (uint64_t)0xbd2f27ce0967d675ull)},
407
+ {__builtin_bit_cast(double, (uint64_t)0x3fe7200000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd4c9e09e173000ull),__builtin_bit_cast(double, (uint64_t)0xbd2e20891b0ad8a4ull)},
408
+ {__builtin_bit_cast(double, (uint64_t)0x3fe7000000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd522ae0738a000ull),__builtin_bit_cast(double, (uint64_t)0x3d2ebe708164c759ull)},
409
+ {__builtin_bit_cast(double, (uint64_t)0x3fe6e00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd57bf753c8d000ull),__builtin_bit_cast(double, (uint64_t)0x3d1fadedee5d40efull)},
410
+ {__builtin_bit_cast(double, (uint64_t)0x3fe6c00000000000ull),__builtin_bit_cast(double, (uint64_t)0x3fd5d5bddf596000ull),__builtin_bit_cast(double, (uint64_t)0xbd0a0b2a08a465dcull)}
411
+ };
412
+ __device__ constexpr double LOG_POLY[5] = {
413
+ __builtin_bit_cast(double, (uint64_t)0xbfe0000000000001ull),
414
+ __builtin_bit_cast(double, (uint64_t)0x3fd555555551305bull),
415
+ __builtin_bit_cast(double, (uint64_t)0xbfcfffffffeb4590ull),
416
+ __builtin_bit_cast(double, (uint64_t)0x3fc999b324f10111ull),
417
+ __builtin_bit_cast(double, (uint64_t)0xbfc55575e506c89full)
418
+ };
419
+ __device__ constexpr double LOG_NEAR_POLY[11] = {
420
+ __builtin_bit_cast(double, (uint64_t)0xbfe0000000000000ull),
421
+ __builtin_bit_cast(double, (uint64_t)0x3fd5555555555577ull),
422
+ __builtin_bit_cast(double, (uint64_t)0xbfcffffffffffdcbull),
423
+ __builtin_bit_cast(double, (uint64_t)0x3fc999999995dd0cull),
424
+ __builtin_bit_cast(double, (uint64_t)0xbfc55555556745a7ull),
425
+ __builtin_bit_cast(double, (uint64_t)0x3fc24924a344de30ull),
426
+ __builtin_bit_cast(double, (uint64_t)0xbfbfffffa4423d65ull),
427
+ __builtin_bit_cast(double, (uint64_t)0x3fbc7184282ad6caull),
428
+ __builtin_bit_cast(double, (uint64_t)0xbfb999eb43b068ffull),
429
+ __builtin_bit_cast(double, (uint64_t)0x3fb78182f7afd085ull),
430
+ __builtin_bit_cast(double, (uint64_t)0xbfb5521375d145cdull)
431
+ };
432
+ __device__ constexpr double LOG_TAB[128][2] = {
433
+ {__builtin_bit_cast(double, (uint64_t)0x3ff734f0c3e0de9full),__builtin_bit_cast(double, (uint64_t)0xbfd7cc7f79e69000ull)},
434
+ {__builtin_bit_cast(double, (uint64_t)0x3ff713786a2ce91full),__builtin_bit_cast(double, (uint64_t)0xbfd76feec20d0000ull)},
435
+ {__builtin_bit_cast(double, (uint64_t)0x3ff6f26008fab5a0ull),__builtin_bit_cast(double, (uint64_t)0xbfd713e31351e000ull)},
436
+ {__builtin_bit_cast(double, (uint64_t)0x3ff6d1a61f138c7dull),__builtin_bit_cast(double, (uint64_t)0xbfd6b85b38287800ull)},
437
+ {__builtin_bit_cast(double, (uint64_t)0x3ff6b1490bc5b4d1ull),__builtin_bit_cast(double, (uint64_t)0xbfd65d5590807800ull)},
438
+ {__builtin_bit_cast(double, (uint64_t)0x3ff69147332f0cbaull),__builtin_bit_cast(double, (uint64_t)0xbfd602d076180000ull)},
439
+ {__builtin_bit_cast(double, (uint64_t)0x3ff6719f18224223ull),__builtin_bit_cast(double, (uint64_t)0xbfd5a8ca86909000ull)},
440
+ {__builtin_bit_cast(double, (uint64_t)0x3ff6524f99a51ed9ull),__builtin_bit_cast(double, (uint64_t)0xbfd54f4356035000ull)},
441
+ {__builtin_bit_cast(double, (uint64_t)0x3ff63356aa8f24c4ull),__builtin_bit_cast(double, (uint64_t)0xbfd4f637c36b4000ull)},
442
+ {__builtin_bit_cast(double, (uint64_t)0x3ff614b36b9ddc14ull),__builtin_bit_cast(double, (uint64_t)0xbfd49da7fda85000ull)},
443
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5f66452c65c4cull),__builtin_bit_cast(double, (uint64_t)0xbfd445923989a800ull)},
444
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5d867b5912c4full),__builtin_bit_cast(double, (uint64_t)0xbfd3edf439b0b800ull)},
445
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5babccb5b90deull),__builtin_bit_cast(double, (uint64_t)0xbfd396ce448f7000ull)},
446
+ {__builtin_bit_cast(double, (uint64_t)0x3ff59d61f2d91a78ull),__builtin_bit_cast(double, (uint64_t)0xbfd3401e17bda000ull)},
447
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5805612465687ull),__builtin_bit_cast(double, (uint64_t)0xbfd2e9e2ef468000ull)},
448
+ {__builtin_bit_cast(double, (uint64_t)0x3ff56397cee76bd3ull),__builtin_bit_cast(double, (uint64_t)0xbfd2941b3830e000ull)},
449
+ {__builtin_bit_cast(double, (uint64_t)0x3ff54725e2a77f93ull),__builtin_bit_cast(double, (uint64_t)0xbfd23ec58cda8800ull)},
450
+ {__builtin_bit_cast(double, (uint64_t)0x3ff52aff42064583ull),__builtin_bit_cast(double, (uint64_t)0xbfd1e9e129279000ull)},
451
+ {__builtin_bit_cast(double, (uint64_t)0x3ff50f22dbb2bddfull),__builtin_bit_cast(double, (uint64_t)0xbfd1956d2b48f800ull)},
452
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4f38f4734ded7ull),__builtin_bit_cast(double, (uint64_t)0xbfd141679ab9f800ull)},
453
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4d843cfde2840ull),__builtin_bit_cast(double, (uint64_t)0xbfd0edd094ef9800ull)},
454
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4bd3ec078a3c8ull),__builtin_bit_cast(double, (uint64_t)0xbfd09aa518db1000ull)},
455
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4a27fc3e0258aull),__builtin_bit_cast(double, (uint64_t)0xbfd047e65263b800ull)},
456
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4880524d48434ull),__builtin_bit_cast(double, (uint64_t)0xbfcfeb224586f000ull)},
457
+ {__builtin_bit_cast(double, (uint64_t)0x3ff46dce1b192d0bull),__builtin_bit_cast(double, (uint64_t)0xbfcf474a7517b000ull)},
458
+ {__builtin_bit_cast(double, (uint64_t)0x3ff453d9d3391854ull),__builtin_bit_cast(double, (uint64_t)0xbfcea4443d103000ull)},
459
+ {__builtin_bit_cast(double, (uint64_t)0x3ff43a2744b4845aull),__builtin_bit_cast(double, (uint64_t)0xbfce020d44e9b000ull)},
460
+ {__builtin_bit_cast(double, (uint64_t)0x3ff420b54115f8fbull),__builtin_bit_cast(double, (uint64_t)0xbfcd60a22977f000ull)},
461
+ {__builtin_bit_cast(double, (uint64_t)0x3ff40782da3ef4b1ull),__builtin_bit_cast(double, (uint64_t)0xbfccc00104959000ull)},
462
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3ee8f5d57fe8full),__builtin_bit_cast(double, (uint64_t)0xbfcc202956891000ull)},
463
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3d5d9a00b4ce9ull),__builtin_bit_cast(double, (uint64_t)0xbfcb81178d811000ull)},
464
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3bd60c010c12bull),__builtin_bit_cast(double, (uint64_t)0xbfcae2c9ccd3d000ull)},
465
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3a5242b75dab8ull),__builtin_bit_cast(double, (uint64_t)0xbfca45402e129000ull)},
466
+ {__builtin_bit_cast(double, (uint64_t)0x3ff38d22cd9fd002ull),__builtin_bit_cast(double, (uint64_t)0xbfc9a877681df000ull)},
467
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3755bc5847a1cull),__builtin_bit_cast(double, (uint64_t)0xbfc90c6d69483000ull)},
468
+ {__builtin_bit_cast(double, (uint64_t)0x3ff35dce49ad36e2ull),__builtin_bit_cast(double, (uint64_t)0xbfc87120a645c000ull)},
469
+ {__builtin_bit_cast(double, (uint64_t)0x3ff34679984dd440ull),__builtin_bit_cast(double, (uint64_t)0xbfc7d68fb4143000ull)},
470
+ {__builtin_bit_cast(double, (uint64_t)0x3ff32f5cceffcb24ull),__builtin_bit_cast(double, (uint64_t)0xbfc73cb83c627000ull)},
471
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3187775a10d49ull),__builtin_bit_cast(double, (uint64_t)0xbfc6a39a9b376000ull)},
472
+ {__builtin_bit_cast(double, (uint64_t)0x3ff301c8373e3990ull),__builtin_bit_cast(double, (uint64_t)0xbfc60b3154b7a000ull)},
473
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2eb4ebb95f841ull),__builtin_bit_cast(double, (uint64_t)0xbfc5737d76243000ull)},
474
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2d50a0219a9d1ull),__builtin_bit_cast(double, (uint64_t)0xbfc4dc7b8fc23000ull)},
475
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2bef9a8b7fd2aull),__builtin_bit_cast(double, (uint64_t)0xbfc4462c51d20000ull)},
476
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2a91c7a0c1babull),__builtin_bit_cast(double, (uint64_t)0xbfc3b08abc830000ull)},
477
+ {__builtin_bit_cast(double, (uint64_t)0x3ff293726014b530ull),__builtin_bit_cast(double, (uint64_t)0xbfc31b996b490000ull)},
478
+ {__builtin_bit_cast(double, (uint64_t)0x3ff27dfa5757a1f5ull),__builtin_bit_cast(double, (uint64_t)0xbfc2875490a44000ull)},
479
+ {__builtin_bit_cast(double, (uint64_t)0x3ff268b39b1d3bbfull),__builtin_bit_cast(double, (uint64_t)0xbfc1f3b9f879a000ull)},
480
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2539d838ff5bdull),__builtin_bit_cast(double, (uint64_t)0xbfc160c8252ca000ull)},
481
+ {__builtin_bit_cast(double, (uint64_t)0x3ff23eb7aac9083bull),__builtin_bit_cast(double, (uint64_t)0xbfc0ce7f57f72000ull)},
482
+ {__builtin_bit_cast(double, (uint64_t)0x3ff22a012ba940b6ull),__builtin_bit_cast(double, (uint64_t)0xbfc03cdc49fea000ull)},
483
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2157996cc4132ull),__builtin_bit_cast(double, (uint64_t)0xbfbf57bdbc4b8000ull)},
484
+ {__builtin_bit_cast(double, (uint64_t)0x3ff201201dd2fc9bull),__builtin_bit_cast(double, (uint64_t)0xbfbe370896404000ull)},
485
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1ecf4494d480bull),__builtin_bit_cast(double, (uint64_t)0xbfbd17983ef94000ull)},
486
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1d8f5528f6569ull),__builtin_bit_cast(double, (uint64_t)0xbfbbf9674ed8a000ull)},
487
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1c52311577e7cull),__builtin_bit_cast(double, (uint64_t)0xbfbadc79202f6000ull)},
488
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1b17c74cb26e9ull),__builtin_bit_cast(double, (uint64_t)0xbfb9c0c3e7288000ull)},
489
+ {__builtin_bit_cast(double, (uint64_t)0x3ff19e010c2c1ab6ull),__builtin_bit_cast(double, (uint64_t)0xbfb8a646b372c000ull)},
490
+ {__builtin_bit_cast(double, (uint64_t)0x3ff18ab07bb670bdull),__builtin_bit_cast(double, (uint64_t)0xbfb78d01b3ac0000ull)},
491
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1778a25efbcb6ull),__builtin_bit_cast(double, (uint64_t)0xbfb674f145380000ull)},
492
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1648d354c31daull),__builtin_bit_cast(double, (uint64_t)0xbfb55e0e6d878000ull)},
493
+ {__builtin_bit_cast(double, (uint64_t)0x3ff151b990275fddull),__builtin_bit_cast(double, (uint64_t)0xbfb4485cdea1e000ull)},
494
+ {__builtin_bit_cast(double, (uint64_t)0x3ff13f0ea432d24cull),__builtin_bit_cast(double, (uint64_t)0xbfb333d94d6aa000ull)},
495
+ {__builtin_bit_cast(double, (uint64_t)0x3ff12c8b7210f9daull),__builtin_bit_cast(double, (uint64_t)0xbfb22079f8c56000ull)},
496
+ {__builtin_bit_cast(double, (uint64_t)0x3ff11a3028ecb531ull),__builtin_bit_cast(double, (uint64_t)0xbfb10e4698622000ull)},
497
+ {__builtin_bit_cast(double, (uint64_t)0x3ff107fbda8434afull),__builtin_bit_cast(double, (uint64_t)0xbfaffa6c6ad20000ull)},
498
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0f5ee0f4e6bb3ull),__builtin_bit_cast(double, (uint64_t)0xbfadda8d4a774000ull)},
499
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0e4065d2a9fceull),__builtin_bit_cast(double, (uint64_t)0xbfabbcece4850000ull)},
500
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0d244632ca521ull),__builtin_bit_cast(double, (uint64_t)0xbfa9a1894012c000ull)},
501
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0c0a77ce2981aull),__builtin_bit_cast(double, (uint64_t)0xbfa788583302c000ull)},
502
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0af2f83c636d1ull),__builtin_bit_cast(double, (uint64_t)0xbfa5715e67d68000ull)},
503
+ {__builtin_bit_cast(double, (uint64_t)0x3ff09ddb98a01339ull),__builtin_bit_cast(double, (uint64_t)0xbfa35c8a49658000ull)},
504
+ {__builtin_bit_cast(double, (uint64_t)0x3ff08cabaf52e7dfull),__builtin_bit_cast(double, (uint64_t)0xbfa149e364154000ull)},
505
+ {__builtin_bit_cast(double, (uint64_t)0x3ff07b9f2f4e28fbull),__builtin_bit_cast(double, (uint64_t)0xbf9e72c082eb8000ull)},
506
+ {__builtin_bit_cast(double, (uint64_t)0x3ff06ab58c358f19ull),__builtin_bit_cast(double, (uint64_t)0xbf9a55f152528000ull)},
507
+ {__builtin_bit_cast(double, (uint64_t)0x3ff059eea5ecf92cull),__builtin_bit_cast(double, (uint64_t)0xbf963d62cf818000ull)},
508
+ {__builtin_bit_cast(double, (uint64_t)0x3ff04949cdd12c90ull),__builtin_bit_cast(double, (uint64_t)0xbf9228fb8caa0000ull)},
509
+ {__builtin_bit_cast(double, (uint64_t)0x3ff038c6c6f0ada9ull),__builtin_bit_cast(double, (uint64_t)0xbf8c317b20f90000ull)},
510
+ {__builtin_bit_cast(double, (uint64_t)0x3ff02865137932a9ull),__builtin_bit_cast(double, (uint64_t)0xbf8419355daa0000ull)},
511
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0182427ea7348ull),__builtin_bit_cast(double, (uint64_t)0xbf781203c2ec0000ull)},
512
+ {__builtin_bit_cast(double, (uint64_t)0x3ff008040614b195ull),__builtin_bit_cast(double, (uint64_t)0xbf60040979240000ull)},
513
+ {__builtin_bit_cast(double, (uint64_t)0x3fefe01ff726fa1aull),__builtin_bit_cast(double, (uint64_t)0x3f6feff384900000ull)},
514
+ {__builtin_bit_cast(double, (uint64_t)0x3fefa11cc261ea74ull),__builtin_bit_cast(double, (uint64_t)0x3f87dc41353d0000ull)},
515
+ {__builtin_bit_cast(double, (uint64_t)0x3fef6310b081992eull),__builtin_bit_cast(double, (uint64_t)0x3f93cea3c4c28000ull)},
516
+ {__builtin_bit_cast(double, (uint64_t)0x3fef25f63ceeadcdull),__builtin_bit_cast(double, (uint64_t)0x3f9b9fc114890000ull)},
517
+ {__builtin_bit_cast(double, (uint64_t)0x3feee9c8039113e7ull),__builtin_bit_cast(double, (uint64_t)0x3fa1b0d8ce110000ull)},
518
+ {__builtin_bit_cast(double, (uint64_t)0x3feeae8078cbb1abull),__builtin_bit_cast(double, (uint64_t)0x3fa58a5bd001c000ull)},
519
+ {__builtin_bit_cast(double, (uint64_t)0x3fee741aa29d0c9bull),__builtin_bit_cast(double, (uint64_t)0x3fa95c8340d88000ull)},
520
+ {__builtin_bit_cast(double, (uint64_t)0x3fee3a91830a99b5ull),__builtin_bit_cast(double, (uint64_t)0x3fad276aef578000ull)},
521
+ {__builtin_bit_cast(double, (uint64_t)0x3fee01e009609a56ull),__builtin_bit_cast(double, (uint64_t)0x3fb07598e598c000ull)},
522
+ {__builtin_bit_cast(double, (uint64_t)0x3fedca01e577bb98ull),__builtin_bit_cast(double, (uint64_t)0x3fb253f5e30d2000ull)},
523
+ {__builtin_bit_cast(double, (uint64_t)0x3fed92f20b7c9103ull),__builtin_bit_cast(double, (uint64_t)0x3fb42edd8b380000ull)},
524
+ {__builtin_bit_cast(double, (uint64_t)0x3fed5cac66fb5cceull),__builtin_bit_cast(double, (uint64_t)0x3fb606598757c000ull)},
525
+ {__builtin_bit_cast(double, (uint64_t)0x3fed272caa5ede9dull),__builtin_bit_cast(double, (uint64_t)0x3fb7da76356a0000ull)},
526
+ {__builtin_bit_cast(double, (uint64_t)0x3fecf26e3e6b2ccdull),__builtin_bit_cast(double, (uint64_t)0x3fb9ab434e1c6000ull)},
527
+ {__builtin_bit_cast(double, (uint64_t)0x3fecbe6da2a77902ull),__builtin_bit_cast(double, (uint64_t)0x3fbb78c7bb0d6000ull)},
528
+ {__builtin_bit_cast(double, (uint64_t)0x3fec8b266d37086dull),__builtin_bit_cast(double, (uint64_t)0x3fbd431332e72000ull)},
529
+ {__builtin_bit_cast(double, (uint64_t)0x3fec5894bd5d5804ull),__builtin_bit_cast(double, (uint64_t)0x3fbf0a3171de6000ull)},
530
+ {__builtin_bit_cast(double, (uint64_t)0x3fec26b533bb9f8cull),__builtin_bit_cast(double, (uint64_t)0x3fc067152b914000ull)},
531
+ {__builtin_bit_cast(double, (uint64_t)0x3febf583eeece73full),__builtin_bit_cast(double, (uint64_t)0x3fc147858292b000ull)},
532
+ {__builtin_bit_cast(double, (uint64_t)0x3febc4fd75db96c1ull),__builtin_bit_cast(double, (uint64_t)0x3fc2266ecdca3000ull)},
533
+ {__builtin_bit_cast(double, (uint64_t)0x3feb951e0c864a28ull),__builtin_bit_cast(double, (uint64_t)0x3fc303d7a6c55000ull)},
534
+ {__builtin_bit_cast(double, (uint64_t)0x3feb65e2c5ef3e2cull),__builtin_bit_cast(double, (uint64_t)0x3fc3dfc33c331000ull)},
535
+ {__builtin_bit_cast(double, (uint64_t)0x3feb374867c9888bull),__builtin_bit_cast(double, (uint64_t)0x3fc4ba366b7a8000ull)},
536
+ {__builtin_bit_cast(double, (uint64_t)0x3feb094b211d304aull),__builtin_bit_cast(double, (uint64_t)0x3fc5933928d1f000ull)},
537
+ {__builtin_bit_cast(double, (uint64_t)0x3feadbe885f2ef7eull),__builtin_bit_cast(double, (uint64_t)0x3fc66acd2418f000ull)},
538
+ {__builtin_bit_cast(double, (uint64_t)0x3feaaf1d31603da2ull),__builtin_bit_cast(double, (uint64_t)0x3fc740f8ec669000ull)},
539
+ {__builtin_bit_cast(double, (uint64_t)0x3fea82e63fd358a7ull),__builtin_bit_cast(double, (uint64_t)0x3fc815c0f51af000ull)},
540
+ {__builtin_bit_cast(double, (uint64_t)0x3fea5740ef09738bull),__builtin_bit_cast(double, (uint64_t)0x3fc8e92954f68000ull)},
541
+ {__builtin_bit_cast(double, (uint64_t)0x3fea2c2a90ab4b27ull),__builtin_bit_cast(double, (uint64_t)0x3fc9bb3602f84000ull)},
542
+ {__builtin_bit_cast(double, (uint64_t)0x3fea01a01393f2d1ull),__builtin_bit_cast(double, (uint64_t)0x3fca8bed1c2c0000ull)},
543
+ {__builtin_bit_cast(double, (uint64_t)0x3fe9d79f24db3c1bull),__builtin_bit_cast(double, (uint64_t)0x3fcb5b515c01d000ull)},
544
+ {__builtin_bit_cast(double, (uint64_t)0x3fe9ae2505c7b190ull),__builtin_bit_cast(double, (uint64_t)0x3fcc2967ccbcc000ull)},
545
+ {__builtin_bit_cast(double, (uint64_t)0x3fe9852ef297ce2full),__builtin_bit_cast(double, (uint64_t)0x3fccf635d5486000ull)},
546
+ {__builtin_bit_cast(double, (uint64_t)0x3fe95cbaeea44b75ull),__builtin_bit_cast(double, (uint64_t)0x3fcdc1bd3446c000ull)},
547
+ {__builtin_bit_cast(double, (uint64_t)0x3fe934c69de74838ull),__builtin_bit_cast(double, (uint64_t)0x3fce8c01b8cfe000ull)},
548
+ {__builtin_bit_cast(double, (uint64_t)0x3fe90d4f2f6752e6ull),__builtin_bit_cast(double, (uint64_t)0x3fcf5509c0179000ull)},
549
+ {__builtin_bit_cast(double, (uint64_t)0x3fe8e6528effd79dull),__builtin_bit_cast(double, (uint64_t)0x3fd00e6c121fb800ull)},
550
+ {__builtin_bit_cast(double, (uint64_t)0x3fe8bfce9fcc007cull),__builtin_bit_cast(double, (uint64_t)0x3fd071b80e93d000ull)},
551
+ {__builtin_bit_cast(double, (uint64_t)0x3fe899c0dabec30eull),__builtin_bit_cast(double, (uint64_t)0x3fd0d46b9e867000ull)},
552
+ {__builtin_bit_cast(double, (uint64_t)0x3fe87427aa2317fbull),__builtin_bit_cast(double, (uint64_t)0x3fd13687334bd000ull)},
553
+ {__builtin_bit_cast(double, (uint64_t)0x3fe84f00acb39a08ull),__builtin_bit_cast(double, (uint64_t)0x3fd1980d67234800ull)},
554
+ {__builtin_bit_cast(double, (uint64_t)0x3fe82a49e8653e55ull),__builtin_bit_cast(double, (uint64_t)0x3fd1f8ffe0cc8000ull)},
555
+ {__builtin_bit_cast(double, (uint64_t)0x3fe8060195f40260ull),__builtin_bit_cast(double, (uint64_t)0x3fd2595fd7636800ull)},
556
+ {__builtin_bit_cast(double, (uint64_t)0x3fe7e22563e0a329ull),__builtin_bit_cast(double, (uint64_t)0x3fd2b9300914a800ull)},
557
+ {__builtin_bit_cast(double, (uint64_t)0x3fe7beb377dcb5adull),__builtin_bit_cast(double, (uint64_t)0x3fd3187210436000ull)},
558
+ {__builtin_bit_cast(double, (uint64_t)0x3fe79baa679725c2ull),__builtin_bit_cast(double, (uint64_t)0x3fd377266dec1800ull)},
559
+ {__builtin_bit_cast(double, (uint64_t)0x3fe77907f2170657ull),__builtin_bit_cast(double, (uint64_t)0x3fd3d54ffbaf3000ull)},
560
+ {__builtin_bit_cast(double, (uint64_t)0x3fe756cadbd6130cull),__builtin_bit_cast(double, (uint64_t)0x3fd432eee32fe000ull)}
561
+ };
562
+ __device__ constexpr double LOG_TAB2[128][2] = {
563
+ {__builtin_bit_cast(double, (uint64_t)0x3fe61000014fb66bull),__builtin_bit_cast(double, (uint64_t)0x3c7e026c91425b3cull)},
564
+ {__builtin_bit_cast(double, (uint64_t)0x3fe63000034db495ull),__builtin_bit_cast(double, (uint64_t)0x3c8dbfea48005d41ull)},
565
+ {__builtin_bit_cast(double, (uint64_t)0x3fe650000d94d478ull),__builtin_bit_cast(double, (uint64_t)0x3c8e7fa786d6a5b7ull)},
566
+ {__builtin_bit_cast(double, (uint64_t)0x3fe67000074e6fadull),__builtin_bit_cast(double, (uint64_t)0x3c61fcea6b54254cull)},
567
+ {__builtin_bit_cast(double, (uint64_t)0x3fe68ffffedf0faeull),__builtin_bit_cast(double, (uint64_t)0xbc7c7e274c590efdull)},
568
+ {__builtin_bit_cast(double, (uint64_t)0x3fe6b0000763c5bcull),__builtin_bit_cast(double, (uint64_t)0xbc8ac16848dcda01ull)},
569
+ {__builtin_bit_cast(double, (uint64_t)0x3fe6d0001e5cc1f6ull),__builtin_bit_cast(double, (uint64_t)0x3c833f1c9d499311ull)},
570
+ {__builtin_bit_cast(double, (uint64_t)0x3fe6efffeb05f63eull),__builtin_bit_cast(double, (uint64_t)0xbc7e80041ae22d53ull)},
571
+ {__builtin_bit_cast(double, (uint64_t)0x3fe710000e869780ull),__builtin_bit_cast(double, (uint64_t)0x3c7bff6671097952ull)},
572
+ {__builtin_bit_cast(double, (uint64_t)0x3fe72ffffc67e912ull),__builtin_bit_cast(double, (uint64_t)0x3c8c00e226bd8724ull)},
573
+ {__builtin_bit_cast(double, (uint64_t)0x3fe74fffdf81116aull),__builtin_bit_cast(double, (uint64_t)0xbc6e02916ef101d2ull)},
574
+ {__builtin_bit_cast(double, (uint64_t)0x3fe770000f679c90ull),__builtin_bit_cast(double, (uint64_t)0xbc67fc71cd549c74ull)},
575
+ {__builtin_bit_cast(double, (uint64_t)0x3fe78ffffa7ec835ull),__builtin_bit_cast(double, (uint64_t)0x3c81bec19ef50483ull)},
576
+ {__builtin_bit_cast(double, (uint64_t)0x3fe7affffe20c2e6ull),__builtin_bit_cast(double, (uint64_t)0xbc707e1729cc6465ull)},
577
+ {__builtin_bit_cast(double, (uint64_t)0x3fe7cfffed3fc900ull),__builtin_bit_cast(double, (uint64_t)0xbc808072087b8b1cull)},
578
+ {__builtin_bit_cast(double, (uint64_t)0x3fe7efffe9261a76ull),__builtin_bit_cast(double, (uint64_t)0x3c8dc0286d9df9aeull)},
579
+ {__builtin_bit_cast(double, (uint64_t)0x3fe81000049ca3e8ull),__builtin_bit_cast(double, (uint64_t)0x3c897fd251e54c33ull)},
580
+ {__builtin_bit_cast(double, (uint64_t)0x3fe8300017932c8full),__builtin_bit_cast(double, (uint64_t)0xbc8afee9b630f381ull)},
581
+ {__builtin_bit_cast(double, (uint64_t)0x3fe850000633739cull),__builtin_bit_cast(double, (uint64_t)0x3c89bfbf6b6535bcull)},
582
+ {__builtin_bit_cast(double, (uint64_t)0x3fe87000204289c6ull),__builtin_bit_cast(double, (uint64_t)0xbc8bbf65f3117b75ull)},
583
+ {__builtin_bit_cast(double, (uint64_t)0x3fe88fffebf57904ull),__builtin_bit_cast(double, (uint64_t)0xbc89006ea23dcb57ull)},
584
+ {__builtin_bit_cast(double, (uint64_t)0x3fe8b00022bc04dfull),__builtin_bit_cast(double, (uint64_t)0xbc7d00df38e04b0aull)},
585
+ {__builtin_bit_cast(double, (uint64_t)0x3fe8cfffe50c1b8aull),__builtin_bit_cast(double, (uint64_t)0xbc88007146ff9f05ull)},
586
+ {__builtin_bit_cast(double, (uint64_t)0x3fe8effffc918e43ull),__builtin_bit_cast(double, (uint64_t)0x3c83817bd07a7038ull)},
587
+ {__builtin_bit_cast(double, (uint64_t)0x3fe910001efa5fc7ull),__builtin_bit_cast(double, (uint64_t)0x3c893e9176dfb403ull)},
588
+ {__builtin_bit_cast(double, (uint64_t)0x3fe9300013467bb9ull),__builtin_bit_cast(double, (uint64_t)0x3c7f804e4b980276ull)},
589
+ {__builtin_bit_cast(double, (uint64_t)0x3fe94fffe6ee076full),__builtin_bit_cast(double, (uint64_t)0xbc8f7ef0d9ff622eull)},
590
+ {__builtin_bit_cast(double, (uint64_t)0x3fe96fffde3c12d1ull),__builtin_bit_cast(double, (uint64_t)0xbc7082aa962638baull)},
591
+ {__builtin_bit_cast(double, (uint64_t)0x3fe98ffff4458a0dull),__builtin_bit_cast(double, (uint64_t)0xbc87801b9164a8efull)},
592
+ {__builtin_bit_cast(double, (uint64_t)0x3fe9afffdd982e3eull),__builtin_bit_cast(double, (uint64_t)0xbc8740e08a5a9337ull)},
593
+ {__builtin_bit_cast(double, (uint64_t)0x3fe9cfffed49fb66ull),__builtin_bit_cast(double, (uint64_t)0x3c3fce08c19be000ull)},
594
+ {__builtin_bit_cast(double, (uint64_t)0x3fe9f00020f19c51ull),__builtin_bit_cast(double, (uint64_t)0xbc8a3faa27885b0aull)},
595
+ {__builtin_bit_cast(double, (uint64_t)0x3fea10001145b006ull),__builtin_bit_cast(double, (uint64_t)0x3c74ff489958da56ull)},
596
+ {__builtin_bit_cast(double, (uint64_t)0x3fea300007bbf6faull),__builtin_bit_cast(double, (uint64_t)0x3c8cbeab8a2b6d18ull)},
597
+ {__builtin_bit_cast(double, (uint64_t)0x3fea500010971d79ull),__builtin_bit_cast(double, (uint64_t)0x3c88fecadd787930ull)},
598
+ {__builtin_bit_cast(double, (uint64_t)0x3fea70001df52e48ull),__builtin_bit_cast(double, (uint64_t)0xbc8f41763dd8abdbull)},
599
+ {__builtin_bit_cast(double, (uint64_t)0x3fea90001c593352ull),__builtin_bit_cast(double, (uint64_t)0xbc8ebf0284c27612ull)},
600
+ {__builtin_bit_cast(double, (uint64_t)0x3feab0002a4f3e4bull),__builtin_bit_cast(double, (uint64_t)0xbc69fd043cff3f5full)},
601
+ {__builtin_bit_cast(double, (uint64_t)0x3feacfffd7ae1ed1ull),__builtin_bit_cast(double, (uint64_t)0xbc823ee7129070b4ull)},
602
+ {__builtin_bit_cast(double, (uint64_t)0x3feaefffee510478ull),__builtin_bit_cast(double, (uint64_t)0x3c6a063ee00edea3ull)},
603
+ {__builtin_bit_cast(double, (uint64_t)0x3feb0fffdb650d5bull),__builtin_bit_cast(double, (uint64_t)0x3c5a06c8381f0ab9ull)},
604
+ {__builtin_bit_cast(double, (uint64_t)0x3feb2ffffeaaca57ull),__builtin_bit_cast(double, (uint64_t)0xbc79011e74233c1dull)},
605
+ {__builtin_bit_cast(double, (uint64_t)0x3feb4fffd995badcull),__builtin_bit_cast(double, (uint64_t)0xbc79ff1068862a9full)},
606
+ {__builtin_bit_cast(double, (uint64_t)0x3feb7000249e659cull),__builtin_bit_cast(double, (uint64_t)0x3c8aff45d0864f3eull)},
607
+ {__builtin_bit_cast(double, (uint64_t)0x3feb8ffff9871640ull),__builtin_bit_cast(double, (uint64_t)0x3c7cfe7796c2c3f9ull)},
608
+ {__builtin_bit_cast(double, (uint64_t)0x3febafffd204cb4full),__builtin_bit_cast(double, (uint64_t)0xbc63ff27eef22bc4ull)},
609
+ {__builtin_bit_cast(double, (uint64_t)0x3febcfffd2415c45ull),__builtin_bit_cast(double, (uint64_t)0xbc6cffb7ee3bea21ull)},
610
+ {__builtin_bit_cast(double, (uint64_t)0x3febeffff86309dfull),__builtin_bit_cast(double, (uint64_t)0xbc814103972e0b5cull)},
611
+ {__builtin_bit_cast(double, (uint64_t)0x3fec0fffe1b57653ull),__builtin_bit_cast(double, (uint64_t)0x3c8bc16494b76a19ull)},
612
+ {__builtin_bit_cast(double, (uint64_t)0x3fec2ffff1fa57e3ull),__builtin_bit_cast(double, (uint64_t)0xbc64feef8d30c6edull)},
613
+ {__builtin_bit_cast(double, (uint64_t)0x3fec4fffdcbfe424ull),__builtin_bit_cast(double, (uint64_t)0xbc843f68bcec4775ull)},
614
+ {__builtin_bit_cast(double, (uint64_t)0x3fec6fffed54b9f7ull),__builtin_bit_cast(double, (uint64_t)0x3c847ea3f053e0ecull)},
615
+ {__builtin_bit_cast(double, (uint64_t)0x3fec8fffeb998fd5ull),__builtin_bit_cast(double, (uint64_t)0x3c7383068df992f1ull)},
616
+ {__builtin_bit_cast(double, (uint64_t)0x3fecb0002125219aull),__builtin_bit_cast(double, (uint64_t)0xbc68fd8e64180e04ull)},
617
+ {__builtin_bit_cast(double, (uint64_t)0x3feccfffdd94469cull),__builtin_bit_cast(double, (uint64_t)0x3c8e7ebe1cc7ea72ull)},
618
+ {__builtin_bit_cast(double, (uint64_t)0x3fecefffeafdc476ull),__builtin_bit_cast(double, (uint64_t)0x3c8ebe39ad9f88feull)},
619
+ {__builtin_bit_cast(double, (uint64_t)0x3fed1000169af82bull),__builtin_bit_cast(double, (uint64_t)0x3c757d91a8b95a71ull)},
620
+ {__builtin_bit_cast(double, (uint64_t)0x3fed30000d0ff71dull),__builtin_bit_cast(double, (uint64_t)0x3c89c1906970c7daull)},
621
+ {__builtin_bit_cast(double, (uint64_t)0x3fed4fffea790fc4ull),__builtin_bit_cast(double, (uint64_t)0xbc580e37c558fe0cull)},
622
+ {__builtin_bit_cast(double, (uint64_t)0x3fed70002edc87e5ull),__builtin_bit_cast(double, (uint64_t)0xbc7f80d64dc10f44ull)},
623
+ {__builtin_bit_cast(double, (uint64_t)0x3fed900021dc82aaull),__builtin_bit_cast(double, (uint64_t)0xbc747c8f94fd5c5cull)},
624
+ {__builtin_bit_cast(double, (uint64_t)0x3fedafffd86b0283ull),__builtin_bit_cast(double, (uint64_t)0x3c8c7f1dc521617eull)},
625
+ {__builtin_bit_cast(double, (uint64_t)0x3fedd000296c4739ull),__builtin_bit_cast(double, (uint64_t)0x3c88019eb2ffb153ull)},
626
+ {__builtin_bit_cast(double, (uint64_t)0x3fedefffe54490f5ull),__builtin_bit_cast(double, (uint64_t)0x3c6e00d2c652cc89ull)},
627
+ {__builtin_bit_cast(double, (uint64_t)0x3fee0fffcdabf694ull),__builtin_bit_cast(double, (uint64_t)0xbc7f8340202d69d2ull)},
628
+ {__builtin_bit_cast(double, (uint64_t)0x3fee2fffdb52c8ddull),__builtin_bit_cast(double, (uint64_t)0x3c7b00c1ca1b0864ull)},
629
+ {__builtin_bit_cast(double, (uint64_t)0x3fee4ffff24216efull),__builtin_bit_cast(double, (uint64_t)0x3c72ffa8b094ab51ull)},
630
+ {__builtin_bit_cast(double, (uint64_t)0x3fee6fffe88a5e11ull),__builtin_bit_cast(double, (uint64_t)0xbc57f673b1efbe59ull)},
631
+ {__builtin_bit_cast(double, (uint64_t)0x3fee9000119eff0dull),__builtin_bit_cast(double, (uint64_t)0xbc84808d5e0bc801ull)},
632
+ {__builtin_bit_cast(double, (uint64_t)0x3feeafffdfa51744ull),__builtin_bit_cast(double, (uint64_t)0x3c780006d54320b5ull)},
633
+ {__builtin_bit_cast(double, (uint64_t)0x3feed0001a127fa1ull),__builtin_bit_cast(double, (uint64_t)0xbc5002f860565c92ull)},
634
+ {__builtin_bit_cast(double, (uint64_t)0x3feef00007babcc4ull),__builtin_bit_cast(double, (uint64_t)0xbc8540445d35e611ull)},
635
+ {__builtin_bit_cast(double, (uint64_t)0x3fef0ffff57a8d02ull),__builtin_bit_cast(double, (uint64_t)0xbc4ffb3139ef9105ull)},
636
+ {__builtin_bit_cast(double, (uint64_t)0x3fef30001ee58ac7ull),__builtin_bit_cast(double, (uint64_t)0x3c8a81acf2731155ull)},
637
+ {__builtin_bit_cast(double, (uint64_t)0x3fef4ffff5823494ull),__builtin_bit_cast(double, (uint64_t)0x3c8a3f41d4d7c743ull)},
638
+ {__builtin_bit_cast(double, (uint64_t)0x3fef6ffffca94c6bull),__builtin_bit_cast(double, (uint64_t)0xbc6202f41c987875ull)},
639
+ {__builtin_bit_cast(double, (uint64_t)0x3fef8fffe1f9c441ull),__builtin_bit_cast(double, (uint64_t)0x3c777dd1f477e74bull)},
640
+ {__builtin_bit_cast(double, (uint64_t)0x3fefafffd2e0e37eull),__builtin_bit_cast(double, (uint64_t)0xbc6f01199a7ca331ull)},
641
+ {__builtin_bit_cast(double, (uint64_t)0x3fefd0001c77e49eull),__builtin_bit_cast(double, (uint64_t)0x3c7181ee4bceacb1ull)},
642
+ {__builtin_bit_cast(double, (uint64_t)0x3fefeffff7e0c331ull),__builtin_bit_cast(double, (uint64_t)0xbc6e05370170875aull)},
643
+ {__builtin_bit_cast(double, (uint64_t)0x3ff00ffff465606eull),__builtin_bit_cast(double, (uint64_t)0xbc8a7ead491c0adaull)},
644
+ {__builtin_bit_cast(double, (uint64_t)0x3ff02ffff3867a58ull),__builtin_bit_cast(double, (uint64_t)0xbc977f69c3fcb2e0ull)},
645
+ {__builtin_bit_cast(double, (uint64_t)0x3ff04ffffdfc0d17ull),__builtin_bit_cast(double, (uint64_t)0x3c97bffe34cb945bull)},
646
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0700003cd4d82ull),__builtin_bit_cast(double, (uint64_t)0x3c820083c0e456cbull)},
647
+ {__builtin_bit_cast(double, (uint64_t)0x3ff08ffff9f2cbe8ull),__builtin_bit_cast(double, (uint64_t)0xbc6dffdfbe37751aull)},
648
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0b000010cda65ull),__builtin_bit_cast(double, (uint64_t)0xbc913f7faee626ebull)},
649
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0d00001a4d338ull),__builtin_bit_cast(double, (uint64_t)0x3c807dfa79489ff7ull)},
650
+ {__builtin_bit_cast(double, (uint64_t)0x3ff0effffadafdfdull),__builtin_bit_cast(double, (uint64_t)0xbc77040570d66bc0ull)},
651
+ {__builtin_bit_cast(double, (uint64_t)0x3ff110000bbafd96ull),__builtin_bit_cast(double, (uint64_t)0x3c8e80d4846d0b62ull)},
652
+ {__builtin_bit_cast(double, (uint64_t)0x3ff12ffffae5f45dull),__builtin_bit_cast(double, (uint64_t)0x3c9dbffa64fd36efull)},
653
+ {__builtin_bit_cast(double, (uint64_t)0x3ff150000dd59ad9ull),__builtin_bit_cast(double, (uint64_t)0x3c9a0077701250aeull)},
654
+ {__builtin_bit_cast(double, (uint64_t)0x3ff170000f21559aull),__builtin_bit_cast(double, (uint64_t)0x3c8dfdf9e2e3deeeull)},
655
+ {__builtin_bit_cast(double, (uint64_t)0x3ff18ffffc275426ull),__builtin_bit_cast(double, (uint64_t)0x3c910030dc3b7273ull)},
656
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1b000123d3c59ull),__builtin_bit_cast(double, (uint64_t)0x3c997f7980030188ull)},
657
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1cffff8299eb7ull),__builtin_bit_cast(double, (uint64_t)0xbc65f932ab9f8c67ull)},
658
+ {__builtin_bit_cast(double, (uint64_t)0x3ff1effff48ad400ull),__builtin_bit_cast(double, (uint64_t)0x3c937fbf9da75bebull)},
659
+ {__builtin_bit_cast(double, (uint64_t)0x3ff210000c8b86a4ull),__builtin_bit_cast(double, (uint64_t)0x3c9f806b91fd5b22ull)},
660
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2300003854303ull),__builtin_bit_cast(double, (uint64_t)0x3c93ffc2eb9fbf33ull)},
661
+ {__builtin_bit_cast(double, (uint64_t)0x3ff24fffffbcf684ull),__builtin_bit_cast(double, (uint64_t)0x3c7601e77e2e2e72ull)},
662
+ {__builtin_bit_cast(double, (uint64_t)0x3ff26ffff52921d9ull),__builtin_bit_cast(double, (uint64_t)0x3c7ffcbb767f0c61ull)},
663
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2900014933a3cull),__builtin_bit_cast(double, (uint64_t)0xbc7202ca3c02412bull)},
664
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2b00014556313ull),__builtin_bit_cast(double, (uint64_t)0xbc92808233f21f02ull)},
665
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2cfffebfe523bull),__builtin_bit_cast(double, (uint64_t)0xbc88ff7e384fdcf2ull)},
666
+ {__builtin_bit_cast(double, (uint64_t)0x3ff2f0000bb8ad96ull),__builtin_bit_cast(double, (uint64_t)0xbc85ff51503041c5ull)},
667
+ {__builtin_bit_cast(double, (uint64_t)0x3ff30ffffb7ae2afull),__builtin_bit_cast(double, (uint64_t)0xbc810071885e289dull)},
668
+ {__builtin_bit_cast(double, (uint64_t)0x3ff32ffffeac5f7full),__builtin_bit_cast(double, (uint64_t)0xbc91ff5d3fb7b715ull)},
669
+ {__builtin_bit_cast(double, (uint64_t)0x3ff350000ca66756ull),__builtin_bit_cast(double, (uint64_t)0x3c957f82228b82bdull)},
670
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3700011fbf721ull),__builtin_bit_cast(double, (uint64_t)0x3c8000bac40dd5ccull)},
671
+ {__builtin_bit_cast(double, (uint64_t)0x3ff38ffff9592fb9ull),__builtin_bit_cast(double, (uint64_t)0xbc943f9d2db2a751ull)},
672
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3b00004ddd242ull),__builtin_bit_cast(double, (uint64_t)0x3c857f6b707638e1ull)},
673
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3cffff5b2c957ull),__builtin_bit_cast(double, (uint64_t)0x3c7a023a10bf1231ull)},
674
+ {__builtin_bit_cast(double, (uint64_t)0x3ff3efffeab0b418ull),__builtin_bit_cast(double, (uint64_t)0x3c987f6d66b152b0ull)},
675
+ {__builtin_bit_cast(double, (uint64_t)0x3ff410001532aff4ull),__builtin_bit_cast(double, (uint64_t)0x3c67f8375f198524ull)},
676
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4300017478b29ull),__builtin_bit_cast(double, (uint64_t)0x3c8301e672dc5143ull)},
677
+ {__builtin_bit_cast(double, (uint64_t)0x3ff44fffe795b463ull),__builtin_bit_cast(double, (uint64_t)0x3c89ff69b8b2895aull)},
678
+ {__builtin_bit_cast(double, (uint64_t)0x3ff46fffe80475e0ull),__builtin_bit_cast(double, (uint64_t)0xbc95c0b19bc2f254ull)},
679
+ {__builtin_bit_cast(double, (uint64_t)0x3ff48fffef6fc1e7ull),__builtin_bit_cast(double, (uint64_t)0x3c9b4009f23a2a72ull)},
680
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4afffe5bea704ull),__builtin_bit_cast(double, (uint64_t)0xbc94ffb7bf0d7d45ull)},
681
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4d000171027deull),__builtin_bit_cast(double, (uint64_t)0xbc99c06471dc6a3dull)},
682
+ {__builtin_bit_cast(double, (uint64_t)0x3ff4f0000ff03ee2ull),__builtin_bit_cast(double, (uint64_t)0x3c977f890b85531cull)},
683
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5100012dc4bd1ull),__builtin_bit_cast(double, (uint64_t)0x3c6004657166a436ull)},
684
+ {__builtin_bit_cast(double, (uint64_t)0x3ff530001605277aull),__builtin_bit_cast(double, (uint64_t)0xbc96bfcece233209ull)},
685
+ {__builtin_bit_cast(double, (uint64_t)0x3ff54fffecdb704cull),__builtin_bit_cast(double, (uint64_t)0xbc8902720505a1d7ull)},
686
+ {__builtin_bit_cast(double, (uint64_t)0x3ff56fffef5f54a9ull),__builtin_bit_cast(double, (uint64_t)0x3c9bbfe60ec96412ull)},
687
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5900017e61012ull),__builtin_bit_cast(double, (uint64_t)0x3c887ec581afef90ull)},
688
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5b00003c93e92ull),__builtin_bit_cast(double, (uint64_t)0xbc9f41080abf0cc0ull)},
689
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5d0001d4919bcull),__builtin_bit_cast(double, (uint64_t)0xbc98812afb254729ull)},
690
+ {__builtin_bit_cast(double, (uint64_t)0x3ff5efffe7b87a89ull),__builtin_bit_cast(double, (uint64_t)0xbc947eb780ed6904ull)}
691
+ };
github-simulator/source/gpu/sim/engine_exceptions.h ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ // Generated by examples/gpu_engine_scan.rs: every float with |x| <= 16 whose
2
+ // engine_sin_cos_raw result differs from x87 FSINCOS. Keys: input bits, sorted.
3
+ #pragma once
4
+ #include <cstdint>
5
+ constexpr int ENGINE_EXCEPTION_COUNT = 0;
6
+ constexpr int ENGINE_EXCEPTION_STEP = 0;
7
+ __device__ constexpr uint32_t ENGINE_EXCEPTIONS[1][3] = {
8
+ {0, 0, 0},
9
+ };
github-simulator/source/gpu/sim/math.h ADDED
@@ -0,0 +1,540 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Bit-exact device ports of the CPU math in src/math/native_math.rs,
2
+ // src/math/godot_math.rs (managed_sin_cos), src/math/double_math.rs and src/nn/network.rs.
3
+ // Build with -ffp-contract=off: a fused multiply-add rounds once and breaks
4
+ // bit equality. Arguments outside the ported domains set an `err` bit instead
5
+ // of taking the rare large-argument paths of the CPU code.
6
+ #pragma once
7
+ #include "compat.h"
8
+ #include <cstdint>
9
+ #include "double_tables.h"
10
+ #include "engine_exceptions.h"
11
+
12
+ namespace altd {
13
+
14
+ enum : uint32_t {
15
+ ERR_MANAGED_LARGE = 1u << 0, // managed_sin_cos |x| > 10000 (sine_large not ported)
16
+ ERR_NATIVE_LARGE = 1u << 1, // musl sinf/cosf |x| >= 0x4dc90fdb (Payne-Hanek not ported)
17
+ ERR_ENGINE_DOMAIN = 1u << 2, // engine_sin_cos |x| > ENGINE_DOMAIN (not validated against x87)
18
+ };
19
+
20
+ __device__ __host__ inline uint32_t fbits(float x) { return __builtin_bit_cast(uint32_t, x); }
21
+ __device__ __host__ inline float ffrom(uint32_t b) { return __builtin_bit_cast(float, b); }
22
+ __device__ __host__ inline uint64_t dbits(double x) { return __builtin_bit_cast(uint64_t, x); }
23
+ __device__ __host__ inline double dfrom(uint64_t b) { return __builtin_bit_cast(double, b); }
24
+
25
+ // ---- musl sinf/cosf (native_math.rs) ------------------------------------
26
+
27
+ __device__ inline float sin_kernel(double x) {
28
+ double z = x * x;
29
+ const double s1 = -0.16666666666666666;
30
+ if (x > -7.8175831586122513e-3 && x < 7.8175831586122513e-3) return (float)(x * (1.0 + s1 * z));
31
+ double w = z * z;
32
+ double r = -0.0001984126984126984 + z * 2.7557319223985893e-6;
33
+ double s = z * x;
34
+ return (float)((x + s * (s1 + z * 0.008333333333333333)) + s * w * r);
35
+ }
36
+ __device__ inline float cos_kernel(double x) {
37
+ double z = x * x;
38
+ if (x > -7.816314697265625e-3 && x < 7.816314697265625e-3) return (float)(1.0 - 0.5 * z);
39
+ return (float)(1.0 + z * (-0.5 + z * (0.041666666666666664 + z * (-0.001388888888888889
40
+ + z * (2.48015873015873e-5 + z * -2.755731922398589e-7)))));
41
+ }
42
+ // Medium reduction; the large path is flagged.
43
+ __device__ inline int reduce_medium(float x, double& y, uint32_t& err) {
44
+ if ((fbits(x) & 0x7fffffffu) >= 0x4dc90fdbu) err |= ERR_NATIVE_LARGE;
45
+ const double toint = 1.5 / 2.220446049250313e-16;
46
+ double n = ((double)x * 6.36619772367581382433e-1 + toint) - toint;
47
+ double xd = (double)x;
48
+ y = xd - n * 1.57079631090164184570 - n * 1.58932547735281966916e-8;
49
+ const double quarter = (double)0.7853981852531433f; // f32 FRAC_PI_4
50
+ if (y < -quarter) { n -= 1.0; y = xd - n * 1.57079631090164184570 - n * 1.58932547735281966916e-8; }
51
+ else if (y > quarter) { n += 1.0; y = xd - n * 1.57079631090164184570 - n * 1.58932547735281966916e-8; }
52
+ return (int)n;
53
+ }
54
+ __device__ inline float native_sin(float x, uint32_t& err) {
55
+ uint32_t bits = fbits(x) & 0x7fffffffu;
56
+ bool negative = signbit(x);
57
+ double a = (double)x;
58
+ const double p = 1.5707963267948966;
59
+ if (bits <= 0x3f490fdau) return bits < 0x39800000u ? x : sin_kernel(a);
60
+ if (bits <= 0x407b53d1u) {
61
+ if (bits <= 0x4016cbe3u) return negative ? -cos_kernel(a + p) : cos_kernel(a - p);
62
+ return sin_kernel(negative ? -(a + 2.0 * p) : -(a - 2.0 * p));
63
+ }
64
+ if (bits <= 0x40e231d5u) {
65
+ if (bits <= 0x40afeddfu) return negative ? cos_kernel(a + 3.0 * p) : -cos_kernel(a - 3.0 * p);
66
+ return sin_kernel(negative ? a + 4.0 * p : a - 4.0 * p);
67
+ }
68
+ if (!isfinite(x)) return altd_invalid(x);
69
+ double y;
70
+ int n = reduce_medium(x, y, err);
71
+ switch (n & 3) { case 0: return sin_kernel(y); case 1: return cos_kernel(y); case 2: return sin_kernel(-y); default: return -cos_kernel(y); }
72
+ }
73
+ __device__ inline float native_cos(float x, uint32_t& err) {
74
+ uint32_t bits = fbits(x) & 0x7fffffffu;
75
+ bool negative = signbit(x);
76
+ double a = (double)x;
77
+ const double p = 1.5707963267948966;
78
+ if (bits <= 0x3f490fdau) return bits < 0x39800000u ? 1.0f : cos_kernel(a);
79
+ if (bits <= 0x407b53d1u) {
80
+ if (bits > 0x4016cbe3u) return -cos_kernel(negative ? a + 2.0 * p : a - 2.0 * p);
81
+ return sin_kernel(negative ? a + p : p - a);
82
+ }
83
+ if (bits <= 0x40e231d5u) {
84
+ if (bits > 0x40afeddfu) return cos_kernel(negative ? a + 4.0 * p : a - 4.0 * p);
85
+ return sin_kernel(negative ? -a - 3.0 * p : a - 3.0 * p);
86
+ }
87
+ if (!isfinite(x)) return altd_invalid(x);
88
+ double y;
89
+ int n = reduce_medium(x, y, err);
90
+ switch (n & 3) { case 0: return cos_kernel(y); case 1: return sin_kernel(-y); case 2: return -cos_kernel(y); default: return sin_kernel(y); }
91
+ }
92
+
93
+ __device__ inline float native_atan(float x) {
94
+ bool negative = signbit(x);
95
+ uint32_t bits = fbits(x) & 0x7fffffffu;
96
+ const float high[4] = {ffrom(0x3eed6338u), ffrom(0x3f490fdau), ffrom(0x3f7b985eu), ffrom(0x3fc90fdau)};
97
+ const float low[4] = {ffrom(0x31ac3769u), ffrom(0x33222168u), ffrom(0x33140fb4u), ffrom(0x33a22168u)};
98
+ if (bits >= 0x4c800000u) return isnan(x) ? x : copysignf(high[3], x);
99
+ int id;
100
+ if (bits < 0x3ee00000u) {
101
+ if (bits < 0x39800000u) return x;
102
+ id = -1;
103
+ } else {
104
+ x = fabsf(x);
105
+ if (bits < 0x3f980000u) {
106
+ if (bits < 0x3f300000u) { id = 0; x = (2.0f * x - 1.0f) / (2.0f + x); }
107
+ else { id = 1; x = (x - 1.0f) / (x + 1.0f); }
108
+ } else if (bits < 0x401c0000u) { id = 2; x = (x - 1.5f) / (1.0f + 1.5f * x); }
109
+ else { id = 3; x = -1.0f / x; }
110
+ }
111
+ float z = x * x;
112
+ float w = z * z;
113
+ float s1 = z * (3.3333328366e-01f + w * (1.4253635705e-01f + w * 6.1687607318e-02f));
114
+ float s2 = w * (-1.9999158382e-01f + w * -1.0648017377e-01f);
115
+ if (id < 0) return x - x * (s1 + s2);
116
+ float r = high[id] - ((x * (s1 + s2) - low[id]) - x);
117
+ return negative ? -r : r;
118
+ }
119
+ __device__ inline float native_atan2(float y, float x) {
120
+ const float pi = 3.14159274101257324f;
121
+ const float pi_lo = ffrom(0xb3bbbd2eu);
122
+ if (isnan(x) || isnan(y)) return altd_nan_operand(y, x); // the CPU's `x + y` returns y when both are NaN
123
+ uint32_t ix = fbits(x), iy = fbits(y);
124
+ if (ix == 0x3f800000u) return native_atan(y);
125
+ uint32_t m = ((iy >> 31) & 1u) | ((ix >> 30) & 2u);
126
+ ix &= 0x7fffffffu; iy &= 0x7fffffffu;
127
+ if (iy == 0) return m <= 1 ? y : (m == 2 ? pi : -pi);
128
+ if (ix == 0) return copysignf(pi / 2.0f, y);
129
+ if (ix == 0x7f800000u) {
130
+ if (iy == 0x7f800000u) {
131
+ const float v[4] = {pi / 4.0f, -pi / 4.0f, 3.0f * pi / 4.0f, -3.0f * pi / 4.0f};
132
+ return v[m];
133
+ }
134
+ const float v[4] = {0.0f, -0.0f, pi, -pi};
135
+ return v[m];
136
+ }
137
+ if (ix + (26u << 23) < iy || iy == 0x7f800000u) return copysignf(pi / 2.0f, y);
138
+ float z = ((m & 2) && iy + (26u << 23) < ix) ? 0.0f : native_atan(fabsf(y / x));
139
+ switch (m) { case 0: return z; case 1: return -z; case 2: return pi - (z - pi_lo); default: return (z - pi_lo) - pi; }
140
+ }
141
+
142
+ // ---- .NET MathF.SinCos (godot_math.rs managed_sin_cos) --------------------
143
+
144
+ // Straight-line for |value| <= 10000; larger arguments are flagged.
145
+ __device__ inline float packed_sine(float value, uint32_t& err) {
146
+ float a = fabsf(value);
147
+ if (!(a <= 10000.0f)) err |= ERR_MANAGED_LARGE;
148
+ float rounded = a * ffrom(0x3ea2f983u) + ffrom(0x4b400000u);
149
+ float n = rounded - ffrom(0x4b400000u);
150
+ float r = a - n * ffrom(0x40490000u);
151
+ r -= n * ffrom(0x3a7da000u);
152
+ r -= n * ffrom(0x34222000u);
153
+ r -= n * ffrom(0x2cb4611au);
154
+ float square = r * r;
155
+ r = ffrom(fbits(r) ^ (fbits(rounded) << 31));
156
+ float p = ffrom(0x362edef8u) * square;
157
+ p += ffrom(0xb94fb7ffu);
158
+ p *= square;
159
+ p += ffrom(0x3c088766u);
160
+ p *= square;
161
+ p += ffrom(0xbe2aaaa6u);
162
+ float result = r + (square * p) * r;
163
+ return ffrom(fbits(result) ^ (fbits(value) & 0x80000000u));
164
+ }
165
+ __device__ inline void managed_sin_cos(float angle, float& s, float& c, uint32_t& err) {
166
+ if (isnan(angle)) { s = angle; c = ffrom((fbits(angle) & 0x7fffffffu) | 0x00400000u); return; }
167
+ float a = fabsf(angle);
168
+ if (!(a >= ffrom(0x39000000u))) { s = angle; c = 1.0f - a * a * 0.5f; return; }
169
+ s = packed_sine(angle, err);
170
+ c = packed_sine(a + 1.57079637050628662f, err);
171
+ }
172
+
173
+ // ---- x87 FSINCOS (native_math.rs engine_sin_cos) --------------------------
174
+ // fdlibm kernels in f64 after a Cody-Waite reduction; for float inputs up to
175
+ // ENGINE_DOMAIN the product n * PIO2_1 and the first subtraction are exact.
176
+ // The rounded float results equal x87 except for the inputs in
177
+ // engine_exceptions.h, found by an exhaustive scan (examples/gpu_engine_scan.rs).
178
+
179
+ __device__ inline void engine_sin_cos_raw(float xf, float& s, float& c) {
180
+ double x = (double)xf;
181
+ double n = rint(x * 6.36619772367581382433e-01);
182
+ double y = (x - n * 1.57079632673412561417e+00) - n * 6.07710050650619224932e-11;
183
+ y = n == 0.0 ? x : y; // keeps -0
184
+ double z = y * y, w = z * z;
185
+ double r = 8.33333333332248946124e-03 + z * (-1.98412698298579493134e-04 + z * 2.75573137070700676789e-06)
186
+ + z * w * (-2.50507602534068634195e-08 + z * 1.58969099521155010221e-10);
187
+ // fdlibm returns tiny arguments unchanged, which also keeps the sign of -0.
188
+ double sy = fabs(y) < 0x1p-27 ? y : y + z * y * (-1.66666666666666324348e-01 + z * r);
189
+ double rc = z * (4.16666666666666019037e-02 + z * (-1.38888888888741095749e-03 + z * 2.48015872894767294178e-05))
190
+ + w * w * (-2.75573143513906633035e-07 + z * (2.08757232129817482790e-09 + z * -1.13596475577881948265e-11));
191
+ double hz = 0.5 * z, one_minus = 1.0 - hz;
192
+ double cy = one_minus + (((1.0 - one_minus) - hz) + z * rc);
193
+ int q = (int)n & 3;
194
+ double sv = (q & 1) ? cy : sy, cv = (q & 1) ? sy : cy;
195
+ sv = (q & 2) ? -sv : sv;
196
+ cv = ((q + 1) & 2) ? -cv : cv;
197
+ s = (float)sv;
198
+ c = (float)cv;
199
+ }
200
+
201
+ constexpr float ENGINE_DOMAIN = 16.0f;
202
+
203
+ // Branch-free binary search over the sorted exception keys.
204
+ __device__ inline void engine_sin_cos(float x, float& s, float& c, uint32_t& err) {
205
+ if (!(fabsf(x) <= ENGINE_DOMAIN)) err |= ERR_ENGINE_DOMAIN;
206
+ engine_sin_cos_raw(x, s, c);
207
+ if (ENGINE_EXCEPTION_COUNT == 0) return;
208
+ uint32_t key = fbits(x);
209
+ int lo = 0;
210
+ for (int step = ENGINE_EXCEPTION_STEP; step > 0; step >>= 1) {
211
+ int probe = lo + step;
212
+ lo = (probe < ENGINE_EXCEPTION_COUNT && ENGINE_EXCEPTIONS[probe][0] <= key) ? probe : lo;
213
+ }
214
+ bool hit = ENGINE_EXCEPTIONS[lo][0] == key;
215
+ s = hit ? ffrom(ENGINE_EXCEPTIONS[lo][1]) : s;
216
+ c = hit ? ffrom(ENGINE_EXCEPTIONS[lo][2]) : c;
217
+ }
218
+
219
+ // ---- musl/Arm double exp and pow (double_math.rs) -------------------------
220
+
221
+ __device__ inline double exp_special(double tmp, uint64_t bits, uint64_t k) {
222
+ if ((k & 0x80000000ull) == 0) {
223
+ bits -= 1009ull << 52;
224
+ double scale = dfrom(bits);
225
+ return dfrom(2032ull << 52) * (scale + scale * tmp);
226
+ }
227
+ bits += 1022ull << 52;
228
+ double scale = dfrom(bits);
229
+ double y = scale + scale * tmp;
230
+ if (fabs(y) < 1.0) {
231
+ double one = y < 0.0 ? -1.0 : 1.0;
232
+ double lo = scale - y + scale * tmp;
233
+ double hi = one + y;
234
+ lo = one - hi + y + lo;
235
+ y = (hi + lo) - one;
236
+ if (y == 0.0) y = dfrom(bits & (1ull << 63));
237
+ }
238
+ return 2.2250738585072014e-308 * y;
239
+ }
240
+ __device__ inline double exp_inner(double x, double xtail, bool negative) {
241
+ uint64_t exponent = (dbits(x) >> 52) & 0x7ff;
242
+ if (exponent < 969) return negative ? -(1.0 + x) : 1.0 + x;
243
+ if (exponent >= 1033) {
244
+ double y = signbit(x) ? 0.0 : INFINITY;
245
+ return negative ? -y : y;
246
+ }
247
+ double kd = round(INV_LN2_N * x);
248
+ uint64_t ki = (uint64_t)(int64_t)kd;
249
+ double r = (x + kd * NEG_LN2_HI_N + kd * NEG_LN2_LO_N) + xtail;
250
+ int idx = 2 * (int)(ki % 128);
251
+ uint64_t bias = negative ? (0x800ull << 7) : 0;
252
+ uint64_t top = (ki + bias) << 45;
253
+ double tail = dfrom(EXP_TAB[idx]);
254
+ uint64_t bits = EXP_TAB[idx + 1] + top;
255
+ double r2 = r * r;
256
+ double tmp = tail + r + r2 * (EXP_POLY[0] + r * EXP_POLY[1]) + r2 * r2 * (EXP_POLY[2] + r * EXP_POLY[3]);
257
+ if (exponent >= 1032) return exp_special(tmp, bits, ki);
258
+ double scale = dfrom(bits);
259
+ return scale + scale * tmp;
260
+ }
261
+ __device__ inline double dexp(double x) {
262
+ if (isnan(x)) return x;
263
+ return exp_inner(x, 0.0, false);
264
+ }
265
+ __device__ inline void pow_log(uint64_t ix, double& out_hi, double& out_lo) {
266
+ uint64_t tmp = ix - 0x3fe6955500000000ull;
267
+ int i = (int)((tmp >> 45) % 128);
268
+ double kd = (double)((int64_t)tmp >> 52);
269
+ uint64_t iz = ix - (tmp & (0xfffull << 52));
270
+ double z = dfrom(iz);
271
+ double invc = POW_TAB[i][0], logc = POW_TAB[i][1], logctail = POW_TAB[i][2];
272
+ double zhi = dfrom((iz + (1ull << 31)) & (~0ull << 32));
273
+ double zlo = z - zhi;
274
+ double rhi = zhi * invc - 1.0;
275
+ double rlo = zlo * invc;
276
+ double r = rhi + rlo;
277
+ double t1 = kd * LN2_HI + logc;
278
+ double t2 = t1 + r;
279
+ double lo1 = kd * LN2_LO + logctail;
280
+ double lo2 = t1 - t2 + r;
281
+ const double* a = POW_POLY;
282
+ double ar = a[0] * r;
283
+ double ar2 = r * ar;
284
+ double ar3 = r * ar2;
285
+ double arhi = a[0] * rhi;
286
+ double arhi2 = rhi * arhi;
287
+ double hi = t2 + arhi2;
288
+ double lo3 = rlo * (ar + arhi);
289
+ double lo4 = t2 - hi + arhi2;
290
+ double p = ar3 * (a[1] + r * a[2] + ar2 * (a[3] + r * a[4] + ar2 * (a[5] + r * a[6])));
291
+ double lo = lo1 + lo2 + lo3 + lo4 + p;
292
+ double y = hi + lo;
293
+ out_hi = y;
294
+ out_lo = hi - y + lo;
295
+ }
296
+ __device__ inline uint32_t integer_kind(uint64_t bits) {
297
+ uint64_t e = (bits >> 52) & 0x7ff;
298
+ if (e < 1023) return 0;
299
+ if (e > 1075) return 2;
300
+ uint64_t bit = 1ull << (1075 - e);
301
+ return (bits & (bit - 1)) ? 0 : ((bits & bit) ? 1 : 2);
302
+ }
303
+ __device__ inline double dpow(double x, double y) {
304
+ // pow(1, y) is 1 for every y, NaN included, as the paths below also give;
305
+ // answering first skips the logarithm, e.g. for godot_ease(0, curve).
306
+ if (dbits(x) == dbits(1.0)) return 1.0;
307
+ const uint64_t SIGN = 1ull << 63;
308
+ uint64_t ix = dbits(x), iy = dbits(y);
309
+ uint32_t topx = (uint32_t)(ix >> 52), topy = (uint32_t)(iy >> 52);
310
+ bool negative = false;
311
+ if (topx - 1u >= 0x7feu || (topy & 0x7ffu) - 0x3beu >= 0x80u) {
312
+ if (y == 0.0 || !isfinite(y)) {
313
+ if (y == 0.0) return 1.0;
314
+ if (x == 1.0) return 1.0;
315
+ if (isnan(x) || isnan(y)) return altd_nan_operand(x, y);
316
+ if (fabs(x) == 1.0) return 1.0;
317
+ if ((fabs(x) < 1.0) == !signbit(y)) return 0.0;
318
+ return y * y;
319
+ }
320
+ if (x == 0.0 || !isfinite(x)) {
321
+ double x2 = isnan(x) ? altd_nan_operand(x, x) : x * x;
322
+ if (signbit(x) && integer_kind(iy) == 1) x2 = dfrom(dbits(x2) ^ (1ull << 63)); // -x2, NaN included
323
+ return signbit(y) && !isnan(x2) ? 1.0 / x2 : x2;
324
+ }
325
+ if (signbit(x)) {
326
+ uint32_t kind = integer_kind(iy);
327
+ if (kind == 0) return dfrom(0xfff8000000000000ull);
328
+ negative = kind == 1;
329
+ ix &= ~SIGN; topx &= 0x7ff;
330
+ }
331
+ if ((topy & 0x7ffu) - 0x3beu >= 0x80u) {
332
+ if (ix == dbits(1.0)) return 1.0;
333
+ if ((topy & 0x7ffu) < 0x3beu) return ix > dbits(1.0) ? 1.0 + y : 1.0 - y;
334
+ return (ix > dbits(1.0)) == (topy < 0x800u) ? INFINITY : 0.0;
335
+ }
336
+ if (topx == 0) ix = (dbits(x * 4503599627370496.0) & ~SIGN) - (52ull << 52);
337
+ }
338
+ double hi, lo;
339
+ pow_log(ix, hi, lo);
340
+ double yhi = dfrom(iy & (~0ull << 27));
341
+ double ylo = y - yhi;
342
+ double lhi = dfrom(dbits(hi) & (~0ull << 27));
343
+ double llo = hi - lhi + lo;
344
+ double ehi = yhi * lhi;
345
+ double elo = ylo * lhi + y * llo;
346
+ return exp_inner(ehi, elo, negative);
347
+ }
348
+
349
+ // ---- Network activation (network.rs game_expm1 / game_tanh) ---------------
350
+
351
+ __device__ inline double game_expm1(double x) {
352
+ uint32_t high = (uint32_t)(dbits(x) >> 32) & 0x7fffffffu;
353
+ bool negative = signbit(x);
354
+ if (high >= 0x4043687au) {
355
+ if (isnan(x)) return x;
356
+ if (isinf(x)) return negative ? -1.0 : x;
357
+ if (negative) return -1.0;
358
+ if (x > 7.09782712893383973096e2) return INFINITY;
359
+ }
360
+ int k; double c;
361
+ if (high > 0x3fd62e42u) {
362
+ double hi, lo;
363
+ if (high < 0x3ff0a2b2u) {
364
+ if (!negative) { hi = x - 6.93147180369123816490e-1; lo = 1.90821492927058770002e-10; k = 1; }
365
+ else { hi = x + 6.93147180369123816490e-1; lo = -1.90821492927058770002e-10; k = -1; }
366
+ } else {
367
+ k = (int)(1.44269504088896338700 * x + (negative ? -0.5 : 0.5));
368
+ hi = x - (double)k * 6.93147180369123816490e-1;
369
+ lo = (double)k * 1.90821492927058770002e-10;
370
+ }
371
+ x = hi - lo; c = (hi - x) - lo;
372
+ } else if (high < 0x3c900000u) {
373
+ return x;
374
+ } else { k = 0; c = 0.0; }
375
+ double hfx = 0.5 * x;
376
+ double hxs = x * hfx;
377
+ double r1 = 1.0 + hxs * (-3.33333333333331316428e-2 + hxs * (1.58730158725481460165e-3 + hxs * (-7.93650757867487942473e-5
378
+ + hxs * (4.00821782732936239552e-6 + hxs * (-2.01099218183624371326e-7)))));
379
+ double t = 3.0 - r1 * hfx;
380
+ double e = hxs * ((r1 - t) / (6.0 - x * t));
381
+ if (k == 0) return x - (x * e - hxs);
382
+ e = x * (e - c) - c;
383
+ e -= hxs;
384
+ if (k == -1) return 0.5 * (x - e) - 0.5;
385
+ if (k == 1) return x < -0.25 ? -2.0 * (e - (x + 0.5)) : 1.0 + 2.0 * (x - e);
386
+ double twopk = dfrom((uint64_t)(0x3ff + k) << 52);
387
+ if (k < 0 || k > 56) {
388
+ double y = x - e + 1.0;
389
+ return (k == 1024 ? y * 2.0 * dfrom(0x7fe0000000000000ull) : y * twopk) - 1.0;
390
+ }
391
+ double inverse = dfrom((uint64_t)(0x3ff - k) << 52);
392
+ return k < 20 ? (x - e + (1.0 - inverse)) * twopk : (x - (e + inverse) + 1.0) * twopk;
393
+ }
394
+ // The scalar tanh, one branch per range. A wave whose lanes fall in different
395
+ // ranges executes every taken branch in turn, and the three expm1 call sites
396
+ // are inlined separately, so the double-precision work (1/32 rate on RDNA3)
397
+ // runs up to three times per layer. Kept for comparison: -DALTD_TANH_BRANCHY.
398
+ __device__ inline double game_tanh_branchy(double value) {
399
+ double x = fabs(value);
400
+ uint32_t high = (uint32_t)(dbits(x) >> 32);
401
+ double t;
402
+ if (high > 0x3fe193eau) {
403
+ if (high > 0x40340000u) t = 1.0 - 0.0 / x;
404
+ else { double u = game_expm1(2.0 * x); t = 1.0 - 2.0 / (u + 2.0); }
405
+ } else if (high > 0x3fd058aeu) {
406
+ double u = game_expm1(2.0 * x); t = u / (u + 2.0);
407
+ } else if (high >= 0x00100000u) {
408
+ double u = game_expm1(-2.0 * x); t = -u / (u + 2.0);
409
+ } else t = x;
410
+ return signbit(value) ? -t : t;
411
+ }
412
+
413
+ // ---- branch-free game_tanh ------------------------------------------------
414
+ // One expm1 evaluation per lane on a selected argument, then selects, so the
415
+ // lanes of a wave never serialize the tanh ranges or the expm1 cases. Each
416
+ // lane's selected expression is exactly what the scalar code evaluates on
417
+ // that path; unselected values are computed and dropped. This is the scalar
418
+ // form of `tanh4`/`expm1_tanh` in src/nn/network_simd.rs, whose CPU tests
419
+ // compare it with the scalar functions bit for bit over every branch,
420
+ // threshold and special value.
421
+
422
+ // 2^k by exponent-field arithmetic, as the scalar code.
423
+ __device__ inline double pow2_of(int32_t k) {
424
+ return dfrom((uint64_t)(int64_t)(k + 0x3ff) << 52);
425
+ }
426
+
427
+ // `game_expm1` for the arguments `game_tanh` selects: finite, below 709.78
428
+ // when positive, so the overflow block of the scalar code never returns early.
429
+ // The integer k is kept as an integer for its comparisons and powers of two
430
+ // (the scalar code's `k as f64` only enters the two reduction products).
431
+ __device__ inline double expm1_lane(double x) {
432
+ uint32_t h = (uint32_t)(dbits(x) >> 32) & 0x7fffffffu;
433
+ bool negative = signbit(x);
434
+ bool reduce = h > 0x3fd62e42u, tiny = h < 0x3c900000u, near = h < 0x3ff0a2b2u;
435
+ // k = +-1 near ln 2, else trunc(x / ln 2 +- 0.5); 0 without reduction.
436
+ double rounded = 1.44269504088896338700 * x + (negative ? -0.5 : 0.5);
437
+ int32_t ki = reduce ? (near ? (negative ? -1 : 1) : __double2int_rz(rounded)) : 0;
438
+ double k = (double)ki;
439
+ // For k = +-1, x - k*ln2_hi and k*ln2_lo equal the scalar x -+ ln2_hi, +-ln2_lo exactly.
440
+ double hi = x - k * 6.93147180369123816490e-1;
441
+ double lo = k * 1.90821492927058770002e-10;
442
+ double xr = reduce ? hi - lo : x;
443
+ double c = reduce ? (hi - xr) - lo : 0.0;
444
+ double hfx = 0.5 * xr;
445
+ double hxs = xr * hfx;
446
+ double p = hxs * -2.01099218183624371326e-7;
447
+ p = hxs * (4.00821782732936239552e-6 + p);
448
+ p = hxs * (-7.93650757867487942473e-5 + p);
449
+ p = hxs * (1.58730158725481460165e-3 + p);
450
+ p = hxs * (-3.33333333333331316428e-2 + p);
451
+ double r1 = 1.0 + p;
452
+ double t = 3.0 - r1 * hfx;
453
+ double e = hxs * ((r1 - t) / (6.0 - xr * t));
454
+ double k0 = xr - (xr * e - hxs);
455
+ e = (xr * (e - c) - c) - hxs;
456
+ double xme = xr - e;
457
+ double km1 = 0.5 * xme - 0.5;
458
+ double k1 = xr < -0.25 ? -2.0 * (e - (xr + 0.5)) : 1.0 + 2.0 * xme;
459
+ double twopk = pow2_of(ki), inverse = pow2_of(-ki);
460
+ // k < 0 || k > 56 (k == 1024 needs x > 709, outside the domain).
461
+ bool far = ki < 0 || ki > 56;
462
+ double far_value = (xme + 1.0) * twopk - 1.0;
463
+ double below20 = (xme + (1.0 - inverse)) * twopk;
464
+ double from20 = ((xr - (e + inverse)) + 1.0) * twopk;
465
+ double general = far ? far_value : (ki < 20 ? below20 : from20);
466
+ double result = ki == 0 ? k0 : (ki == -1 ? km1 : (ki == 1 ? k1 : general));
467
+ return tiny ? x : result;
468
+ }
469
+
470
+ __device__ inline double game_tanh(double value) {
471
+ #ifdef ALTD_TANH_BRANCHY
472
+ return game_tanh_branchy(value);
473
+ #else
474
+ double x = fabs(value);
475
+ uint32_t h = (uint32_t)(dbits(x) >> 32);
476
+ // Above 0x40340000 (|x| > 20, infinities, NaN) the scalar path stays a
477
+ // branch: NaN must keep its payload, and a NaN routed through the selects
478
+ // below can lose it on this hardware (fabs folded into the 32-bit select
479
+ // as a float source modifier, which quiets or canonicalizes under IEEE
480
+ // mode). Finite lanes never take it during inference.
481
+ if (h > 0x40340000u) {
482
+ double t = 1.0 - 0.0 / x;
483
+ return signbit(value) ? -t : t;
484
+ }
485
+ bool big = h > 0x3fe193eau, mid = h > 0x3fd058aeu, normal = h > 0x000fffffu;
486
+ double t = expm1_lane(mid ? 2.0 * x : -2.0 * x);
487
+ double d = t + 2.0;
488
+ // The range's own division: 2 / (t + 2) for the large range, t / (t + 2)
489
+ // otherwise; the small range's (-t) / (t + 2) is the exact negation.
490
+ double q = (big ? 2.0 : t) / d;
491
+ double result = big ? 1.0 - q : (mid ? q : (normal ? -q : x));
492
+ return signbit(value) ? -result : result;
493
+ #endif
494
+ }
495
+
496
+ // ---- math profiles (crate::math::profile) ----------------------------------
497
+ // The Windows UCRT functions of math/kernels/ucrt.h. World::math_profile picks a
498
+ // profile for the whole simulation, so the branches below never diverge.
499
+
500
+ #define ALTD_MATH_FN __device__ inline
501
+ #define ALTD_MATH_TABLE __device__ constexpr
502
+ #define ALTD_MATH_CONST constexpr
503
+ #include "../../math/kernels/ucrt.h"
504
+
505
+ // MathProfile::index.
506
+ enum : uint32_t { MATH_PROTON = 0, MATH_WIN10_FMA3 = 1, MATH_WIN11_FMA3 = 2 };
507
+
508
+ // sinf and cosf are musl's with the Windows exceptions. Inputs beyond
509
+ // WIN_TRIG_LIMIT are flagged by the musl reduction already.
510
+ __device__ inline float profile_sin(uint32_t math, float x, uint32_t& err) {
511
+ float musl = native_sin(x, err);
512
+ return math == MATH_PROTON ? musl : win_sinf_fix(x, musl);
513
+ }
514
+ __device__ inline float profile_cos(uint32_t math, float x, uint32_t& err) {
515
+ float musl = native_cos(x, err);
516
+ return math == MATH_PROTON ? musl : win_cosf_fix(x, musl);
517
+ }
518
+ // The profile is uniform, so these branch rather than select (WGSL's select
519
+ // evaluates both operands).
520
+ __device__ inline float profile_atan2(uint32_t math, float y, float x) {
521
+ if (math == MATH_PROTON) return native_atan2(y, x);
522
+ return win_atan2f(y, x);
523
+ }
524
+ __device__ inline double profile_exp(uint32_t math, double x) {
525
+ if (math == MATH_PROTON) return dexp(x);
526
+ if (math == MATH_WIN10_FMA3) return win10_exp(x);
527
+ return win11_exp(x);
528
+ }
529
+ __device__ inline double profile_pow(uint32_t math, double x, double y) {
530
+ if (math == MATH_PROTON) return dpow(x, y);
531
+ if (math == MATH_WIN10_FMA3) return win10_pow(x, y);
532
+ return win11_pow(x, y);
533
+ }
534
+ __device__ inline double profile_tanh(uint32_t math, double x) {
535
+ if (math == MATH_PROTON) return game_tanh(x);
536
+ if (math == MATH_WIN10_FMA3) return win10_tanh(x);
537
+ return win11_tanh(x);
538
+ }
539
+
540
+ } // namespace altd
github-simulator/source/gpu/sim/rays.h ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // BSP queries of src/track/bsp.rs with explicit stacks instead of recursion:
2
+ // RayTree::raycast and Node::closest, which share the RayTree arena.
3
+ #pragma once
4
+ #include "world.h"
5
+ #include "math.h"
6
+
7
+ namespace altd {
8
+
9
+ // `Bounds::beside`: box (cx, cy, hx, hy) lies beyond `margin` on one side of the ray's line.
10
+ __device__ inline bool beside(float4 box, float2 start, float2 b, float margin) {
11
+ float cross = b.x * (box.y - start.y) - b.y * (box.x - start.x);
12
+ return fabsf(cross) > fabsf(b.x) * (box.w + margin) + fabsf(b.y) * (box.z + margin);
13
+ }
14
+
15
+ // `classify`: side of p relative to the wall (origin, direction).
16
+ __device__ inline int classify(float4 wall, float2 p) {
17
+ float cross = wall.z * (p.y - wall.y) - wall.w * (p.x - wall.x);
18
+ return cross > 0.0001f ? 1 : (cross < -0.0001f ? -1 : 0);
19
+ }
20
+
21
+ // `godot_math::ray_wall_hit`
22
+ __device__ inline bool ray_wall_hit(float2 start, float2 b, float4 wall, float2& hit) {
23
+ float denominator = wall.z * b.y - b.x * wall.w;
24
+ float dx = wall.x - start.x, dy = wall.y - start.y;
25
+ float ray_fraction = ((0.0f - wall.w) * dx + wall.z * dy) / denominator;
26
+ float wall_fraction = (b.x * dy - b.y * dx) / denominator;
27
+ hit = make_float2(wall.x + wall.z * wall_fraction, wall.y + wall.w * wall_fraction);
28
+ return !(fabsf(denominator) < 1e-6f) && ray_fraction >= 0.0f && ray_fraction <= 1.0f
29
+ && wall_fraction >= 0.0f && wall_fraction <= 1.0f;
30
+ }
31
+
32
+ // `RayTree::raycast`. The recursion "near subtree, else own walls, else the
33
+ // far subtree if the ray crosses the partition" becomes a descent loop that
34
+ // pushes each entered node, and a pop that tests the node's own walls and
35
+ // then continues into its far child.
36
+ __device__ inline bool raycast(const World& w, float2 start, float2 end, float2& hit) {
37
+ float2 b = make_float2(end.x - start.x, end.y - start.y);
38
+ float scale = w.ray_magnitude + fabsf(start.x) + fabsf(start.y);
39
+ float margin = fabsf(b.x) + fabsf(b.y) > 1e-10f ? 1e-3f + 2e-5f * scale : INFINITY;
40
+ uint32_t stack[MAX_RAY_DEPTH];
41
+ int depth = 0;
42
+ uint32_t current = w.ray_node_count ? 0 : NONE;
43
+ for (;;) {
44
+ while (current != NONE) {
45
+ const RayNode& node = w.ray_nodes[current];
46
+ if (beside(node.subtree, start, b, margin)) break;
47
+ float4 partition = w.ray_walls[node.first];
48
+ int a = classify(partition, start), e = classify(partition, end);
49
+ bool front_first = a == 1 || (a == 0 && e == 1);
50
+ // Low bit: the ray crosses the partition, so the far side follows.
51
+ stack[depth++] = current << 1 | (a != e);
52
+ current = front_first ? node.front : node.back;
53
+ }
54
+ if (depth == 0) return false;
55
+ uint32_t entry = stack[--depth];
56
+ const RayNode& node = w.ray_nodes[entry >> 1];
57
+ if (!beside(node.own, start, b, margin)) {
58
+ float distance = 3.40282347e38f;
59
+ bool found = false;
60
+ for (uint32_t i = node.first; i < node.last; i++) {
61
+ float2 p;
62
+ bool ok = ray_wall_hit(start, b, w.ray_walls[i], p);
63
+ float dx = p.x - start.x, dy = p.y - start.y;
64
+ float d2 = dx * dx + dy * dy;
65
+ bool better = ok && d2 < distance;
66
+ distance = better ? d2 : distance;
67
+ hit = better ? p : hit;
68
+ found |= better;
69
+ }
70
+ if (found) return true;
71
+ }
72
+ float4 partition = w.ray_walls[node.first];
73
+ bool front_first = classify(partition, start) == 1
74
+ || (classify(partition, start) == 0 && classify(partition, end) == 1);
75
+ current = (entry & 1) ? (front_first ? node.back : node.front) : NONE;
76
+ }
77
+ }
78
+
79
+ // `Node::closest`: own walls, then the near subtree, then the far subtree
80
+ // when the partition line is closer than the best wall so far.
81
+ __device__ inline float2 closest_wall_point(const World& w, float2 p) {
82
+ float distance = INFINITY;
83
+ float2 closest = make_float2(0.0f, 0.0f);
84
+ uint32_t stack[MAX_RAY_DEPTH];
85
+ int depth = 0;
86
+ uint32_t current = w.ray_node_count ? 0 : NONE;
87
+ for (;;) {
88
+ while (current != NONE) {
89
+ const RayNode& node = w.ray_nodes[current];
90
+ for (uint32_t i = node.first; i < node.last; i++) {
91
+ float4 wall = w.ray_walls[i];
92
+ float len2 = wall.z * wall.z + wall.w * wall.w;
93
+ float t = (p.x * wall.z + p.y * wall.w) - (wall.x * wall.z + wall.y * wall.w);
94
+ t = t < 0.0f ? 0.0f : (t > len2 ? len2 : t); // f32::clamp
95
+ t = t / len2;
96
+ float2 candidate = len2 == 0.0f ? make_float2(wall.x, wall.y)
97
+ : make_float2(wall.x + wall.z * t, wall.y + wall.w * t);
98
+ float dx = p.x - candidate.x, dy = p.y - candidate.y;
99
+ float d2 = dx * dx + dy * dy;
100
+ bool better = d2 < distance;
101
+ distance = better ? d2 : distance;
102
+ closest = better ? candidate : closest;
103
+ }
104
+ stack[depth++] = current;
105
+ current = classify(w.ray_walls[node.first], p) >= 0 ? node.front : node.back;
106
+ }
107
+ if (depth == 0) return closest;
108
+ const RayNode& node = w.ray_nodes[stack[--depth]];
109
+ float4 partition = w.ray_walls[node.first];
110
+ float t = ((p.x - partition.x) * partition.z + (p.y - partition.y) * partition.w)
111
+ / (partition.z * partition.z + partition.w * partition.w);
112
+ float dx = p.x - (partition.x + partition.z * t), dy = p.y - (partition.y + partition.w * t);
113
+ bool far = dx * dx + dy * dy < distance;
114
+ current = far ? (classify(partition, p) >= 0 ? node.back : node.front) : NONE;
115
+ }
116
+ }
117
+
118
+ } // namespace altd
github-simulator/source/gpu/sim/sensors.h ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // `Car::sensor` (src/physics/car.rs) for every sensor kind, reading the GPU car state.
2
+ #pragma once
3
+ #include "rays.h"
4
+ #include "track.h"
5
+
6
+ namespace altd {
7
+
8
+ constexpr float PI_F = 3.14159265358979323846f;
9
+
10
+ struct Sensors {
11
+ uint32_t count, needs_path; // needs_path: some sensor reads the path offset
12
+ SensorDesc s[MAX_SENSORS];
13
+ };
14
+
15
+ // Cars whose controls update this tick (Schedule::update_controls).
16
+ struct ReadMask {
17
+ uint32_t batch_size, batch_index, batches_per_tick, require_active;
18
+ };
19
+
20
+ __device__ inline bool reads(const ReadMask& m, const Car& car, uint32_t index) {
21
+ uint32_t batch = index / m.batch_size;
22
+ return (batch + 8 - m.batch_index) % 8 < m.batches_per_tick && (!m.require_active || (car.flags & CAR_ACTIVE));
23
+ }
24
+
25
+ __device__ inline Vec2 actual_velocity(const Car& c) { return (c.flags & CAR_PENDING_VELOCITY) ? Vec2{0.0f, 0.0f} : c.velocity; }
26
+ __device__ inline float actual_angular(const Car& c) { return (c.flags & CAR_PENDING_VELOCITY) ? 0.0f : c.angular_velocity; }
27
+ __device__ inline Vec2 actual_acceleration(const Car& c) { return (c.flags & CAR_PENDING_VELOCITY) ? c.reset_acceleration : c.acceleration; }
28
+ __device__ inline Vec2 cached_right(const Car& c) { return (c.flags & CAR_RESET_RIGHT) ? Vec2{0.0f, 0.0f} : normalized(c.basis_x); }
29
+ // `Car::cached_front`: cached_right rotated by -PI/2, whose managed sine and
30
+ // cosine are constant (godot_math::QUARTER_TURN_BACK).
31
+ __device__ inline Vec2 cached_front(const Car& c) {
32
+ Vec2 r = cached_right(c);
33
+ float s = ffrom(0xbf7fffffu), co = ffrom(0xb3bbbd2eu);
34
+ return Vec2{r.x * co - r.y * s, r.x * s + r.y * co};
35
+ }
36
+
37
+ // `Car::sensor_path_offset` (actual position equals position: no transform reset is pending).
38
+ __device__ inline float sensor_path_offset(const World& w, const Car& car) {
39
+ return curve_closest_offset(w, path_sensor_point(w, car.position));
40
+ }
41
+
42
+ // `Car::sensor`. `here` is sensor_path_offset for the path sensors.
43
+ __device__ inline double sensor_value(const World& w, const VehicleDesc& v, const Car& car, const SensorDesc& s,
44
+ float here, uint32_t& err) {
45
+ switch (s.kind) {
46
+ case S_RAYCAST: {
47
+ Vec2 local = s.offset; // `Car::ray_local`, precomputed on the host (with_ray_offset)
48
+ Vec2 end = transform_point(car.position, car.basis_x, car.basis_y, local);
49
+ float2 hit;
50
+ if (!raycast(w, make_float2(car.position.x, car.position.y), make_float2(end.x, end.y), hit)) return 0.0;
51
+ if (hit.x == end.x && hit.y == end.y) return 0.0;
52
+ return (double)(1.0f - length(Vec2{hit.x, hit.y} - car.position) / s.b);
53
+ }
54
+ case S_DISTANCE_FROM_WALL: {
55
+ float2 wall = closest_wall_point(w, make_float2(car.position.x, car.position.y));
56
+ return (double)rclamp(length(car.position - Vec2{wall.x, wall.y}) / s.a, 0.0f, 1.0f);
57
+ }
58
+ case S_SPEED: return (double)rclamp(length(car.velocity) / v.max_velocity_f, 0.0f, 1.0f);
59
+ case S_VELOCITY_FRONT:
60
+ return signed_sensor(dot(actual_velocity(car), cached_front(car)), v.max_velocity_f);
61
+ case S_VELOCITY_SIDE:
62
+ return signed_sensor(dot(actual_velocity(car), cached_right(car)), v.max_velocity_f / 2.0f);
63
+ case S_ACCELERATION_FRONT:
64
+ return signed_sensor(dot(actual_acceleration(car), cached_front(car)), s.a);
65
+ case S_ACCELERATION_SIDE:
66
+ return signed_sensor(dot(actual_acceleration(car), cached_right(car)), s.a);
67
+ case S_ANGULAR_VELOCITY: return signed_sensor(actual_angular(car), 2.0f);
68
+ case S_WHEEL_ANGLE: {
69
+ uint32_t i = 0;
70
+ while (i + 1 < v.wheel_count && !v.wheels[i].steering) i++;
71
+ return signed_sensor(car.wheel_angle[i], 45.0f);
72
+ }
73
+ case S_BOOST_CAPACITY: return py_clamp((double)car.boost_energy, 0.0, 1.0);
74
+ case S_GRIP: {
75
+ Vec2 p = car.position + rotated(s.offset, car.rotation, err);
76
+ return (double)w.surfaces[vehicle_surface(w, p)].grip;
77
+ }
78
+ case S_CORRECT_DIRECTION: {
79
+ if (w.path_points < 2) return 0.0;
80
+ Vec2 tangent = normalized(curve_direction(w, here, err));
81
+ Vec2 forward = normalized(cached_front(car));
82
+ return (double)rclamp(dot(forward, tangent), -1.0f, 1.0f);
83
+ }
84
+ case S_TRACK_CURVATURE: {
85
+ if (w.path_points < 2) return 0.0;
86
+ Vec2 tangent = normalized(curve_direction(w, here, err));
87
+ if (w.curve_length <= 0.0f) return 0.0;
88
+ float speed = rclamp(length(actual_velocity(car)) / v.max_velocity_f, 0.0f, 1.0f);
89
+ float lo = rmax(s.a, 0.0f), hi = rmax(s.b, lo);
90
+ float offset = fmodf(here + (lo + (hi - lo) * speed), w.curve_length);
91
+ Vec2 ahead = normalized(curve_direction(w, offset, err));
92
+ return (double)rclamp(fabsf(profile_atan2(w.math_profile, cross(tangent, ahead), dot(tangent, ahead))) / PI_F, 0.0f, 1.0f);
93
+ }
94
+ }
95
+ return 0.0;
96
+ }
97
+
98
+ } // namespace altd