Skip to content

Rust + KID pivot — M2 Part 1: mora-core#42

Merged
halgari merged 15 commits intomasterfrom
m2-mora-core
Apr 21, 2026
Merged

Rust + KID pivot — M2 Part 1: mora-core#42
halgari merged 15 commits intomasterfrom
m2-mora-core

Conversation

@halgari
Copy link
Copy Markdown
Owner

@halgari halgari commented Apr 21, 2026

Summary

Fleshes out mora-core per
docs/superpowers/plans/2026-04-21-rust-kid-pivot-plan-4-mora-core.md:

  • FormId / FullFormId newtypes with hex Display, parts helpers, serde.
  • Patch enum + PatchFile postcard format (magic MORA, version 1,
    load_order_hash, Vec). Patch::AddKeyword is the sole variant
    at this plan; enum is append-only for serialization stability.
  • Distributor trait + DistributorStats with AddAssign. Placeholder
    EspWorld marker that Plan 5 replaces with the real mora-esp view.
  • PatchSink with exact-match dedup + stable sort on finalize.
  • DeterministicChance — full KID chance pipeline:
    • szudzik.rs — Szudzik pairing
    • splitmix.rs — SplitMix64 state init (golden-verified vs. reference C)
    • xoshiro.rs — Xoshiro256** one-shot (golden-verified vs. reference C;
      my plan had the wrong golden value, subagent corrected via ref C impl)
    • msvc_uniform.rs — MSVC uniform_real_distribution<float> port
    • fnv.rs — FNV-1a-32 inlined (standard vectors for "a", "foobar")
    • mod.rs — high-level passes(kw, form_id, chance) + distribution
      sanity (10k samples at chance=50 → 47-53% pass rate)
  • docs/src/mora-core-reference.md — source-of-truth for all the above.

No ESP parsing, no SKSE interaction, no unsafe, no new workspace deps.

Test plan

  • cargo test --workspace72 tests pass (27 from M1 + 45 new).
  • cargo clippy --all-targets -- -D warnings clean.
  • cargo fmt --check clean.
  • cargo xwin check --target x86_64-pc-windows-msvc --workspace clean.
  • Self-hosted skyrim-integration will fail until the Unraid runner image is refreshed (Plan 3 merge noted this).

Notable implementation details

  • Xoshiro golden value was wrong in my plan. The correct first-u64 for
    SplitMix64(0)-seeded state is 0x99EC5F36CB75F2B4 (subagent verified by
    running the reference C implementation from prng.di.unimi.it). Test
    uses the correct value.
  • FNV-1a-32 is inlined (~5 lines). No fnv crate dep.
  • EspWorld is a placeholder struct in mora-core::distributor
    empty unit struct that Plan 5 will replace.

Next up

Plan 5: mora-esp — mmap ESP/ESL/ESM reader, TES4 header parsing,
plugins.txt loader, load-order resolution, record iterators.

halgari added 15 commits April 20, 2026 20:54
Delivers mora-core's full surface: FormId newtypes, Patch enum +
PatchFile postcard format, Distributor trait with placeholder EspWorld
(Plan 5 replaces), PatchSink dedup/sort/finalize, DeterministicChance
with inlined FNV-1a-32 + Szudzik + SplitMix64 + Xoshiro256** + MSVC
uniform_real_distribution<float> port.

Pure Rust, no unsafe, no platform-specific code. Bit-identity vs. real
KID verified in M4's golden-harness plan.

14 tasks across 7 phases. No new workspace deps.
Source of truth for FormId, PatchFile postcard format, Patch enum
serialization stability, Distributor trait, PatchSink semantics,
and the KID-bit-compatible deterministic chance algorithm (Szudzik
pairing + FNV-1a + SplitMix64 + Xoshiro256** + MSVC float draw).
Cited by subsequent tasks when defining types.
mora-core/src: lib.rs declares form_id/patch/distributor/patch_sink/
chance modules, each populated in its own task. All stubs are
minimally typed so cargo check stays green through Plan 4.
FormId wraps u32 with local_id / mod_index / from_parts helpers and
hex Display. FullFormId holds (plugin, local_id) for load-order-
independent references; resolved to FormId by mora-esp in Plan 5.
5 unit tests cover parts decomposition, construction, and display.
Patch has one variant (AddKeyword) at this plan; future variants
must be appended (postcard serializes by declaration order).
PatchFile wraps magic + version + load_order_hash + Vec<Patch> with
to_bytes / from_bytes that validate magic and version on parse.
Opcode_tag + target accessors for PatchSink sorting.
Four tests: empty file, one AddKeyword, bad magic rejected, future
version rejected. Exercises the postcard pipeline end-to-end.
Trait: name + lower(world, chance, sink) -> Result<Stats, Error>.
Stats: rules/candidates/patches/rejected counts + AddAssign for
summing across frontends. EspWorld is a placeholder marker; Plan 5
replaces with mora_esp::EspWorld.
HashSet-backed dedup rejects identical patches; finalize sorts by
(opcode_tag, target) and wraps in a PatchFile with magic/version/
load_order_hash. 5 unit tests.
Ports clib_util::hash::szudzik_pair. Wrapping arithmetic matches C++
uint64 overflow semantics. 6 unit tests: identity, greater-branch,
less-branch, equal-case, asymmetry, overflow determinism.
Seeds 4-word Xoshiro state from a single u64 via 4 SplitMix64 steps.
Matches the canonical algorithm at prng.di.unimi.it/splitmix64.c.
3 tests: first-4-outputs for seed=0 (golden values), determinism,
distinct seeds produce distinct states.
Matches https://prng.di.unimi.it/xoshiro256starstar.c. Plan 4 only
needs a single next_u64 per chance roll (no stream state). 3 tests:
first-output-from-seed-0 (golden), determinism, state divergence.

Golden value corrected to 0x99EC5F36CB75F2B4 — verified against
reference C implementation; spec contained an incorrect value.
Four-step port matching MSVC STL on x64:
 1. raw_u64 as f64 / 2^64
 2. narrow to f32 (IEEE 754 round-to-nearest)
 3. >=1.0 -> 0.0 snap (MSVC generate_canonical guard)
 4. multiply by 100.0 in f32
Four tests: raw=0 -> 0.0, raw=u64::MAX snapped -> 0.0, raw=2^63 ->
50.0 (exact), range invariant for several raws.
Matches KID's clib_util::hash::fnv1a_32. Standard algorithm:
offset basis 0x811c9dc5, prime 0x01000193, byte-by-byte, u32
wrapping arithmetic. 5 tests: empty input -> offset basis,
'a' golden value, 'foobar' golden value, determinism, distinct
outputs.
passes(keyword, form_id, chance) -> bool: full KID pipeline
(FNV-1a-32 + Szudzik + SplitMix + Xoshiro + MSVC draw + threshold compare).
Chance=0 always fails, chance>=100 always passes (trivial paths).
6 tests: trivial paths, determinism, keyword variation, range
invariant over 1000 form_ids, distribution sanity at chance=50
(47-53% pass rate over 10k rolls).

M4's golden harness validates bit-identity against a real KID run
once the self-hosted runner image is refreshed.
Whitespace normalization across mora-core. No functional changes.
@halgari halgari merged commit a23ab7f into master Apr 21, 2026
11 of 12 checks passed
@halgari halgari deleted the m2-mora-core branch April 21, 2026 03:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant