This release is a pre-release and may not be stable for production use.
tiny-bclibc-wasm
LGPL WebAssembly build of the tiny_bclibc ballistic solver for CPython, PyPy and Pythonista.
import tiny_bclibc for CPython, PyPy and Pythonista, with the same API as the natmod .mpy
from micropython-bclibc. A script written
for a MicroPython board runs here unchanged.
Inside is tiny_bclibc (pure C99)
compiled to WebAssembly: one ~58 KB .wasm that imports nothing. A small runner loads it into
whichever WebAssembly host is available. No C extension, no per-platform build.
import tiny_bclibc as bc
shot = bc.Shot(bc=0.310, weight_grain=168.0, diameter_inch=0.308, length_inch=1.2,
muzzle_velocity_fps=2750.0, sight_height_ft=0.125, twist_inch=11.0)
bc.zero(shot, 100 / 0.3048) # 100 m zero
hold_rad, windage_rad, point = bc.aim(shot, 300 / 0.3048)
rows, reason = bc.fire(shot, bc.Request(range_limit_ft=3280.84, range_step_ft=328.084))
for r in rows:
print(r[bc.T_DISTANCE], r[bc.T_VELOCITY], r[bc.T_HEIGHT])
See examples/basic.py, and micropython-bclibc's README ("Module API", "Usage examples") for the
full API: Shot, Wind, Config, Request, integrate, integrate_stream, integrate_at,
find_zero_angle, zero_point, zero, aim, fire, find_apex, find_max_range, MultiBC,
and the TRAJ_FLAG_*, T_* and INTERP_* constants.
Installation
uv
uv add tiny-bclibc-wasm
# With wasmtime, the in-process WebAssembly host (otherwise Node or WebKitGTK JavaScriptCore is used)
uv add tiny-bclibc-wasm[wasmtime]
# As a py-ballisticcalc engine (see "py-ballisticcalc engine" below)
uv add tiny-bclibc-wasm[pybc]
# Everything
uv add tiny-bclibc-wasm[wasmtime,pybc]
pip
pip install tiny-bclibc-wasm
# With wasmtime, the in-process WebAssembly host (otherwise Node or WebKitGTK JavaScriptCore is used)
pip install tiny-bclibc-wasm[wasmtime]
# As a py-ballisticcalc engine (see "py-ballisticcalc engine" below)
pip install tiny-bclibc-wasm[pybc]
# Everything
pip install tiny-bclibc-wasm[wasmtime,pybc]
Pythonista and PythonIDE (iOS)
The ordinary wheel: it is pure Python (py3-none-any) with the .wasm modules inside. In StaSh (Pythonista) or
PythonIDE's pip, pip install tiny-bclibc-wasm; it pulls in wasmhost,
which runs the WebAssembly. See Pythonista.
Typing
The package is fully typed and ships py.typed. The code uses Python 3.10 annotations, which is
what Pythonista runs, and passes pyright in strict mode. src/tiny_bclibc/__init__.pyi describes
the public API. In it, a trajectory row is Row, a tuple of 15 floats followed by the int flag.
The .s fields of Shot, Wind, Config and Request are typed dataclasses (ShotProps,
WindFields, ...). mypy's stubtest (a pre-commit hook) checks that the stub matches the module.
WebAssembly hosts
| Host | Where | How it is detected |
|---|---|---|
wasmtime |
anywhere with the wasmtime package |
import wasmtime (uv add tiny-bclibc-wasm[wasmtime]) |
wasm3 |
CPython 3.11+ with pywasm3 | import wasm3; install it from git: uv add "pywasm3 @ git+https://github.com/wasm3/pywasm3" (its PyPI release predates the API used here) |
jscontext |
Pythonista and PythonIDE (iOS) | JavaScriptCore's JSContext through objc_util (or rubicon-objc, see wasmhost) |
jsc |
Linux, macOS | JavaScriptCore through its C API (ctypes), no PyGObject (apt install libjavascriptcoregtk-4.1-0) |
gi-jsc |
Linux | WebKitGTK's JavaScriptCore through PyGObject (apt install gir1.2-javascriptcoregtk-4.1 python3-gi) |
node |
anywhere with Node.js | node on PATH |
With nothing configured, the first host that starts wins, in the order shown: the in-process runtimes when installed, then the JavaScript engines (on Pythonista nothing above jscontext can be installed, so it is the pick there). Each host's
constructor is its own probe: it fails when its runtime is missing (objc_util/wasmtime/gi
won't import, node isn't on PATH, the JS engine has no WebAssembly). You can override it
with TINY_BCLIBC_HOST=<name> or tiny_bclibc.set_host("<name>"). tiny_bclibc.host() reports
which host is in use.
All of them are backends of wasmhost: a call is one batch (allocate
the input, write it, call the export, read the output), which is one trip to a JavaScript engine.
tests/test_hosts.py checks that every host available on the machine returns bit-identical
results.
py-ballisticcalc engine
tiny_bclibc.pybc makes tiny_bclibc an integration engine for
py-ballisticcalc 3.0.0b1 or newer (Python 3.11+).
The package registers it as a py-ballisticcalc entry point, so installing it is enough:
uv add "tiny-bclibc-wasm[pybc]"
from py_ballisticcalc import Calculator
calc = Calculator(engine="tiny_bclibc_wasm+tsitouras-dp")
| Engine | Class | Name (py-ballisticcalc 3.0.0b3+) | Legacy name (2.2.10+, deprecated in 3.0.0b3) |
|---|---|---|---|
| double precision | TinyBclibcWasmTsitourasEngineDP |
tiny_bclibc_wasm+tsitouras-dp |
tiny_bclibc_wasm_engine |
| single precision | TinyBclibcWasmTsitourasEngineSP |
tiny_bclibc_wasm+tsitouras-sp |
tiny_bclibc_wasm_sp_engine |
On Python 3.10, the [pybc] extra installs py-ballisticcalc 2.2.10, its last release for 3.10. That
is partial backward compatibility: the engines work, but 3 of 2.2.10's tests fail, because 2.x
merges ZERO/MACH/APEX events onto a nearby RANGE row and these engines keep them as separate rows,
as 3.x does. Use the legacy name there (2.x has no <engine>+<method> names).
The engine runs on the same WebAssembly host as the rest of the package (see above). It does not
support dense_output. The single-precision engine has float32's limits: 18 of py-ballisticcalc's
tests fail on it. Pythonista has no entry points, so there you pass the import path,
Calculator(engine="tiny_bclibc.pybc:TinyBclibcWasmTsitourasEngineDP"); see
examples/py_ballisticcalc_engine.py.
The engine is tested with py-ballisticcalc's own suite. The py-ballisticcalc submodule pins py-ballisticcalc,
and uv sync installs it from there, so the package and its tests/ always come from one commit:
git submodule update --init # bclibc + py-ballisticcalc
uv run pytest py-ballisticcalc/tests --engine=tiny_bclibc_wasm+tsitouras-dp
git -C py-ballisticcalc checkout v3.0.0 && uv lock # move to another py-ballisticcalc release
Build
The build compiles the .wasm modules itself: setup.py runs build_wasm.py on every build.
The compiler is the ziglang PyPI package, which is a build requirement, so nothing needs to be
installed beforehand. The package version comes from git tags via setuptools_scm.
git submodule update --init
uv sync # editable install; compiles src/tiny_bclibc/tiny_bclibc_{dp,sp}.wasm
uv build # sdist + wheel (the wheel is built from the sdist, so it compiles too)
uv run --with ziglang python build_wasm.py # just recompile the modules (ziglang only exists in the isolated build env)
uv sync recompiles the modules when the wrapper sources, the headers or the build hooks change.
The sdist carries only bclibc/tiny_bclibc/{include,wasm} from the submodule, plus the bclibc
version, so pip install tiny_bclibc_wasm-*.tar.gz builds anywhere Python does.
TINY_BCLIBC_CC="clang --sysroot=<wasi-sysroot>" switches to another wasm32 compiler.
Precision: double by default. TINY_BCLIBC_PRECISION=single loads the float32 build, the same
trade-off as the natmod's single-precision builds. TINY_BCLIBC_WASM=/path/to/x.wasm loads a
specific module.
Test
uv run pytest # everything below, on the automatically picked backend
uv run pytest --wasm-backend node # ... on one backend: wasmtime | wasm3 | node | jsc | gi-jsc
uv run pytest --cov # with coverage
uv run python tests/run_natmod_suite.py # just the natmod suite, current host/precision
uv run pyright && uv run ruff check # types, lint
uv run pytest py-ballisticcalc/tests --engine=tiny_bclibc_wasm+tsitouras-dp # py-ballisticcalc's suite (Python 3.11+)
If the backend passed to --wasm-backend can't start, the run stops with an error; the tests are
never silently skipped. CI (.github/workflows/tests.yml) runs the suite on wasmtime and on Node
on Linux, Windows and macOS with CPython 3.10, CPython 3.14 and PyPy 3.11, and on wasm3 wherever
pywasm3 installs (CPython 3.11+). It also runs it on
WebKitGTK JavaScriptCore with and without JIT, then combines coverage from all three runtimes and
uploads it to Codecov. Every leg on Python 3.11+ also runs py-ballisticcalc's suite on the
tiny_bclibc_wasm+tsitouras-dp engine.
pytest checks that every available host returns identical results (tests/test_hosts.py). It
also runs micropython-bclibc's tests/test_bclibc.py unmodified in both precisions
(tests/test_natmod_suite.py); that test is skipped when the suite can't be found.
run_natmod_suite.py runs the natmod's own acceptance suite against this package. By default it
takes the suite from a sibling ../micropython-bclibc checkout. Current results: 19/19 on CPython
3.8–3.14 under wasmtime, Node and WebKitGTK JavaScriptCore (with and without JIT), in both
precisions. On PyPy, 17/19: the two failing checks are the suite's own memory measurements, which
use tracemalloc, and PyPy doesn't have it.
Pythonista
Install it with pip (see above), then import tiny_bclibc as bc. JSContext is picked automatically. The
package is plain Python; its one dependency is wasmhost, which has a self-test to run on the device
(import wasmhost; wasmhost.selftest()) that reports the Objective-C bridge, WebAssembly and BigInt and the
cost of a call. It has passed on Pythonista (StaSh, Python 3.10.4) and on PythonIDE (Python 3.14.7).
Differences from the natmod
Shot/Wind/Config/Requesthold Python floats instead of float32 bytearrays. That way the double-precision module gets full-precision inputs. They keep the same(buf, s)namedtuple shape with the fields on.s(shot.s.props.barrel_elevation_rad,w.s.velocity_fps, ...), butbufisNone.integrate_streamruns the callbacks after the trajectory is computed, so the whole trajectory costs one host call. An early stop still reports reason 5, but it doesn't skip the remaining integration.bench()measures the WebAssembly host's f32/f64 speed (the same loops, compiled to wasm), not the CPU directly. It is a way to compare hosts.
License
Copyright (C) 2026 Dmytro Yaroshenko (o-murphy)
This library is free software: you can redistribute it and/or modify it under the terms of the
GNU Lesser General Public License v3.0 (see LICENSE), the same license as
bclibc, whose tiny_bclibc the shipped .wasm modules
are compiled from.
Release files for tiny-bclibc-wasm 0.0.4b1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tiny_bclibc_wasm-0.0.4b1.tar.gz | 156.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tiny_bclibc_wasm-0.0.4b1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 236.9 kB
Release files / tiny_bclibc_wasm-0.0.4b1.tar.gz
| Download URL | tiny_bclibc_wasm-0.0.4b1.tar.gz |
|---|---|
| Size | 156.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e5ca042ba117457a032784c98d875a1cb744d1f941b1c542497ea4d2b2152a92
|
|
BLAKE2b-256 checksum How to use checksums |
ddf53aa926a9b46a4502b0e5d7511149c40292575025eeee531907403c187bbc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / tiny_bclibc_wasm-0.0.4b1-py3-none-any.whl
| Download URL | tiny_bclibc_wasm-0.0.4b1-py3-none-any.whl |
|---|---|
| Size | 80.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dc156d8d2682aaa5eb6cd2a34942f0595a3847d142a0ad6b5be6a45ed261653b
|
|
BLAKE2b-256 checksum How to use checksums |
f9a766714cad902dd5089a1276cefea71b4431134ffd287740e3910079de610f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency log