From 63c33b777b199a05b1ceae54f157f857b3f635f5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 22:14:43 +0000 Subject: [PATCH 1/2] Take the site's copy from the README instead of writing new copy The front page was reading like a product page for the crate rather than like the crate. It opened on a slogan I made up ("physics and mathematics that check their own answers"), and the section under it was six invented cards with parallel punchy headings, which is the shape a landing page takes when nobody has anything specific to say. The repository already has 46,000 characters written in a voice, so the page should be using it. What changed: * The headline is now the crate's actual one-line description, and the standfirst is the README's opening paragraph verbatim. * The six invented cards are gone. In their place: "At a glance", which is the README's own table, and "Design", which is its five design bullets verbatim. * "Install and run something" is "Install"; "Where to go next" and "Three doors, depending on what you came for" are "Further reading", with the README's own descriptions of the guide, the map and the docs. * The four testing mechanisms now carry the README's wording rather than my compression of it. One of the invented cards was also wrong. It said adding a velocity to a time was "a compile-time-shaped error" -- `Quantity::add` checks the dimensions at run time and returns `Err`, which is what the quick-start snippet three sections down asserts. The claim is gone with the card. The hero's gradient headline is gone too. Nothing on the page is highlighted for emphasis it does not have. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01M6M2MuUNrr1XHjshdrXfwH --- site/index.html | 300 +++++++++++++++++++++++++----------------------- site/styles.css | 33 ++++++ 2 files changed, 189 insertions(+), 144 deletions(-) diff --git a/site/index.html b/site/index.html index bc49061..070ef97 100644 --- a/site/index.html +++ b/site/index.html @@ -3,11 +3,11 @@ -rust_physics_engine — checkable physics, mathematics and engineering in Rust - +rust_physics_engine + - + @@ -49,15 +49,17 @@
-

Zero dependencies · Rust 2021 · MIT

-

Physics and mathematics that
check their own answers

+

Zero dependencies · Rust 2021 · MIT

+

A zero-dependency Rust library for physics, + mathematics and engineering computation

- 6,365 public functions and methods across - 71 modules, and not one of them ships on - the strength of having run without panicking. Every routine here is written so - that something about it can be checked — against a closed form, a - conservation law, an independent implementation, or an exact identity over - integers. + The aim is not breadth for its own sake. Every routine here is written so that + something about it can be checked — against a closed form, against a + conservation law, against an independent implementation of the same quantity, or + against an exact identity over integers. A test that only asserts a function ran + is not evidence, and the test suite is built around that distinction. Where a + result is approximate the error has a stated bound; where it is exact the + assertion uses ==.

@@ -68,80 +70,53 @@

Physics and mathematics that
check their own answers
- 6,365public functions & methods + 6,365public functions and methods
71top-level modules
-
4,770tests passing
+
4,193unit tests
97.89%line coverage
-
20Kani proof harnesses
+
20Kani harnesses
0dependencies

- +
-

A test that only asserts a function ran is not evidence

-

- The aim is not breadth for its own sake. Breadth is what you get when the - thing you actually insist on — that every result has something to be - measured against — turns out to be possible in one field after another. -

+

At a glance

-
-
-

Stated error bounds

-

- Where a result is approximate the tolerance comes from the method's own - error term, not from a number tuned until the test went green. Where it is - exact the assertion uses ==. -

-
-
-

Failure you can tell apart

-

- Solvers return Result rather than panicking on - non-convergence, so a caller can distinguish “did not converge” from - “converged, to this”. -

-
-
-

NaN is rejected, not ignored

-

- Argument guards are written !(x > 0.0) rather than - x <= 0.0, so a NaN fails the check instead of sliding - through it. -

-
-
-

One table per constant

-

- Physical constants come from math::constants; anything - elsewhere is a re-export, and a test enforces the agreement. The values - fixed by the 2019 SI redefinition are exact. -

-
-
-

Dimensions in the type

-

- units::quantity carries dimensions alongside the number, so - adding a velocity to a time is a compile-time-shaped error rather than a - plausible-looking float. -

-
-
-

Exactness where it matters

-

- f64 throughout, except where it would be wrong to be: - exact works over arbitrary-precision integers and rationals, - and dimensional null spaces are computed over Rational. -

-
+
+ + + + + + + + + + + + + + + + + + + + + + + +
Public functions and methods6,365 (4,124 free functions, 2,241 methods)
Public types431 structs, enums and traits
Top-level modules71 public, across 296 source files — see the + module map
Source266,122 lines of Rust
Unit tests4,193
Property tests577, across 49 files
Line coverage97.89% (174,685 lines, 3,681 uncovered)
Function coverage99.33% (20,200 functions, 136 uncovered)
Formal verification20 Kani harnesses (13 in CI, 7 behind kani-slow)
Undefined behaviourMiri-clean; the crate contains no unsafe
API documentationevery module carries a //! summary; cargo doc is + warning-free, and CI denies rustdoc warnings
Dependenciesnone — Cargo.lock holds exactly one package
Edition2021, f64 throughout
@@ -150,11 +125,7 @@

Exactness where it matters

-

Install and run something

-

- The Rust snippet below is examples/readme_quickstart.rs, - compiled and run by CI on every push, so it cannot drift out of date. -

+

Install

@@ -181,14 +152,12 @@

Install and run something

-
-

Nothing else comes with it

-

- Cargo.lock holds exactly one package: this crate. No - transitive tree to audit, no version resolution to lose an afternoon to, - and no build script. -

-
+

Quick start

+

+ This snippet is + examples/readme_quickstart.rs, + compiled and run by CI, so it cannot drift out of date. +

@@ -209,7 +178,7 @@

Nothing else comes with it

// Quantities carry their dimensions, and addition checks them. let v = Quantity::new(3.0, Dim::new(1, 0, -1, 0, 0, 0, 0)); // m/s let t = Quantity::new(2.0, Dim::TIME); -let d = v.mul(&t).unwrap(); // 6 m — a length, exactly +let d = v.mul(&t).unwrap(); // 6 m, a length, exactly assert!(v.add(&t).is_err()); // a velocity is not a time // Exact rational arithmetic over arbitrary-precision integers. @@ -227,17 +196,17 @@

Nothing else comes with it

What's in it

- 71 public modules across - 296 source files. Search by name or by - subject — the summaries are the first line of each module's own - documentation, and the figures are parsed from the sources rather than - written down. Press / to jump to the filter. + 71 top-level modules, across + 296 source files. Summaries are the first + sentence of each module's //! documentation and the figures are + parsed from the sources, so nothing here is transcribed. Press / + to filter.

@@ -245,7 +214,7 @@

What's in it

- Module data is generated at build time — see + Module data is generated at build time — see the module map.

@@ -265,58 +234,61 @@

How it's tested

Unit tests 4,193

Every one asserts something checkable: a closed form, a conservation law, a - known reference value, or an exact identity — with a tolerance derived from - the method's error term. + known reference value, or an exact identity. Approximate results carry a + stated tolerance derived from the method's error term, not one tuned until + the test passed.

Property tests 577

- Randomised tests over invariants that must hold for every input: - transform round-trips, algebraic laws, leapfrog FDTD energy conservation, - and agreement between two independent implementations. + Randomized tests over invariants that must hold for every input, not + for a chosen example. They are what catch the cases nobody thought to write + down: round-trips, algebraic laws, conservation, and agreement between two + independent implementations of the same quantity.

-

Kani 20 harnesses

+

Formal verification 20 harnesses

- Model checking with CBMC: over a bounded input domain it proves - absence of panics, overflow and division by zero rather than sampling for - them. Thirteen run in CI; seven slower ones sit behind a feature flag. + Kani model-checks with CBMC: for a bounded input domain it proves absence of + panics, overflow and division by zero, rather than sampling. Thirteen run in + CI; seven that CBMC cannot decide in a CI-sized budget sit behind the + kani-slow feature.

-

Miri strict provenance

+

Miri no unsafe

- The crate contains no unsafe, so Miri is a backstop rather - than the primary check. It runs against core::, where index - and slice reasoning is densest. + The crate contains no unsafe, so Miri is a backstop rather than + the primary check. It runs against core:: — the interval and + dual-number arithmetic, where index and slice reasoning is densest — under + -Zmiri-strict-provenance.

-

Why 97.89% coverage is not the point

+

Why the coverage number is not the point

Coverage says a line ran, not that anything checked what it did. Some of the worst gaps this project has found were in lines with full coverage:

  • - The strong Lucas test — the second half of Baillie–PSW — was replaced - wholesale with return true and the entire suite stayed green. - Composites are rejected by Miller–Rabin first, so the Lucas branch is - essentially never reached. It is now tested directly against the strong - Lucas pseudoprimes below 20,000, computed independently rather than read - off the implementation. + The strong Lucas test — the second half of Baillie–PSW — + was replaced wholesale with return true, and the entire suite + stayed green. Composites are rejected by Miller–Rabin first, so the Lucas + branch is essentially never reached in practice. It is now tested directly + against the strong Lucas pseudoprimes below 20,000 (5459, 5777, 10877, 16109, + 18971), computed independently rather than read off the implementation.
  • - Knuth's add-back correction in BigInt division, taken - for roughly two divisions in a billion. + Knuth's add-back correction in BigInt division, taken for + roughly two divisions in a billion.
  • - The Lentz convergence threshold in the continued-fraction - evaluators. + The Lentz convergence threshold in the continued-fraction evaluators.

@@ -326,16 +298,55 @@

Why 97.89% coverage is not the point

+ +
+
+
+

Design

+
+
    +
  • + Zero dependencies. Cargo.lock holds exactly one package: + this crate. +
  • +
  • + f64 throughout, except where exactness is the point — + exact works over arbitrary-precision integers and rationals, and + units::dimensional computes null spaces over Rational + because a group is exactly dimensionless or it is not. +
  • +
  • + Input validation. Public functions validate their inputs: positive mass, + non-zero denominators, physical bounds. Solvers return Result + rather than panicking on non-convergence. The guards are written + !(x > 0.0) rather than x <= 0.0 so that NaN is + rejected too. +
  • +
  • + One table per constant. Physical constants come from + math::constants; duplicates elsewhere are re-exports, and a test + enforces the agreement. The values fixed by the 2019 SI redefinition are exact. +
  • +
  • + Documented specification choices. Where a definition is genuinely + ambiguous — the Frobenius number with a unit coin, Stern–Brocot + indexing, which parenthesization a unit string means — the reading is + stated in the doc comment rather than left implicit. +
  • +
+
+
+ -
+

From Python

- Published to PyPI as numeria: 4,086 of the crate's 4,149 - free functions, 2,254 of its 2,277 methods, 416 of its 426 types and every - constant. The bindings are generated from this crate's source, so they cannot - fall behind it. + Published to PyPI as numeria: 4,086 of this crate's 4,149 free + functions, 2,254 of its 2,277 methods, 416 of its 426 types and every constant. + The bindings are generated from this crate's source, so they cannot fall behind + it.

@@ -357,57 +368,58 @@

From Python

numeria.
  • - Result errors become exceptions under one - PhysicsError root. + Result errors become exceptions under one PhysicsError + root.
  • - A Vec3 argument accepts a plain (x, y, z) tuple. + A Vec3 argument accepts (x, y, z).
  • - Complex, BigInt and Rational cross - over as Python's own complex, int and + Complex, BigInt and Rational cross over + as Python's own complex, int and fractions.Fraction.
  • - Anywhere the library takes a &dyn Fn, a Python callable - will do. + Anywhere the library takes a &dyn Fn, a Python callable will + do.
  • - -
    + +
    -

    Where to go next

    -

    Three doors, depending on what you came for.

    +

    Further reading

    -

    The guide →

    +

    The guide

    - A walk through the library by doing things with it: putting a spacecraft in - orbit, pulling a tone out of noise, solving a differential equation and - proving the answer converges at the predicted rate. Every code block is a - file CI compiles and runs. + Start here. A walk through the library by doing things with it: putting + a spacecraft in orbit, pulling a tone out of noise, solving a differential + equation and proving the answer converges at the predicted rate, and the tools + for not being wrong. Every code block is a file in examples/ that + CI compiles and runs, and the output shown is what it actually prints.

    -

    The module map →

    +

    The module map

    - A generated map of all 71 public modules - and every file beneath them, with line counts, public-item counts and - summaries. Produced from the sources; CI fails if it is out of date. + A generated map of every one of the 295 modules: a size-annotated tree, tables + by subject area, and a row per module with its line count, public item counts + and summary. Produced from the sources, so nothing in it is transcribed; CI + fails if it is out of date.

    -

    The API →

    +

    The API documentation

    - Full rustdoc, built from this commit. Every module carries a summary, and - CI builds the documentation with -D warnings, so a broken - intra-doc link fails the build rather than shipping. + The crate root carries an orientation page and every module has a summary. + Built here from the current commit with -D warnings, the same + flag CI uses, so a broken intra-doc link fails the build rather than shipping.

    diff --git a/site/styles.css b/site/styles.css index 6984119..0cef8f3 100644 --- a/site/styles.css +++ b/site/styles.css @@ -182,6 +182,10 @@ h1 .grad { font-size: 1.13rem; color: var(--fg-muted); max-width: 62ch; margin: 0 0 32px; } .lede strong { color: var(--fg); font-weight: 600; } +.lede code { + font-family: var(--mono); font-size: .87em; color: var(--fg); + background: var(--panel-2); padding: .08em .35em; border-radius: 4px; +} .cta { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 40px; } .btn { @@ -446,3 +450,32 @@ td code, th code { white-space: nowrap; } .site-footer nav { display: flex; flex-wrap: wrap; gap: 20px; font-size: .87rem; align-items: flex-start; } .site-footer nav a { color: var(--fg-muted); } .site-footer nav a:hover { color: var(--fg); } + + +/* The front page's h1 is the crate's one-line description rather than a + slogan, so it needs a smaller step than a headline would. */ +.lede-h1 { font-size: clamp(1.75rem, 3.8vw, 2.75rem); line-height: 1.14; } + +/* A subheading and a paragraph inside a column that is not .prose. */ +h3.sub { font-size: 1rem; font-weight: 620; margin: 26px 0 8px; } +.prose-p { margin: 0; color: var(--fg-muted); font-size: .93rem; } +.prose-p code { font-family: var(--mono); font-size: .87em; } + +/* The At a glance table is a two-column definition list, so its row headers + sit in the first column rather than along the top. */ +.inner > .table-wrap th { + width: 1%; white-space: nowrap; vertical-align: top; + font-weight: 600; color: var(--fg); +} +.inner > .table-wrap td { color: var(--fg-muted); } +.inner > .table-wrap code { + font-family: var(--mono); font-size: .87em; + background: var(--panel-2); padding: .08em .35em; border-radius: 4px; +} + +/* One column of running text rather than a card grid. */ +.checklist.wide li { max-width: 82ch; } +.checklist code { + font-family: var(--mono); font-size: .87em; + background: var(--panel-2); padding: .08em .35em; border-radius: 4px; +} From 36af08e58c6150faac6685e5c7020ca5344c78b8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 22:31:08 +0000 Subject: [PATCH 2/2] Drop the em dashes from the site's own copy Rewritten with colons, commas, parentheses and full stops instead. The changes are to punctuation only, no claim on the page changed: checked - against a closed form -> checked: against a closed form 296 source files - see the map -> 296 source files (see the map) none - Cargo.lock holds one package -> none. Cargo.lock holds one package the point - exact works over -> the point: exact works over ambiguous - the Frobenius number - the -> ambiguous (the Frobenius number), the The strong Lucas test - the second -> The strong Lucas test, the second half of Baillie-PSW - was replaced half of Baillie-PSW, was replaced The Miri card was reordered rather than repunctuated, since it had two parenthetical dashes stacked in one sentence. Also the page titles, which used " - " as a separator, now use a middot, and the empty-filter message and two file-header comments. En dashes stay where they join two names, Baillie-PSW and Stern-Brocot. Those are the README's own and are not em dashes. The one in tools/build_site.py was the fallback for a module with no //! summary, matching the fallback in gen_module_map.py. It is now a plain hyphen. It never renders, since check_module_docs.py fails CI if any source file is missing its summary. The guide and module-map pages still carry 20, because they render docs/GUIDE.md and docs/MODULE_MAP.md as written. Those are source files, not site copy, so this leaves them alone. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01M6M2MuUNrr1XHjshdrXfwH --- README.md | 4 ++-- site/404.html | 2 +- site/index.html | 30 +++++++++++++++--------------- site/main.js | 4 ++-- site/styles.css | 2 +- tools/build_site.py | 4 ++-- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index a5ce8c1..0859140 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ A zero-dependency Rust library for physics, mathematics and engineering computation. -**[rust-physics-engine.mimsec.com](https://rust-physics-engine.mimsec.com)** — the +**[rust-physics-engine.mimsec.com](https://rust-physics-engine.mimsec.com)** has the guide, a searchable index of all 71 modules, and the full API documentation built from the current commit. @@ -982,7 +982,7 @@ python3 -m http.server --directory _site ``` That builds everything except `/api`, which is `cargo doc --no-deps` output -copied to `_site/api` — the workflow does it in one more step. +copied to `_site/api`. The workflow does it in one more step. ## License diff --git a/site/404.html b/site/404.html index 29510ef..c0aab8d 100644 --- a/site/404.html +++ b/site/404.html @@ -3,7 +3,7 @@ -Not found — rust_physics_engine +Not found · rust_physics_engine diff --git a/site/index.html b/site/index.html index 070ef97..a9f3e44 100644 --- a/site/index.html +++ b/site/index.html @@ -54,7 +54,7 @@

    A zero-dependency Rust library for physics, mathematics and engineering computation

    The aim is not breadth for its own sake. Every routine here is written so that - something about it can be checked — against a closed form, against a + something about it can be checked: against a closed form, against a conservation law, against an independent implementation of the same quantity, or against an exact identity over integers. A test that only asserts a function ran is not evidence, and the test suite is built around that distinction. Where a @@ -98,8 +98,8 @@

    At a glance

    Public types 431 structs, enums and traits Top-level modules - 71 public, across 296 source files — see the - module map + 71 public, across 296 source files (see the + module map) Source266,122 lines of Rust Unit tests4,193 Property tests577, across 49 files @@ -113,7 +113,7 @@

    At a glance

    every module carries a //! summary; cargo doc is warning-free, and CI denies rustdoc warnings Dependencies - none — Cargo.lock holds exactly one package + none. Cargo.lock holds exactly one package Edition2021, f64 throughout @@ -206,7 +206,7 @@

    What's in it

    @@ -214,7 +214,7 @@

    What's in it

    - Module data is generated at build time — see + Module data is generated at build time. See the module map.

    @@ -261,9 +261,9 @@

    Formal verification 20 harnesses

    Miri no unsafe

    The crate contains no unsafe, so Miri is a backstop rather than - the primary check. It runs against core:: — the interval and - dual-number arithmetic, where index and slice reasoning is densest — under - -Zmiri-strict-provenance. + the primary check. It runs under -Zmiri-strict-provenance against + core::, the interval and dual-number arithmetic, where index and + slice reasoning is densest.

    @@ -276,8 +276,8 @@

    Why the coverage number is not the point

    • - The strong Lucas test — the second half of Baillie–PSW — - was replaced wholesale with return true, and the entire suite + The strong Lucas test, the second half of Baillie–PSW, was + replaced wholesale with return true, and the entire suite stayed green. Composites are rejected by Miller–Rabin first, so the Lucas branch is essentially never reached in practice. It is now tested directly against the strong Lucas pseudoprimes below 20,000 (5459, 5777, 10877, 16109, @@ -310,7 +310,7 @@

      Design

      this crate.
    • - f64 throughout, except where exactness is the point — + f64 throughout, except where exactness is the point: exact works over arbitrary-precision integers and rationals, and units::dimensional computes null spaces over Rational because a group is exactly dimensionless or it is not. @@ -329,9 +329,9 @@

      Design

    • Documented specification choices. Where a definition is genuinely - ambiguous — the Frobenius number with a unit coin, Stern–Brocot - indexing, which parenthesization a unit string means — the reading is - stated in the doc comment rather than left implicit. + ambiguous (the Frobenius number with a unit coin, Stern–Brocot indexing, + which parenthesization a unit string means), the reading is stated in the doc + comment rather than left implicit.
    diff --git a/site/main.js b/site/main.js index 6586cb6..93150c3 100644 --- a/site/main.js +++ b/site/main.js @@ -1,4 +1,4 @@ -/* rust_physics_engine — site behaviour. +/* rust_physics_engine site behaviour. No framework and no build step. Five independent pieces, each of which no-ops when the elements it wants are not on the page. */ @@ -195,7 +195,7 @@ return (m.name + ' ' + m.area + ' ' + m.summary).toLowerCase().indexOf(query) >= 0; }); grid.innerHTML = shown.length ? shown.map(card).join('') : - '

    No module matches that. Try a subject — ' + + '

    No module matches that. Try a subject: ' + 'wavelet, orbit, prime.

    '; if (counter) { counter.textContent = shown.length === window.MODULES.length diff --git a/site/styles.css b/site/styles.css index 0cef8f3..ca36dbd 100644 --- a/site/styles.css +++ b/site/styles.css @@ -1,4 +1,4 @@ -/* rust_physics_engine — site styles. +/* rust_physics_engine site styles. No webfonts and no framework: the crate has no dependencies and the site loads nothing it does not serve itself. Everything below is driven by the tokens in :root, which is also how the light theme is a re-declaration diff --git a/tools/build_site.py b/tools/build_site.py index 20ecd45..018c51c 100644 --- a/tools/build_site.py +++ b/tools/build_site.py @@ -70,7 +70,7 @@ def module_rows() -> tuple[list[dict], dict]: for top in sorted({m.split("::")[0] for m in mods if m != "lib"} & public): files = [m for name, m in mods.items() if name == top or name.startswith(f"{top}::")] - summary = mods.get(top, {}).get("summary", "—").replace(r"\|", "|") + summary = mods.get(top, {}).get("summary", "-").replace(r"\|", "|") rows.append({ "name": top, "area": area_of.get(top, "Other"), @@ -401,7 +401,7 @@ def build_doc_pages(out: str) -> None: """ with open(os.path.join(out, page), "w", encoding="utf-8") as fh: - fh.write(shell(f"{title} — rust_physics_engine", description, + fh.write(shell(f"{title} \u00b7 rust_physics_engine", description, article, page.replace(".html", "")))