Client Runtime
Engine-owned documentation. This page describes reusable client runtime behavior in
Source/Client/; game UI policy, gameplay rules, and concrete content remain in the embedding project.
Purpose
The client runtime turns baked resources, server state, local input, and scripts into the player-facing game view. It does not own game design decisions. Instead, it provides the reusable engine pieces that a game project drives through scripts, configuration, resources, and server messages.
Read this page together with:
- EntityModel.md for the entity/property/prototype model that client views wrap.
- MapsMovementGeometry.md for map positions, path finding, line tracing, and movement contexts.
- Networking.md for command buffers, transports, and property sync.
- FrontendAndRendering.md for platform windows, input, audio, and renderer backends.
- WebDebugging.md, AndroidDebugging.md, and Debugging.md for platform-specific validation flows.
Source paths inspected
Source/Client/Client.hSource/Client/Client.cppSource/Client/ClientConnection.hSource/Client/ClientConnection.cppSource/Client/ResourceManager.hSource/Client/ResourceManager.cppSource/Client/FontManager.hSource/Client/FontManager.cppSource/Client/MapView.hSource/Client/MapView.cppSource/Scripting/ClientMapScriptMethods.cppSource/Scripting/AngelScript/CoreScripts/Gui.fosSource/Client/CritterView.hSource/Client/CritterHexView.hSource/Client/ItemView.hSource/Client/ItemHexView.hSource/Client/LocationView.hSource/Client/PlayerView.hSource/Client/DefaultSprites.hSource/Client/DefaultSprites.cppSource/Client/ModelAnimation.hSource/Client/ModelAnimation.cppSource/Client/ModelBakedData.hSource/Client/ModelBakedData.cppSource/Client/ModelManager.hSource/Client/ModelManager.cppSource/Client/ModelInstance.hSource/Client/ModelInstance.cppSource/Client/ModelInformation.hSource/Client/ModelInformation.cppSource/Client/ModelHierarchy.hSource/Client/ModelHierarchy.cppSource/Client/ModelSprites.hSource/Client/ModelSprites.cppSource/Client/ModelSpriteLayout.hSource/Client/ModelSpriteLayout.cppSource/Common/AnimationInfo.hSource/Common/AnimationInfo.cppSource/Common/ModelBounds.hSource/Common/ModelBounds.cppSource/Client/ParticleSprites.hSource/Client/ParticleSprites.cppSource/Client/RenderTarget.hSource/Client/RenderTarget.cppSource/Tests/Test_ClientEngine.cppSource/Tests/Test_ClientRuntimeApi.cppSource/Tests/Test_ClientDataValidation.cppSource/Tests/Test_ClientServerIntegration.cppSource/Tests/Test_ModelBaker.cppSource/Tests/Test_ModelAnimationRuntime.cpp
Runtime owner: ClientEngine
ClientEngine in Source/Client/Client.h is the central client-side engine object. It derives from BaseEngine and AnimationResolver, owns the high-level client lifecycle, and exposes event hooks used by client scripts.
Major responsibilities:
- initialize and shut down the client runtime;
- run the per-frame
MainLoop(); - process application input events;
- connect and disconnect from a server through
ClientConnection; - create, register, unregister, and look up client-side entities by id;
- receive and apply network messages for critters, items, maps, custom entities, time sync, movement, actions, and properties;
- own client-facing managers such as sprites, effects, fonts, sounds, video playback, resources, cache storage, and render targets;
- raise engine events such as
OnStart,OnLoop,OnConnected,OnDisconnected, render-map stages, input events, entity in/out events, and map load/unload events.
ClientEngine is intentionally broad: it is the composition root where Common-layer data (Entity, properties, prototypes, networking buffers) meets Frontend-layer services (Application, render, input, audio) and game scripts.
Client lifecycle
A typical client lifetime has these phases:
- Application initialization happens in the frontend layer.
Applicationowns the main window, renderer, input, and audio. See FrontendAndRendering.md. - Resource filesystem selection starts through
GetClientResources(GlobalSettings&)inSource/Client/Client.cpp, which builds the client-sideFileSystemview used by runtime managers. Installed clients may add a higher-priority writable resource overlay above the read-only base resources; see ConfigurationAndDataSources.md and ClientUpdater.md. - Engine construction wires settings, resources, the main application window, generated metadata, script modules, and client managers.
OnStart/script initialization gives scripts their first client-side entry point.Source/Tests/Test_ClientEngine.cppvalidates that script module init and loop calls are callable on a self-contained client runtime.- The main loop processes frontend input, network packets, scripted loop callbacks, visual effects, video playback, map processing, and rendering-facing updates.
- Network connection starts with
Connect(), which delegates transport setup and handshake work toClientConnection. - Map and entity state arrive through network messages, are represented as client view entities, and are updated through property sync and movement/action packets.
- Shutdown disconnects networking, destroys inner entities, clears caches and render targets, and releases frontend resources.
When changing startup or shutdown behavior, keep script events, manager lifetime, entity registration, and network callbacks in sync; these paths are tightly coupled.
Server connection and message dispatch
ClientConnection (Source/Client/ClientConnection.h, Source/Client/ClientConnection.cpp) owns the client-side transport state. It hides whether the current connection is interthread, TCP sockets, or UDP-capable sockets.
Important responsibilities:
Connect()selects the connection mode fromClientNetworkSettingsand creates aNetworkClientConnection;CreateNetworkConnection(bool use_udp)selects socket or UDP socket transport;Process()advances connect, receive, dispatch, and send work;Disconnect()tears down active transport and raises the configured disconnect handler;TryFallbackToTcp()provides a fallback path when UDP setup fails;Net_SendHandshake(),Net_OnHandshakeAnswer(), andNet_OnPing()handle connection-level protocol messages;AddMessageHandler(NetMessage, MessageCallback)binds protocol messages toClientEngine::Net_On...handlers.
ClientEngine owns the semantic handlers. Examples include Net_OnInitData, Net_OnAddCritter, Net_OnRemoveCritter, Net_OnProperty, Net_OnLoadMap, Net_OnSomeItems, Net_OnRemoteCall, Net_OnAddCustomEntity, and Net_OnRemoveCustomEntity.
For protocol format details, use Networking.md. For client/server handshake validation, see Source/Tests/Test_ClientServerIntegration.cpp, especially ClientAndServerHandshakeOverInterthreadTransport.
Client-side script continuations scheduled through ScheduleDelayedCallback() are processed once per main-loop pass from a snapshot of callbacks already due at the start of that pass. A callback that schedules another zero-delay callback, including Yield(0), resumes on the next pass instead of re-entering immediately. This prevents script wait loops from starving the next network/input tick.
Entity and view model
Client-side game objects are not raw server entities. They are view entities that combine Common-layer entity data with client-only rendering, input, and presentation state.
Primary view types:
PlayerView— client representation of the current player entity.LocationView— client-side location entity.MapView— map entity plus map rendering, local spatial indexes, fog, lighting, scrolling, zoom, and hit testing.CritterView— critter entity visible outside the current map context.CritterHexView— critter placed on a map hex, with movement, direction, and map rendering behavior.ItemView— item entity visible in inventory or generic client contexts.ItemHexView— item placed on a map hex, with map flags, sprite placement, blockers, and lighting implications.- custom client entities — created by
CreateCustomEntityView()when a synced custom entity entry arrives.
ClientEngine::RegisterEntity() and ClientEngine::UnregisterEntity() maintain the id-to-entity lookup used by network handlers and scripts. Source/Tests/Test_ClientEngine.cpp validates that client entities can be registered and removed from the lookup.
Script GUI ItemView widgets cache the item handles bound to their cells. Resort() keeps a cell only when the source returns the same handle instance; a replacement clone with the same entity id is rebound so item draw callbacks observe its current count and other projected properties.
3D model runtime architecture
The former 3dAnimation and 3dStuff umbrella modules have been removed. The
client 3D path now uses same-named Model*.h / Model*.cpp module pairs with
one ownership boundary per module. There is no client-side ModelMesh utility
module: mesh representation types live with ModelHierarchy. There is also no
separate ModelPoseRuntime module: renderer-independent pose/link operations
are part of ModelAnimation.
| Module | Responsibility |
|---|---|
ModelManager |
Runtime entry point, model-description and hierarchy caches, baked mesh loading, and construction of model instances. |
ModelHierarchy |
Shared loaded ModelBone topology, mesh/bind data, model textures/effects, and the mesh representation types used by hierarchy and instances. It does not own mutable animation pose output. |
ModelInformation |
One resolved baked .fo3d description: hierarchy reference, canonical/runtime joint identities, animation lookup tables, cuts/links, draw metadata, and one immutable ModelAnimationRuntimeRig. |
ModelInstance |
All mutable per-instance state: controller timelines, runtime pose, world-matrix snapshots, mesh state and batching, cuts, attachments, particles, procedural transforms, projection, and draw submission. |
ModelAnimation |
Engine animation-controller timeline, transition, callback, reverse/freeze/play-once, and binding semantics together with the engine-owned clip/rig/pose contract, Ozz-backed sampling and blending behind PImpl, canonical-joint mapping, linked-pose resolution, and validated rest-pose matrix construction for direct raw models. |
ModelBakedData |
Small defensive reader helpers shared by client model loaders, especially count-versus-unread-data preflight before allocation. |
ModelSprites |
Adapter from ModelManager / ModelInstance to atlas-backed and direct-scene sprite rendering. |
The ownership direction is deliberate:
ModelManagercaches sharedModelHierarchyandModelInformationobjects;- every created
ModelInstancereferences oneModelInformationbut owns its mutable controllers, pose buffers, matrices, mesh overrides, and children; - shared
ModelBonenodes retain topology, rest/bind, and drawable data only; pose results never flow back into the shared hierarchy; - canonical animation joints may exist without a physical
ModelBone, so canonical indexing and cross-model pose links belong toModelAnimation, notModelHierarchy.
This split removes the old include-everything dependency. A caller includes the manager, information, hierarchy, instance, animation, or baked-data contract it actually consumes. Small passive types stay with their owning module instead of forming empty translation units.
ModelAnimationRuntimeClip, ModelAnimationRuntimeRig, and
ModelAnimationRuntimePose keep their backend state behind PImpl. The public
header exposes only engine-owned runtime values and spans; Ozz headers, archive
objects, sampling contexts, and matrix buffers stay in
ModelAnimation.cpp. ModelAnimationData defines the native versioned wire
contract shared with the baker, while
ModelAnimationConverter owns offline conversion. The engine API does not name
or model interchangeable animation backends: Ozz is an implementation detail of
the engine’s native model-animation runtime and baked format.
Critter model animation
3D critter models use separate body/action and movement animation controllers. ModelInstance::PlayAnim() applies
animation-specific speed (AnimSpeed) to the body/action controller, while RefreshMoveAnimation() assigns gait and
movement-speed scaling to the movement controller’s track. When both are active, the movement controller advances with
the model base/link/global speed only; it must not inherit the current body action’s AnimSpeed, otherwise fast actions
such as use/pick-up make the leg cycle run too quickly while the critter is moving.
An animation name prefixed with ~ plays the source clip in reverse: playback time t samples the clip at
duration - fmod(t, duration), so an exact loop boundary restarts at the clip end. If interpolation is disabled, the
same nearest-key rule is applied at that reversed source time.
Every baked .fo3d has the required versioned LFMODINF schema-1 header and a
required LFOZZRIG payload generated for pinned ozz-animation 0.16.0.
ModelInformation strictly loads and owns one immutable
ModelAnimationRuntimeRig. Its PImpl contains the canonical Ozz skeleton,
unique clips, base/clip remaps, presence masks, nearest timelines, and resolved
state/action binding table. The loader rejects old unversioned files and any
partial or inconsistent rig; it never falls back to another payload after an
Ozz load error. The final mesh-only wire transition uses compatibility marker
0.0.30 and requires a full resource rebake.
Ozz is the production animated-pose path. The body and movement controllers
advance timeline/event state only. Each registered animation stores direct Ozz
clip index/duration/reverse metadata and an immutable bound-joint set derived
from the clip presence mask. A joint is bound only when its canonical and exact
runtime names match, so this gate deliberately keeps a resource-renamed model
root out of a source-root animation. Per-track allowed-joint and transition-
suppression masks further filter those bindings before the track state feeds the
per-instance Ozz pose.
Ozz performs clip sampling, body blending,
movement-only joint replacement, procedural body/head pre-rotation, and
local-to-model evaluation. Each ModelInstance snapshots the resulting world
matrices, and skin palettes, linked children, particles, and bone queries read
that owning instance snapshot. Link-all attachments override only the matched
joint after evaluation and deliberately do not recompute its descendants,
preserving the established attachment order.
Canonical joint names, exact runtime lookup names, and name-to-index bindings
are independent of the physical ModelBone hierarchy. Base joints retain a
read-only physical bone for meshes and cuts; animation-contributed joints have
no ModelBone and are never materialized into the shared cached hierarchy.
The base root deliberately keeps its resource-path runtime alias, while
contributed joints use their canonical names, preserving exact legacy lookup
and root-animation suppression semantics. Runtime particles, attachment
resolution, link-all matching, and bone-position queries operate on canonical
indices; link-all can therefore bind contributed joints without physical
bones. Authored one-bone link validation remains base-hierarchy-only in the
current baking schema.
Static .fo3d instances evaluate an empty Ozz track set so canonical rest and
procedural body/head transforms follow the same runtime as animated instances.
Only direct raw-model instances remain outside Ozz and build parent-ordered
world matrices through the validation helpers owned by
ModelAnimation.
The client script pair Game.DrawCritter3d(...) and
Game.GetDrawCritter3dBounds(...) supports reusable GUI layout around a model
sprite. After drawing an instance, the bounds query returns two rectangles
relative to the draw anchor, or false when that instance has not produced a
valid model sprite. drawRect covers the selected animation’s complete cycle
and continuous facing range, including its projected shadow. viewRect is the
stable logical model-and-layers rectangle used by names, coarse picking, and
similar presentation. GUI preview code fits and centres the draw rectangle;
world-space overlays use the stable view rectangle as their logical anchor,
without duplicating 3D projection rules or depending on the current atlas crop.
The former custom pose evaluator and shared mutable matrix-output table have
been removed. Baked model meshes now begin with the mandatory LFMODMSH
schema-1 header and contain only the recursive hierarchy/bind/drawable mesh
payload. The client consumes it exactly and rejects headerless, mismatched,
truncated, or trailing data; no serialized TRS tail and no legacy mesh fallback
remain. Runtime clip identity, duration, joint presence, and sampling come
exclusively from the baked Ozz rig.
The nested LF archive hash detects accidental corruption but is not an authentication mechanism. Ozz deserialization assumes the baked resource pack is trusted; deployments that permit attacker-rewritable packs must authenticate the pack before this loader runs.
MapView: map presentation and local spatial state
MapView is the largest client view class because it bridges several subsystems:
- map file/static-data loading through
LoadFromFile()andLoadStaticData(); - map processing through
Process(); - map rendering through
DrawMap()and staged render events onClientEngine; - field indexes for items and critters;
- coordinate conversion, zoom, scrolling, screen-to-map and map-to-screen transformations;
- path finding and cut-path helpers built on
PathFinding::FindPath(); - line tracing for bullets and light fans;
MapView::ApplyLightFantraces every light source out to its fullDistancein hexes (kept at >= 1) and forwards per-light falloff metadata to primitive shaders viaPrimitivePoint::EggData→ vertex attribute slot 3 (InTexEggCoord):LightFanToPrimitveswrites the traced radius (hexes) and the normalized center alpha so a shader can reconstruct each fragment’s hex-distance-from-edge and fade over a fixed hex band independent of the light’s total length. A critter’s light fan follows the critter’s sprite offset (HexView::GetSpriteOffsetPtr()) so the light stays exactly under the sprite — the two must never diverge. The offset is kept bounded at the source:MovingContext::EvaluateRawProgress’scurrent_hexcan lag the smooth lerp, and client prediction reconciliation inReceiveCritterMovingcan fold an inter-hex delta into the offset during rapid step taps, butCritterHexView::StopMovingcashes any accumulated offset back into the hex (snapping to the hex the sprite actually reached and keeping only the sub-hex remainder, withhex + offsetinvariant so the sprite never jumps), so the offset cannot run away and drag the fan off the critter. Items use the sameGetSpriteOffsetPtr(); - fog-of-war layers;
- local lighting sources and render targets;
- mapper mode helpers used by engine tools.
MapView is still a client-side view over the Common map model. Reusable coordinate/pathfinding rules belong in MapsMovementGeometry.md; presentation details such as render targets, light textures, transparent eggs, map scrolling, and hit testing belong here and in FrontendAndRendering.md.
Map light source intensity is authored as a percentage magnitude (0..100, with negative values keeping the same magnitude but opting into constant/personal capacity semantics). MapView clamps the current animated percentage, converts it to an internal raw falloff scale (0..10000), and then scales light-map RGB to the engine light range (0..200) and primitive alpha to 0..255 through the source’s day-light capacity percentage. SetDayColors() must invalidate applied light fans when either the day color or the light-capacity percentage changes, because both feed cached per-hex lighting.
The reusable map presentation API includes SetExtraScrollOffset() for script-owned transient camera offsets. The engine applies the offset to the map view, but game-specific screen effects such as quake/shake timing and fade overlays are owned by embedding-project scripts.
Resources, sprites, effects, and render targets
The client resource path starts with a FileSystem from GetClientResources() and is organized by runtime managers:
ResourceManagerindexes resource files, resolves item default sprites, loads and caches critter animation frames, handles Fallout-style animation frame mapping, and exposes sound-name mappings.SpriteManagerowns sprite factories, atlases, primitive drawing, draw ordering, scissor stack, window/screen sizing, and render-target drawing.DefaultSpriteFactoryloads atlas sprites and sprite sheets from default image/animation resources, including the optional per-frame silhouette mesh produced byImageBaker. The resource decoder also fills theSpritepayload of the commonAnimationInforecord with frame count, duration, directions, and resolved per-frame bounds; this payload remains available in builds without 3D support.EngineMetadatareads the matching compact version 1SpriteInfo/<PackName>.foinfoindexes at startup, so common sprite metadata queries do not decode pixel payloads. A source-backed sprite uses its mesh for ordinary full-image draws; an explicit empty mesh skips the draw, while a missing/quad mesh keeps the four-vertex path used by runtime-generated atlas sprites.-
With
FO_ENABLE_3D,ModelSpriteFactoryturns model resources into atlas-backed sprites. It asksEngineMetadatafor the already parsed version 2 aggregate, idle-priority view, and per-animation bounds fromModelAnimationInfo.foinfo; the client model layer never reopens or reparses that companion, and no authored.fo3dDrawSizeorViewSizeremains. Enabled body/movement animation envelopes are projected through the active model transform across every facing direction to derive a power-of-two logical scratch frame large enough for the body and projected shadow. The separate view envelope (Unarmed + Idle, any Idle, then deterministic fallback) seeds the bodyViewRect. Runtime layer and child-model bounds extend both the view/name rectangle and the aggregate horizontal-lighting frame; the envelope resets when mesh composition changes and otherwise only grows. Names, coarse picking, transparent eggs, flying text, and attachments therefore stay inside automatically derived bounds without authored sizes. A body/movement animation switch can refresh the scratch frame, but it must retain this accumulated configuration view envelope instead of falling back to the root model’s idle-only view; otherwise a turn animation temporarily moves the name and flying-text anchor for equipped critters.The model is rendered into a reusable 2x scratch target for the automatic frame. Per-animation prediction and exact weighted skinning of referenced combined-mesh vertices choose the atlas crop. If the evaluated pose requires a larger scratch frame, the factory expands it and rerenders before copying; the bounded retry loop fails rather than accepting a clipped frame that does not converge. The cropped sprite offset preserves the fixed model root, hit-test coordinates, and stable horizontal lighting gradient. Scratch-frame setup does not reserve atlas space; allocation happens only after the final crop is known. A changed placement is prepared locally, rendered, and published only after the atlas copy succeeds, while failures request an immediate redraw and retain the old allocation. An atlas slot only expands while its active animation/mesh/shadow envelope identity is unchanged, then may shrink once when a transition settles or mesh composition changes. Model-attached particles enable SPARK live-AABB computation; emitted quads and trails drive bounded frame expansion after their first update, with the advertised canvas retained as the pre-update fallback. Frame changes rebase already emitted atlas-space particles so expansion does not move them, and particles force a full-frame crop. A non-default model effect also disables the tight crop; effects that displace vertices beyond ordinary skinned geometry need a separate conservative rendering contract because bounds schema version 2 does not encode shader displacement.
ParticleSpriteFactorydoes the same for particle resources.EffectManagerloads default/minimal effects, resolves script-selected effects, and updates per-frame effect buffers.FontManagerloads fonts and formats/draws text, including inline color tags.RenderTargetManagercreates, resizes, pushes, pops, reads, clears, dumps, and deletes offscreen render targets.
For 3D critter views, idle refresh plays alive-state animations from the beginning. Dead condition idles freeze on their final frame. Other non-alive condition idles freeze on their first frame, so embedding projects should author that first frame as the intended resting pose for the condition.
These managers are renderer-facing but not renderer-specific. They talk through IAppRender / Renderer abstractions, so the same client logic can run against OpenGL, Direct3D, or the null renderer depending on platform/build configuration.
ParticleManager and ParticleSystem are backend-neutral dispatch facades used
by sprites and model attachments. ParticleRuntime.cpp is the composition point:
it creates the enabled ParticleRuntimeBackend implementations, and the manager
selects one by resource extension. Every live particle owns exactly one
ParticleRuntimeSystem; common timing, scale, and render scheduling stay in
ParticleSystem, while simulation and backend-specific rendering are virtual
runtime operations.
Resource invalidation follows the same neutral boundary:
SpriteManager -> ParticleSpriteFactory -> ParticleManager notifies every
backend through ParticleRuntimeBackend::InvalidateResource(), because a
changed file may be a dependency rather than a backend-owned root asset. SPARK
drops the matching parsed graph for a changed .spk and clears its graph
cache when a texture or render-effect dependency changes, so the next particle
creation reloads both graph and dependency. Failed loads are not cached.
Backends without a parsed-asset cache keep the invalidation operation as a
no-op.
VisualParticles.h / .cpp contain no particle-backend names or feature guards.
Concrete types and capabilities live in their extension files. The SPARK editor
performs its checked typed access only after crossing the neutral
GetRuntimeSystem() boundary, so adding another runtime does not add another
branch, enum value, or vendor type to the common facade.
Particle backends are independent build features: FO_SPARK_PARTICLES and
FO_EFFEKSEER_PARTICLES both default to OFF. The embedding project may enable
one or both; the particle sprite factory advertises only the extensions owned by
enabled backends, and a disabled backend does not compile or link its upstream
runtime.
SPARK .spark sources are baked to .spk binaries.
SparkParticleRuntimeBackend accepts only .spk and loads it through SPARK’s
binary loadFromBuffer path; XML never reaches runtime. The binary path must remain
behaviorally equivalent to its stream loader; truncated/oversized payloads,
unknown object types, descriptor-signature mismatches, zero/out-of-range object
references, and references to an incompatible object class invalidate the graph.
Custom FOnline SPARK object registration is shared by the baker, editor, and
client through the thread-safe EnsureSparkParticleObjectsRegistered() path.
SparkQuadRenderer::Setup() resolves the effect and texture before a particle
is returned; missing render dependencies produce a normal load failure instead
of a later exception on the first draw. A renderer newly added in the SPARK
editor is bound to the owning runtime before the preview graph is initialized;
while its effect or texture is still unassigned it draws nothing, allowing the
author to complete the renderer without dereferencing an incomplete backend
state.
Baked raw .efk resources select EffekseerParticleRuntimeBackend behind the
same facade. The client never
loads .efkproj XML or invokes the build-time compiler. This includes Web:
the host pipeline must bake .efk before packaging. Each ClientEngine or
Mapper instance owns an Effekseer core manager; each Create parses a new
effect, while the shared particle sprite factory separately caches successfully
loaded atlas textures. Effekseer advances hierarchy, emission, and lifetime
state; custom renderer callbacks copy the evaluated render snapshot into
FOnline-owned packets. No Effekseer graphics backend participates. The initial
capability gate is CPU Sprite/Ring-only and rejects unsupported renderer or
material families before returning a particle system. Dynamic callback checks
still fail closed on non-finite evaluated data, invalid UV ranges, or an atlas
filter mismatch and retire that already-created handle. Simulation update is
separate from draw, so effect lifetime does not pause when a direct-scene sprite
is outside the rendered viewport; stopped handles are advanced through
Effekseer’s deferred removal queues before their wrapper is released.
The exception is an explicit direct-scene prewarm request: it remains pending
until the first DrawInScene can provide the current transform, and scheduled
updates pause meanwhile. Effekseer prewarm advances exactly one second, then
resynchronizes the update clock before ordinary simulation resumes so the
offscreen wait is not double-counted.
Sprite mesh geometry is independent of the pixel-exact hit mask. FillAtlas
still derives hit testing directly from source alpha and Render.SpriteHitValue;
contour simplification/dilation only changes which triangles are submitted for
drawing. Cropped regions, repeated patterns, fonts, render-target blits, and
padded custom-effect/contour draws intentionally construct quads because their
sampling rectangle is not the source sprite silhouette.
Fonts and Inline Color Tags
FontManager::FormatText() strips @color:0xBBGGRR@ / @color:0xAABBGGRR@ tags and records the parsed ucolor value in the formatted text’s per-glyph color buffer during draw formatting. The reset tag is @color@; it restores the previous inline color, or the base draw color when no inline color is active. FontFlag::NoColorize still strips these tags, but keeps rendering with the caller-provided base color.
Game.BindFont(font, path, defaultScale = 1.0) can downscale the bound font slot. The scale is applied once at bind time: glyph bitmaps are re-rasterized in place inside the font’s atlas region with an area-average filter, and every metric (advances, offsets, line height, space width) is rounded to integers at the target size. The runtime text pipeline (Game.GetTextInfo(...), Game.GetTextLines(...), Game.DrawText(...)) therefore always works in plain integer pixel coordinates — a scaled font behaves exactly like a font authored at the smaller size, with no fractional glyph positions. The scale must be in (0..1]; upscaling a bitmap font is rejected — author a bigger font asset for larger text.
Input and script-facing hooks
ClientEngine::ProcessInputEvent() receives frontend InputEvent values and raises higher-level script events such as:
OnMouseDown,OnMouseUp,OnMouseMove;OnTouchDown,OnTouchMove,OnTouchUpfor raw per-finger touch streams, plusOnTouchTap,OnTouchDoubleTap,OnTouchScroll,OnTouchZoomfor aggregated gestures;OnKeyDown,OnKeyUp,OnInputLost;OnScreenScrollandOnScreenSizeChanged;- map render-stage events such as
OnRenderMap_BeforeTiles,OnRenderMap_AfterSprites, andOnRenderMap_AfterFlushMap.
Input semantics originate in Source/Frontend/Application.h; game-specific UI behavior should stay in scripts and GUI resources owned by the embedding project.
Client scripts can synthesize local input through the same runtime path for automation and embedded-client probes. Game.SimulateMouseMove(pos), Game.SimulateMouseDown(pos, button), and Game.SimulateMouseUp(pos, button) preserve held-button state across a raw mouse gesture, including positions outside the render window; Game.SimulateMouseClick(pos, button) sends a complete mouse click or wheel event. Game.SimulateTouchDown(fingerId, pos), Game.SimulateTouchMove(fingerId, pos, offsetPos), and Game.SimulateTouchUp(fingerId, pos) send raw touch streams, Game.SimulateTouchTap(pos) sends a completed tap event, Game.SimulateKeyPress(key, text) sends one key down/up pair, and Game.SimulateKeyboardPress(key1, key2, key1Text, key2Text) remains available for two-key sequences.
For local critter movement prediction, ClientEngine::CritterMoveTo() synchronizes any active MovingContext to the current client frame before starting a new movement or sending a stop request. It then normalizes the local hex/offset pair before the next request is sent, so rapid start/stop input does not report one-frame-stale or overlarge offsets to the server.
Client-side validation tests
Use the smallest relevant test scope when changing client runtime behavior:
Source/Tests/Test_ClientEngine.cpp— self-contained client engine startup, entity registration, script module initialization, and loop callbacks.Source/Tests/Test_ClientServerIntegration.cpp— client/server handshake over interthread transport and observable client connection events.Source/Tests/Test_ClientDataValidation.cpp— inbound remote-call payload validation for UTF-8, enums, floats, hashed strings, bools, truncation, and ref-type payloads.Source/Tests/Test_ClientRuntimeApi.cpp— client runtime ABI/export/result validation for the host/runtime split described in ClientUpdater.md.
Exact test target names are generated by the embedding project’s CMake/BuildTools configuration; do not hard-code one project’s target names in engine docs.
Change checklist
When changing client runtime code, verify:
ClientEnginestartup/shutdown still leaves no stale registered entities or render targets.- New network messages are documented in Networking.md and bound through
ClientConnection::AddMessageHandler(). - New synced state has a clear ownership boundary between Common entities/properties and client-only view state.
- Map presentation changes do not duplicate coordinate/pathfinding rules already owned by MapsMovementGeometry.md.
- Resource changes describe whether they affect
ResourceManager,SpriteManager, a sprite factory,EffectManager, orRenderTargetManager. - Platform-specific rendering/input implications are reflected in FrontendAndRendering.md, WebDebugging.md, or AndroidDebugging.md as appropriate.