1 game server catalog load health and fallback policy
forgejo-actions edited this page 2026-07-15 12:43:13 +00:00

Game Server Catalog Load Health And Fallback Policy

Purpose

Game-server catalog health turns file reads, JSON parsing, duplicate ids, and cross-catalog integrity checks into one startup decision. Required failures stop the server before database connection and runtime assembly proceed. Optional failures may substitute empty content, but remain visible as warnings and zero-count summaries.

ServerContentBundle Order

ServerContentBundle::load resolves the workspace asset root and uses one mutable CatalogLoadHealth for the complete load. Order is part of the authority contract:

  1. ServerGameplayCatalogs::load_from_asset_root loads game data, NPC services, quests, dialogue, and the manually attached world catalogs.
  2. Gameplay integrity checks add duplicate-id and cross-catalog reference failures after all gameplay catalogs are available.
  3. SceneCombatCatalogs::load_from_asset_root receives the already parsed GameDataCatalog. Mob loot hydration therefore reuses the same normalized item and loot-table indexes instead of parsing game data again.
  4. Scene-combat integrity checks add normalized mob/NPC duplicate failures and related reference failures.
  5. The bundle checks has_required_failures() once, after every load and integrity pass has had a chance to report. Any required issue returns the complete health object.
  6. On success, the same aggregated health snapshot is cloned into gameplay and scene-combat catalogs, then both catalog sets are wrapped in Arc.

The bundle test server_content_bundle_loads_game_data_once_and_builds_normalized_indexes verifies that game data has one summary and is reused for mob loot hydration. server_content_bundle_reports_duplicate_and_missing_references_in_one_health_report verifies that duplicate item/mob ids and missing loot references survive in one failure report.

Required And Optional Fallback

Requirement Read/parse result Health projection Startup result
Required The loader returns Default temporarily so later catalogs and integrity checks can still run. It does not record a successful summary for that fallback. Error, blocking, CatalogLoad domain. The aggregate fails after collection. The empty value must never reach a running server.
OptionalFallbackToEmpty The loader returns Default and records its zero-value telemetry summary. Warning, non-blocking, CatalogLoad domain. Startup may continue with the related feature incomplete.

CatalogLoadStage::Read separates path/permission/missing-file failures from CatalogLoadStage::Parse JSON or semantic failures. A registry-backed issue targets catalog_registry:<registry_id>; a manual issue targets catalog:<label>. Both carry the resolved source path and a reason explaining whether the issue blocks startup.

Current intentional exceptions are code-backed, not global policy:

  • Server dialogue uses the manual dialogue/dialogue_catalog.json descriptor and may fall back to an empty catalog.
  • Enemy content treats a missing file as an optional, silent empty catalog and other read failures as optional warnings. Once the file exists, malformed JSON and invalid references are required failures.
  • The other manually attached gameplay world catalogs are currently required. Do not infer optional behavior merely because their runtime type implements Default.

Aggregated Health

CatalogLoadHealth preserves an ordered list of issues and successful/fallback summaries. Summaries carry catalog label, relative path, optional registry id, and catalog-specific metrics such as item, quest, mob, route, or encounter counts. Duplicate detection canonicalizes ASCII ids before reporting, so case or surrounding whitespace cannot hide a collision.

validation_report(group) converts the aggregate into the shared validation schema. format_required_failures("server content") includes only blocking messages and is the error returned by load_authoritative_catalogs. This is why startup can report several independent missing, duplicate, and dangling-reference problems in one run instead of stopping at the first unreadable file.

Because the successful bundle installs the same health snapshot in both catalog owners, startup calls to gameplay_catalogs.log_summary() and scene_combat_catalogs.log_summary() can emit the same aggregate issue/summary under two catalog_domain values. Correlate repeated entries by registry id, path, source, and stage rather than counting each domain copy as a separate load.

Registry And Manual Boundaries

The world-domain catalog registry owns semantic identity, aggregate paths, source patterns, item-id namespaces, source priority, fallback rules, and authority availability. It intentionally performs no filesystem probing and does not attach loaders.

The current authoritative registry is game data, quest, NPC service, mob, and NPC. Gameplay uses CatalogDescriptor::from_registry for game data, quest, and NPC service. Scene combat uses registry metadata for NPC and for its custom mob loader, which also reads sorted mob fragments and hydrates loot from shared game data.

Dialogue exists in the wider registry but has authority_available == false there; its aggregate authoring path is not the manual server dialogue path. World event, interaction, object, mechanism, gameplay assembly, scene phase, NPC routine, world time, world travel, world exploration, housing, and enemy content are also manually attached today. Manual loaders must explicitly supply the same health data: label, path, requirement, stage, error, telemetry, and any integrity issues.

Adding an enum or registry row does not make the server load a catalog. Conversely, adding only a manual server loader leaves editor/catalog discovery without shared registry identity. Choose the boundary deliberately.

Logs And Triage

Successful or optional-fallback bundles use these structured messages:

  • loaded authoritative catalog at info with catalog_domain, optional registry_id, catalog, path, and metric text.
  • optional catalog load fell back to empty at warning with domain, registry, catalog, relative path, resolved source, stage, and error.
  • loaded authoritative gameplay catalogs and loaded authoritative scene combat catalogs with owner-specific aggregate counts.

CatalogLoadHealth::log_issues defines required catalog load failed at error, but normal startup returns a failed bundle before the per-owner summary log calls. The startup error is therefore the formatted server content failed required catalog health: ... aggregate. If startup exits before database connection, search that message for the first source path and stage, then fix every reported issue before retrying.

Use these focused checks while triaging:

cargo test -p aether-game-server catalog_health
cargo test -p aether-game-server server_content_bundle
cargo test -p aether-game-server startup_catalog_loading_fails_before_runtime_with_required_catalog_errors
cargo test -p aether-world-domain catalog_registry

Adding A Catalog

  1. Decide whether the catalog needs shared semantic identity. If so, add its registry id, descriptor, source patterns, item-id kinds, source layers, fallback rule, and authority availability in catalog_registry.rs. Keep an authoring-only registry out of authoritative_catalog_registry() until a server owner actually loads it.
  2. Define the file type, normalized runtime catalog, Default, conversion/parser, and telemetry. Use CatalogDescriptor::from_registry for a simple JSON aggregate. A custom sharded or hydration loader may remain manual, but should copy requirement/path/registry id from the shared descriptor when one exists.
  3. Attach the loader to ServerGameplayCatalogs or SceneCombatCatalogs in dependency order. Shared prerequisites must load once and be passed down, as game data is passed to scene combat.
  4. Record read and parse failures with the correct requirement. Record a summary on success and on allowed empty fallback. Add duplicate and cross-reference checks to the post-load integrity pass so all dependencies are available.
  5. Add owner count fields to log_summary, and add fixture-workspace tests for success, missing/invalid input, fallback behavior, normalized duplicate ids, and aggregate cross-catalog failures.
  6. Run the focused commands above, then run cargo run -p xtask -- quality-gate --root . --profile full when the change affects shared registry metadata or startup behavior.

Do not downgrade a required catalog to optional merely to make startup pass. Optional means the running feature is explicitly designed to operate against an empty catalog and expose the degraded state.