A point or a direction in 3D. +y is up.
Vocabulary
:= []
The types that cross the seam between your game and the engine. Nothing in
them names a game. Import them with import pf.Vocabulary.
A rotation as a unit quaternion.
A colour with linear channels. A Light colour carries its intensity and can exceed 1. A Draw tint stays from 0 to 1.
A UI colour with linear channels from 0 to 1. Alpha is not premultiplied.
What the host gives launch and then init, the same value both times.
Tests start from Config.empty.
seed: the number that starts your game's randomness. The host reads it fromROCCO_SEED. The default is 0.assets: the names of the asset files on disk, with no ids.args: the words after the game's name on the command line, in the order typed. The host does not parse them.
What your game decides before the window opens. The host reads a Launch
once, before init, and never again. Start from Launch.default.
Each note names the later Scene field that may replace the field and the run mode that overrides it. A field with neither changes only on restart.
window: the Window. Headless opens no window. It lays out the UI againstwidthandheightat dpi 1 and ignores the rest of the Window.step_hz: the fixed step rate in Hz. Above zero.stepgets adtof one over it.shadow_map: the width and height of the shadow map in texels. Above zero.capacity: the table sizes. No Scene field replaces them.
Window : {
width : U32,
height : U32,
title : Str,
fullscreen : Bool,
high_dpi : Bool,
samples : Msaa,
vsync : Bool,
}
The window that a Launch opens. As in Launch, a field with no note about a Scene field or a run mode changes only on restart.
width,height: the size in logical pixels. Above zero. No Scene field replaces them.title: the title at start. A later Scene field may replace it.fullscreen: start fullscreen. A later Scene field may replace it.high_dpi: render at the full resolution of the display. UI sizes stay in logical pixels.samples: the MSAA for the window and the pipelines.vsync: wait for the display each frame. Lockstep turns it off, whatever this field says.
Msaa : [Off, X2, X4, X8]
The MSAA sample count. Off is one sample.
Capacity : {
meshes : U32,
skies : U32,
draws : U32,
groups : U32,
ui_quads : U32,
sounds : U32,
voices : U32,
}
The sizes of the engine's tables. Past a capacity, a Load gives id 0, the host draws the first entries, or a new Speaker waits for a voice, and the host logs once.
meshes: mesh slots. At least 4, for the Fallback mesh and the three Primitives, and at most 65536.skies: sky slots. At least 2, for the reserved slot and one sky, and at most 65536.draws: Draws per Frame. Above zero and at most 2793466. The uniform buffer grows with it.groups: the Groups per Scene that the host pairs to interpolate. Above zero and at most 1048576. Past it, the host pairs the first Groups.ui_quads: UI quads per Frame. Above zero and at most 16384, the u16 index bound.sounds: Sound slots. At least 2, for the reserved slot and one Sound, and at most 65536.voices: the Plays and Speakers that sound at once. Above zero and at most 65536. When the voices are full, a Play takes the voice of the oldest Play, and a new Speaker waits silent for a free voice.
Manifest : List({ name : Str, id : U32, duration : F32, clips : List({ name : Str, id : U32, duration : F32 }) })
The name-to-id list that loaded gets for one Pack. It holds every name
that the Load asked for. A name that did not load has id 0. Keep the ids
you need in your Model.
name: an asset name from the Load.id: the mesh, sky or Sound id of the name, or 0.duration: the length of a Sound in seconds at pitch 1. A mesh, a sky or a name that did not load has 0.Sound.durationreads it.clips: the Clips of a skinned mesh, in file order. Every other entry has an empty list. Each Clip has itsnamein the file, its Clipid, and itsduration, the time of its last key in seconds. Clip ids start at 1 and are unique across a Pack.Mesh.clipandMesh.clip_durationin the mesh package read them.
Input : { held : List(U16), pressed : List(U16), mouse : { dx : F32, dy : F32 }, gamepads : List(Gamepad) }
What the host gives step each fixed step. Tests start from Input.empty.
held: the key codes that are down at this step.pressed: the key codes that went down since the previous step.mouse: the mouse movement since the previous step.gamepads: one Gamepad for each connected pad, at most 4. With no pad the list is empty.
The key codes are sokol's. Use Key to name them.
Gamepad : {
slot : U8,
held : List(U8),
pressed : List(U8),
left_stick : { x : F32, y : F32 },
right_stick : { x : F32, y : F32 },
left_trigger : F32,
right_trigger : F32,
}
One connected gamepad at one step. Use Button to name the buttons.
slot: from 0 to 3. A pad keeps its slot while it stays connected. A new pad takes the lowest free slot.held: the button codes that are down at this step. A button that is down when its pad connects is held, not pressed.pressed: the button codes that went down since the previous step.left_stick,right_stick: inside the unit disc.+xis right and+yis up. The host applies a dead zone, so a stick at rest is 0.left_trigger,right_trigger: from 0.0, at rest, to 1.0, pulled all the way. The host applies a small dead zone.
Where the eye is, the point it looks at, the up direction and the vertical field of view in radians. The host interpolates it.
Draw : { id : U64, mesh : U32, pos : Vec3, scale : Vec3, rot : Quat, scale_rot : Quat, tint : Rgb, pose : Pose }
One thing to draw in a Scene. Start from Draw.default.
id: the draw id. The host pairs a Draw with the Draw of the same id in the previous Scene and interpolates between them.mesh: a mesh id from a Manifest. An id that the engine does not know draws the Fallback mesh.pos: the position in the space of the Draw's Group, or in the world for a Draw with no Group.scale: acts along the axes ofscale_rot. Thenrotturns the result. A Group's rot turns it after that. A Group has no scale.rot,scale_rot: unit quaternions.tint: the host multiplies the mesh colour by it.pose: the Clips that pose a skinned mesh. A mesh with no Skin ignores it.
The Clips that pose a skinned mesh in one Draw. Your Model owns every
value. The host samples both Clips and mixes them by fade. Start from
Pose.still.
clip: a Clip id from the Manifest entry of the Draw's mesh. 0 is no Clip, and the mesh draws in its bind pose. A Clip of another mesh also draws the bind pose, and the host logs once.time: the time inclip, in seconds. The host does not wrap it. UsePose.loopto repeat a Clip.fade_clip,fade_time: a second Clip and its time, with the same rules.fade: the weight offade_clip, from 0 to 1. At 0 the host draws onlyclip.
Collider : { id : U64, pos : Vec3, rot : Quat, shape : [Box(Vec3), Sphere(F32), Capsule({ radius : F32, half_height : F32 })] }
A shape that Queries can test. Build one with Collider.box,
Collider.sphere or Collider.capsule.
id: the draw id of the Collider. A Query skips the Collider whose id is the Query's owner.pos,rot: the Pose in the space of the Collider's Group.rotturns the shape and is a unit quaternion. The host composes the world Pose from the current Scene before it answers the Queries.shape: aBoxcarries its half extents. ACapsuleis a radius around a segment ofhalf_heightalong its owny, whichrotturns. The host does not test aCapsuleyet.
Two Colliders that overlap, from a ContactsOf Query. normal and depth
separate them.
The nearest Collider that a Raycast hit, with the owner and slot of its
Query.
OverlapHit : { owner : U64, slot : U32, id : U64 }
One Collider that an Overlap touched, with the owner and slot of its Query.
QueryResults : { contacts : List(Contact), rays : List(RayHit), overlaps : List(OverlapHit) }
What the host gives step for the Queries of the previous Scene. Each
kind of Query has one flat list. A miss has no row. The first step gets
QueryResults.empty. Read it with the functions in QueryResults.
Query : [ContactsOf(U64), Raycast({ owner : U64, slot : U32, origin : Vec3, dir : Vec3, max_distance : F32, exclude : List(U64) }), Overlap({ owner : U64, slot : U32, shape : [Box(Vec3), Sphere(F32)], pos : Vec3, exclude : List(U64) })]
A value in Scene.queries that asks the engine about the Colliders. The
host runs it against that Scene and gives the QueryResults to the next
step. Build one with the functions in Query.
The host skips the owner's own Collider. Two Queries of one kind with the
same owner and slot: the host keeps the first. An Overlap box carries
its half extents and does not turn.
Light : [Ambient(Rgb), Directional({ dir : Vec3, color : Rgb, shadow : Bool }), Point({ pos : Vec3, color : Rgb, range : F32 })]
One light in the Scene. Build one with the functions in Light.
Ambient: light from all directions. A Group does not change it.Directional:diris the direction the light travels. A Group turns it. The first Directional that setsshadowis the Sun, the only Light that casts a shadow.Point: falls off to zero atrange. A Group moves and turnspos.
A colour carries its intensity and can exceed 1. The host uses the first
Ambient and the first 8 other Lights, in the order of a depth-first walk
of Scene.nodes. A Scene with no Light draws black.
How a Box sizes along one axis, in logical pixels. The names and the defaults are Clay's.
Fit: fits its children, betweenminandmax.Grow: fills the free space, betweenminandmax.Fixed: the given size.Percent: a fraction of the parent, from 0 to 1.
A max of 0 means no limit.
Align : [Start, Center, End]
Where a Box puts its children along one axis.
AttachPoint : [LeftTop, LeftCenter, LeftBottom, CenterTop, CenterCenter, CenterBottom, RightTop, RightCenter, RightBottom]
The point of a floating Box that the host puts on the same point of the window.
BoxConfig : { width : Sizing, height : Sizing, pad : { left : F32, right : F32, top : F32, bottom : F32 }, gap : F32, direction : [Row, Col], child_align : { x : Align, y : Align }, background : Rgba, radius : F32, floating : [NoFloat, Float({ attach : AttachPoint, offset : { x : F32, y : F32 } })] }
The rectangle that a Box Element draws. The names and the defaults
are Clay's. Sizes are in logical pixels.
direction: lays out the children in aRowor aCol.radius: the corner radius.floating: aFloatBox attaches to a point of the window atoffsetand ignores its parent's layout.
One string in the engine's font, at a size in logical pixels and a
colour. The names and the defaults are Clay's. The layout measures it. With wrap set to Words, the layout
breaks lines between words. With None, it does not break lines.
Element
:= [Box(BoxConfig, List(Element)), Text(Text)]
One part of the UI tree. Write the tags directly.
Box: a rectangle and its children, laid out in list order inside it.Text: one string, a leaf.
An Element keeps its identity by its place in the tree. The type is nominal, because only a nominal recursive union crosses the seam.
Node
:= [Group({ id : U64, pos : Vec3, rot : Quat }, List(Node)), Draw(Draw), Light(Light), Collider(Collider), Speaker(Speaker)]
One part of the 3D tree. Write the tags directly.
Group: a Group id, a Pose and the children. The children's Poses are local to the Group. The host pairs a Group with the first Group of the same id in the previous Scene. Group ids and draw ids are apart.Draw,Light,Collider,Speaker: the leaves.
The host interpolates a Node's local Pose and then composes it, if its parent Group id is the same in both Scenes. If the parent changed, it interpolates between the two world Poses. A game with no Groups writes a flat list of leaves. The type is nominal, because only a nominal recursive union crosses the seam.
Scene : { camera : Camera, nodes : List(Node), cursor : Cursor, sky : U32, ui : List(Element), queries : List(Query) }
What view returns each fixed step. Start from Scene.empty and set only
the fields you decide.
nodes: the root Nodes of the 3D tree. A root Node's Pose is in the world.sky: a sky id from a Manifest. 0, or an id that the engine does not know, draws no sky.ui: the root Elements of the UI tree, drawn flat over the 3D view. The layout reads them in list order.[]draws no UI.queries: the Queries that the host runs against this Scene. The nextstepgets their QueryResults.
Where a Play or a Speaker sounds. The Listener is the Camera of the current Scene.
Here: at the Listener, with no direction. AHereSpeaker ignores its Group.At: atpos. It falls off linearly to zero atrange, as a Point Light does, and pans to the side it is on. Arangeof 0 or less is silent. A Play'sposis in the world. A Speaker'sposis local to its Group, as a Draw'sposis.
A Sound that the game controls while its Speaker stays in the Scene.
Start from Speaker.default.
id: the Speaker id. The host pairs a Speaker with the Speaker of the same id anywhere in the previous Scene, so a Speaker that moves to another Group keeps playing. A new id starts the Sound and a missing id stops it with a short fade. If two Speakers in one Scene share an id, the first in a depth-first walk plays and the host logs once. Speaker ids are apart from draw ids and Group ids.sound: a Sound id from a Manifest. Id 0, or an id that the engine does not know, plays nothing and logs once. A changed Sound id restarts the Speaker.gain: linear. 1 plays the Sound as recorded. A negative gain plays as 0.pitch: the playback rate. 1 plays the Sound as recorded. 0 or less plays at 1 and logs once.at: the Place. The host takes it from the current Scene, with no interpolation, as it takes the Listener.repeat:Loopplays the Sound again at its end.Onceplays it to its end.Sound.durationgives its length.
A changed gain, pitch or Place applies on the next fixed step.
Cursor : [Free, Locked]
If the mouse is Free or Locked. The host locks or frees the mouse only
when the value changes.
Command : [Quit, Load({ pack : Str, assets : List(Str) }), Unload(Str), Play({ sound : U32, gain : F32, pitch : F32, at : Place })]
A thing for the host to do once. init and step return them, and the
host runs them in list order.
Quit: stop the game. The host binds no key to it.Load: load each named asset of the pack, then callloadedwith the pack name and its Manifest.Unload: drop the pack, and unload each asset that no other loaded pack holds.Play: play a Sound once, to its end or until its pack unloads. The game cannot stop it. It starts after theviewof the same step, from the Listener of that Scene.sound,gain,pitchandatfollow the rules of a Speaker, butatis in the world. Build one withSound.play.