Files

wolf_3d_flutter

Flutter integration package for the shared Wolfenstein 3D runtime.

What This Package Provides

wolf_3d_flutter layers Flutter-specific host concerns on top of wolf_3d_dart:

  • high-level engine facade (Wolf3dFlutterEngine),
  • Flutter input adapter,
  • platform audio integration,
  • renderer host widgets and runtime mode helpers,
  • app/session managers and persistence adapters,
  • shader-enabled rendering support.

Primary entrypoint: lib/wolf_3d_flutter.dart

Prerequisites

  • Flutter SDK
  • Dart SDK ^3.11.1
  • Workspace dependency on wolf_3d_dart

Setup

From this directory:

flutter pub get

Usage

Typical host initialization pattern:

final Wolf3dFlutterEngine engine = await Wolf3dFlutterEngine(
  debug: kDebugMode,
).init();

init() handles platform setup, audio init, configured external discovery, and optional seeded game injection.

To load packaged game data from wolf_3d_assets, use Wolf3dFlutterEngine.loadGameDataFromAssets(...) and pass the result via seededGames:

final retail = await Wolf3dFlutterEngine.loadGameDataFromAssets(
  version: GameVersion.retail,
  packageName: 'wolf_3d_assets',
  assetDirectory: 'assets/retail',
);

final shareware = await Wolf3dFlutterEngine.loadGameDataFromAssets(
  version: GameVersion.shareware,
  packageName: 'wolf_3d_assets',
  assetDirectory: 'assets/shareware',
);

final Wolf3dFlutterEngine engine = await Wolf3dFlutterEngine(
  debug: kDebugMode,
).init(seededGames: [retail, shareware]);

The facade itself lives in lib/engine/wolf3d_flutter_engine.dart and is re-exported through the package barrel at lib/wolf_3d_flutter.dart. External consumers should keep importing the barrel unless they have a specific reason to target the engine library directly.

The same pattern applies to the Flutter input adapter and the desktop persistence adapters: they now live in focused subdirectories and are re-exported through lib/wolf_3d_flutter.dart.

For full host wiring examples, see:

  • apps/wolf_3d_gui/lib/main.dart

Package Structure

  • lib/wolf_3d_flutter.dart — barrel export for the public Flutter package surface.
  • lib/engine/wolf3d_flutter_engine.dart — high-level engine facade and discovery bootstrap.
  • lib/input/ — Flutter-specific input adapters.
  • lib/persistence/ — desktop persistence adapters for saves and renderer settings.
  • lib/renderer/ — renderer host widgets.
  • lib/managers/ — runtime/session/display/persistence managers.
  • lib/audio/ — platform-aware audio backends.
  • shaders/wolf_world.frag — base fragment shader included in package configuration.
  • shaders/wolf_world_bloom.frag — bloom-enabled fragment shader variant.

Integration Notes

  • Keep UI/platform concerns in this package or app hosts, not in wolf_3d_dart.
  • Use exported APIs from lib/wolf_3d_flutter.dart rather than importing private internals from lib/src in dependencies.
  • Shader paths are declared in this package pubspec.yaml and must stay synchronized with renderer usage.

Shader Architecture And Performance Notes

This package ships two shader variants:

  • shaders/wolf_world.frag (base pass, no bloom taps)
  • shaders/wolf_world_bloom.frag (bloom-enabled variant)

The renderer selects one variant in Dart based on runtime settings. This is a performance decision: when bloom is disabled, we do not run bloom sampling code at all.

No-Branch Shader Policy

For package-owned shader sources, do not use if statements. Use branchless selection patterns (mix, step, smoothstep, mask algebra) instead.

Static check:

rg "\bif\s*\(" packages/wolf_3d_flutter/shaders

Expected result: no matches in source shader files.

Why This Is Different From Dart

If you are primarily a Dart developer, this is the key mindset shift:

  • Dart code runs on CPU cores with branch prediction and comparatively cheap control flow.
  • Fragment shaders run across many pixels in parallel on GPU SIMD/SIMT lanes.
  • If neighboring pixels take different branches, the GPU can serialize branch paths (divergence), reducing throughput.
  • Texture reads are usually more expensive than scalar ALU math. Removing bloom work entirely when disabled is often better than trying to gate it inside one shader.

In short: in Dart, if can be good structure. In fragment shaders, branchless math and pass selection are often better for frame time.

Dart-Style Thinking vs Shader-Style Thinking

CPU/Dart style:

if (effectsEnabled) {
  uv = warp(uv);
}
if (outsideScreen(uv)) {
  return bezelColor;
}

Shader style used here:

vec2 effectiveUv = mix(uv, warpedUv, effectsMask);
float bezelMask = (1.0 - insideMask) * effectsMask;
vec3 outColor = mix(screenColor, bezelColor, bezelMask);

Both produce feature-equivalent behavior, but the second keeps execution paths uniform across fragments.

Shader Block Guide (What / Why)

  1. UV normalization: Convert fragment coordinates to 0..1 UV so all sampling math is resolution agnostic.

  2. Barrel warp: Simulates curved CRT glass by pushing UVs outward as radius increases.

  3. Inside/outside mask: Computes whether warped UV remains on the emissive screen rectangle. This replaces branch-based bezel routing.

  4. Edge-aware AA: Computes local luma span from N/S/E/W neighbors and blends toward neighborhood average only where contrast indicates potential aliasing.

  5. CRT modulation: Applies scanlines, moving sweep, center lift, and vignette to mimic phosphor and lens behavior.

  6. Bezel shading: Uses overflow distance and edge bleed sampling to build depth, inner lip, and scene-tinted glow on bezel regions.

  7. Bloom variant only: Adds three-ring cross taps, brightness gating, and tone mapping. This code is in a separate shader so bloom-off mode avoids paying this cost.

Constant Tuning Reference

  • Warp factor: 0.045 Higher = stronger curvature.

  • AA blend ceiling: 0.45 Higher = softer edges, more blur risk.

  • Scanline band: 0.88 + 0.12 * sin(...) Lower floor or higher amplitude increases CRT stripe intensity.

  • Sweep speed: uTime * 0.08 Higher = faster sweep line travel.

  • Bloom ring radii: 3, 7, 13 texels Larger radii spread glow farther but increase halo size.

  • Bloom gain: 0.42 Higher = brighter bloom before tone map.

  • Tone map: color / (color + 0.75) * 1.75 Controls highlight rolloff and midtone lift.

Glossary

  • UV: normalized texture coordinates in [0, 1].
  • Luma: perceived brightness estimate from RGB.
  • Mask: scalar 0..1 value used to blend between alternatives.
  • Vignette: edge darkening effect.
  • Tone map: compresses highlights into displayable range.
  • Tap: one texture sample read.
  • Divergence: parallel shader lanes taking different branches.

Profiling Expectations

  • Bloom disabled: base shader variant runs, no bloom taps.
  • Bloom enabled: bloom shader variant runs, additional texture sampling cost.
  • Effects disabled: both shaders still remain branchless; effect contribution is blended out by mask values.

Troubleshooting

  • No discovered game data: confirm configured/persisted data directory paths are valid.
  • Desktop behavior mismatch: verify desktop windowing and audio dependencies are available on the target OS.
  • Render issues after shader changes: confirm shader path and package config are still aligned.