FERRETGame engine over Babylon.js
Ferret

@ferret/platform

One interface over OS Beroun and the browser: saved state, suspend, exit, screen size, device class, render scale and quality tiers.

24 exports · source in packages/platform/src

Classes

BerounPlatform class

class BerounPlatform extends DomPlatform implements FerretPlatform

OS Beroun: the global Platform object that /platform.js provides.

readonly kind: "beroun"
constructor(api: any = (globalThis as any).Platform)
async loadState<T>()Saved state of the game (null when there is none).
async saveState(state: unknown)
exit()Leave the game: OS Beroun closes the app; a browser goes back to the game's start page.
async capabilities()
log(msg: string)Plain text to the host's log (the OS Beroun shell's log; the browser console).

packages/platform/src/platform.ts:67

BrowserPlatform class

class BrowserPlatform extends DomPlatform implements FerretPlatform

An ordinary desktop or mobile browser.

readonly kind: "browser"
constructor(private key = 'ferret', private startPage = './')
async loadState<T>(): Promise<T | null>Saved state of the game (null when there is none).
async saveState(state: unknown)
exit()Leave the game: OS Beroun closes the app; a browser goes back to the game's start page.
async capabilities()
async requestFullscreen()
log(msg: string)Plain text to the host's log (the OS Beroun shell's log; the browser console).

packages/platform/src/platform.ts:87

Display class

class Display

Keeps an engine's canvas, hardware scaling, quality and cameras in step with the screen. Resize, rotation, fullscreen and a move to an external screen are handled without reloading anything.

quality: QualitySettings
renderScale: number
xrActive: booleana VR session owns the render target and the field of view: the screen scaling and FOV are left alone while it runs
onChange?: (d: Display) => void
constructor(readonly engine: BABYLON.AbstractEngine, readonly platform: { screen(): ScreenInfo; onResize(fn: (s: ScreenInfo) => void): () => void; kind: string }, opts: { tier?: QualityTier; baseVerticalFov?: number; minHorizontalFov?: number } = {})
setTier(tier: QualityTier)
setXr(on: boolean)Enter or leave VR: the xr tier while a session runs, the screen's tier after.
trackCamera(cam: BABYLON.Camera)
get aspect(): number
apply()
applyFov()
dispose()

packages/platform/src/display.ts:77

HeadlessPlatform class

class HeadlessPlatform implements FerretPlatform

Tests: everything in memory, nothing needs a DOM.

readonly kind: "headless"
state: unknown
exited: boolean
logs: string[]
size: ScreenInfo
async loadState<T>()Saved state of the game (null when there is none).
async saveState(s: unknown)
onSuspend(fn: (reason: string) => void | Promise<void>)Called before the page goes away (OS Beroun: the app is closed, ~2 s to save; browser: hidden/closed).
async suspend(reason = 'closed')
exit()Leave the game: OS Beroun closes the app; a browser goes back to the game's start page.
async capabilities()
screen()
onResize(fn: (s: ScreenInfo) => void)
resize(w: number, h: number, density = 1, deviceClass?: DeviceClass)
log(msg: string)Plain text to the host's log (the OS Beroun shell's log; the browser console).

packages/platform/src/platform.ts:135

Functions

classifyDevice function

function classifyDevice(width: number, height: number, kind: PlatformKind, coarse: boolean): DeviceClass

packages/platform/src/platform.ts:37

detectLevelTier function

function detectLevelTier(o: TierOptions = {}): TierChoice

The level tier for this browser, with the reason (for the log and for a debug line): - a software renderer, little memory, data saving or a slow network -> low; - phones and tablets -> mobile; - a desktop with its own graphics card (NVIDIA, AMD, Apple M, Intel Arc) -> desktop; - an old integrated card or few cores / little memory -> mobile; - nothing known -> desktop on a desktop, mobile on a touch device. The page can still give the player a switch; this is only the first choice.

packages/platform/src/tier.ts:69

detectPlatform function

function detectPlatform(opts: { key?: string; startPage?: string } = {}): FerretPlatform

Pick the implementation: OS Beroun when /platform.js is present, else the browser.

packages/platform/src/platform.ts:159

fovFor function

function fovFor(aspect: number, baseVertical = 0.9, minHorizontal = 1.0): number

Vertical field of view for an aspect ratio: fixed vertical FOV, so the view widens with the aspect; on tall (portrait) screens it grows so that the horizontal FOV does not fall below minHorizontal (the playable view stays the same).

packages/platform/src/display.ts:55

initialTier function

function initialTier(deviceClass: DeviceClass, kind: 'beroun' | 'browser' | 'headless'): QualityTier

The tier chosen from the device class. The console is always low; a benchmark may refine the others later.

packages/platform/src/display.ts:34

probeDevice function

function probeDevice(): DeviceProbe

Reads the device from the browser. The graphics card is read from a throw-away canvas that is released at once.

packages/platform/src/tier.ts:21

renderScaleFor function

function renderScaleFor(screen: ScreenInfo, tier: QualityTier, requested = 1): number

The render scale (<= 1) that keeps the frame within the tier's pixel budget.

packages/platform/src/display.ts:45

uiScaleFor function

function uiScaleFor(screen: ScreenInfo): number

UI scale from the screen's physical size, not its pixels: text stays readable on a console and on a phone.

packages/platform/src/display.ts:63

Interfaces

DeviceProbe interface

interface DeviceProbe

What the browser tells about the machine. Every field is optional: a browser may hide any of them.

renderer?: stringthe graphics card (WEBGL_debug_renderer_info): ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 ...)
memoryGB?: numbernavigator.deviceMemory, GB, rounded down to a power of two and capped at 8
cores?: number
saveData?: booleanthe user asked the browser to save data
connection?: stringnavigator.connection.effectiveType

packages/platform/src/tier.ts:8

FerretPlatform interface

interface FerretPlatform

One interface, two implementations (plus a headless one for tests). OS Beroun: Platform.state / onClose / exit / capabilities of /platform.js. Browser: IndexedDB/localStorage state, visibilitychange/pagehide as suspend, fullscreen, orientation.

readonly kind: PlatformKind
loadState<T = any>(): Promise<T | null>Saved state of the game (null when there is none).
saveState(state: unknown): Promise<void>
onSuspend(fn: (reason: string) => void | Promise<void>): () => voidCalled before the page goes away (OS Beroun: the app is closed, ~2 s to save; browser: hidden/closed).
exit(): voidLeave the game: OS Beroun closes the app; a browser goes back to the game's start page.
capabilities(): Promise<string[]>
screen(): ScreenInfo
onResize(fn: (s: ScreenInfo) => void): () => void
requestFullscreen?(): Promise<void>
log(msg: string): voidPlain text to the host's log (the OS Beroun shell's log; the browser console).

packages/platform/src/platform.ts:20

QualitySettings interface

interface QualitySettings
tier: QualityTier
renderScale: number1 = native size x density; lower renders fewer pixels (hardware scaling = 1 / renderScale)
shadowMapSize: number
shadowCascades: number
lodBias: number
maxTextureSize: number
fxaa: boolean
msaa: number
postProcessing: boolean
drawDistance: number
foveation?: numberVR: fixed foveation 0..1 (the headset renders the edges of the view at lower resolution)
framebufferScale?: { min: number; max: number; start: number }VR: the range the framebuffer scale may move in, and where it starts
streaming?: { radius: number; budget: number }VR: scale of the streaming radii and of the per-frame instantiation work

packages/platform/src/display.ts:5

ScreenInfo interface

interface ScreenInfo
width: number
height: number
density: numberdevicePixelRatio
portrait: boolean
deviceClass: DeviceClass

packages/platform/src/platform.ts:6

TierChoice interface

interface TierChoice
tier: LevelTier
reason: string

packages/platform/src/tier.ts:48

TierOptions interface

interface TierOptions
override?: string | nullthe tier asked for in the address (?tier=) or in a setting: wins when it is one of the tiers and the level has it
screen?: Pick<ScreenInfo, 'deviceClass'>
probe?: DeviceProbedefault: probeDevice()
available?: readonly LevelTier[]the tiers the level package has (default: all three). A tier that is not there gives way to the nearest one.

packages/platform/src/tier.ts:50

Types

DeviceClass type

type DeviceClass = 'console' | 'phone' | 'tablet' | 'desktop'

packages/platform/src/platform.ts:4

LevelTier type

type LevelTier = 'desktop' | 'mobile' | 'low'

The quality variants a level package comes in (tiers.json of the exporter): a folder per tier, desktop the richest.

packages/platform/src/tier.ts:4

PlatformKind type

type PlatformKind = 'beroun' | 'browser' | 'headless'

packages/platform/src/platform.ts:3

QualityTier type

type QualityTier = 'low' | 'medium' | 'high' | 'xr'

packages/platform/src/display.ts:3

Constants

LEVEL_TIERS const

const LEVEL_TIERS: readonly LevelTier[]

packages/platform/src/tier.ts:5

QUALITY const

const QUALITY: Record<QualityTier, QualitySettings>

packages/platform/src/display.ts:25