gamemantra.aigamemantra.ai|Developer Docs

Modules · UI Runtime

UI Runtime

The gamemantra Experience overlay renders AI-generated offers directly inside your Unreal or Unity game. C++ controls show/hide; Dart renders the UI. No WebView. No separate process.

Architecture

Game Event (C++/C#)
GM_GetNextOffer (libcurl → backend)
GM_Dart_On OfferReceived
NavigationService .pushNamed('/offer')
Experience renders offer UI

Engine Boot Sequence

Phases 0–4 are mandatory and must not be reordered. Call on game launch before any scene loads.

engine_boot
// Unreal: called from GameMantraSubsystem::Initialize()
// Order is MANDATORY — never reorder.

// Phase 0: Code push — apply any pending OTA patch FIRST
FlutterCodePush::CheckAndApply(game_id);

// Phase 1: Init Experience engine dimensions
FlutterUnity_Init(screen_width, screen_height, /*pixel_ratio=*/1.0f);

// Phase 2: Init EGL render loop (captures Unity/Unreal shared context)
FlutterEngineInitRenderLoop();

// Phase 3: Start the engine
FlutterEngineRun(updated_snapshot_path);

// Phase 4: First present → Dart fires 'initSession' automatically
// No manual call needed here.

Show / Hide Overlay

The overlay is hidden by default. Call showFlutterUI() when you want the Experience layer to composite over your game frame, and hideFlutterUI() to remove it. Both calls are instant — no re-initialisation needed.

show_hide
// Show the Experience UI overlay (e.g. when an offer arrives)
showFlutterUI();

// Hide the Experience UI overlay (e.g. on game resume, level start)
hideFlutterUI();

Requesting & Responding to Offers

Call GM_GetNextOffer at natural trigger points. The SDK fires the offer callback asynchronously — your game thread never blocks. Dart shows the /offer route automatically viaNavigationService.pushNamed.

offer_flow
// 1. Request an offer at a natural trigger point (level fail, store open, etc.)
GM_GetNextOffer(game_id, R"({
  "player_id":  "player_001",
  "session_id": "sess-uuid",
  "trigger":    "level_fail"
})");

// 2. C++ receives the async callback → enqueues to Dart
// (GM_Dart_OnOfferReceived fires in sdk_client.dart)

// 3. Player interacts → respond
GM_RespondToOffer(game_id, R"({
  "offer_id": "offer-uuid",
  "accepted": true
})");

Named Routes

Register routes in flutter_app/lib/main.dart. C++ pushes routes via the callback queue — never use overlays or Stack-based listeners (they don't fire in embedded Experience without vsync).

main.dart — onGenerateRoute
// flutter_app/lib/main.dart — register named routes
MaterialApp(
  navigatorKey: NavigationService.navigatorKey,
  onGenerateRoute: (settings) {
    switch (settings.name) {
      case '/offer':        return MaterialPageRoute(builder: (_) => const OfferPage());
      case '/store':        return MaterialPageRoute(builder: (_) => const StorePage());
      case '/balance':      return MaterialPageRoute(builder: (_) => const BalancePage());
      default:              return MaterialPageRoute(builder: (_) => const GameView());
    }
  },
);

// C++ fires a route via EnqueueDartCallback → GM_PumpCallbacks drains:
// NavigationService.navigatorKey.currentState?.pushNamed('/offer');
// NavigationService.navigatorKey.currentState?.pop();  // dismiss

Kill Switch

Polled every 60 seconds automatically. Check the level before requesting offers or showing the overlay.

kill_switch
// Polled every 60s automatically. Check before requesting an offer:
int level = GM_GetKillSwitchLevel();
// 0 = NONE           → normal operation
// 1 = OFFERS_KILL    → suppress offers, continue telemetry
// 2 = TELEMETRY_KILL → suppress telemetry, continue offers
// 3 = FULL_KILL      → suppress everything

if (level == 3) {
  // FULL_KILL — hide Experience overlay entirely
  hideFlutterUI();
}

0 — NONE

Normal. All features active.

1 — OFFERS_KILL

Suppress offers. Telemetry continues.

2 — TELEMETRY_KILL

Suppress telemetry. Offers continue.

3 — FULL_KILL

Suppress everything. Hide overlay.

App Pause / Resume

Always flush events and hide the overlay on pause. On resume, re-check the kill switch before showing. Never call FlutterEngineResetContext from your code — the Unreal plugin calls it internally.

lifecycle
// On app pause (background)
void OnAppPause() {
  hideFlutterUI();       // hide overlay
  GM_FlushEvents();      // flush buffered telemetry
  // Do NOT call FlutterEngineResetContext — this crashes the render thread
}

// On app resume (foreground)
void OnAppResume() {
  // Re-check kill switch immediately on resume
  int level = GM_GetKillSwitchLevel();
  if (level != 3) {
    showFlutterUI();
  }
}

Critical Rules

  • Never call FlutterEngineResetContext — crashes Unity render thread
  • Never call runApp() before initFlutterUnreal()
  • Never wrap an entire Scaffold in BlockPointer — only wrap tappable widgets
  • Never hardcode pixelRatio from device DPI — always pass 1.0
  • Never use addPostFrameCallback or SchedulerBinding — use Timer.periodic(16ms) for gmPumpCallbacks
  • Never apply a code push patch mid-session — apply only on game launch (Phase 0)