Tool-assisted game movies
When human skills are just not enough

Submission #6217: Buhbai, DevilSquirrel, EuniverseCat, fishmcmuffins, KT & keylie's Linux Celeste in 27:04.43

Console: Linux
Game name: Celeste
Game version: any
ROM filename: Celeste.bin.x86_64
Emulator: libTAS v1.3.2
Movie length: 27:04.43
FrameCount: 97466
Re-record count: (unknown)
Author's real name:
Author's nickname: Buhbai, DevilSquirrel, EuniverseCat, fishmcmuffins, KT & keylie
Submitter: keylie
Submitted at: 2019-01-04 16:04:42
Text last edited at: 2019-08-24 10:58:52
Text last edited by: keylie
Download: Download (37864 bytes)
Status: published
Click to view the actual publication
Submission instructions
Discuss this submission (also rating / voting)
List all submissions by this submitter
List pages on this site that refer to this submission
View submission text history
Back to the submission list

Version info:

libTAS version: 1.3.2

Annotation info:

Game: Celeste, v1.2.6.1, drm-free version (available at https://mattmakesgames.itch.io/celeste), 64-bit binary.

OS: Ubuntu 18.04 amd64 with Linux kernel 4.13.0-36-generic, libTAS v1.3.2. Game executable is `/path/to/game/Celeste.bin.x86_64`.

libTAS must be configurated with `Runtime > Recycle threads` unchecked. In `File > Executable options`, library path must be set to `/path/to/game/lib64`.

Like with SteamWorld Dig 2 movie, there is an issue in this game regarding sync and loading times. The movie must be paused for a short amount of time at specific frames to let the game load stages. This is especially true for the game startup which features a lot of thread spawning. To make the movie to sync, the beginning should be run with frame advance until the main menu appear (from frame 1 to 94). At frame 64, the game should print a bunch of "LOAD MAP DATA: XXX" on the terminal, and at frame 70 the game should print "LOADED : 1150ms". After that, the game should be paused at specific frames before and after each level. To help that, here is the code that I added in file "src/program/GameLoop.cpp" in line 196 before "emit startFrameBoundary();" which contains all the frames to pause.

         int pause_frames[] = {414, 2005, 2054, 2061, 2664, 6071, 6073, 6466, 6473, 7077, 13136, 13138, 13531, 13538, 14142, 27686, 27688, 28081, 28088, 28741, 36554, 36556, 36949, 36956, 37560, 41662, 41711, 41718, 42075, 49564, 49566, 49959, 49966, 50482, 54160, 54209, 54216, 54573, 68934, 68936, 69329, 69336, 69528, 69590, 97103, 97105};
         for (int pp = 0; pp < (sizeof(pause_frames)/sizeof(pause_frames0; pp++)
             if (pause_framespp == (context->framecount
Finally, there's another little section that must be frame advanced because we couldn't find the exact frame when loading occurs, between frame 69528 and 69590.

It should be noted that because this movie aims for fastest in-game time, and all loadings occur outside levels (where the in-game time is paused), it is not critical to simulate accurate loading times.

Before launching the TAS, one specific setting must be set in the game: the speedrun clock must be set to "File". It is mandatory because, starting from v1.2.6.0, the game has shorter animations in the hub after a level has been completed (berry collection, B-side/heart unlocking) with this particular setting. For encoding purpose, there are a few other settings that can be changed in the game menu before playing back the movie, which does not affect sync. The screen size can be changed. Also, we recommend to disable the screen shake and turn on photosensitive mode, it makes the video more pleasant to watch.

Author's comments and explanations:
Celeste TASing has been made possible thanks to DevilSquirrel, by modifying the game to read inputs from a file, as well as showing useful values and providing a friendly input editor. This game has been featured during the TAS block at SGDQ 2018. Since then, the any% TAS has been improved by almost two minutes, and inputs were converted to libTAS to make this submission possible.

(Link to video)

Game objectives

  • Emulator used: libTAS 1.3.2
  • Aims for fastest in-game time

All movement techniques are described in the game resource page. This submission text only focuses on specific tricks.

libTAS conversion

The input conversion between Celeste TAS tool and libTAS did not cause any issue. Inputs from each level were converted separately, and inputs outside level (intro, hub, etc.) were done inside libTAS. Indeed, Celeste TAS tool ignore loadings, it feeds inputs only when pulled by the game engine.

There was only one desync at the end of stage 7A, where one frame needed to be deleted. Because it happened after a feather section, we suspect that this has to do with analog input conversion. Indeed, Celeste TAS tool input format contains analog inputs described as a stick angle in integer degrees (0 - 359), and feeds the converted X/Y coordinates to the high-level game code (into XNA/FNA game controller struct). On the contrary, libTAS feeds raw analog inputs as 16-bit X and Y coordinates, before any processing (such as deadzone) is performed by the XNA/FNA framework. So the script that converts Celeste TAS tool inputs to libTAS inputs requires some floating point operations, which could lead to small differences in the obtained analog inputs.

Also, Celeste TAS tool does not keep track of rerecords, so this value was set to 0.

In-game vs real-time

This TAS aims for fastest in-game time. This follows the convention for unassisted speedruns, as the game was designed with speedrunning in mind, providing a robust in-game timer, as well as displaying individual level times. Moreover, optimizing this game for real time would lead to differences, because several animations such as dashing, crystals or bounces freeze the stage and the in-game timer for several frames. A TAS aiming at real time would limit the use of dashes, resulting in slower movement but less freeze frames.


The normal way of unlocking a chapter in this game is by completing the previous chapter. However, each chapter holds a secret cassette, which unlocks a special stage called B-side. By unlocking and completing the B-side of a chapter, the next chapter is unlocked. In this category, it is in fact faster (in-game time) to unlock and complete the B-side of chapters 5 and 6 instead of doing the levels normally.

Stage by stage comments


The main optimization of this stage is using double corner boosts.


We avoid most of the springs because it reset our speed.


We use the cutscene warp to place us directly into the dream block to save a bit of time. When getting out of dream blocks, we can jump twice for some horizontal boosting. We can also dash diagonally downward and jump at the same time to execute an instant hyper dash. Collisions inside dream blocks have a bit of leniency, so we can cut through corners. The end of the stage features several ultra dashes.


This level features a lot of crouched dashes to go through dust bunnies. When getting near Oshiro during the escape, time is slowing down but the in-game timer still runs at full speed, so we must avoid getting close to him, or knock him off.


The wind adds (or remove) a constant value to our movement, except when dashing. Thus, we try to dash as much as possible when the wind is against us, and to avoid dashing when the wind is pushing us forward. The snowballs cancel our dash, which is very handy. However, we don't have much freedom to manipulate where they are appearing.


We head to the cassette to unlock 5B. Inside the cassette screen, red/blue blocks are synchronized with the music. As a consequence, they are not delayed when the timer is frozen by a dash. So, to save in-game time, we have to dash as much as possible when we are waiting for red/blue blocks.


Grabbing Theo cancels our dash, so we can setup a grounded ultra dash and grab him to conserve this speed. However, grabbing Theo takes a lot of time, which makes this section very difficult to optimize.


Analog stick is used when in feather form for more precise movement.


Bumpers send us further if we press the direction away from them when bounced.


When the wind is pushing downward, we can jump on spikes if at a precise position. This is due to the order of steps in the game engine, we get pushed downward after the check for spikes, and we can jump away from the spikes during the next frame.


Huge thanks to all Celeste speedrunners and the Celeste discord for all the suggestions and feedback.

feos: Judging...

feos: Replacing the movie with a 1565 frame improvement. Also I cleared the rerecord count (per author agreement), fixed a path, and reordered a sentence in the annotations.

feos: This games works the same as SteamWorld Dig 2 in terms of level loading: it spawns threads in-between levels, and libTAS can't control them. The situation is exactly the same, no gameplay trick is related to this, as no gameplay is there when loading happens. And the movie also aims for in-game time, which only counts gameplay times.

So exactly like that other submission, this movie relies on mid-level time pauses, and for exact same reasons I consider it acceptable. See my judgment there for all the details.

Great feedback, accepting to Moons (similar in that regard too).

fsvgm777: Processing.

Similar submissions (by title and categories where applicable):