Conversation
Delivers mora-kid as a library: KID INI parser + KidDistributor producing Vec<Patch> from an EspWorld + rule set. Weapon + Armor only; NOT + MATCH filter buckets active (ALL/ANY log-and-skip for Plan 7); trait predicates parsed but subrecord-dependent evaluation deferred. 16 tasks across 10 phases. Also extends mora-esp with KYWD record type + EspWorld::keywords() + resolve_keyword_by_editor_id() so editor-ID references resolve to FormIds. Uses world.records(sig) directly in the distributor (not world.weapons()/armors()) to preserve source plugin_index — critical for ESL-slot plugins which share FormID high byte 0xFE.
- Add KYWD signature constant to signature.rs - Add keyword submodule to records/mod.rs - Implement KeywordRecord with EDID parsing in records/keyword.rs Enables keyword editor-ID resolution for mora-kid rule processing.
keywords() iterator yields (FormId, KeywordRecord) pairs. resolve_keyword_by_editor_id(edid) scans for a matching editor-ID (case-insensitive, matches KID iequals). Required by mora-kid for editor-ID reference resolution.
Modules: distributor, filter, ini, pipeline, reference, rule, traits_armor, traits_weapon. All stubs typed so cargo check stays green through Plan 6.
Reference::parse(str) dispatches on content: ~ (FormIdWithPlugin), .es (PluginName), all-hex (FormIdOnly), else (EditorId). resolve_form(world) maps to a FormId where possible (PluginName returns None — plugin filters are non-form refs). 6 unit tests including the all-hex-edid-vs-formid quirk that matches KID.
3 tests against synthetic KYWD plugin: resolve editor-ID, resolve FormIdWithPlugin, unknown editor-ID returns None.
RecordType enum distinguishes Weapon/Armor/Other(string). FilterBuckets carries MATCH/NOT (active) + ALL/ANY (parsed but unsupported at M3, distributor logs-and-skips). Traits is a small enum dispatching to per-type trait structs. SourceLocation tracked for diagnostics.
WeaponAnimType enum (10 weapon types). WeaponTraits struct with animation types, enchantment flag, template flag, damage range, weight range. parse_range helper reads D(min max) / W(min max). 7 tests covering empty/NONE/animations/flags/range/mixed/unknown/case.
ArmorType enum (Heavy/Light/Clothing). ArmorTraits struct with armor types, enchantment flag, template flag, AR range, weight range, body slots (30..=61). 6 tests.
ini::parse_file / parse_ini_content handle comments, sections, exclusive-group skip, per-line rule parsing. filter::parse_filter_field dispatches tokens to MATCH/NOT/ALL/ANY buckets by prefix. filter::evaluate runs NOT + MATCH (ALL/ANY log-and-skip at M3, activation in Plan 7). 10+ tests total.
Scans weapons + armors in the world, evaluates each rule's filter pipeline (NOT + MATCH active; ALL/ANY log-and-skip), runs KID-compatible deterministic chance roll seeded by (keyword_editor_id, form_id), emits Patch::AddKeyword. Unresolved keyword refs / unsupported record types log and skip. Stats accumulated per rule/candidate.
discover *_KID.ini in data_dir, parse all rules, build KidDistributor, run against world with KID-compatible chance, return finalized Vec<Patch>. This is the public entry that Plan 7's mora-cli will call to produce mora_patches.bin.
One integration test exercising comments, sections, simple/chance/ traits/filters rules, exclusive-group skip, unsupported-type, and missing-type-skipped-with-warning.
5 tests against synthetic ESP with 1 KYWD + 1 WEAP + 1 ARMO: - distributes to weapon - distributes to armor - unresolved keyword -> rule skipped - unsupported record type -> rule skipped - chance=0 -> nothing emitted Exercises full mora-kid pipeline end-to-end with real mora-esp.
Changes: - Applied cargo clippy --fix for minor lints (mostly iterator-style and formatting) across mora-kid. - Restructured KidDistributor's stats init to use struct-update syntax (clippy::field_reassign_with_default). - cargo fmt canonical whitespace across mora-kid.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Delivers
mora-kidas a library perdocs/superpowers/plans/2026-04-21-rust-kid-pivot-plan-6-mora-kid.md:Referenceenum — KID's 4-way parse dispatch (editor-ID, FormID~Plugin, plugin-only, bare hex), withresolve_form(world)helperKidRuleAST — keyword,RecordType,FilterBuckets,Traits, chance, source location+ALL,-NOT,*ANY, none MATCH (M3: NOT+MATCH active; ALL/ANY log-and-skip, Plan 7 activates)WeaponTraits/ArmorTraits;/#), sections, ExclusiveGroup-skip, chance default, NONE sentinelKidDistributor— iteratesworld.records(WEAP)/world.records(ARMO)(preserving plugin_index for ESL correctness), evaluates filter pipeline, KID-bit-compatible chance roll, emitsPatch::AddKeywordpipeline::compile(data_dir, world)— top-level entry that Plan 7's mora-cli will callEspWorld::keywords()+resolve_keyword_by_editor_id()docs/src/kid-ini-grammar.md— grammar reference derived from KID source analysisTest plan
cargo test --workspace— 167 total tests pass (126 prior + 41 new across mora-kid)cargo clippy --all-targets -- -D warningscleancargo fmt --checkcleancargo xwin check --target x86_64-pc-windows-msvc --workspacecleanScope discipline
RecordType::Other(String)) parsed but distributor logs and skips.+) and ANY (*) parsed into AST but evaluator treats as pass (Plan 7 activates).Notable implementation details
world.records(sig)+ manual parse instead ofworld.weapons()/world.armors(). The latter dropsplugin_index, which is critical for ESL plugins (all share FormID high-byte0xFE— can't reconstruct plugin_index from FormID alone).tests/distribute.rsby giving each test a unique plugin filename and plugins.txt suffix. Subagent caught this during execution.Armor|||HEAVY,-E(3 pipes) puts HEAVY,-E in the Chance field; correct isArmor||HEAVY,-E(2 pipes).Next up
Plan 7: mora-cli end-to-end — wire
pipeline::compileinto themora compilesubcommand, produce the first realmora_patches.bin, stage toData/SKSE/Plugins/, set up the MoraRuntime integration end-to-end.