Skip to main content

tiny-bclibc-wasm

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.

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
jscontext Pythonista (iOS) JavaScriptCore's JSContext through objc_util
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)
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. 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 the JS hosts run one shared glue snippet, and wasmtime calls the exports directly. tests/test_hosts.py checks that every host available on the machine returns bit-identical results.

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 runtime
uv run pytest --wasm-runtime node          # ... on one runtime: wasmtime | wasm3 | node | 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

If the runtime passed to --wasm-runtime 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.

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

Copy src/tiny_bclibc/ (with the built .wasm files) into Pythonista, next to your script, then import tiny_bclibc as bc. JSContext is picked automatically. The package is plain Python and needs no dependencies.

Differences from the natmod

  • Shot/Wind/Config/Request hold 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, ...), but buf is None.
  • integrate_stream runs 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.

Release files for tiny-bclibc-wasm 0.0.1.post1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tiny-bclibc-wasm 0.0.1.post1
File Size Uploaded
tiny_bclibc_wasm-0.0.1.post1.tar.gz 126.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for tiny-bclibc-wasm 0.0.1.post1
File Interpreter ABI Platform
tiny_bclibc_wasm-0.0.1.post1-py3-none-any.whl Python 3 none any Details

Total release size: 197.8 kB

Release files / tiny_bclibc_wasm-0.0.1.post1.tar.gz

Download URL tiny_bclibc_wasm-0.0.1.post1.tar.gz
Size 126.9 kB
Tags Source
SHA-256 checksum
How to use checksums
e7aa09b7f4420ad1fc29cd026f8e86eaef5d04e4b70bbe537b23d385cdbbc36f
BLAKE2b-256 checksum
How to use checksums
ca61269ad4cbec2b63517cef92479e5a089bf8c75795b3fa8fde90e813c58711
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 23, 2026.

Transparency log

Release files / tiny_bclibc_wasm-0.0.1.post1-py3-none-any.whl

Download URL tiny_bclibc_wasm-0.0.1.post1-py3-none-any.whl
Size 70.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
27ab1d2f02a0d1ea12437bc891bab736eb98ec8270dec6208b55a12bbaf219f1
BLAKE2b-256 checksum
How to use checksums
0375ce6e2a2f1d82b0cc831eb5b6641c9691c73dbfc490c1732decb19e5610b5
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 23, 2026.

Transparency log

Release history Release notifications | RSS feed

0.0.2

2 release files

This release

0.0.1.post1 This release

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page