CHECKS OF TUTORIALS 5 AND 6 krueng.ai/procedural_animation.html Tutorial 5 (steps 8 and 9) lets an AI model pick a gecko's moves. Tutorial 6 (steps 10 and 11, and "the big game") runs the model on your own computer, once and then in the smallest agent, and shows how a game with nine agents is built, with all its code. The page's footer says what was checked. These are the scripts that made those checks. Run them again, and see for yourself. WHAT YOU NEED 1. The tutorial's files, in one folder. Every code block on the page has a Download button. You need: lizard.py, ask.py, hunt.py, decide.py, agent.py, ask_output.txt, decide_output.txt, agent_output.txt, game_question.json and game_answer.json. For game_copy.py you also need gecko_dragonfly.html. 2. These scripts, in a folder named "checks" inside that folder: your folder/ lizard.py ask.py hunt.py agent.py ... checks/ README.txt window_runs.py wording.py ... Each script looks for the tutorial's files one folder up. 3. Python 3.8 or newer, and nothing else, for most of them. The list says what the others need. decide.py and agent.py are different from the other files. They run the model on your computer, so they need Python 3.11 or newer, the decider-ai library (pip install decider-ai, about 1 GB with PyTorch on Windows), the model (a 3.8 GB download on the first run) and about 8 GB of free memory. They need no GPU. Their checks also run without all that: see agent_file.py below. 4. The model's server. ask.py and hunt.py ask it over the internet, and so do the checks. It runs on one small computer in Chiang Mai and is sometimes off. Then every check that asks the model fails. Run the Python scripts from the checks folder: python window_runs.py hunt THE SCRIPTS window_runs.py hunt hunt.py in a real window. The gecko creeps up on a resting dragonfly and catches it, leaves a flying one alone, and misses one that flies off as the jump begins. About 17 questions to the model. Keep the real mouse out of the window. window_runs.py off With the model's address unreachable, the window says "no answer", the gecko stands still, and nothing is written to the error stream. No questions. window_runs.py mouse Animal("mouse") has none of the hunter's parts and walks to the mouse, as in step 7. No questions. window_runs.py main hunt.py started through its own main(), for 6 seconds. About 5 questions. wording.py Does the model follow the rules, whatever order the moves come in? For ask.py: the four things hunt.py's sees() can say, each with the three moves in all six orders. For agent.py: its two sentences, each with its two moves in both orders. 28 questions. The wording was chosen with this test, so a clean result is a fit and not a score. your_turn.py rules The changes to RULES and MOVES in step 8's Your Turn list, and one idea that failed. 33 questions. your_turn.py numbers The changes to THINK, CREEP and DASH in step 9's Your Turn list. The gecko is stepped by hand. No questions. Windows flash open and shut. your_turn.py decide The changes to decide.py in step 10's Your Turn list, each one run. 5 questions. your_turn.py agent The changes to agent.py in step 11's Your Turn list, each run through main(), and one idea that changed nothing. 29 questions. agent_file.py Not a check. wording.py and your_turn.py use it to load agent.py and decide.py. Those two run the model themselves, with the decider-ai library. Where that library is installed, the checks use it. Where it is not, a stand-in takes its place and sends the same questions to the model's server. Each check prints which one it used: "library" or "server". model_timing.py decide.py, model_timing.py agent.py Runs the file for real, with the model on this computer, and times it: starting Python and the library, loading the model, and each question. It needs what the file needs. No questions to the server. one_decision.py Sends game_question.json, one real question of the big game, and writes the answer to game_answer.json. 1 question. game_copy.py Opens gecko_dragonfly.html from disk, as you would after a download. A copy has no server beside it, so it must ask the game's public address. About 20 questions. It needs Playwright for Python: pip install playwright playwright install chromium make_traces.py, then compare_with_page.js The page's pictures are JavaScript, written again from the Python. make_traces.py steps the Python animals by hand and writes every number to traces.json (about 3 MB). compare_with_page.js steps the page's animals with the same mouse moves and the same picks, and compares: the hunter for 1,000 frames, the lizard, the fish and the snake for 400. It also compares the words: the page's script with hunt.py, with agent.py and with decide.py. No questions. page_checks.js The page in a real browser: every part is there, every code block gives back its file, every block has its buttons, the live pictures get answers, the button of step 10 gets decide.py's answer, the button of step 11 prints what agent_output.txt holds, Thai and Chinese show, and a 400 px screen does not scroll sideways. About 35 questions. public_timing.py Seconds for one question through the model's public address, each on a new connection, as ask.py sends them. 12 questions. It needs curl and nslookup. Two more checks are not in this folder. measure.py is with the tutorial's files. It prints every number the page states about its own animation files. It builds the real classes and steps them by hand. The site's own source has a script that writes each file into its code block on the page and fails when a block and its file differ. page_checks.js checks the same thing from the outside, in the browser. THE TWO BROWSER CHECKS compare_with_page.js and page_checks.js are one function each, for a Playwright page. Run one with a Playwright browser tool ("run code", with the file), or in a Playwright script: await ()(page) They check the page that is open in the browser. Open the live page first: https://krueng.ai/procedural_animation.html. With no such page open, they open it. compare_with_page.js needs traces.json. The live site does not have it. Write the full path of your traces.json into TRACES_FILE at the top of the file, and the script hands it to the page from your disk. On the live site, page_checks.js says "not on this server" for three code blocks. They are the files of the big game. The site prints them from its source and does not keep them as files. FOR THE SITE'S OWN SOURCE In the source, this folder is HTML/procedural_animation_assets/checks/, and the game's files are in spark/gecko_game/. Before a change goes live, check a preview: python -m http.server 8917 --bind 127.0.0.1 --directory . (in the top folder) open http://127.0.0.1:8917/HTML/procedural_animation.html, then run the two .js files That preview serves traces.json and the game's three files too, so nothing says "not on this server". Stop the server when you are done. traces.json is not kept: this folder's .gitignore names it. After a change: - ask.py: run it and keep what it printed (python ask.py > ask_output.txt). Then write the files into the page, then wording.py and your_turn.py rules. - hunt.py: measure.py, write the files into the page, the four window_runs.py scenarios, your_turn.py numbers, then make_traces.py and compare_with_page.js. - decide.py: run it and keep what it printed (python decide.py > decide_output.txt). Then write the files into the page, your_turn.py decide, make_traces.py and compare_with_page.js. decide.py asks agent.py's question, word for word; the page's button in step 10 has the same words (DECIDE in the page's script). - agent.py: run it and keep what it printed (python agent.py > agent_output.txt). Then write the files into the page, wording.py, your_turn.py agent, make_traces.py, compare_with_page.js and page_checks.js. The page's button in step 11 has the same words and numbers, written again in JavaScript (AGENT in the page's script). - a file of the big game: write the files into the page. If it was the game's page, also game_copy.py. If the words of a request changed: one_decision.py --from-game. - the page's script: make_traces.py, compare_with_page.js, page_checks.js. - WHO, RULES or MOVES: they are written three times, in ask.py, in hunt.py and in the page's script. compare_with_page.js compares the last two. WHAT THEY PRINTED Windows 11, Python 3.12.10, Tk 8.6.15. The model was Mapika/decider-2b, version 11. For ask.py and hunt.py it was on our server, at https://spark-wav2lip-1.taila00da8.ts.net/gecko/v1/systemone. For decide.py and agent.py it was on the computer that ran the file. 2 October 2026, Tutorial 5: - window_runs.py hunt: 914 frames in 20.5 seconds (44.5 a second), 18 questions, 1.08 to 1.21 seconds apart. Crept from 350 px to 119, jumped, caught within 10 frames. Left the flying dragonfly alone for 6 seconds. Missed the one that flew off. Nothing on the error stream. Three earlier runs gave gaps of 1.01 to 1.25 seconds. - window_runs.py off: "no answer", head still at its start place after 7 seconds. - window_runs.py mouse: no "doing", no "caught"; the head stopped 28 px from the mouse. - your_turn.py rules: with RULES empty the gecko creeps at a flying dragonfly (0.83 and 0.68). Without "creep" in MOVES it jumps from too far (0.77). A lazy gecko needs both changes: with only the rule changed it still creeps (0.98), and with the sentence "Creep closer. Never do this." it waits (0.91). The dragonfly's own rules work (1.00 and 1.00). A move and a rule for sleeping at night did not work: at night the gecko still jumped (0.89), so that idea is not on the page. - your_turn.py numbers: with THINK = 200 the first question is at frame 200, not 50. With CREEP = 4 the creep takes 100 frames, not 150. With DASH = 3 the jump takes 39 frames, not 9 or 10. The lazy gecko's picks leave it 350 px away for all 900 frames. - public_timing.py, four runs (the last on 3 October): 0.86 to 1.60 seconds a question, the middle one 0.93 to 1.03. 3 October 2026, Tutorial 6: - agent.py, on a laptop with no GPU (Windows 11, Intel Core Ultra 7 155H, 24 GB of memory, decider-ai 1.8.1, PyTorch 2.14.1 for the CPU, transformers 5.18.0). pip install decider-ai took 72 seconds and made a folder of 902 MB. The first run took 9 minutes: the model is a 3.76 GB download, and it came at 7 MB a second. After that, model_timing.py measured: import 6.2 s, load 9.1 s, the four questions 1.66 1.75 1.81 1.78 s. The process held 8.1 GB of memory at most. It printed four questions (creep, creep, creep, jump) and "The gecko catches the dragonfly." - agent.py, on a GPU (NVIDIA DGX Spark, Linux, PyTorch 2.11.0, the same library): import 4.0 s, load 28.3 s, the four questions 91.53 0.02 0.02 0.02 s. The first question is slow because the library builds its fast path once. With transformers 5.6.0 the library failed ("fused_causal_conv1d_fn() missing 1 required positional argument"); with 5.18.0 it ran. With the GPU hidden it failed inside triton ("0 active drivers"). - decide.py, on the same laptop: 16 to 20 seconds in all. model_timing.py printed: import 5.0 s, load 9.4 s, the one question 1.82 s. It printed one answer: "jump", 0.9982. On the GPU: 227 seconds in all, nearly all of it the fast path, and 62 seconds with Decider("Mapika/decider-2b", use_graphs=False). Then the file's long lines were split. Python read the same program (ast.dump was the same), and a new run printed decide_output.txt again, to the last digit, in 16.7 seconds. - your_turn.py decide, through the library on the laptop and through the server: too far gives "creep" (0.9987). A third move, wait, with a flying dragonfly and no rule for it still gives "creep" (0.85). With the rule "If it is flying, stay still." it gives "wait" (0.998). Printing only the move prints "jump". - wording.py: ask.py right in every order in 4 of 4 situations, agent.py in 2 of 2, through the library and through the server. - your_turn.py agent, through the library on the laptop, through the library on the GPU, and through the server: the same each time. From 9 steps, 8 questions and a catch. From 12 steps, 10 creeps and "The gecko never jumped." With JUMP = 4, one creep and a catch. With the first "sees" sentence changed to "close enough", a jump from 5 steps and "The jump is too short." A wild gecko did not work: with the rule and both sentences changed to "always jump", the picks were the same as before. The words "too far to jump at" decide the pick. - one_decision.py: 1,032 bytes sent, 257 bytes back, "jump" 0.9849, 269 tokens read and 0 written. - game_copy.py: opened from file://, 16 answers in 6 seconds (21 in an earlier run), none failed, all asked at the public address. - compare_with_page.js: the words are the same, agent.py's and decide.py's too. The hunter: 66,076 numbers, the biggest difference 0.00000000055, and no other difference in 1,000 frames. The lizard, the fish and the snake: 72,000 numbers, the biggest difference 0.0000000000008. - page_checks.js, on a preview of the source after the last change: no part missing, all 19 code blocks the same as their files, all 13 check scripts there. Step 8's three answers took 0.22 to 0.30 seconds. The live gecko caught the dragonfly twice in 13 seconds, and then once more with the pointer as the dragonfly. Step 10's button printed "jump" (confidence 0.9963 from our server; the file printed 0.9964). Step 11's button printed agent_output.txt exactly. Thai and Chinese showed in every part, and a 400 px screen did not scroll sideways. On the console: only YouTube's line (see THINGS TO KNOW). THINGS TO KNOW - The model's numbers differ a little from run to run. The first number in ask_output.txt was 0.9247 in one run of ask.py and 0.9129 in the next. The picks did not change. - Windows flash open and shut. Keep the real mouse out of them: in hunt.py the mouse is the dragonfly. - The model's computer does other work too. At a busy moment an answer takes much longer. Do not trust a time from a busy moment. - The page's video is YouTube's player. Now and then it writes a line of its own to the browser's console ("Permissions policy violation: compute-pressure", or on Windows "The powerPreference option is currently ignored when calling requestAdapter()"). That line is not from this page: with YouTube blocked, it does not come.