Mocking Data Models
This article covers how to develop and test your UI without a running game engine by mocking data models , events , and calls entirely in JavaScript. You will learn what a model is, how to detect your environment, and how to simulate the full data lifecycle that would normally come from the game’s backend.
Frontend developers should not have to launch a heavy 3D game engine just to test whether a health bar fills correctly or an inventory grid renders properly. By mocking data models directly in JavaScript, you can build, test, and iterate on your UI completely independently of the game backend. Once you are satisfied with the result, the same UI code works without modification when connected to the real engine.
What is a Model?
Section titled “What is a Model?”Before diving into mocking, it is important to understand what a model is in the context of Gameface.
A model is a named JavaScript object that represents a slice of game state. It could hold anything the UI needs to display: player health, inventory items, character names, quest progress, or settings. In a production environment, the game engine (the C++ backend) exposes these models to the frontend. During development, you create them yourself in JavaScript.
What makes models powerful is data-binding . Gameface can connect a model’s properties directly to the DOM using special HTML
attributes. Instead of manually writing document.getElementById("health").innerText = player.health every time a value changes, you declare the
relationship once in your HTML and the engine keeps the DOM synchronized automatically.
Here is a minimal example to illustrate the concept:
// Create a model named 'PlayerModel' with initial game stateengine.createJSModel("PlayerModel", { health: 100, name: "Warrior",});import TextBlock from '@components/Basic/TextBlock/TextBlock';
const PlayerHud = () => ( <> <TextBlock attr:data-bind-value="{{PlayerModel.name}}" /> <TextBlock attr:data-bind-value="{{PlayerModel.health}}" /> </>);<!-- The DOM automatically displays the model's values --><div data-bind-value="{{PlayerModel.name}}"></div><div data-bind-value="{{PlayerModel.health}}"></div>The double-curly-brace syntax ({{PlayerModel.health}}) is Gameface’s data-binding expression. You will explore this in depth in the
Data-Binding Basics article. For now, the key takeaway is: models are
the data source, and data-binding is the mechanism that connects them to the DOM.
Detecting Your Environment
Section titled “Detecting Your Environment”A well-structured project should automatically enable mock data only when the UI is not running inside the actual game engine. This way you keep one codebase that works in both contexts without manual toggling. There are two simple ways to detect your environment:
- Using the engine’s own event system.
- Using Vite’s environment variables.
The most straightforward approach uses the engine’s own event system. The game backend can fire a specific event (e.g., "InEngine") when the view loads inside the engine. If that event never fires, you know you are running in a standalone browser or the Gameface Player.
let isInEngine = false;
engine.whenReady.then(() => { engine.on("InEngine", () => { isInEngine = true; });
// The 'InEngine' event fires synchronously during whenReady // if the backend triggers it on OnReadyForBindings. if (!isInEngine) { console.log("Running outside the engine. Loading mock data..."); // Create Mocked Models here initializeMocks(); } else { console.log("Running inside the engine. Using real game data."); }});If your project uses Vite, you can leverage its --mode flag to control which environment file is loaded.
The idea is simple: different modes load different .env files, and a VITE_MOCK_DATA variable in those files controls whether mocks are active.
First, create two environment files in your project root:
VITE_MOCK_DATA=trueVITE_MOCK_DATA=falseThen set up your package.json scripts so each mode loads the correct file:
{ "scripts": { "dev": "vite dev --mode development", "dev:engine": "vite dev --mode engine" }}When you run npm run dev, Vite loads .env.development and sets VITE_MOCK_DATA to "true". When you run npm run dev:engine, Vite loads .env.engine where mocking is disabled. Your code reads the variable and branches accordingly:
if (import.meta.env.VITE_MOCK_DATA === 'true') { console.log("Dev mode: Loading mock data..."); initializeMocks();}Mocking Data Models
Section titled “Mocking Data Models”Creating a Model
Section titled “Creating a Model”To create a mock model, use the engine.createJSModel(modelName, modelObject) method. This registers a named JavaScript object as a data-binding model and exposes it as a global variable.
engine.createJSModel("player", { maxHealth: 100, currentHealth: 75, heroType: "warrior", pet: { type: "wolf", name: "Fang", }, traits: [ { strength: 10, intelligence: 20 }, { strength: 15, intelligence: 17 }, ],});After calling createJSModel, a global variable player becomes available. You can immediately bind its properties in your HTML:
<div class="player-card"> <h2 data-bind-value="{{player.heroType}}"></h2> <div class="health-bar" data-bind-style-width="{{player.currentHealth}}"></div> <p>Pet: <span data-bind-value="{{player.pet.name}}"></span></p></div>
<!-- Iterating over arrays --><ul data-bind-for="trait:{{player.traits}}"> <li>STR: <span data-bind-value="{{trait.strength}}"></span></li></ul>Synchronizing Mocked Data
Section titled “Synchronizing Mocked Data”In a real game, the backend pushes updates to data-bound models automatically. When you are mocking in JavaScript, you must manually tell the engine that a model has changed and that it should update the DOM.
Simply changing a property on the model object does nothing to the DOM by itself:
// This changes the JS object but the DOM still shows 75player.currentHealth = 50;To force the UI to reflect the new value, you need a two-step process:
-
Call engine.updateWholeModel(model) to mark the model as “dirty” (i.e., tell the engine “this model’s data has changed and needs re-reading”).
-
Call engine.synchronizeModels() to flush all dirty models to the DOM. The engine walks through every model you marked in step 1, compares the current values to the bound DOM nodes, and applies the updates.
Here is the correct pattern:
// Update the propertyplayer.currentHealth = 50;
// Notify the engine that this model changedengine.updateWholeModel(player);
// Flush all pending model updates to the DOMengine.synchronizeModels();Augmenting Existing Models
Section titled “Augmenting Existing Models”Sometimes the backend model is only partially implemented while you are building the UI. You might have a player model coming from the engine with
health and name, but the UI also needs gold and questLog that the backend team has not exposed yet.
Use engine.createOrMergeModel(name, jsObject) for this scenario. It checks whether a model with that name already exists:
- If the model does not exist, it creates it (same as
createJSModel). - If the model already exists, it adds only the missing properties from your object. Existing values remain untouched.
// Assume the backend already created a 'player' model with:// { maxHealth: 100, currentHealth: 50, weapon: { name: "sniper" } }
// This adds 'gold' and 'weapon.damage' without touching existing valuesengine.createOrMergeModel("player", { gold: 500, currentHealth: 999, // Ignored: already exists, stays at 50 weapon: { name: "rifle", // Ignored: already exists, stays at "sniper" damage: 75, // Added: did not exist before },});Unregistering a Model
Section titled “Unregistering a Model”When a model is no longer needed (for example, when transitioning between screens or cleaning up after a match), you can remove it entirely:
engine.unregisterModel(player);This removes the model from the data-binding system and deletes the associated global variable. Any DOM elements still bound to this model will no longer receive updates.
Mocking Events & Calls
Section titled “Mocking Events & Calls”In the previous article, you learned about engine.on,
engine.trigger, and engine.call for event communication. When developing without the engine, you need a way to simulate the data flow. There are
two approaches to this:
The Universal Approach: engine.on + engine.trigger
Section titled “The Universal Approach: engine.on + engine.trigger”An important fact to internalize: engine.on handlers fire regardless of where the event originates. Whether the event is
triggered from your own JavaScript code (engine.trigger) or from the game’s backend, the same engine.on handler catches it.
This means your standard development workflow is straightforward:
// This handler is your real production code.// It runs the same way whether the trigger comes from JS or from the backend.engine.on("PlayerHealthChanged", (newHealth) => { document.getElementById("healthDisplay").innerHTML = newHealth;});During development, since there is no backend to trigger the event, you simulate it yourself:
// Simulating the backend during dev - fire the event manuallyengine.trigger("PlayerHealthChanged", 80);In production, the backend fires PlayerHealthChanged instead, your engine.on handler catches it identically, and you simply remove your dev
engine.trigger calls. This covers the majority of frontend mocking needs.
Mocking Backend-Side Behavior: engine.mockEvent
Section titled “Mocking Backend-Side Behavior: engine.mockEvent”engine.mockEvent solves a different problem. It is not about mocking your own frontend handlers. It is about simulating what the backend
would do in response to events your UI sends out.
Consider this scenario: your UI has a “Quit Game” button that fires engine.trigger("UI_QuitGame"). In production, a backend handler catches that
event and shuts down the game. During development mockEvent lets you simulate that backend reaction:
// Simulate what the backend would do when the UI signals "quit"engine.mockEvent("UI_QuitGame", () => { console.log("Mock backend: Received quit signal. Cleaning up..."); showExitConfirmation();});The key behavior of mockEvent: once a real backend handler is registered for "UI_QuitGame", the mock automatically becomes inactive. You do not
need to remove it manually.
document.getElementById("quit-btn").addEventListener("click", () => { // During dev: the mockEvent handler fires // In production: the real backend handler fires instead engine.trigger("UI_QuitGame");});Mocking Calls: engine.mockCall
Section titled “Mocking Calls: engine.mockCall”The same pattern applies to engine.call. Use engine.mockCall(eventName, callback) to simulate what the backend would return
when your UI requests data. The mock only responds when no real backend handler exists.
Your mock callback should return the data that the real backend would return. This value becomes the resolved value of the Promise returned by
engine.call:
// Simulate the backend response for a weapon data requestengine.mockCall("getActiveWeapon", (playerIdx) => { return { __Type: "Weapon", name: "Sniper", damage: 100, rarity: "legendary", };});
// Your UI code works identically in dev and productionengine.call("getActiveWeapon", 1).then((weapon) => { console.log(`Equipped: ${weapon.name} (${weapon.damage} dmg)`);});This is where mockCall is genuinely useful: your UI uses engine.call to request data, and during dev the mock provides a response. In production,
the real backend handler resolves the Promise instead, and the mock becomes inactive automatically.
© 2026 Coherent Labs. All rights reserved.