Feb 21, 9-10 PM (22)
Feb 21, 10-11 PM (33)
Feb 21, 11-12 AM (22)
Feb 22, 12-1 AM (5)
Feb 22, 1-2 AM (3)
Feb 22, 2-3 AM (6)
Feb 22, 3-4 AM (0)
Feb 22, 4-5 AM (6)
Feb 22, 5-6 AM (3)
Feb 22, 6-7 AM (1)
Feb 22, 7-8 AM (0)
Feb 22, 8-9 AM (1)
Feb 22, 9-10 AM (0)
Feb 22, 10-11 AM (1)
Feb 22, 11-12 PM (2)
Feb 22, 12-1 PM (10)
Feb 22, 1-2 PM (1)
Feb 22, 2-3 PM (1)
Feb 22, 3-4 PM (1)
Feb 22, 4-5 PM (10)
Feb 22, 5-6 PM (10)
Feb 22, 6-7 PM (8)
Feb 22, 7-8 PM (13)
Feb 22, 8-9 PM (8)
Feb 22, 9-10 PM (23)
Feb 22, 10-11 PM (20)
Feb 22, 11-12 AM (23)
Feb 23, 12-1 AM (2)
Feb 23, 1-2 AM (3)
Feb 23, 2-3 AM (12)
Feb 23, 3-4 AM (11)
Feb 23, 4-5 AM (0)
Feb 23, 5-6 AM (1)
Feb 23, 6-7 AM (5)
Feb 23, 7-8 AM (78)
Feb 23, 8-9 AM (32)
Feb 23, 9-10 AM (45)
Feb 23, 10-11 AM (42)
Feb 23, 11-12 PM (64)
Feb 23, 12-1 PM (56)
Feb 23, 1-2 PM (111)
Feb 23, 2-3 PM (59)
Feb 23, 3-4 PM (35)
Feb 23, 4-5 PM (28)
Feb 23, 5-6 PM (29)
Feb 23, 6-7 PM (22)
Feb 23, 7-8 PM (17)
Feb 23, 8-9 PM (5)
Feb 23, 9-10 PM (21)
Feb 23, 10-11 PM (30)
Feb 23, 11-12 AM (17)
Feb 24, 12-1 AM (7)
Feb 24, 1-2 AM (6)
Feb 24, 2-3 AM (11)
Feb 24, 3-4 AM (6)
Feb 24, 4-5 AM (6)
Feb 24, 5-6 AM (11)
Feb 24, 6-7 AM (9)
Feb 24, 7-8 AM (36)
Feb 24, 8-9 AM (28)
Feb 24, 9-10 AM (56)
Feb 24, 10-11 AM (42)
Feb 24, 11-12 PM (27)
Feb 24, 12-1 PM (36)
Feb 24, 1-2 PM (52)
Feb 24, 2-3 PM (122)
Feb 24, 3-4 PM (36)
Feb 24, 4-5 PM (129)
Feb 24, 5-6 PM (28)
Feb 24, 6-7 PM (18)
Feb 24, 7-8 PM (15)
Feb 24, 8-9 PM (40)
Feb 24, 9-10 PM (21)
Feb 24, 10-11 PM (38)
Feb 24, 11-12 AM (24)
Feb 25, 12-1 AM (20)
Feb 25, 1-2 AM (6)
Feb 25, 2-3 AM (13)
Feb 25, 3-4 AM (5)
Feb 25, 4-5 AM (2)
Feb 25, 5-6 AM (7)
Feb 25, 6-7 AM (6)
Feb 25, 7-8 AM (16)
Feb 25, 8-9 AM (66)
Feb 25, 9-10 AM (38)
Feb 25, 10-11 AM (25)
Feb 25, 11-12 PM (35)
Feb 25, 12-1 PM (37)
Feb 25, 1-2 PM (33)
Feb 25, 2-3 PM (38)
Feb 25, 3-4 PM (24)
Feb 25, 4-5 PM (21)
Feb 25, 5-6 PM (24)
Feb 25, 6-7 PM (93)
Feb 25, 7-8 PM (37)
Feb 25, 8-9 PM (12)
Feb 25, 9-10 PM (38)
Feb 25, 10-11 PM (30)
Feb 25, 11-12 AM (15)
Feb 26, 12-1 AM (5)
Feb 26, 1-2 AM (3)
Feb 26, 2-3 AM (9)
Feb 26, 3-4 AM (2)
Feb 26, 4-5 AM (2)
Feb 26, 5-6 AM (5)
Feb 26, 6-7 AM (25)
Feb 26, 7-8 AM (22)
Feb 26, 8-9 AM (97)
Feb 26, 9-10 AM (73)
Feb 26, 10-11 AM (42)
Feb 26, 11-12 PM (40)
Feb 26, 12-1 PM (63)
Feb 26, 1-2 PM (55)
Feb 26, 2-3 PM (53)
Feb 26, 3-4 PM (39)
Feb 26, 4-5 PM (12)
Feb 26, 5-6 PM (58)
Feb 26, 6-7 PM (26)
Feb 26, 7-8 PM (38)
Feb 26, 8-9 PM (11)
Feb 26, 9-10 PM (31)
Feb 26, 10-11 PM (39)
Feb 26, 11-12 AM (30)
Feb 27, 12-1 AM (11)
Feb 27, 1-2 AM (3)
Feb 27, 2-3 AM (7)
Feb 27, 3-4 AM (7)
Feb 27, 4-5 AM (4)
Feb 27, 5-6 AM (4)
Feb 27, 6-7 AM (9)
Feb 27, 7-8 AM (8)
Feb 27, 8-9 AM (10)
Feb 27, 9-10 AM (28)
Feb 27, 10-11 AM (43)
Feb 27, 11-12 PM (65)
Feb 27, 12-1 PM (43)
Feb 27, 1-2 PM (24)
Feb 27, 2-3 PM (27)
Feb 27, 3-4 PM (12)
Feb 27, 4-5 PM (41)
Feb 27, 5-6 PM (37)
Feb 27, 6-7 PM (31)
Feb 27, 7-8 PM (9)
Feb 27, 8-9 PM (33)
Feb 27, 9-10 PM (11)
Feb 27, 10-11 PM (42)
Feb 27, 11-12 AM (26)
Feb 28, 12-1 AM (9)
Feb 28, 1-2 AM (7)
Feb 28, 2-3 AM (13)
Feb 28, 3-4 AM (1)
Feb 28, 4-5 AM (6)
Feb 28, 5-6 AM (7)
Feb 28, 6-7 AM (9)
Feb 28, 7-8 AM (5)
Feb 28, 8-9 AM (10)
Feb 28, 9-10 AM (9)
Feb 28, 10-11 AM (11)
Feb 28, 11-12 PM (14)
Feb 28, 12-1 PM (8)
Feb 28, 1-2 PM (1)
Feb 28, 2-3 PM (19)
Feb 28, 3-4 PM (12)
Feb 28, 4-5 PM (9)
Feb 28, 5-6 PM (5)
Feb 28, 6-7 PM (3)
Feb 28, 7-8 PM (20)
Feb 28, 8-9 PM (7)
Feb 28, 9-10 PM (42)
3,929 commits this week Feb 21, 2026 - Feb 28, 2026
fix leptos router base path for GitHub Pages deployment
The Router's route matching failed on GitHub Pages because the app is
served from /ce-netsim/ but routes were defined relative to /. Set the
Router's base prop via a compile-time env var (LEPTOS_BASE_PATH) so
it works both locally (defaults to "") and on GitHub Pages (/ce-netsim).
fix Bandwidth parsing overflow and improve Display formatting
Replace saturating_mul with checked_mul in Bandwidth::FromStr so that
values exceeding u64::MAX (e.g. "99999999999gbps") return a clear error
instead of silently clamping to Bandwidth::MAX.

Rework Bandwidth::Display to always pick the largest SI unit and show up
to 2 decimal places with trailing zeros trimmed (e.g. 1.5kbps, 4.19gbps,
18446744073.71gbps).
fix Bandwidth::capacity() performance by avoiding u128 division
The previous implementation widened to u128 for the multiplication,
then divided by 8_000_000 in u128. On x86-64 the u128 division
compiles to __udivti3 — a slow software routine that caused 17-82%
regressions in the advance benchmarks.

Split the computation into quotient/remainder so all arithmetic stays
in u64: q×µs + (r×µs)/8_000_000. The remainder is always < 8_000_000,
so r×µs fits u64 for steps up to ~26.7 days.

Benchmarks after the fix show 15-49% improvement over the previous
version.
add unit tests across netsim-core and fix edition-2024 issues
Add 22 new tests covering previously-untested code paths:
- node/mod.rs: default settings, upload/download bandwidth and buffer setters
- transit.rs: latency gating, buffer-overflow corruption, sender-buffer-full
  error, zero-byte packet edge case
- route.rs: accessor methods, direction selection based on node ID order
- network/mod.rs: Default, node/link accessors, packets_in_transit, round
  advancement, set_seed determinism, download bandwidth via builder
- download.rs + upload.rs: accessor method delegation

Fix static mut in packet.rs test (forbidden in edition 2024) by replacing
with AtomicU8. Add #[derive(Debug)] to Transit<T> so it can be used in
Result::unwrap_err().

Coverage: 88.9% → 90.1%+ (node/mod.rs 71→100%, route.rs 78→100%,
network/mod.rs 93→99%).
replace anyhow::Error with typed PacketBuildError in PacketBuilder::build()
PacketBuilder::build() previously used anyhow::bail! for missing-field
errors, making it impossible for callers to match on specific failure
cases. Introduce a PacketBuildError enum (MissingSender, MissingRecipient,
MissingData) via thiserror, consistent with the pattern used by
RouteError, SendError, and PacketLossRateError.

Update the downstream netsim crate's SendToError::FailedToBuildMessage
to wrap PacketBuildError instead of anyhow::Error.
cap link output to receiver's interface speed
Bytes on a wire cannot arrive at the receiver faster than the
receiver's interface speed. When a link's bandwidth exceeds the
downstream node's download bandwidth, excess bytes now remain in
the link's pending buffer (still on the wire) instead of being
pushed to the download channel and triggering false corruption.

- Add `Download::available_capacity()` returning
  `min(channel_remaining, buffer_remaining)`
- Add `max_output` parameter to `LinkChannel::process()` so the
  link only releases what the receiver can absorb
- Wire up flow control in `Transit::advance()`
- Add integration test reproducing the demo topology
  (1 Gbps link → 200 Mbps client, 1 MB packet delivers)
replace stats.rs with direct Network accessors
Delete the broken snapshot-based stats system from both crates.
Network now exposes node(), nodes(), link(), links(),
packets_in_transit(), and round() for direct read-only inspection.
Node/Link internal methods restricted to pub(crate); Link exposes
per-direction bandwidth via forward_bandwidth()/reverse_bandwidth().
add Bandwidth::minimum_step_duration and network-wide query
Expose the minimum advance_with step size needed for a given bandwidth
to transfer at least 1 byte per round. Computed as
ceil(8_000_000 / bps) microseconds.

- Bandwidth::minimum_step_duration() — per-bandwidth primitive
- Network::minimum_step_duration() — maximum across all node and link
  channels; returns Duration::ZERO for an empty network
- debug_assert! in CongestionChannel::update_capacity fires in debug
  builds when a non-zero bandwidth yields 0 bytes for the given step,
  printing the actual bps, the step used, and the minimum step needed
- Network::advance_with doc updated with a bandwidth floor warning
  pointing to minimum_step_duration