Drop-in adoption

Bring one JGengine system into the game you already have.

You don't have to adopt an engine to use its parts. Pull in a minimap, health, damage, or XP — over your own entities, store, and loop — with a single install and a tiny adapter. Here's how each one drops in, and the honest tradeoffs.

1

Install one package

bun add @jgengine/core (and @jgengine/react for UI). Zero-dep core.

2

Write a tiny adapter

A get/set over your store, or one entity → marker projection.

3

Call it from your loop

Apply results and tick(dt) yourself. Your renderer never changes.

🗺️Minimap

Render a minimap from the array you already have

Project your own units into display markers and hand them to the minimap. It reads a static array, a subscribable store, or a native MarkerSet — no copying entities into JGengine.

You keep
Your entity array/ECS/store, your styling and chrome.
You get
A framed minimap with categorized markers and a facing arrow — no GameProvider, no three.js.
Minimap.tsx
import { Minimap } from "@jgengine/react/map";
// units are YOUR objects — nothing is mirrored into a JGengine store.
const markers = units.map((u) => ({ id: u.id, position: [u.x, 0, u.z], kind: u.team }));
<Minimap markers={markers} center={[cam.x, cam.z]} worldRadius={120} />;

❤️Health & resources

Health, shields, mana — over your own state

A serializable current/max/min pool with a two-method adapter. Nothing privileges “health”; the same primitive drives shields, stamina, durability, or any named resource in the store you already own.

You keep
Where the numbers live — a plain object, Zustand, Redux, an ECS component, a DB row.
You get
Clamped, inspectable pool changes with the exact applied amount and min/max flags.
health.ts
import { applyStatPoolDelta, createStatPool } from "@jgengine/core/stats/statPool";
const pools = { hero: { health: createStatPool({ current: 80, max: 100 }) } };
const access = {
get: (id, stat) => pools[id]?.[stat] ?? null,
set: (id, stat, next) => { pools[id][stat] = next; },
};
const hit = applyStatPoolDelta(access, "hero", "health", -12); // hit.applied === -12

⚔️Damage

Resolve a hit, keep the provenance

A pure, deterministic damage resolver: channel-vs-trait matchup, receiver modifiers, and a status roll against an injected RNG. It returns the final impact and every stage — you apply it to whatever holds your entities' health.

You keep
Your entities, your health sink, when a hit happens.
You get
A serializable, replay-safe resolution you can run on the authority and mirror to clients.
damage.ts
import { resolveDamageHit } from "@jgengine/core/combat/damageResolution";
import { applyStatPoolDelta } from "@jgengine/core/stats/statPool";
const result = resolveDamageHit({
channel: "kinetic",
impact: 24,
target: enemy.id,
targetTraits: enemy.armored ? ["armored"] : [],
matchup: { entries: { kinetic: { armored: { impact: 0.5 } } } },
});
// Drain it through the same two-method adapter from health.ts above.
applyStatPoolDelta(access, enemy.id, "health", -result.impact); // -12 vs the armored enemy

XP & leveling

XP curves and level-ups on your save format

Deterministic XP curves, surplus rollover, level caps, and one event per reached level — written back through a two-method adapter over the player state you already persist.

You keep
Player identity, your save schema, your event bus, when XP is granted.
You get
grantXp: curve math, rollover, caps, and ordered level-up callbacks with no store to adopt.
xp.ts
import { leveling } from "@jgengine/core/game/progression";
const track = leveling({ xpForLevel: { kind: "power", base: 80, exponent: 1.35, round: "floor" }, maxLevel: 20 });
// access reads/writes save.players[id].xp / .level — your data, your store.
const gained = track.grantXp(access, "p1", 900, (level) => emit("level-up", { level }));

The tradeoffs

What you get, and what you take on.

Drop-in adoption is a deliberate deal: a tiny surface and total ownership of your world, in exchange for wiring the SDK deliberately doesn't do for you.

Why it's worth it

  • Add one system, not an architecture

    Keep your renderer, entity store, React setup, and game loop. You import a single capability, not a framework that wants to own main().

  • Your data stays yours

    A small get/set (or one projection) adapter reads the state you already own. Nothing is mirrored into a parallel JGengine store to use a calculation or renderer.

  • Serializable & deterministic

    State is plain data; time and randomness are injected. The same pieces drop into saves, multiplayer authority, replays, and tests without special-casing.

  • Tree-shakable, zero-dep core

    Direct imports like @jgengine/core/stats/statPool pull only what you touch. The core package has no dependencies of its own.

What you take on

  • You write the adapter and the wiring

    The get/set, the projection, the raycast, and the tick/update call site are yours. It's a few lines, but the SDK deliberately doesn't reach into your world for you.

  • Presentation stays your job

    These are calculations, renderers, and mechanics — not a turnkey game. Rendering, input, audio, and persistence remain on your side of the seam.

  • Some systems still want native integration

    The full runtime, the authoritative multiplayer host, and editor-authored scenes assume a JGengine host. A portable kernel exists underneath where practical, but the drop-in path stops there.

  • Explicit beats magic

    You call tick(dt) and apply results yourself. That's more wiring than a black box — and the reason it fits engines you didn't write.

How to read a capability

Every system is one of three modes.

Before you adopt one, you know exactly how deep it reaches into your project.

Pure portable

Plain data in, plain data out. No runtime, renderer, store, or React. Curves, matchups, pool math.

Adapter portable

Works over your state through a small structural interface. You keep ownership of entities, updates, and rendering.

Native integration

Uses a JGengine host (GameContext, the shell). Reserved for what genuinely needs it, with a portable kernel underneath.

More drop-in systems land as they're built — inventory, quests, weapon plumbing, and multiplayer plumbing follow the same adapter shape.