Controls
Keyboard
| Action | Keys |
|---|---|
| Move | W A S D or arrow keys |
| Dash | Space |
| Pause | P or Escape |
| Pick a level-up card | Number keys |
| Reroll / Skip / Banish | R / X / B (while you have charges) |
Touch
| Action | Gesture |
|---|---|
| Move | Drag anywhere |
| Dash | Flick |
| Pause | Tap the pause button |
| Pick a card | Tap it, or Reroll, Skip, Banish |
A flick starting on a map tip's Settings button still reaches the movement controls. Settings opens only on a still press; moving more than 6 CSS pixels suppresses its click.[1][2]
With Flashing effects off, poteto's arrival hides its holographic foil instead of painting it.[3]
Source: About tibo.dog and llms.txt.
Other keys
Verified (key map in boot-CPW8VVmR.js and the game's input code):
| Key | Action |
|---|---|
| P | Pause / resume during a run (Escape also works) |
| M | Mute / unmute |
| T | Auto-advance story lines on or off |
| Space | Skip story lines |
| R | Play again (end screen) |
| S | Share (end screen) |
| Escape | Back |
| Enter / Space | Activate the focused button |
| Arrow keys | Move focus in lists |
Cards are numbered 1, 2, 3 (4) in order. The on-screen hint relabels W A S D for AZERTY, QWERTZ, Dvorak and Colemak layouts.
Dash details
Verified: a dash moves 48 units over 9 ticks (0.15 s), each charge recharges in 5 s, Tibo starts with 1 charge and the input is buffered for 6 ticks. Tibo ignores normal damage while dashing (and during dashGrace from Ghost Pepper). A touch flick is a stroke of at least 30 px over the last 120 ms, moving at least 400 px/s when the finger lifts, or 900 px/s while it is still down (dash.flickPx, flickWindowMs, flickPxPerSec, heldFlickPxPerSec).
Gamepad
No gamepad controls were found; the code only records that a gamepad was connected (for analytics).
Settings
Verified from the settings panel (settings-panel-CCcMA-sy.js) and defaults in boot-CPW8VVmR.js. Tabs: Sound, Language, Comfort, Graphics, Privacy.
| Setting | Options | Default |
|---|---|---|
| Music volume | slider | full |
| Sound volume | slider | full |
| Language | 27 languages | browser language, unless the address has ?lang= (see Languages) |
| Color-blind mode | Off, Protanopia, Deuteranopia, Tritanopia (with a Danger / XP / Health colour preview) | Off |
| Weapon effects | slider | full |
| Screen shake | Off, Low, Full | Full |
| Flashing effects | On / Off | On |
| Damage numbers | On / Off | On |
| Hitbox (“Shows where shots hit Tibo.”) | On / Off | On |
| Recommended picks | On / Off | On |
| Map tips (“Explains each new place on the map once.”) | On / Off (Comfort tab) | On |
| Quality | Auto, Low, Medium, High | Auto |
| Battery saver | Off, On, Auto (Graphics tab) | Off |
| Analytics (“Anonymous gameplay stats help poteto improve the game. No name, account or IP address is recorded.”) | On / Off | On (off by default in the EU and EEA, per the game's privacy page) |
Battery saver
Loading behavior: On now limits drawing immediately, before the extra Battery saver module has downloaded. That module is requested only once a run is active or its intro has started, rather than from the menu. The same pacing object is kept when loading finishes; Auto's power and load checks still need the module. Controlled old/new probes covered Off, Auto and On in the menu, run and both intro states; after loading, On still admitted 30 of 60 callbacks at 60 Hz, versus 60 for Off and idle Auto.[4]
Verified: On targets 30 drawn frames a second; it does not change the simulation's tick rate. Auto saves power while an unplugged battery is at 20% or less, under serious/critical CPU pressure when the browser exposes it, or after 30 hot one-second observation windows during active play. A window is hot when work exceeds 60% of its time or average frames take more than 1.25 times their target interval; a cool window reduces the accumulated count by one. Heat-triggered saving lasts until a new run state. Graphics quality accounts for the intentional slower drawing rather than treating it as an accidental frame-rate drop.[5][6][7]
Measured with this build's actual Battery saver module and controlled frame/power inputs: On admitted 30 of 60 callbacks at 60 Hz, while Off and idle Auto admitted 60. Sustained 11 ms work per 16.67 ms frame activated Auto after about 30.48 s; sustained 25 ms frames against a 16.67 ms target activated it after 30 s. At 9 ms work it stayed off. An unplugged battery at 20.1% did not trigger it; 20.0% did, and charging cleared that trigger. These probes check the controller, not real-device temperature or battery-life improvements.
Map tips explain a nearby, unused point of interest once per kind. Read tips are remembered in this browser across runs; turning tips off does not clear that history. The first tip includes a link to turn them off in Settings. Tips wait while overlays or story scenes block them.[8][9]
Other open tabs pick up a changed Map tips setting the next time they read it. A storage-clear event also clears the cached setting; with no saved value, tips default to On. This synchronizes the setting, not the history of read tips.[10]
The hitbox setting shows a bright green ring with a dark edge on both sides at Tibo's enemy-shot collision centre; it does not change collision size. Color-blind modes give the ring its own color: pink-red for protanopia, teal for deuteranopia and magenta for tritanopia. The colored stroke is 3 CSS pixels wide, or 2 when the viewport's shorter side is 320 CSS pixels or less; the dark stroke underneath is 7 or 5 pixels wide, respectively.[11][12][13]
Updating settings saved before version 3 turns the hitbox on once, even if it was off; you can turn it off again.[14]
If the game closed unexpectedly, it restarts on Low quality: “The game closed unexpectedly last time, so it started on Low. Pick a quality to switch back.” Repeated crashes start lean 3D at half resolution, without adaptive quality or automatic climbing. This is not a 2D renderer.[15]
Graphics recovery
Verified: play stops while the renderer is not ready. The run also waits at the endgame boundary until its lazy content, simulation and view are ready. Failed scene downloads retry after 1 s, then 2 s, 4 s and at most 8 s between attempts. A failed early sprite-page download can be retried too. There is no flat 2D fallback or renderer-selection URL parameter.[16][17]
After WebGL context loss, the game waits up to 5 s for restoration, then tries a fresh context. If that recovery also fails, graphics stop. Scene construction has a separate recovery path: its first failure retries 3D on Low at half resolution, with adaptive quality off; another construction failure stops graphics. The stopped screen says “The 3D graphics stopped” and offers Reload and Copy link; input and audio stop too. Missing WebGL 2 at startup still shows the unsupported-device screen.[18][19][20]
An active intro or landing cutscene ends as skipped if its renderer stops reporting ready.[21]
Updating the game
Verified: the game checks /version.json while the title screen, player pause menu or end-of-run stats screen is open, and every 180 s during active play. It does not force a reload during active play.[22][23][24]
When it finds a different build, it downloads that build's assets in the background for the next reload. Downloads run one at a time at low priority, wait for idle time and include the selected language's files. Save-Data and slow 2G connections skip warming; cross-origin assets are excluded. Download failures do not end the run.[25][26][27]
After an update reload, the first gesture that unlocks audio can fade music in rather than starting it abruptly. This applies when music is not held (paused).[28][29]
On the idle title screen (the Reset screen), a detected update is rechecked and can load automatically. The changelog and a charging Reset button are not idle. A session-storage guard prevents repeatedly auto-reloading into the same offered build; if the guard cannot be saved, or the same update remains after reloading, the title screen offers Update game instead.[30][31]
Starting a non-Story run from the end screen can also load a detected update first, then start the selected difficulty. The saved selection is consumed once and expires after 60 s. If the update or selection cannot be saved, the game starts the run without reloading.[32][33][34]
After a successful update reload, Game updated briefly replaces the title-screen changelog label. A run relaunched after an update gets a temporary HUD changelog button; opening it pauses the run. The label swaps after 3 s and the HUD notice fades after about 30 s. The changelog opens the newest day and the day containing your current build; other dates can be expanded.[35][36]
The pause menu still offers Update game with confirmation: “Your current run will end.” It also shows the current game commit. Updating from a shared-run URL removes its run and stats parameters and returns /r/ to /.[37]
Browser requirements
Verified: WebGL 2 is required. If it is unavailable, the game shows “This device can't run tibo.dog” with a Copy link button, rather than starting the low-detail fallback. If copying is blocked, the screen displays the link for manual copying.[38][39]
Languages
Verified: the game has 27 languages. English is built in; every other language loads an interface file (i18n-<code>-ui) and a game-content file (i18n-<code>-content). Settings → Language lists them by their own names, in this order:
| Language | In English | Code | Direction |
|---|---|---|---|
| English | English | en |
Left to right |
| 简体中文 | Simplified Chinese | zh-CN |
Left to right |
| 繁體中文 | Traditional Chinese (Taiwan) | zh-TW |
Left to right |
| 日本語 | Japanese | ja |
Left to right |
| 한국어 | Korean | ko |
Left to right |
| Español | Spanish | es |
Left to right |
| हिन्दी | Hindi | hi |
Left to right |
| Deutsch | German | de |
Left to right |
| Français | French | fr |
Left to right |
| Português (Brasil) | Brazilian Portuguese | pt |
Left to right |
| Türkçe | Turkish | tr |
Left to right |
| Bahasa Indonesia | Indonesian | id |
Left to right |
| Русский | Russian | ru |
Left to right |
| Bahasa Melayu | Malay | ms |
Left to right |
| Filipino | Filipino | fil |
Left to right |
| मराठी | Marathi | mr |
Left to right |
| Tiếng Việt | Vietnamese | vi |
Left to right |
| తెలుగు | Telugu | te |
Left to right |
| தமிழ் | Tamil | ta |
Left to right |
| বাংলা | Bengali | bn |
Left to right |
| ไทย | Thai | th |
Left to right |
| Hausa | Hausa | ha |
Left to right |
| Naijá | Nigerian Pidgin | pcm |
Left to right |
| العربية | Arabic | ar |
Right to left |
| فارسی | Persian | fa |
Right to left |
| עברית | Hebrew | he |
Right to left |
| اردو | Urdu | ur |
Right to left |
How the game picks a language
Verified: when the page loads, the game uses the first of these that gives a supported language:[40]
- A
langparameter in the address, for examplehttps://tibo.dog/?lang=de.[41] - The language picked earlier in Settings, saved in this browser.
- The browser's preferred languages, in the browser's order.
- English.
On a shared-run link (an address with run or stats), the saved language and the browser come first, and the lang parameter is used only when neither gives a supported language.
Codes are matched by language only, so a region is ignored: pt-PT gives Brazilian Portuguese (pt) and es-MX gives Spanish. Chinese in Traditional script (such as zh-TW, zh-HK or Cantonese yue) gives zh-TW; other Chinese gives zh-CN. Unsupported codes are skipped.[42]
Once a language has loaded, the game writes it into the address as ?lang=<code> (and removes the parameter for English), so a copied link opens in the same language. If the translation fails to load, the game starts in English.[43][44]
Switching language
Verified: picking a language in Settings → Language (for example from the pause menu) relabels the game at once, without reloading or ending the run, and saves the choice in this browser's settings (tibo.settings.v1). Clearing site data forgets it.[45][46]
Display details
- Verified: Arabic, Persian, Hebrew and Urdu switch the page to right-to-left layout.[47]
- Verified: numbers are formatted with Western digits (0–9) in every language (Unicode locale extension
-u-nu-latn).[48] - Verified: a text missing from a translation falls back to English. The boss amusing keeps its English name in every language; its title is translated.[49]
Checked in Chromium on the live game (build main-BnCKpQy_.js, whose language code matches the documented build) on 2026-11-05: ?lang=de showed German; ?lang=pt-PT became ?lang=pt; zh-HK and yue became zh-TW; an unknown code fell back to English and dropped the parameter; ?lang=ar set right-to-left layout; a browser preferring Polish, then Japanese, started in Japanese; a language picked in Settings mid-run relabelled the pause menu at once and was used again on the next visit without a parameter; on a shared-run link the saved language beat ?lang=.
Names in other languages
Every weapon, passive, item, dog, enemy, boss, biome and event page has a collapsed Names in other languages table with its in-game name in all 26 other languages, taken from the game's content files. The wiki search also matches these names, and the names of points of interest and Grok Bots, so you can search in your own language.
Sources
Game version 75: main-CSuyb4iv.js / boot-CPW8VVmR.js / sim-BkfBhQfw.js from https://tibo.dog/, researched 2026-11-05. See Versions & updates.
- About tibo.dog
- Settings UI in
boot-CPW8VVmR.js
Code references
- poi-tips-vFYb3sXD.js
Math.hypot(e.clientX-y.x,e.clientY-y.y)>6 - poi-tips-vFYb3sXD.js
e.stopPropagation(),y.moved||a.openSettings() - run-extras-l2haGSVw.js
casefoil:t?this.holo.value?.paint(this.shot,e.frames):this.holo.value?.hide(); - boot-CPW8VVmR.js
r===void 0?(this.pace.hold(n===`on`),n===`on`&&(e.loop.pace=this.pace)):r.frame(e,t) - battery-saver-D5hqPNwb.js
this.hotWindows>=this.cfg.hotWindows&&(this.heat=n?`slow`:`load`) - battery-saver-D5hqPNwb.js
t(!r.charging&&r.level<=e) - boot-CPW8VVmR.js
batterySaver:{windowSec:1,hotDuty:.6,slowRatio:1.25,hotWindows:30,lowBattery:.2} - boot-CPW8VVmR.js
var Io=`tibo.poi-tips-setting.v1` - poi-tips-vFYb3sXD.js
key:`tibo.poi-tips.v1` - boot-CPW8VVmR.js
e.key!==`tibo.poi-tips-setting.v1`&&e.key!==null||(n=void 0) - scene-B2rwbRKj.js
Bne={line:3,halo:7},Vne={line:2,halo:5} - scene-B2rwbRKj.js
Hne={off:{line:`#23f000`,halo:Kn},protanopia:{line:`#ee5566`,halo:Kn},deuteranopia:{line:`#11aa88`,halo:Kn},tritanopia:{line:`#ff22ff`,halo:Kn}} - scene-B2rwbRKj.js
u=a.cfg.player.hitRadius - boot-CPW8VVmR.js
field:`hitbox`,from:3 - boot-CPW8VVmR.js
return{params:t.params,settings:n,lean:!0} - main-CSuyb4iv.js
if(!this.renderer.ready()||(this.endgameWait??=ve({chunks:this.chunks,host:this.hud.root})).holds(this.game.state))returnloading; - hd2d-renderer-t_OyO5wm.js
this.retrySec=Math.min(a.renderer.retryMaxSec,2*this.retrySec) - hd2d-renderer-t_OyO5wm.js
this.failed?{...n,softwareGl:!0} - boot-CPW8VVmR.js
tier:low,scale:xn.minScale,adaptive:!1,climb:!1 - no-webgl2-screen-DtaAe71v.js
The 3D graphics stopped - boot-CPW8VVmR.js
if(this.host.renderer.cutscene()!==ready)return void this.end(!0); - main-CSuyb4iv.js
K(this)&&this.updates.playing(e) - boot-CPW8VVmR.js
function Nf(e){return e.flow.stage.kind===run&&!e.game.over&&!Mf(e)} - boot-CPW8VVmR.js
e-this.lastAt<this.source.recheckSec - boot-CPW8VVmR.js
this.live=t,this.source.found?.(t),this.onChange() - warm-update-QoPnnjP0.js
var f=new Set([slow-2g,2g]) - warm-update-QoPnnjP0.js
e.origin===t.origin&&n.add(e.href) - boot-CPW8VVmR.js
let n=this.joining&&!this.musicHeld - chip-worklet-C9vAAiRn.js
state=fadingIn,n.left=n.inFrames - main-CSuyb4iv.js
this.splash.idle&&this.updates.settle(e,()=>this.splash.idle) - boot-CPW8VVmR.js
let n=e===this.held||!this.source.remember(e);if(t&&n)return this.held=e,void this.onChange();this.reloadNow() - boot-CPW8VVmR.js
n!==this.held&&this.source.remember(n)&&t.arm()?this.reloadNow():t.start() - boot-CPW8VVmR.js
maxAgeMs:e});return i.kind===run?i.level:void 0 - boot-CPW8VVmR.js
update:{url:/version.json,waitSec:5,recheckSec:180,settleSec:15,relaunchSec:60,noteSec:3,hudNoteSec:30} - boot-CPW8VVmR.js
swapMs:1e3*T.update.noteSec,fadeMs:1e3*T.update.hudNoteSec-400 - main-CSuyb4iv.js
this.pause===none&&this.canPause()&&(this.setPause(player),this.menus.openLog()) - boot-CPW8VVmR.js
t.pathname===/r/&&(t.pathname=/);for(let e of ku)t.searchParams.delete(e) - no-webgl2-screen-DtaAe71v.js
This device can't run tibo.dog - no-webgl2-screen-DtaAe71v.js
Copy is blocked here. Long-press the link to copy it. - boot-CPW8VVmR.js
let t=e.saved??si(e.browser);return(e.sharedRun?t??e.link:e.link??t)??`en` - boot-CPW8VVmR.js
lang:zs(t.get(Rs)) - boot-CPW8VVmR.js
case`zh`:case`yue`:return t.script===`Hant`?`zh-TW`:`zh-CN`;default:return ni.find(e=>e===t.language) - boot-CPW8VVmR.js
function Hs(e){Vs({set:Bs(e),drop:e===`en`?[Rs]:[]})} - boot-CPW8VVmR.js
catch(t){return e.report(t),await e.use(`en`),`en`} - boot-CPW8VVmR.js
async pick(e){await this.set(e),this.app.locale===e&&this.app.changeSettings({locale:e})} - boot-CPW8VVmR.js
var pu=`tibo.settings.v1` - boot-CPW8VVmR.js
document.documentElement.dir=t?.dir??ri(e) - boot-CPW8VVmR.js
-u-nu-latn - boot-CPW8VVmR.js
di.has(e)||j.messages[e]===void 0?ci.messages[e]===void 0?void 0:ci._(e):j._(e)