Files
wolf_dart/packages/wolf_3d_dart/README.md
T
hans d393ca98ec Refactor menu rendering and asset registry structure
- Updated SoftwareRenderer to incorporate MenuHeaderBand for handling spear variant menus and improved backdrop drawing.
- Refactored asset registry imports to organize menu-related assets under a dedicated menu structure.
- Enhanced game session snapshot tests to validate menu theme restoration for spear variant games.
- Added tests for classic menu presentation module to ensure palette consistency with canonical constants.
- Implemented tests for spear asset registry to verify correct menu VGA index resolutions.
- Created unit tests for MenuHeaderBand to validate functionality in rendering menu headers and sidebars.
- Adjusted HUD module imports to align with new menu structure.

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
2026-03-24 23:35:56 +01:00

9.3 KiB

wolf_3d_dart

Core non-Flutter Wolfenstein 3D runtime package used by both CLI and Flutter hosts.

What This Package Provides

wolf_3d_dart contains the platform-neutral simulation/runtime surface:

  • engine and session lifecycle,
  • game-data loading and data types,
  • renderer backends and frame-buffer abstractions,
  • menu state/navigation models,
  • input/audio host abstractions,
  • entity and gameplay logic.

Public Library Surfaces

Primary entry libraries in lib/:

  • wolf_3d_engine.dart — engine exports and runtime contracts.
  • wolf_3d_data.dart / wolf_3d_data_types.dart — game-data discovery and DTOs.
  • wolf_3d_renderer.dart — rendering/backends integration points.
  • wolf_3d_audio.dart — audio interfaces and host backends.
  • wolf_3d_input.dart — input abstractions.
  • wolf_3d_menu.dart — menu models/managers and the registry-backed WolfMenuPresentation helpers for hosts.
  • wolf_3d_host.dart — host-level glue contracts.

Implementation details live under lib/src/.

Setup

From this directory:

dart pub get

Development Commands

From this directory:

dart analyze
dart test

Architecture Notes

  • Hosts own platform concerns (windowing, lifecycle, platform input wiring).
  • This package owns deterministic engine/frame progression and shared game logic.
  • Frame-buffer sizing is controlled by hosts through engine APIs.
  • Rendering code is maintained under lib/src/rendering/.
  • Menu coordination is split under lib/src/menu/manager/; public consumers should prefer lib/wolf_3d_menu.dart or the internal barrel at lib/src/menu/menu_manager.dart instead of reaching into individual implementation files.
  • Menu presentation is selected through AssetRegistry.menuPresentation, which keeps retail/shareware/Spear variants and user-defined menu overrides aligned with the rest of the registry system.

Custom Menus

Custom menu support is split across two registry modules:

  • MenuPicModule maps symbolic menu keys such as MenuPicKey.title or an episode selection entry to concrete VGA picture indices in WolfensteinData.vgaImages.
  • MenuPresentationModule defines the palette indices and higher-level menu art lookups that renderers and hosts consume.

That split is intentional:

  • MenuPicModule answers "which image index represents this menu asset for this game/mod?"
  • MenuPresentationModule answers "which colors and optional art should the UI use?"

In practice, most custom variants will either:

  • reuse an existing MenuPicModule and only change colors/presentation, or
  • provide both a custom MenuPicModule and a matching MenuPresentationModule when the menu art layout itself changes.

Using Menu Presentation From Loaded Data

Once game data has been loaded, bind menu presentation through the active registry:

final WolfMenuPresentation menu = WolfMenuPresentation(data);

final int panelColor = menu.panelColor;
final VgaImage? title = menu.title;
final VgaImage? episode1 = menu.episodeOption(0);

This is the normal path for renderers and any UI that should track the active game variant automatically.

Fallback Presentation Before Data Loads

Host-owned screens that appear before game data discovery can still use menu-consistent colors:

const WolfMenuPresentation classicMenu = WolfMenuPresentation.classic();
const WolfMenuPresentation spearMenu = WolfMenuPresentation.spear();

Those fallback constructors expose colors without requiring a loaded WolfensteinData instance. Art getters return null until real data is attached.

Implementing A Custom MenuPicModule

Use MenuPicModule when your mod changes which VGA pictures back the classic menu keys:

class ModMenuPics extends MenuPicModule {
  const ModMenuPics();

  @override
  MenuPicRef? resolve(MenuPicKey key) {
    switch (key) {
      case MenuPicKey.title:
        return const MenuPicRef(140);
      case MenuPicKey.optionTitle:
        return const MenuPicRef(141);
      case MenuPicKey.customizeTitle:
        return const MenuPicRef(142);
      default:
        return null;
    }
  }

  @override
  MenuPicKey episodeKey(int episodeIndex) {
    switch (episodeIndex) {
      case 0:
        return MenuPicKey.episode1;
      case 1:
        return MenuPicKey.episode2;
      case 2:
        return MenuPicKey.episode3;
      default:
        return MenuPicKey.episode1;
    }
  }

  @override
  MenuPicKey difficultyKey(Difficulty difficulty) {
    switch (difficulty) {
      case Difficulty.easy:
        return MenuPicKey.skill1;
      case Difficulty.medium:
        return MenuPicKey.skill2;
      case Difficulty.hard:
        return MenuPicKey.skill3;
      case Difficulty.expert:
        return MenuPicKey.skill4;
    }
  }
}

Returning null from resolve means that the key is not provided by that module.

Implementing A Custom MenuPresentationModule

Use MenuPresentationModule when you want to change menu colors, point existing menu concepts at different art, or selectively omit optional art:

Custom Menu Presentation Example

class ModMenuPresentation extends MenuPresentationModule {
  const ModMenuPresentation();

  @override
  int get backgroundIndex => 111;

  @override
  int get panelIndex => 97;

  @override
  int get borderIndex => 87;

  @override
  int get emphasisIndex => 10;

  @override
  int get warningIndex => 14;

  @override
  int get mutedIndex => 8;

  @override
  int get selectedTextIndex => 19;

  @override
  int get unselectedTextIndex => 23;

  @override
  int get disabledTextIndex => 4;

  @override
  int get headerTextIndex => 15;

  @override
  VgaImage? controlBackground(WolfensteinData data) => null;

  @override
  VgaImage? title(WolfensteinData data) => null;

  @override
  VgaImage? heading(WolfensteinData data) => null;

  @override
  VgaImage? selectedMarker(WolfensteinData data) => null;

  @override
  VgaImage? unselectedMarker(WolfensteinData data) => null;

  @override
  VgaImage? optionsLabel(WolfensteinData data) => null;

  @override
  VgaImage? customizeLabel(WolfensteinData data) => null;

  @override
  VgaImage? credits(WolfensteinData data) => null;

  @override
  VgaImage? episodeOption(WolfensteinData data, int episodeIndex) => null;

  @override
  VgaImage? difficultyOption(WolfensteinData data, Difficulty difficulty) =>
      null;

  @override
  VgaImage? mappedPic(WolfensteinData data, int index) => null;
}

final registry = AssetRegistry(
  sfx: mySfxModule,
  music: myMusicModule,
  entities: myEntityModule,
  hud: myHudModule,
  menu: myMenuPicModule,
  menuPresentation: const ModMenuPresentation(),
);

The presentation module should treat its image-returning methods as optional hooks:

  • return a VgaImage when that surface has variant-specific art,
  • return null when the presentation intentionally has no image for that concept,

Palette Conversion Guardrail

When mapping target RGB menu tones to a VGA palette index (for example, preserving Wolf classic dark-red theme), resolve nearest colors from ColorPalette.argbFromVgaIndex() values.

Do not use ColorPalette.findClosestPaletteIndex() for this specific workflow, because its channel interpretation is legacy-oriented and can produce hue-swapped matches (for example, red targets resolving to blue-ish indices).

In short:

  • For variant-defined menu colors: use explicit palette indices from MenuPresentationModule.
  • For host-defined fallback RGB tones: find nearest VGA index by comparing RGB distance against argbFromVgaIndex() output.
  • use mappedPic(...) only for legacy numeric menu art lookups that still matter for a renderer path.

Wiring A Fully Custom Registry

To ship a complete custom menu variant, provide both modules through AssetRegistry when loading data:

final registry = AssetRegistry(
  sfx: mySfxModule,
  music: myMusicModule,
  entities: myEntityModule,
  hud: myHudModule,
  menu: const ModMenuPics(),
  menuPresentation: const ModMenuPresentation(),
);

If your menu art still follows the built-in retail/shareware layout, you may not need a custom MenuPicModule. In that case, keep the built-in module and only swap menuPresentation.

Choosing The Right Extension Point

  • Change colors only: implement MenuPresentationModule.
  • Change symbolic menu art mapping: implement MenuPicModule.
  • Change both colors and art layout: implement both modules.
  • Build a host setup screen before data loads: use WolfMenuPresentation.classic() or WolfMenuPresentation.spear().

For most host code, prefer the public wolf_3d_menu.dart surface instead of importing internal files directly.

Non-Goals

  • Flutter widgets/screens are not part of this package.
  • Bundled app assets are handled by wolf_3d_assets.

Troubleshooting

  • Parity regressions: run targeted tests under test/engine/ and test/entities/.
  • Host integration issues: verify host packages/apps are using exported surfaces from lib/ rather than private src/ paths.