Ljh684/moonnet-lab/src/sim does not have a README file

    Rng

    pub struct Rng {
    s0 : UInt64
    s1 : UInt64
    s2 : UInt64
    s3 : UInt64
    }

    The simulator's random source: xoshiro256**.

    Why hand-roll instead of using the standard library: a reproducible experiment must produce the same numbers on every backend and every future version of this package. The algorithm is frozen here, its reference vectors are pinned in the test suite, and nothing about it depends on the platform's word size or on how a host language seeds its own generator.

    Rng::fork

    fn Rng::fork(self : Rng, label : String) -> Rng

    Derive an independent stream from this generator plus a label.

    This is how the simulator gives every flow, every link and every loss process its own randomness without any of them sharing draw order: renaming a flow changes its stream, but adding an unrelated flow does not disturb it.

    Rng::new

    fn Rng::new(seed : UInt64) -> Rng

    Rng::next_bool

    fn Rng::next_bool(self : Rng, p : Double) -> Bool

    true with probability p. p <= 0 never fires and p >= 1 always does, which keeps scenario files free of edge-case surprises.

    Rng::next_double

    fn Rng::next_double(self : Rng) -> Double

    A uniform Double in [0, 1), built from the top 53 bits so every value is exactly representable and the generator never returns 1.0.

    Rng::next_double_between

    fn Rng::next_double_between(self : Rng, low : Double, high : Double) -> Double

    A uniform Double in [low, high).

    Rng::next_exponential

    fn Rng::next_exponential(self : Rng, mean : Double) -> Double

    An exponential sample with the given mean, from the inverse transform -mean * ln(U). Used for inter-arrival times and for burst gaps.

    Rng::next_int

    fn Rng::next_int(self : Rng, bound : Int) -> Int

    A uniform Int in [0, bound), using rejection sampling so that the distribution is exactly uniform rather than modulo-biased. Determinism is preserved because the number of rejections depends only on the sequence.

    Rng::next_int64

    fn Rng::next_int64(self : Rng, bound : Int64) -> Int64

    A uniform Int64 in [0, bound).

    Rng::next_u64

    fn Rng::next_u64(self : Rng) -> UInt64

    Scheduled

    pub(all) struct Scheduled {
    time : Time
    seq : Int
    tag : String
    action : () -> Unit
    }

    One unit of scheduled work.

    An event carries a human readable tag next to the closure so that a trace of a run can be read (and diffed) without a debugger.

    Sim

    pub struct Sim {
    now : Time
    seq : Int
    processed : Int
    recording : Bool
    record_events : Bool
    heap : Array[Scheduled]
    rng : Rng
    journal : Array[String]
    }

    The deterministic simulation core.

    A simulation is a pure function of its scenario and its seed. Sim owns virtual time, the event queue and the random source; nothing reads the wall clock and nothing depends on hash iteration order, so two runs of the same configuration produce byte-identical journals and metrics.

    Sim::after

    fn Sim::after(self : Sim, delay : Time, tag : String, action : () -> Unit) -> Unit

    Schedule action to run delay from now.

    Sim::at

    fn Sim::at(self : Sim, time : Time, tag : String, action : () -> Unit) -> Unit

    Schedule action at an absolute virtual time.

    Sim::is_recording

    fn Sim::is_recording(self : Sim) -> Bool

    Whether the journal is on.

    Callers that would have to build a string to describe an event should ask first. Building it unconditionally makes every run pay for a trace nobody asked for, and on a transfer with tens of thousands of segments that cost dominates everything else.

    Sim::journal

    fn Sim::journal(self : Sim) -> Array[String]

    The journal collected so far.

    Sim::new

    fn Sim::new(seed : UInt64) -> Sim

    Create a simulator whose randomness is derived from seed.

    Sim::note

    fn Sim::note(self : Sim, message : String) -> Unit

    Append a line to the journal, if journalling is on.

    Sim::now

    fn Sim::now(self : Sim) -> Time

    The current virtual time.

    Sim::pending

    fn Sim::pending(self : Sim) -> Int

    How many events are waiting.

    Sim::processed

    fn Sim::processed(self : Sim) -> Int

    How many events have run so far.

    Sim::rng

    fn Sim::rng(self : Sim) -> Rng

    The deterministic random source of this run.

    Sim::run

    fn Sim::run(self : Sim, max_events? : Int) -> Unit

    Drain the queue.

    max_events is a safety valve: a model bug that schedules events from inside an event handler could otherwise spin forever, and a hung experiment is worse than a loud failure.

    Sim::run_for

    fn Sim::run_for(self : Sim, duration : Time) -> Unit

    Run for duration of virtual time starting now.

    Sim::run_until

    fn Sim::run_until(self : Sim, deadline : Time) -> Unit

    Run every event up to deadline, leaving later events queued, and advance the clock to deadline so that a run of an empty period still moves time forward the way a real experiment would.

    Sim::schedule_at

    fn Sim::schedule_at(self : Sim, time : Time, tag : String, action : () -> Unit) -> Unit

    The single scheduling entry point.

    Scheduling into the past is a bug in the model, not a recoverable condition, so it aborts loudly with the offending tag instead of silently producing a result that looks plausible but cannot happen on a real wire.

    Sim::start_journal

    fn Sim::start_journal(self : Sim) -> Unit

    Turn on the event journal. Journalling costs memory, so a long run keeps it off unless the caller asks for a trace to inspect or to diff.

    Sim::start_trace

    fn Sim::start_trace(self : Sim) -> Unit

    Record explicit notes without retaining every scheduled event tag.

    This is useful for protocol traces that need state transitions but not the simulator's lower-level event schedule.

    Sim::step

    fn Sim::step(self : Sim) -> Bool

    Run the earliest event. Returns false when the queue is empty.

    Sim::step_until

    fn Sim::step_until(self : Sim, condition : () -> Bool, max_events? : Int) -> Bool

    Run until condition holds, and report whether it ever did.

    Draining the queue tells you the simulation is over; this tells you when the thing you were measuring actually happened. The distinction matters because a protocol leaves superseded timer events behind: they are ignored when they fire, but they are still events, and draining them pushes the clock past the moment of interest. Measuring a transfer by reading the clock after a full drain would report the last stale timer instead of the last byte.

    SplitMix64

    pub struct SplitMix64 {
    state : UInt64
    }

    SplitMix64, the seeding construction recommended for the xoshiro family.

    A single UInt64 seed is expanded into a well-mixed state so that seeds as similar as 1 and 2 still produce unrelated streams.

    SplitMix64::new

    fn SplitMix64::new(seed : UInt64) -> SplitMix64

    SplitMix64::next_u64

    fn SplitMix64::next_u64(self : SplitMix64) -> UInt64

    Time

    pub(all) struct Time {
    ps : Int64
    } derive(Compare, Eq, Hash,
    Debug
    )

    Simulation time is a signed 64-bit count of picoseconds.

    Picoseconds are small enough to express the serialization time of a single 64-byte frame on a 100 Gbps link (5_120 ps) without rounding, and large enough that one Int64 spans roughly 106 days of simulated time.

    Keeping time in an integer type is what makes experiments reproducible: every scheduling decision compares exact integers, never floating point values that could round differently on different backends.
    impl Add for Time
    impl Show for Time
    impl Sub for Time

    Time::fibre_distance_km

    fn Time::fibre_distance_km(self : Time) -> Double

    Distance in kilometres that light travels through fibre in t.

    Time::from_ms

    fn Time::from_ms(ms : Int64) -> Time

    Time::from_ms_f

    fn Time::from_ms_f(ms : Double) -> Time

    Fractional milliseconds, for scenario files where 12.5ms reads better than 12500us. The conversion truncates towards zero.

    Time::from_ns

    fn Time::from_ns(ns : Int64) -> Time

    Time::from_ps

    fn Time::from_ps(ps : Int64) -> Time

    Time::from_s

    fn Time::from_s(seconds : Int64) -> Time

    Time::from_us

    fn Time::from_us(us : Int64) -> Time

    Time::propagation

    fn Time::propagation(distance_km : Double, speed_km_per_s : Double) -> Time

    Propagation delay of distance_km kilometres over a medium where signals travel at speed_km_per_s (light in fibre is about 200_000 km/s).

    Time::render

    fn Time::render(self : Time) -> String

    Rendering picks the largest unit that keeps the value readable and always prints three fractional digits, using integer arithmetic so that the output is identical on every backend.

    Time::serialize

    fn Time::serialize(bytes : Int64, bps : Int64) -> Time

    Serialization time of bytes bytes on a link of bps bits per second.

    The result is rounded down to a whole picosecond, which is the only rounding the simulator ever performs. bytes * 8 * 10^12 must fit in Int64, so a single transmission may be at most about 1.1 MB; larger transfers are modeled as a series of packets.

    Time::to_ms_f

    fn Time::to_ms_f(self : Time) -> Double

    Milliseconds as a float, for metrics and plots only. Never feed the result back into scheduling.

    Time::to_ps

    fn Time::to_ps(self : Time) -> Int64

    Time::zero

    fn Time::zero() -> Time

    The instant a simulation starts.

    fnv1a64

    fn fnv1a64(text : String) -> UInt64

    FNV-1a over the UTF-8 bytes of a label, used to turn names into stable numbers. Any hash would do; what matters is that it never changes.

    mix64

    fn mix64(a : UInt64, b : UInt64) -> UInt64

    Combine two 64-bit values into one, so that the pair determines the result and the order matters.

    This is what gives a packet a fate derived from its identity: the same identity on the same link always produces the same number, whatever else happened on that link in between.

    rotl64

    fn rotl64(value : UInt64, shift : Int) -> UInt64