Skip to content

Add authored open item catalog - #113

Merged
alexjercan merged 23 commits into
masterfrom
open-item-catalog
Oct 11, 2026
Merged

alexjercan merged 23 commits into
masterfrom
open-item-catalog

Conversation

@alexjercan

@alexjercan alexjercan commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Replace closed ItemType stock with RON-authored ItemDesignId, ItemDesign, and the merged GameItems catalog. Base content defines ten mandatory engine roles: hull plate; PDC round, rail slug, and torpedo; four ores; Rations; and SalvagedParts.
  • Apply one item-pack rule at lint and load: validate required attributes, categories and canister-sized ore; reject duplicate definitions within a pack and unrelated-pack ID conflicts; allow dependent packs to replace a whole item. Refuse unknown stock IDs.
  • Migrate inventory, canisters, reload, mining, loot, generated ships, trade, UI, commands, snapshots, content, examples and docs. Save inspection checks saved-state item IDs before Load is enabled; saving an invalid state refuses before writing.

Breaking formats

  • Stock maps now use quoted authored item IDs, for example {"HullPlate": 8}. WORLD_SAVE_FORMAT and the content-catalog digest version advance to 2. Incompatible saves are refused; no automatic migration is provided.
  • All ten mandatory roles must exist under their fixed categories. A dependent mod may replace their other attributes, while unrelated packs defining the same item ID are refused.

Verification and limits

  • git diff --check and cargo fmt --check passed. Focused cargo test --locked -p runs passed for nova_assets, nova_gameplay, nova_world_base, nova_menu, nova_interface, nova_ship, and nova_authoring (including content lint and generated-RON parity). Final independent source review found no blockers or majors; it noted a minor duplicate test catalog. Initial PR CI failed on the synthetic merge: newer master WFC tests still used removed ItemType, and seven inventory-pane wasm Clippy findings remained. Commit d75453d2a merges current master without rewriting the published Plan D commit, adapts the WFC callers to GameItems, and fixes the Clippy findings. Focused WFC reserve tests (3), the turret dry-reload test, debug example check, fmt, and wasm interface Clippy passed locally. Updated-head CI is running; do not merge before it passes.
  • No GPU or full flown open-world journey was run. Focused checks do not cover every affected crate, full workspace tests, or Clippy. The source review did not inspect every changed line. This branch is based on cbf7a2a3b; newer unrelated WFC commits on master were not rebased into it.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

WFC arena linting discards the loaded item catalog, causing valid generated ammunition reserves to be rejected at startup.

2 open findings
What changed in this PR

Replaces the closed item enum with an authored, moddable item catalog spanning content loading, gameplay, saves, UI, examples, and documentation.

Changes:

  • Adds ItemDesignId, ItemDesign, GameItems, validation, and base item content.
  • Migrates inventory, trading, mining, reloads, world generation, saves, and commands.
  • Updates authored content, examples, tests, and creator/player documentation.
File Description
webmods/​the-ledger/​ledger_06_siege_line.content.ron Quotes inventory item IDs.
webmods/​the-ledger/​ledger_05_freight_lane.content.ron Quotes inventory item IDs.
webmods/​the-ledger/​ledger_04_cold_patrol.content.ron Quotes inventory item IDs.
webmods/​the-ledger/​ledger_03_blue_survey.content.ron Quotes inventory item IDs.
webmods/​the-ledger/​ledger_02_rock_garden.content.ron Quotes inventory item IDs.
webmods/​the-ledger/​ledger_01_drift_run.content.ron Quotes inventory item IDs.
webmods/​the-ledger/​CHANGELOG.md Documents the format break.
web/​src/​wiki/​sections/​mining-beam.md Updates item-mass source references.
web/​src/​wiki/​interface.md Documents catalog-backed inventory.
web/​src/​wiki/​getting-started.md Documents asynchronous save validation.
web/​src/​wiki/​commands.md Documents catalog-backed item commands.
web/​src/​docs-manifest.js Updates creator-page metadata.
web/​src/​create/​objects.md Documents inventory IDs and base items.
web/​src/​create/​mod-files.md Documents authored items and overlays.
web/​src/​create/​base-content.md Documents base item content.
tasks/​20260930-100831/​TASK.md Records the approved design.
examples/​systems/​system_world_sectors.rs Supplies catalogs to world fixtures.
examples/​systems/​system_world_resume.rs Migrates save/resume item fixtures.
examples/​shared/​world_fixture/​mod.rs Pins item catalogs in generators.
examples/​shared/​world_fixture/​clustered.rs Uses catalog-backed ship generation.
examples/​screenshots/​screenshot_ship_repair.rs Migrates repair stock setup.
examples/​screenshots/​screenshot_mining_beam.rs Migrates ore assertions.
examples/​screenshots/​loop_helm_orders.rs Lints against loaded items.
examples/​screenshots/​loop_goto_standoff.rs Lints against loaded items.
examples/​screenshots/​lesson_interface_ship.rs Migrates repair lesson stock.
examples/​screenshots/​lesson_generated_world.rs Uses authored IDs and names.
examples/​screenshots/​lesson_combat_reach.rs Migrates ammunition stock.
examples/​screenshots/​lesson_combat_battery.rs Migrates reserve assertions.
examples/​screenshots/​lesson_cargo.rs Migrates cargo-flow assertions.
examples/​playable/​world_ships.rs Migrates canister and hold logic.
examples/​playable/​world_sector_inspector.rs Supplies the item catalog.
examples/​playable/​world_field_slices.rs Supplies the item catalog.
examples/​playable/​world_field_clouds.rs Supplies the item catalog.
examples/​playable/​world_features.rs Supplies the item catalog.
examples/​playable/​world_encounters.rs Migrates loot identities.
examples/​playable/​world_clusters.rs Pins items during planning.
examples/​playable/​wfc_arena/​result.rs Passes items when restarting.
examples/​playable/​wfc_arena/​pause.rs Passes items when restarting.
examples/​playable/​wfc_arena/​lobby.rs Passes items into arena creation.
examples/​playable/​trade_loop.rs Migrates trade ledgers.
examples/​playable/​shape_bench.rs Lints with loaded items.
examples/​playable/​mine_and_sell.rs Migrates mining and sale flow.
examples/​playable/​first_shift_ships.rs Lints with loaded items.
examples/​playable/​first_shift_map.rs Lints with loaded items.
examples/​playable/​block_bench.rs Lints with loaded items.
docs/​sections.md Updates reload identity documentation.
crates/​nova_world/​src/​tests/​frozen.rs Migrates frozen-world fixtures.
crates/​nova_world/​src/​tests.rs Migrates materialization assertions.
crates/​nova_world_base/​src/​tests.rs Tests catalog-backed generation.
crates/​nova_world_base/​src/​test_support.rs Arms save fixtures with items.
crates/​nova_world_base/​src/​sector_ships.rs Generates stock from item designs.
crates/​nova_world_base/​src/​save/​session.rs Rejects invalid items before saving.
crates/​nova_world_base/​src/​save/​mod.rs Validates saved item IDs.
crates/​nova_world_base/​src/​lib.rs Exposes checks and wires catalogs.
crates/​nova_world_base/​src/​layered.rs Pins items in world generators.
crates/​nova_world_base/​src/​clusters.rs Passes items into sector planning.
crates/​nova_world_base/​Cargo.toml Enables item test fixtures.
crates/​nova_wfc/​src/​check.rs Adds item-aware scenario linting.
crates/​nova_ship/​src/​sections/​turret_section/​setup.rs Uses authored PDC identity.
crates/​nova_ship/​src/​sections/​turret_section/​firing.rs Migrates reload tests.
crates/​nova_ship/​src/​sections/​torpedo_section/​bay.rs Migrates torpedo reloads.
crates/​nova_ship/​src/​sections/​railgun_section/​tests.rs Migrates railgun reserves.
crates/​nova_ship/​src/​sections/​railgun_section/​firing.rs Uses authored slug identity.
crates/​nova_ship/​src/​sections/​integrity.rs Exposes frozen fragment items.
crates/​nova_ship/​src/​sections/​frozen.rs Exposes frozen section items.
crates/​nova_ship/​src/​sections/​frozen_piece.rs Installs test item fixtures.
crates/​nova_ship/​src/​sections/​cargo_intake_section.rs Uses catalog-backed cargo mass.
crates/​nova_ship/​src/​sections/​ammo.rs Migrates reload identities.
crates/​nova_ship/​src/​input/​targeting/​radar.rs Migrates canister fixture identity.
crates/​nova_scenario/​src/​tests/​docked_helm.rs Installs test item fixtures.
crates/​nova_scenario/​src/​objects/​spaceship.rs Builds inventories from catalogs.
crates/​nova_scenario/​src/​objects/​beacon.rs Migrates inventory fixture masses.
crates/​nova_scenario/​src/​objects/​asteroid.rs Exposes frozen asteroid items.
crates/​nova_scenario/​src/​loader/​lifecycle.rs Lints scenarios against items.
crates/​nova_scenario/​src/​actions/​spawn.rs Migrates spawn fixtures.
crates/​nova_scenario/​src/​actions/​ship.rs Clones non-copy reload state.
crates/​nova_probe/​src/​capabilities/​snapshot.rs Records authored item IDs.
crates/​nova_probe/​Cargo.toml Enables item test fixtures.
crates/​nova_modding/​src/​lib.rs Adds the Item content variant.
crates/​nova_menu/​src/​tests/​support.rs Installs menu item fixtures.
crates/​nova_menu/​src/​tests/​leave.rs Opens saves with item catalogs.
crates/​nova_menu/​src/​lib.rs Polls background save checks.
crates/​nova_menu/​src/​leave.rs Validates items while reopening saves.
crates/​nova_menu/​Cargo.toml Enables menu item fixtures.
crates/​nova_interface/​src/​ship/​sections.rs Uses authored repair identity.
crates/​nova_interface/​src/​ship/​scene.rs Uses authored repair identity.
crates/​nova_interface/​src/​inventory/​mod.rs Migrates inventory UI state.
crates/​nova_hud/​src/​situation.rs Migrates reload reserve checks.
crates/​nova_hud/​src/​pickup_sight.rs Migrates pickup fixtures.
crates/​nova_hud/​src/​cargo_canister_chips.rs Displays authored names and mass.
crates/​nova_hud/​src/​ammo_readout.rs Migrates ammunition readouts.
crates/​nova_gameplay/​src/​test_support.rs Adds a fixture item catalog.
crates/​nova_gameplay/​Cargo.toml Enables Arc serialization.
crates/​nova_editor/​src/​event.rs Supplies item catalogs to lint.
crates/​nova_core/​tests/​world_resume_refusal.rs Validates resume with items.
crates/​nova_console/​src/​inspect.rs Clones reload identity state.
crates/​nova_console/​src/​completion.rs Publishes live item completions.
crates/​nova_console/​src/​cheats.rs Resolves item commands dynamically.
crates/​nova_console/​Cargo.toml Enables item test fixtures.
crates/​nova_command/​src/​commands.rs Adds live item completion vocabulary.
crates/​nova_bench/​scenarios/​slingshot.content.ron Quotes inventory item IDs.
crates/​nova_bench/​scenarios/​range.content.ron Quotes inventory item IDs.
crates/​nova_bench/​scenarios/​hunt.content.ron Quotes inventory item IDs.
crates/​nova_bench/​scenarios/​docking_warship_tender.content.ron Quotes inventory item IDs.
crates/​nova_bench/​scenarios/​arsenal.content.ron Quotes inventory item IDs.
crates/​nova_authoring/​src/​generation.rs Generates base item content.
crates/​nova_authoring/​src/​base_content/​scenarios/​tutorial/​range.rs Migrates authored ammunition.
crates/​nova_authoring/​src/​base_content/​scenarios/​open_world.rs Migrates initial open-world stock.
crates/​nova_authoring/​src/​base_content/​scenarios/​main_menu/​duel.rs Migrates duel reserves.
crates/​nova_authoring/​src/​base_content/​mod.rs Adds items to base content.
crates/​nova_authoring/​src/​base_content/​items.rs Defines the base item catalog.
crates/​nova_assets/​tests/​mod_binary_resources.rs Adds required base items.
crates/​nova_assets/​src/​plugin.rs Adds items to merge fixtures.
crates/​nova_assets/​src/​merge.rs Merges and publishes item catalogs.
crates/​nova_assets/​src/​lib.rs Exposes item validation.
crates/​nova_assets/​Cargo.toml Enables item test fixtures.
CHANGELOG.md Announces authored items and save checks.
assets/​mods/​example/​README.md Documents the example item.
assets/​mods/​example/​example.content.ron Adds example mod cargo.
assets/​mods/​example/​example.bundle.ron Updates example metadata.
assets/​mods/​example/​CHANGELOG.md Records example-mod breaking change.
assets/​base/​scenarios/​tutorial.content.ron Regenerates quoted stock IDs.
assets/​base/​scenarios/​open_world.content.ron Regenerates quoted stock IDs.
assets/​base/​scenarios/​menu_duel.content.ron Regenerates quoted stock IDs.
assets/​base/​scenarios/​drill_stop.content.ron Regenerates quoted stock IDs.
assets/​base/​scenarios/​drill_momentum.content.ron Regenerates quoted stock IDs.
assets/​base/​scenarios/​drill_gunnery.content.ron Regenerates quoted stock IDs.
assets/​base/​scenarios/​drill_autopilot.content.ron Regenerates quoted stock IDs.
assets/​base/​items/​base.content.ron Adds generated base items.
assets/​base/​base.bundle.ron Registers base item content.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/nova_wfc/src/check.rs Outdated
Comment thread web/src/docs-manifest.js
alexjercan and others added 20 commits October 11, 2026 09:31
Claim: a cell's frozen record decided what it spawns in two places, on two
threads, reconciled by length asserts and clear() calls in materialize_sector.

Change: prepare_cell takes the Arc<FrozenSector> the job holds and returns a
PreparedSector { coord, planets: Vec<PreparedPlanetoid>, bodies: PreparedBodies }
with PreparedBodies::{Fresh { rocks, ships, arrivals }, Thawed(..)}. Every
record travels with what was prepared for it (PreparedRock, PreparedPlanetoid,
PreparedFrozenBody built by one constructor), so nothing is paired by
position. materialize_sector no longer takes a record; FrozenSectors::consume
drops the ledger copy on the materialize frame. Deleted: the two clear() calls,
both assert_eq! on lengths, the Vec::new() visited branch, FrozenSector::rocks,
FrozenSectors::take, the rocks.next() panics in thaw_record, and
PreparedSector::description (the inspector example draws the description on
its worker beside the payload). SectorDescription::object_count stays: three
description readers use it.

Verification: nix develop --command cargo check -p nova_world -p nova_world_base
-p nova_scenario -p nova_menu -p nova_interface --all-targets; cargo check
--examples --keep-going; cargo test -p nova_world --lib (39 passed).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DRN1MeaNCeRLWpWp1nBQA
Claim: FrozenBodyType::PendingShip(Box<SectorShip>) wrote a generator record,
inline design included, into the world save, so every SectorShip change was a
save format change and a generator record carried serde for that alone.

Change: FrozenBodyType::PendingShip { id: String }. The cell's worker
regenerates the cell anyway, and PreparedFrozenBody::prepare resolves the id
against the regenerated ships into PreparedFrozenBody::HeldShip; a missing id
panics with the coord and id (the save's catalog digest pins the generator, so
it is an assembly fault). check_saved_items has no stock to check on a held
ship; the pending-ship fixture and case leave the item-refusal test. SectorShip
and SectorShipCrew drop their serde derives: no save holds one. WORLD_SAVE_FORMAT
2 -> 3 (unshipped; no migration, no changelog entry).

Verification: nix develop --command cargo check -p nova_world -p nova_world_base
-p nova_scenario -p nova_menu -p nova_interface --all-targets; cargo check
--examples --keep-going; cargo test -p nova_world --lib (39 passed); cargo test
-p nova_world_base --lib (91 passed).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DRN1MeaNCeRLWpWp1nBQA
Claim: three callers re-applied `SpaceshipConfig.allegiance` and
`lootable` beside `spaceship_scenario_object` (the scenario spawn action,
the sector spawn, `thaw_ship`), and the screenshot lineup hand-inserted
the same components, so a new caller could forget them.

Change: the bundle carries a `SpaceshipSpawnPolicy` component holding the
authored side and loot flag; `insert_spaceship_sections` applies the
allegiance override and `LootableShipMarker` and removes the policy, the
way it consumes `ShipInventoryStock`. A bevy bundle cannot hold an
optional component, and the controller marker's requirement default only
exists once that observer inserts the marker, so the observer is where the
override lands. The three re-applications and the lineup's extra
components are deleted. The sector spawn test now runs on an app with
`SpaceshipPlugin`, since the side and the marker land through the
observer; it reads the consumed stock back from `ShipInventory`.

Verification: cargo fmt; cargo check -p nova_world -p nova_world_base
-p nova_scenario -p nova_menu -p nova_interface --all-targets clean;
cargo check --examples --keep-going no errors; cargo test -p nova_world
--lib 39 passed; cargo test -p nova_world_base --lib 91 passed; cargo
test -p nova_scenario --lib spaceship 20 passed; cargo test -p
nova_scenario --lib allegiance 4 passed (the authored-override spawn
test still holds through the observer).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DRN1MeaNCeRLWpWp1nBQA
Claim: `materialize_sector` invented spawn policy the generator never
stated: it built each rock's `AsteroidConfig` around a texture it owned,
turned a `SectorShipCrew` into an `AIControllerConfig` with its own
engage delay, and decided that a derelict is unflown, Neutral and
lootable. Two generators (base game, example fixture) and every consumer
had to agree with it by convention.

Change: `SectorAsteroid { id, position, config: AsteroidConfig }` and
`SectorShip { id, position, rotation, clearance, condition, civilization,
role, spawn: SpaceshipConfig }`. `SectorShipCrew` and `check_ship_crew`
are deleted. `validate_manifest` refuses a rock with `seed: None` or a
`AssetRef::Handle` texture, a ship with `allegiance: None`, a catalog
(non-Inline) design, a player controller, an AI on a derelict, and an AI
route the controller could not fly, through the `patrol_stops_fault` the
scenario lint and the spawn already share. `plan_ship` decides the
derelict policy and the Enemy arrival grace (`ENEMY_ENGAGE_DELAY` moves
to `nova_world_base::sector_ships`), and the base generator and the
example fixtures name `ASTEROID_TEXTURE_PATH` themselves; `nova_world`
drops its `nova_assets` dependency and `materialize_sector` its texture
parameter. `SectorShip::design()` and `SectorAsteroid::seed()` read the
fields the trusted description guarantees. `canonical()` prints the new
fields (side, loot flag, capabilities, controller apart from its route,
texture, destroy sound, lock signature) and so changes the pinned
window digest in `nova_world_base` tests: the bodies are the same, the
text moved, and the test doc now says to state which of the two moved.
Records derive `Clone, Debug` uniformly; `PartialEq` is dropped from
`SectorAsteroid` because the scenario configs do not implement it and
`canonical()` is the record's equality. The nova_world_base window test
now proves the crew policy where it lives (leash, Enemy grace, unflown
Neutral lootable wreck).

Verification: cargo fmt; cargo check -p nova_world -p nova_world_base
-p nova_scenario -p nova_menu -p nova_interface --all-targets clean;
cargo check --examples --keep-going clean; cargo test -p nova_world
--lib 39 passed (23 refusal cases, canonical identity 15 variants);
cargo test -p nova_world_base --lib 91 passed; cargo test -p
nova_scenario --lib spaceship 20 passed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DRN1MeaNCeRLWpWp1nBQA
Claim: a sector object id could be any string, so validate_manifest had
to check each one started with its cell's slug at runtime.

Change: `SectorObjectId(String)` with a private field, built only by
`sector_id(coord, name, index)`, with Display and
`From<SectorObjectId> for EntityId`. The prefix invariant moves into the
constructor and the `starts_with` check in validate_manifest is deleted;
the duplicate-id check stays. SectorAsteroid, SectorPlanet, SectorShip,
clusters.rs Candidate/PlannedBody and the example world fixtures carry
the typed id; SectorFault keeps `id: String` via Display, and
PendingShip keeps `id: String` because a frozen body may be any live
entity. Tests build ids with `sector_id` instead of literals.

Verification: cargo fmt; cargo check -p nova_world -p nova_world_base
-p nova_scenario -p nova_menu -p nova_interface --all-targets; cargo
check --examples --keep-going; cargo test -p nova_world --lib (39 ok);
cargo test -p nova_world_base --lib (91 ok); cargo test -p nova_scenario
--lib spaceship (20 ok).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DRN1MeaNCeRLWpWp1nBQA
Claim: `sync_open_world` ran every frame of every app that installs
NovaWorldBasePlugin, including a campaign that never opens a world.

Change: the system runs under
`resource_exists::<OpenWorldSession>.or_else(resource_exists::<WorldConfig<NovaLayeredWorld>>)`
(bevy 0.19 deprecates `.or`). The arming tests call the system directly
and are unchanged; the system doc states the gate.

Verification: cargo fmt; cargo check -p nova_world -p nova_world_base
-p nova_scenario -p nova_menu --all-targets; cargo test
-p nova_world_base --lib (91 ok).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DRN1MeaNCeRLWpWp1nBQA
Claim: save/transients.rs reached `live_sectors`, `SectorRoot` and
`SectorSnapshotError` through crate paths because the prelude lacked
`live_sectors`.

Change: add `live_sectors` to `nova_world::prelude`; transients.rs
imports from the prelude only.

Verification: cargo fmt; cargo check -p nova_world -p nova_world_base
-p nova_scenario -p nova_menu --all-targets; cargo check --examples
--keep-going.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DRN1MeaNCeRLWpWp1nBQA
Claim: the doc said a save holds only the sectors the player changed;
every cell the window held live is recorded as Visited and kept.

Change: doc only. The save records each live cell as Visited on every
save and never drops one, records a body that moved into an unheld cell
as an arrival, and still needs a format bump on a generator change
because unvisited cells regenerate from the seed.

Verification: cargo check -p nova_world_base --all-targets (part of the
check run above).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DRN1MeaNCeRLWpWp1nBQA
Claim: a top-level transient with a physics body and no save record
panicked the game mid-save, unlike every other save fault; a saved hold
or canister over its cap was neither refused on open nor before write.

Change: `TransientKind::Unknown` returns
`SectorSnapshotError::InvalidSavedState`, so the session goes
`WorldSaveStatus::Failed` and keeps the last save. New
`check_saved_stock(state, items)` beside `check_saved_items` in
save/mod.rs: the player's and every ledger ship's hold weighs at most
its capacity, every ledger canister at most CARGO_CANISTER_MAX_MASS_G;
`read_state` maps it to `WorldRefusal::Unreadable` and `snapshot_world`
runs it before the write. `FrozenShip::inventory()` and
`FrozenCanister::contents()` expose the frozen stock read-only. The
retire-path panic in nova_world stays.

Verification: cargo fmt; cargo check -p nova_world -p nova_world_base
-p nova_scenario -p nova_menu -p nova_ship --all-targets; cargo check
--examples --keep-going; cargo test -p nova_world_base --lib (93 ok,
with the two new behaviour tests); cargo test -p nova_scenario --lib
spaceship (20 ok).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DRN1MeaNCeRLWpWp1nBQA
Claim: the Load button ran `open_world` (lock, header and state read,
every stock check) synchronously on the main thread, inside the click
observer.

Change: `on_load_world` spawns the open on `IoTaskPool` into the new
`WorldOpen` resource and ignores clicks while one runs; `poll_world_open`
takes the finished task in the Load picker's Update chain, before the
list redraw, and resumes the world or writes the refusal onto the row
exactly as the click did. The refusal UI is unchanged. The picker tests
run frames until the open lands.

Verification: cargo fmt; cargo check -p nova_world -p nova_world_base
-p nova_scenario -p nova_menu --all-targets; cargo check --examples
--keep-going; cargo test -p nova_menu --lib load_screen (3 ok).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DRN1MeaNCeRLWpWp1nBQA
Claim: the ten items engine code spends, reloads, mines and draws were a
string table (ITEM_ROLES and ten ITEM_* consts), so nothing was exhaustive
over them and every use allocated a fresh ItemDesignId.

Change: `ItemRoleType` in nova_gameplay::inventory, a closed enum with
`ALL`, `const fn id()` (the authored catalog id) and `const fn category()`;
`From<ItemRoleType> for ItemDesignId`; `Borrow<str> for ItemDesignId` so
`GameItems::role(ItemRoleType)` looks a role up without allocating and panics
with the role name when the catalog lacks it. ITEM_ROLES and the ITEM_*
consts are deleted; every consumer in crates/ and examples/ goes through the
enum. `ore_for_asteroid_kind` returns `Option<ItemRoleType>`; `BeamAim`
carries the role and `carve` clones the catalog id from `GameItems::role`
per pulse; `ship_stock` mixes are `(ItemRoleType, f32)` and clone catalog
ids; wfc_arena's AMMO_HOLD_SHARE is keyed by role. The three section reload
inserts keep a per-section `.into()` (no GameItems in those observers; one
allocation per section spawn, as before).

Verification: cargo fmt; cargo check --all-targets on nova_gameplay,
nova_ship, nova_scenario, nova_world_base, nova_authoring, nova_wfc,
nova_editor, nova_console, nova_assets, nova_hud, nova_interface, nova_probe,
nova_menu, nova_world clean; cargo check --examples --keep-going clean;
cargo test -p nova_gameplay --lib inventory 10 passed; -p nova_assets --lib
items 3 passed; -p nova_scenario --lib mining 9 passed; -p nova_world_base
--lib stock 1 passed; --example wfc_arena reserve 4 passed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DRN1MeaNCeRLWpWp1nBQA
Claim: `nova_gameplay::test_support::test_items` restates the ten base
rows of `item_catalog()`, so a price or mass retune in the builder left every
gameplay test green on the old table. The fixture cannot call the builder
(nova_authoring depends on nova_gameplay).

Change: `the_test_item_fixture_matches_the_base_catalog` in
crates/nova_authoring/src/base_content/items.rs asserts, in catalog order,
that the fixture holds every builder id with the same name, category, mass_g,
ask_cr and bid_cr, and no extra row. nova_authoring gains a dev-dependency on
nova_gameplay with `test-support` to reach the fixture. The authored values
stay in nova_authoring.

Verification: cargo check -p nova_authoring --all-targets clean; cargo test
-p nova_authoring --lib items 1 passed; a probe that changed the fixture's
hull plate bid to 31 failed the test with the row diff, then was reverted.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DRN1MeaNCeRLWpWp1nBQA
Claim: `hull_errors` ran the content gate with `GameItems::default()`, so a
generated hull that ever carried stock would have every real item reported
unknown and the editor's Generate verb would refuse every hull.

Change: `hull_errors(hull, sections, items)` takes the loaded catalog the way
`refuse_broken_ships` does; the comment that explained the empty catalog is
gone. The editor's `generate_ship` reads `Option<Res<GameItems>>` beside the
sections and refuses with the same "content has not finished loading" note
when either is absent; `collapse` threads it through. The nova_wfc tests and
the editor's generate fixture build the catalog from
`nova_authoring::generation::build_items`.

Verification: cargo check -p nova_wfc -p nova_editor --all-targets clean;
cargo check --examples --keep-going clean; cargo test -p nova_wfc --lib 18
passed; cargo test -p nova_editor --lib generate passed (count in the
report).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DRN1MeaNCeRLWpWp1nBQA
Claim: lint_walk resolved a bundle's effective content twice.
`effective_item_bundles` walked `transitive_deps` from nova_mod_format
(depth-capped, deps-first order) for the item lint; `ship_part_packs` ran its
own stack DFS over a `by_id` map for the generated-ship snapshot. Both added
base as the implicit dependency and both followed declared dependencies
transitively, but the DFS had no depth cap and emitted packs in stack order,
so a chain deeper than MAX_DEP_DEPTH would lint its items against base and
the bundle alone while its ship parts walked the whole chain.

Change: one `effective_bundles(bundle, all) -> Vec<&WalkedBundle>`, the
former item walk, feeds both. `ship_part_packs(bundle, all)` maps each
effective bundle to a `ShipPartPack` that still names its declared
dependencies plus base, so a dependency missing from the walk is still
reported by the snapshot as before. The `transitive_deps` rule won: it is
the mod format's own walk, the one `item_pack_faults` and the runtime merge
resolve against, and `ShipPartSnapshot::build` accepts packs in any order,
so the DFS's order carried nothing. `repo_ship_part_packs` keeps its
signature and id-ordered result.

Verification: cargo check -p nova_authoring --all-targets clean; cargo
check --examples --keep-going clean; cargo test -p nova_authoring --lib
lint_walk 10 passed; cargo test -p nova_authoring --test generated_wrecks 2
passed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012DRN1MeaNCeRLWpWp1nBQA
@alexjercan
alexjercan merged commit 86084eb into master Oct 11, 2026
7 checks passed
@alexjercan
alexjercan deleted the open-item-catalog branch October 11, 2026 16:35
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.

2 participants