The tide command
tide builds and runs games. A game includes the .tide scripts and .tprefab authored trees in its folder and subfolders, with the .c and .cpp files and libraries there when it calls C (see Calling C), and the packages its tide.packages lists (see Packages). Commands take the game's folder, or use the current one. Saving a prefab also triggers the normal watched build; existing live instances are not automatically rewritten with new authored values.
tide <command> [folder] [options]| Command | What it does |
|---|---|
tide run [folder] |
Builds the game, plays it and reloads when you save |
tide build [folder] |
Builds the game into <folder>/build, named after the folder |
tide schedule [folder] |
Shows which systems can run at the same time, and why the others wait |
tide shaders [folder] |
Shows the shaders the game's frags and verts compile to, as GLSL and WGSL |
tide add <source> |
Adds a package to the game in the current folder: from git, a folder, or one that comes with tide |
tide update [package] |
Moves the game's packages from git to the newest commit of what they follow |
tide eject <package> |
Copies one of the game's packages into the game's folder, to change as the game's own |
tide fetch [folder] |
Downloads the game's packages from git that this machine doesn't have yet, and builds nothing |
tide editors |
Adds Tide to VS Code, Cursor, VSCodium and Windsurf |
tide open <file.tprefab> |
Opens the native scene editor |
tide upgrade |
Updates tide to the newest version |
tide version |
Shows tide's version and channel, where it's installed, and which compilers it found |
tide help |
Lists every option |
tide login / tide logout |
Authorizes or revokes this CLI's hosting access through the browser |
tide deploy [folder] |
Creates or reuses a project and saves its link in tide.deploy.json; builds and uploads locally. --server-only omits the client, --bundle-only only builds |
tide cc <options> |
clang, built into tide, for C you build yourself (see Calling C) |
Managed deployment, project selection, prepaid credit and current availability are described in Managed hosting.
open
tide open scenes/Level.tprefab
tide open scenes/Level.tprefab --project .
tide open scenes/Level.tprefab --check
tide open scenes/Level.tprefab --previewThe desktop app edits rooted prefabs and scenes.
It uses the nearest enclosing tide.packages as the project root, or the
prefab's folder if there is none. Use --project <folder> when scripts live
elsewhere in your game's folder. The selected root is visible in the window.
The first version uses folder/package discovery; it does not read CMake game
manifests. Packages must already be available locally (tide fetch downloads them).
--check runs the same authoring validation without opening a window and returns
1 for project errors. It inspects asset metadata, rather than compiling/importing
the full game. For automated UI runs, --frames, --capture and --script use
the normal hidden input/capture loop. The default editor size is
1440 by 900; --size W H accepts 1100..8192 by 650..8192. TIDE_HIDDEN
requires one of these bounded modes or --check.
See the editor controls and current limits.
--preview starts with compiled views enabled on authored match entities;
--local-preview uses local placement. Only [RunInEditor] and [EditorOnly]
views execute, with no gameplay initialization or systems. The full native build
toolchain is required. Bounded captures/scripts wait for the current preview
build and return 1 if it fails, rather than capturing stale state. --check
still performs only front-end validation, even with a preview flag.
run and build
| Option | What it does |
|---|---|
--release |
Optimizes game and package code, the way players get it |
--web |
A web page (WebGPU, or WebGL 2 where the browser has none), built with clang's WebAssembly target |
--android |
An Android app, for phones and the emulator (see Android) |
--define <SYMBOL> |
Defines a symbol for the game's #if lines, as many times as there are symbols. DEBUG is defined without --release. schedule and shaders take it too |
--title <title> |
The window's title, over the game's title setting (default: that, or the folder's name) |
--stats |
Shows the stats in the window's corner, as the game's stats setting does. run starts them open |
--debug-views |
Turns the debug views on, as the game's debugViews setting does: F7 shows the profiler, which says where a frame's time and a tick's go |
--size <width> <height> |
run only: the window's size in pixels, 960 by 540 without it (not on the web or Android) |
--capture, --capture-audio, --microphone, --frames, --script, --scale, --players |
run only: plays the game without a window, to see what it draws and hear what it plays, with several players if you like |
--seed <n> |
run only: the seed of the matches the game starts, Random.seed, from 0 to 2147483647. Without it, every match gets another one |
-o <path> |
build only: where the program goes |
--no-open |
run --web only: serves the page without opening a browser |
The engine is optimized in every build. Without --release, generated game code and the game's and packages' C/C++ stay unoptimized for debugging; engine and game code retain debug symbols and assertions, and DEBUG is defined. Engine objects are cached, so ordinary game edits reuse them. This applies to native, web and Android builds, including hot reload.
tide build --release --web makes one self-contained .html file with the game inside. It opens straight from disk, and can be put online as it is.
Web releases compress the program and assets inside that file and use a smaller page runtime. Decoding is included, so playing or building a packaged Tide game needs no extra tools or downloads. Debug and hot-reload pages keep their readable runtime and original payloads.
Standalone release builds remove unreachable native code and data, and strip debug symbols; runtime references and required C or shared-library exports are preserved. Tide-only games also omit unreferenced assets. Debug and hot-reload builds keep their usual code and asset loading behavior.
tide run builds and plays with hot reload by default, on desktop, the web or Android. Runs without a window play once. On desktop, closing the game window ends the command too.
A game's assets (its asset declarations) are packed with it: beside the program as <name>.pack, which has to stay next to it; inside the web page; and in the APK. Nothing else in the folder goes in. Under tide run, a file an asset names is watched like the game's code, and a saved image is in the running game within a moment, with no rebuild.
A web page draws with WebGPU where the browser has it on a GPU, and with WebGL 2 where it doesn't; games draw the same with either. Adding ?backend=webgl (or ?backend=webgpu) to the page's address picks one, for example to check a problem a player reports with the other.
A desktop game draws with Vulkan where the machine has a Vulkan 1.3 driver, and with OpenGL 3.3 where it doesn't; games draw the same with either, and nothing in a game says which. The TIDE_BACKEND environment variable picks one for a run: TIDE_BACKEND=opengl, or TIDE_BACKEND=vulkan, which ends the program with a message where there's no Vulkan rather than falling back.
On a Mac, Vulkan is MoltenVK, Vulkan on Metal: tide build puts libMoltenVK.dylib beside the game, which uses it where the Mac has no Vulkan loader of its own. Ship it with the game; its license is in the tide package's licenses/moltenvk.
An Android game draws with Vulkan where the device has Vulkan 1.3, and with OpenGL ES 3 where it doesn't. An app started with adb shell am start takes the same choice as an extra of its intent: --es tide.backend opengl or --es tide.backend vulkan.
Stats
--stats shows what the game is doing in the window's bottom right corner. So does stats = true; in the game's settings, with no option to remember:
settings
{
stats = true;
}They're a drawer. Closed, it's one line: the frame rate, and in a match another machine runs, your ping. A click or a tap on that line opens it, and so does F8: the rest is listed above the line, which stays where it is, so a click in the same place closes it again.
- It starts closed when the game asks, with its
statssetting, and when a build does (tide build --stats, CMake'sSTATS): what players get is the one line.tide run --statsstarts it open, since whoever typed that wants to read it. Undertide run, a hot reload leaves it as you left it. - A click or a tap on it is never the game's. Neither the game's input nor its views read that press, from the frame it lands in. A press that began elsewhere and crosses the drawer stays the game's. The game still sees F8, as it sees every key.
- Closed, it measures nothing. The frame rate is a count of frames. What the list shows (a frame's work, each system's time, the memory) is measured while the list is open, so a game can ship with the line.
- The bottom right corner is its own. A game that asks for the stats keeps its own buttons out of that corner: the line is about 80 pixels wide and 40 high, and about 190 wide with a ping in it.
The list is two columns, a label on the left and its value on the right, in four groups: what a frame takes, the match, the machine, and the systems that take longest.
| Line | What it is |
|---|---|
60 fps |
Frames a second, over the last second. With ping 34 ms after it in a match another machine runs: the line that's always there |
cpu |
What a frame's work takes on its thread, all of it: the ticks it runs, the views and the drawing, without the wait for the display |
render |
Listed under cpu as the part of it that prepares and submits the frame's draw list, including its render targets |
gpu (22 draw calls) |
GPU drawing time, including render targets, on Vulkan (desktop and Android), desktop OpenGL and WebGPU devices with timestamp queries, with how many draw calls the frame took in its label: a run of shapes, of text or of one mesh's instances is one. Results arrive asynchronously, and it overlaps cpu, so the two aren't added. Where the backend has no GPU timer (WebGL 2, OpenGL ES 3), the draw calls have a line of their own, draw calls |
vertices |
Vertices submitted this frame across all render-target passes, counting each index for each instance at its selected LOD. Includes shapes, text, meshes, blits and clear triangles; excludes the host's overlays. A quad counts as six, even with four shared corners. Clipped or culled geometry still counts; this measures submitted geometry, not how many pixels were visible or how often the GPU ran the vertex shader after caching |
ping (delay 3 ticks) |
The network's round trip to the machine running the match, in the list when it's this machine, and in its label the input delay when there is one (see Input delay) |
network |
What goes over the network each second, up and down: ↑2.0 ↓0.5 KB/s |
entities |
How many entities the match has |
threads |
The threads ticks run on |
memory |
The memory the program has, as the system counts it. On the web it's the program's WebAssembly memory, which only grows |
vram |
What the game's drawing has on the GPU, by the engine's own count: every texture, mesh and render target it made, by what each holds. Not the window's own framebuffer, nor what the driver keeps beside them, so it's the same number on every backend |
renderer |
What draws: Vulkan, OpenGL 3.3, OpenGL ES 3, WebGPU or WebGL 2 |
Move |
The three systems that took longest on average per measured run, over the last half second. A system keeps its row while it stays among the three, and one that joins them takes the row of the one it replaces, so the rows don't shuffle |
Timings change twice a second; frame rate and network rates change once a second. The panel is as wide as its widest label and value, and while it's open it keeps the widest it has been, so its edge doesn't jump as a long system name comes and goes. The drawer shows once there's a first frame rate, a quarter of a second in. GPU time excludes the drawer and display wait; on WebGPU it sums render-pass durations. WebGL 2 and OpenGL ES 3 have no GPU timer here. Outside a match, the list has the lines that aren't a match's. On a screen too low for the whole list, it keeps the lines there's room for, from its top.
A setting is part of the build, so tide build keeps it: take stats out before players get the game, unless the frame rate in a corner is what you want them to have.
Provisional
The drawer is new. Its key, what its one line says, and that only tide run --stats starts it open may still change.
Threads on the web
A game's ticks run on every core when the page is cross-origin isolated: served with these two headers, which let it share its memory with the workers its threads run in.
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corptide run --web serves the page with them. Where a page is put online, the host has to send them: on itch.io, it's the SharedArrayBuffer support option in the game's embed settings. A page without them, or opened from disk, runs the same game on one thread, with the same results, so web players on either kind of page play together. Workers only start once a tick has enough work for them.
Android
tide build --android makes an Android app, <folder>/build/<name>.apk, for phones and tablets (arm64) and Android's emulator (x86-64), on Android 10 and up. With --release, it also makes <name>.aab, for Google Play. tide run --android builds it, installs it on the phone or emulator that's connected, starts it, and shows what it prints until it ends. --host, --join and --connect go to it as they do on a computer, and Android players play in the same rooms as everyone else.
To play on your phone, turn on USB debugging once: in Settings > About phone, tap Build number seven times, then turn on USB debugging in System > Developer options. Connect the phone, and allow your computer when it asks.
Building for Android needs two things of Google's: the NDK, Android's C library and headers, and adb, which installs apps. tide uses the ones Android Studio installs. Without them, the first tide build --android asks to download them from Google, under Google's license (the Android SDK License Agreement), and keeps what it needs of them next to itself: about 30 MB. Set TIDE_ACCEPT_ANDROID_LICENSE=1 to accept it without being asked, as a build server would.
- The app's ID is the game's
appIdsetting (see Settings). Without one, it'sdev.tide.<the game's name>, which is fine for testing, but--releaseneeds one. - Its name under its icon is the game's
titlesetting, or--title, or else the game's name. - Its icon is
icon.pngin the game's folder: a square PNG, 512 by 512 pixels is plenty. Without one, it's Tide's. - The Android libraries in the game's folder (
.sofiles) go in the app with the game (see Calling C). tidesigns apps with a key it makes on your computer the first time,~/.android/tide.pem. A phone only takes an update to an app signed with the same key, so keep a copy of it somewhere safe, and don't share it: whoever has it can sign apps as you.tide run --androidreplaces an app another computer installed.- To sign with another key (a build server's, or one you already have), set
TIDE_ANDROID_KEYto its file: PEM, the private key (RSA, 2048 bits or more) and then its certificate.openssl pkcs12 -in upload.p12 -nodes -out key.pemmakes one from a keystore; a.jkskeystore becomes a.p12first withkeytool -importkeystore -srckeystore upload.jks -destkeystore upload.p12 -deststoretype PKCS12. - Each build's version code, which Android only updates an app to a higher one of, is the minutes since 2020 began, so every build is newer than the last. Where
SOURCE_DATE_EPOCHis set, it stands in for now. The version people see is the game'sversionsetting, like1.2.0, or else 1.0. - The back button comes to the game as Escape, as in Unity.
- With
tide run --android, saving a.tideor C file builds the app again, installs it and starts it over: a phone can't swap code into a running app, so the match starts over too. Typerand press Enter to start it over yourself; closing the app ends the run.
Google Play
<name>.aab is an App Bundle, which is what Google Play takes: Play makes the APKs from it, each phone getting only the library for its CPU. To put a game on Play:
- Make a developer account in the Play Console (Google charges a one-time fee), and create the app there.
- Give the game its
appId, and build it withtide build --android --release. - Upload
build/<name>.aabto a release: internal testing is the quickest way to try it on your own phone. Play signs the APKs it makes with a key of its own, which it keeps (Play App Signing);~/.android/tide.pemis then your upload key, and Play only takes uploads signed with it. - For an update, build again and upload the new
.aab: its version code is higher than the last one's.
Play's other requirements, like the version of Android an app is made for (Android 16) and support for phones with 16 KiB memory pages, tide meets itself. A library the game brings has to be linked for those pages too, and tide warns when one isn't (see Calling C). If you lose your upload key, Play can take a new one (the app's App integrity page asks for its certificate: the part of the key's file from -----BEGIN CERTIFICATE----- on).
Hot reload
While tide run plays a game, saving a .tide file rebuilds it, and so does saving one of its C or C++ files, headers or libraries. The game carries on with the new code in the same window:
- If you changed only code (systems, views, event handlers, methods), the match and everything local, like menus, carry on where they are.
- If you changed data (components, fields, singletons, the input), the game is carried over to it by name, and goes on from where it was.
- If the new code has errors,
tideprints them and the game keeps running its last build.
Carrying the game over works like this:
- Every field of the same name keeps its value. An
intfield that became afloatis converted, and so is an int vector that became a float vector of the same size. An enum field keeps its member by name; a value that's none of its members (see From a number) gets the field's default. - A new field gets its declared default. So does a field whose type changed to one it doesn't convert to, like
float2tofloat3. New text and list fields start empty. - Renaming a field is removing it and adding a new one: its value is lost.
- An entity keeps its ID. When you remove a component, entities that had it keep their other components. An entity whose remaining components can't exist together in the new code is dropped.
- A changed default doesn't change existing entities, only new ones.
- A waiting task carries on when its async function's code is the same, and is dropped when it changed: where it was waiting is gone.
- Local fields carry over with the rest of the local state, by name: a mesh a view baked stays baked. Change what bakes it and it stays as it was, until the view bakes again or you start the game over.
- Local handlers of the match's events hear nothing twice across a reload. The ticks a client had predicted and not had confirmed by then tell its
verifiedhandlers nothing, when the new build confirms them as they were. - If the scene the game is in is gone, the game starts over.
tide says what happened after each reload, like reloaded, and carried the game over to its new data layout; 1 field reset; 2 entities dropped.
When the game gets into a state you don't want, press Ctrl+R in the game, or type r and press Enter in tide's terminal, to fully reload it. On the web, Cmd+R does the same on macOS. This starts the match, local state, GUI and the game's C or C++ globals over, without carrying state from the old run. With a match on several windows (--host in one, --connect localhost in another), each window hot reloads when you save, and players stay in the match. A window that joined another's match joins it again a second after that match ends without it: when the other window starts it over, or is still on a build with another data layout. On the web, a hot reload goes on with the match closed for now: the room closes, and the other players drop out.
tide run --web hot reloads too. tide serves the game's page at an address on your machine, like http://127.0.0.1:52407/, opens it in your browser, and keeps running until you press Ctrl+C. What each hot reload did shows in the browser's console. VS Code and JetBrains IDEs can play it beside your code instead (see Editors).
Hot reload is for tide run. tide build makes a plain program, or a page with nothing of it. Runs without a window play once, with no hot reload.
Dedicated servers
tide run --server --anonymous --port 7777 --max-matches 64
tide build --server --release--server builds a native console service with no window, rendering, audio,
views or local player. Its cache is separate from ordinary native builds, and
its default output is <folder>/build/<name>-server (with .exe on Windows),
beside its own <name>-server.pack when needed. run runs it once without hot
reload. Server executables support:
| Option | Meaning |
|---|---|
--anonymous |
Allow anonymous match creation and joining for quick startup |
--port <n> |
Listening UDP port, default 7777 |
--max-matches <n> |
Concurrent match limit, default 64, maximum 4096 |
--max-connections <n> |
Admitted/pending connection limit, default 256, maximum 65536 |
--udp-only |
Omit the shared WebRTC room |
--frames <n> |
Bound a test run to this many service iterations |
The service uses wall-clock time and sleeps between iterations, normally about
one millisecond. --frames bounds service iterations; it does not render frames
or make networking deterministic. TIDE_HIDDEN requires this bound. Interactive
services stop on Ctrl+C or SIGTERM. Empty matches expire after 60 seconds by
default; a custom host can change that interval.
Clients use --connect localhost:7777/0 to request a new match, or replace 0
with its match ID. --join K7QF2M/0 does the same through the server's WebRTC
room. The admitted client prints its match ID and exposes the complete address
through Session.room. Web clients use the room form.
Without anonymous policies, the game supplies
void tide_server_entry(tide_hosting_desc *host) in its C sources to configure
authentication, match startup and process callbacks. A local menu Main requires
a startup callback selecting a match scene. The C entry point is the first
implementation; a Tide orchestration declaration has no agreed syntax yet.
See dedicated hosting.
Closed Tide-only server builds automatically retain only simulation assets.
Games or packages with C/C++ or prebuilt libraries retain all assets because C
can find entries by name. --strip-server-assets explicitly enables server
pruning for such projects when their C has no additional asset dependencies.
This option also selects --server. Source asset validation still runs for all
declarations. Standalone server links discard unreachable client code in debug
and release. --web, --android and capture/view options do not apply to a
server executable; browser players still connect through WebRTC.
Multiplayer
tide run can start a match others join, or join one. For --host, the game's Main scene must be the match's (see Scenes); a game that starts in a menu does the same from code (see Matches).
| Option | What it does |
|---|---|
--host [port] |
Opens the match for others to join: in a room, whose code the game can show, and on UDP port 7777 by default (not on the web) |
--join <code> |
The match in the room with this code, like K7QF2M |
--connect <address> |
The match at an address, like 192.168.1.5 or localhost:7777 (not on the web) |
To check a match with no window on screen, --players plays several players at once and takes a picture from each.
In a web page's address
A game's web page takes the same from its address, so a link can say which match it's for:
| In the address | What it does |
|---|---|
?host |
As --host: the game opens its match, in a room |
?join=K7QF2M |
As --join K7QF2M: the game joins the match in that room |
?lag=100 |
The page's player has a ping of 100 milliseconds, the round trip to the match's server, to see a match as on a slow connection with both players at one desk. What the trip takes already counts: the page holds back what it sends and what it receives by half of what's missing each. A page that hosts the match adds nothing |
?embedded |
The game is part of a page that scrolls, in a frame of it, and leaves that page its scrolling: the wheel, and a finger that swipes up or down, until the player taps the game |
?wait |
The game loads, and starts when the page around it says so |
A page that shows a game in a frame of the same site reads the frame's Tide.room, the code of the room the game hosts or joined ("" for none), and can set its Tide.lag while the game runs. Its Tide.minLag is the least a lag can make the ping: the ping the game measured while its page added nothing, undefined until it has measured a few so. A page that wants it starts with no lag, as these docs do. That's how these docs put two players side by side: the first frame's address says ?host, and the second's ?join= with the code the first one got.
Such a page starts a game that waits by calling the frame's Tide.start(). With ?wait or without, it gives a promise that's kept once the game has drawn something, so the page can keep the frame out of sight until there's something in it. These docs load an example as you scroll near it, and start it when it's in view.
A page can have the browser download a game before any frame shows it: it loads the game's page once in a frame nobody sees, with #download after the address, and removes that frame once it has loaded. Nothing runs in it, and the frame that shows the game later finds its page in the browser's cache. A plain fetch of the page doesn't do that: browsers keep a frame's page apart from what a script fetches.
With ?embedded, the wheel always scrolls the page around the game. On a touchscreen, a swipe that starts on the game scrolls the page too, until the player taps the game: the tap reaches the game, and from then on so does every touch, swipes included, until the player taps the page somewhere else.
Without a window
tide run can play a game with no window at all, to see what it draws or to check it still runs: from a script, on a build server, or for an agent that shouldn't open windows on your screen. The game runs once, as it was built, and tide ends with the game's exit code. Nothing is watched or reloaded.
tide run --frames 300 --capture menu.png| Option | What it does |
|---|---|
--capture <file.png> |
Writes the run's last frame as a PNG |
--capture-audio <file.wav> |
Writes everything the run played as a WAV (16-bit stereo at 48 kHz). A game with no sound gives silence, a 60th of a second a frame |
--microphone <file> |
A WAV or Ogg Vorbis file the microphone plays in the run, looped, where a window's run would hear the capture device (Clip.Microphone). Without it a run's microphone is silent |
--frames <n> |
Ends the run after n frames |
--script <file> |
Input by frame from a file, which also says when to capture and when to quit (see Scripts) |
--players <n> |
Plays the game n times at once, as the n players of one match (see Several players) |
--scale <s> |
Draws as on a display scaled this much, like 1.5 or 2 (1 without it). Text and edges are fitted to the display's pixels, so they look different there, and render targets at the display's scale have that many more pixels |
--size <width> <height> |
The window's size in pixels, 960 by 540 without it |
--seed <n> |
The seed of the run's matches, 0 without it |
Provisional
These options' names and the script's format are new, and may still change.
The picture is what the window would show: the views, the GUI over them, and the stats and the profiler when they're on. It's --size times --scale pixels: --size 1280 720 --scale 2 is 2560 by 1440. The stats are as a player would have them: one line unless the run asked with --stats, and a script's click on that line, or its key f8, opens them.
Nothing of the run depends on the machine it's on, or on what's happening there:
- Time is made up. Every frame is a 60th of a second long, however long it took, and frames come as fast as the machine runs them: 300 frames take about a second.
- Input is made up. The game gets what the script says and nothing else: never the real keyboard, mouse or gamepad, and it leaves the clipboard alone. The cursor is locked whenever the game asks for it.
- The window's size and scale are what you asked for, whatever the display is.
- The match's seed is 0. In a window,
Random.seedis another number every match. In a run like this it's 0 every time, so what the game rolls comes out the same.--seed 7picks another seed, to see the game roll otherwise: the same seed is the same run again.
So a run repeats exactly: the match goes the same way on every machine, and the same machine writes the same pictures, byte for byte. Between machines, pixels a GPU blends (the edges of shapes, a shader's gradients) can differ a little. The stats' list and the profiler are the exception, while they're open: they show what this machine's clock measured, which no two runs agree on. The stats' one line doesn't. What the game reads from disk, like its saves, is still this machine's. Made-up time doesn't wait for anyone, so a run like this isn't for a match with another machine, or with another run: neither would wait for the other. A match with several players is one run, which plays them all.
Something has to end a run nobody can close: --frames, or the script's quit. The run's exit code says how it went:
| Exit code | What happened |
|---|---|
| 0 | The run ended at --frames or quit, and its pictures were written |
| 1 | A picture couldn't be written. Folders aren't made for it |
| 2 | An option or a line of the script is wrong, or the run would need a window where nobody watches: nothing ran, and the message names what to fix |
A game tide build made takes the same options itself, so a build server needs no tide: build/mygame --frames 300 --capture menu.png. These runs are for computers: on the web, drive the page with a headless browser instead, and take its screenshots.
Several players
--players <n> plays the game n times at once, with no window, as the n players of one match, and takes a picture from each:
tide run --players 2 --script play.txt --size 640 360Each player is a copy of the game, a program of its own, as on a machine of its own. The copies go a frame at a time, together, so frame 300 is the same moment for every player. Their network is made up, as the run's time and input are: what one sends in a frame, the others read in the next, in the same order every run. Nothing leaves the machine: no port opens, and no relay is asked. So a run of several repeats exactly, as a run of one does, and goes as fast as the machine plays it.
- Who's in the match. If the game's
Mainscene is the match's, player 1 opens it, as with--host, and the others join it before the first frame: there's nothing to script. A game that starts in a menu opens and joins from its own code, as it does for its players, at the script's clicks and keys. - Rooms. A match that's open is in a room, and each player's room has a code of its own, six of one letter:
AAAAAAis player 1's,BBBBBBplayer 2's,CCCCCCplayer 3's, and so on through the letters a code has (noIorO).Session.roomreads it, andSession.Join("AAAAAA")joins player 1's match, so a script types it where a player would:260 player 2 text AAAAAA. A room nobody has open fails at once, withDisconnectedandFailed, as a room that isn't one does. - Addresses.
Session.Connectreaches the first player whose match is open on the address's port, 7777 unlessSession.Open(port)picked another. The rest of the address says nothing here, and no name is looked up. A port nobody opened never answers, and the game times out, as on a network. - One script for everyone. A line says whose it is after its frame:
120 player 2 key d down. Input that names no player is player 1's, so a script for one player plays as it did.quitand--framesend the run for everyone. - Pictures from each. A file gets its player's number before the extension:
capture fight.pngwritesfight-1.pngandfight-2.png, and--capture,--capture-audioandcapture-audiogo the same way.400 player 2 capture hers.pngtakes player 2's alone, ashers-2.png. - A game that stops ends the run. When one player's game ends before the run does (it stopped, or couldn't write a picture), the run says which player's and at which frame, the others end with it, and the exit code is that game's (1, when the system stopped it).
# Player 1 walks up while player 2 walks right, and each machine takes a picture of the match
120 key w down
120 player 2 key d down
300 capture walk.png
310 quitA packet takes a frame each way, in every run, so a player's ping is a few frames long, and the same each time. A run has 32 players at most, and no more than the game's maxPlayers. It takes no --host, --join or --connect: who's in the match is the run's to say, or the game's. A match whose host leaves ends for the others: it can't change hands, which goes through the relay.
--players 1 is a run of one whose network is made up too: its match opens in room AAAAAA with nobody to join it, and its pictures have no number.
Provisional
This is new, and its details may still change: the option's name, the rooms' codes, what an address reaches, and how a script and its files say whose they are.
Where nobody watches
Set the TIDE_HIDDEN environment variable where nobody is there to look at a window, or to close one: on a build server, or in the shell of an agent that works on your machine while you do. Any value but nothing or 0 counts. No Tide game shows a window there, however it's started:
- A run with no window goes as ever:
--frames,--captureand--script. - Any other run is refused, with exit code 2, before anything is built or opened. Only closing its window would end it, and nobody could. The message says what to give the run instead.
tide run --webserves the page and opens no browser.- Nobody listens there either: a program that opens its window hidden, as a test does, opens no audio device and no microphone.
- A game that stops says why on stderr and shows no dialog. On Windows, a debug build that stops would otherwise show one and wait for a click.
A game tide build made follows it too, on its own. Leave the variable out of your own terminal and your editor, where you want the window.
Scripts
A script is a text file. Each line is the frame something happens at, then what happens. Frames count from 1, and lines are in the order of their frames. A blank line, or one that starts with #, says nothing.
# Into a practice round, then a few steps to the right
30 mouse 640 353
31 button left
120 key d down
200 capture fight.png
260 key d up
300 quittide run --script play.txt --size 1280 720| Line | What happens |
|---|---|
key <name> down, key <name> up |
A key goes down or up, by its name in Devices: w, space, enter, leftShift, upArrow, digit1 |
key <name> |
A tap: down for this frame, and up at the next |
button <name> down, up, or neither |
A mouse button, the same way: left, right, middle, back or forward. button left is a click where the mouse is |
mouse <x> <y> |
The mouse goes to a place, in pixels from the window's top left, with y down, as the GUI's and Draw.Screen's are |
move <dx> <dy> |
The mouse moves by so much, across and down: what turns a camera whose cursor is locked |
scroll <x> <y> |
The wheel turns, y positive away from you |
text <characters> |
Characters typed, to the line's end, as devices.keyboard.text and the GUI's fields get them: 32 a frame, as a paste comes. No key is pressed for them: Enter and Backspace are key lines |
touch <finger> down <x> <y> |
A finger touches the screen. <finger> is a number you pick, to say which one moves and lifts |
touch <finger> move <x> <y> |
It moves |
touch <finger> up |
It lifts, where it was, or at an <x> <y> after it |
pad <button> down, up, or neither |
A gamepad's button, by name: buttonSouth, start, dpad.up |
pad leftStick <x> <y>, pad rightStick <x> <y> |
A stick, from -1 to 1, with y up |
pad leftTrigger <value>, pad rightTrigger <value> |
A trigger, from 0 to 1 |
capture <file.png> |
Writes the frame, once it drew, as a PNG. A file a script names is beside the script |
capture-audio <file.wav> |
Writes what played since the last such line, or since the run began, as a WAV beside the script |
quit |
Ends the run once this frame drew |
player <n> before any of these but quit |
In a run of several players, the line is player n's: 120 player 2 key d down. Without it, input is player 1's and a capture is every player's |
What a line does is in the devices at its frame, as if a player had done it since the frame before. So it goes the way a player's input goes: the GUI takes a click on one of its widgets and the game doesn't get it, and a modal keeps the keys from the game.
The mouse starts outside the window, on no widget. A gamepad is connected from the script's first pad line, and a touchscreen from its first touch.
A line that's wrong stops the run before it starts, and the message says which, and what to write:
tide: play.txt:7: no key is named 'shift': did you mean 'leftShift'?add and update
tide add and tide update change the tide.packages of the game in the current folder (see Packages).
| Command | What it does |
|---|---|
tide add github.com/owner/repo |
The newest commit of the repository's default branch |
tide add github.com/owner/repo@dev |
The newest commit of the dev branch, which tide update follows |
tide add github.com/owner/repo@v1 |
The newest v1.x.y tag, which tide update follows |
tide add github.com/owner/repo//physics |
The package in the repository's physics folder |
tide add ../shared |
A package in a folder on this machine |
tide add box2d |
A package that comes with tide, by its name |
tide update |
Every package from git, to the newest commit of what it follows |
tide update physics |
One, by its name, its repository's name or its source |
Both add the lines of the packages the game's packages need that it doesn't list yet.
eject
tide eject <package> copies one of the game's packages, from git or one that comes with tide, into packages/<its folder's name> in the game's folder, and makes its line in tide.packages that folder. The package is the game's own from there: its files change with the game's, and tide update and tide upgrade no longer move it. It's named as for tide update.
tide eject rooms # the rooms line becomes ./packages/roomsA package that's a folder already is left where it is, and a folder already at packages/<name> is an error, with nothing copied.
tide run and tide build download the packages from git that aren't on this machine yet. tide fetch does only that, each at the commit tide.packages pins, and adds the same lines: a build after it needs no network, as on a build server that fetches in one step and compiles in another.
upgrade
| Option | What it does |
|---|---|
--nightly |
Follow nightly versions from now on |
--stable |
Follow stable versions from now on |
--version <v> |
Install exactly this version |
tide says, at most once a day, when a newer version is out. Set the TIDE_NO_UPDATE_CHECK environment variable to stop it looking, as on a build server.
Files
tide keeps its work in a hidden .tide folder in the game's folder. You can delete it any time, and git ignores it. Packages from git are kept for every game, once each commit: in %LOCALAPPDATA%\Tide\packages on Windows and ~/.tide/packages elsewhere, or where TIDE_PACKAGES says. tide build puts what it makes in build/, with the game's .dll, .so or .dylib libraries next to the program. C files and libraries in hidden folders or in build/ aren't part of the game.
A game's first build compiles the engine's C with it, and its packages'; the builds after that compile only what changed. tide compiles those files as many at once as the machine has processors, and what a build says and makes is the same however many that is. On a machine short of memory, set the TIDE_JOBS environment variable to how many to compile at once.