IronBee Gamer:用 LLM 写规则训练 30ms 本地模型玩浏览器游戏
Show HN: Teaching a 30ms local model, play browser games with LLM written rules
IronBee Gamer 让 LLM 为每个浏览器游戏编写规则与提取器,再由一个 30ms 本地决策引擎实时操作游戏,无需游戏提供 API 或钩子。
通过快速决策引擎实时玩浏览器游戏。LLM 根据所玩的游戏为每个游戏训练配置文件。
IronBee Gamer 打开一个浏览器游戏,读取页面绘制的内容到一个小的 JSON 状态,并请求决策引擎选择每一步操作。它无需游戏提供任何帮助:不需要 API,也不需要游戏暴露的钩子。
你可以在本地 Web UI 中观看游戏过程。每个决策都显示在旁边:引擎看到的状态、它选择的动作以及概率。
Laya 在 UI 中玩 Flappy Bird,三十秒的片段。右侧:每个决策做出时的状态(flap 或
wait)、做出决策所依据的状态,以及训练器编写的规则。点击可查看全尺寸的静态图。
page (canvas / engine)
└─ perception adapter ── generic, per rendering tech (2D canvas draw recorder, Phaser / PixiJS / Cocos dump,
any canvas as a small colour grid), or a reader of the game's own state the
trainer wrote from the page's code
└─ extract(raw, memory) ── per game, written by the trainer (an LLM), < 1 ms
└─ state JSON (features, never the answer)
└─ decision engine ── Jev (hosted) or Laya (local, fine-tuned per game)
└─ keys held / the pointer held / a click
└─ the game's clock runs one tick ← the game moves only between decisions
分工。每个部分各司其职:
- 训练器(通过编码代理 CLI 的 LLM:Claude Code CLI 或 Codex CLI,通过 UI 的 Trainer 药丸选择)编写逻辑:状态计算什么,例如距离、事情发生的快慢以及当前可能的情况。它还编写将这些特征映射到动作的规则。
- 决策引擎通过将这些规则应用于状态来做出每一个实时决策。
携带答案的状态(advice: "JUMP")会让引擎成为橡皮图章。此类字段在引擎看到状态之前被剥离,并告知训练器移除了多少个。
快速开始
npm install npm run build npm link # once: puts the `ibgamer` command on your PATH ibgamer laya setup # once: a Python environment for Laya, the local engine (needs python3) npm run ui # the web UI at http://127.0.0.1:1986 (the same as `ibgamer ui`)
没有 npm link 时,npm start -- <command> 运行相同的命令(npm start -- play dino --seconds 30)。
在 UI 中,⇩ Hugging Face 下载已训练的游戏——每个都带有其配置文件版本和 Laya 的模型
(每个游戏约 700 MB)。然后选择一个游戏并按 Play。浏览器默认是无头的,实时视图会显示它。使用 --headed 查看真实窗口。在游戏的第一帧之前(Laya 服务器启动、浏览器、页面加载:几秒钟),屏幕会显示它处于哪一步、持续了多久,以及如果游戏无法启动的原因。当游戏结束时,屏幕会说明原因:Time's up
(其游戏秒数已播放完——不会挂起)、Game over 或 Stopped。
每个部分需要什么:
- 使用 Laya 游玩(默认):上述 Python 环境,以及游戏的模型——已下载或在此训练。
- 使用 Jev 游玩:其密钥,
export TYPESAFE_API_KEY=…(或在工作目录中的.env中)。 - 训练和添加游戏:一个编码代理 CLI,已安装并登录——Claude Code CLI(
claude)或 Codex CLI(codex)。UI 顶部的 Trainer 药丸显示使用哪一个以及哪个模型,并进行选择。
ibgamer library list # the games and their active profiles ibgamer play dino --seconds 30 # plays in the terminal; --watch paces it to the game's own speed ibgamer train flappy-bird # checks how it plays, fixes what loses (else trains for a higher score), keeps it only if it plays better ibgamer check pacman-ghosts # plays a seed twice: the same frame for frame, or where it first differs ibgamer play pop-the-lock --lag 45 # real time simulated on the paused clock: each decision lands 45 ms late, every run the same ibgamer play dino --tick 48 # tried with 48 ms of game time between two decisions instead of the version's tick
库
库列出了应用知道如何访问、启动和测量的每个游戏,以及玩它的配置文件。
十一个内置游戏,每个都处于 Laya 正在玩的游戏过程中(Pac-Man 两次:其第一关,以及稍后 幽灵逃跑的一关)。
| 游戏 | 游玩来源 | 读取方式 |
|---|---|---|
| Chrome Dino | wayou/t-rex-runner,Chrome 离线游戏的副本 | 2D canvas |
| Flappy Bird | floppybird 由 nebez 制作(Apache-2.0) | 游戏自身的状态(HTML) |
| Pac-Man | Pacman Canvas 由 platzh1rsch 制作(CC0) | 像素(颜色网格) |
| Doodle Climb | Phaser examples | Phaser |
| Pop the Lock | Phaser examples | Phaser |
| Super Coin Box | Phaser examples | Phaser |
| Tetris | Phaser examples | Phaser(游戏自身的状态) |
| Crazy Snake | Phaser examples | Phaser(游戏自身的状态) |
| Racer | Javascript Racer 由 Jake Gordon 制作(MIT) | 游戏自身的状态(一个页面读取器) |
| Infinite Mario | mariohtml5,作者 Robert Kleffner(Unlicense) | 游戏自身的状态(一个页面读取器) |
| Breakout | Javascript Breakout,作者 Jake Gordon(MIT) | 游戏自身的状态(一个页面读取器) |
除 Chrome Dino 外,其余全部由应用自行添加并训练:训练器根据页面所展示内容的样本——或在能读取页面代码时根据其代码——逐一搭建、调优,再由 Laya 学习。它们的游戏定义是唯一手写的部分,而 Breakout 的定义也是在 UI 中制作的。每个游戏的表现如何、各项得分,都在 docs/measurements.md 中。
该库有两个根,合为一体:
- 内置(
library/,随包发布):经过研究的游戏,连同其完整的训练历史(每个版本、调优器的分析、回归测试以及测试回放的窗口)。 - 你的(
~/.ibgamer/library,或IBGAMER_LIBRARY_DIR):你添加的游戏,以及训练产生的每个版本。经过训练的内置游戏,其新版本会出现在这里,与随包发布的版本并列。你随时可以重新启用较旧的版本。
<game>/game.json how to reach, start and measure it — never how to play it
<game>/profiles/v<N>.json how to play it: extractor, rules, actions, timing, regression tests, results
<game>/windows/<id>.json raw frames before a failure, replayed offline by the regression tests
<game>/samples/… what training looked at (a raw sample, sprite crops, a screenshot)
<game>/decisions/*.jsonl the engine's decisions while playing: distillation data for Laya
<game>/laya/<name>/ a Laya checkpoint fine-tuned on this game (v<profile>-<hash>-r<round>, ~650 MB)
要分享一个游戏,使用 ibgamer library export <game> <dir>,它会把每个版本写入同一个目录。使用 ibgamer library import <dir> 来添加一个。游戏的提取器是 JavaScript。它运行在自己的 V8 上下文中,没有 Node 全局变量,没有 eval,有时间限制,只有 JSON 跨越边界。不过,只添加你信任的游戏。
Hugging Face 库
训练好的游戏在 Hugging Face 上分享,位于一个模型仓库中,每个游戏一个文件夹(默认是 ironbee-ai/ironbee-gamer-library,使用其他仓库则用 IBGAMER_HF_REPO):每个游戏的定义、其配置版本、其窗口和样本,以及 Laya 对其所玩版本的检查点——不是每个版本的每一轮,也不是决策日志。从它拉取的游戏可立即游玩,且已训练:Laya 无需蒸馏,Jev 用你自己的密钥。
- 在 UI 中,库上方的 ⇩ Hugging Face 会列出共享的游戏——Laya 和 Jev 各自如何游玩、其大小,以及你的库中已有它的哪些内容——并可下载一个(Download、Update)。在你的库中训练过的游戏,只有在第二次点击后才会被替换;其决策日志会保留。
ibgamer library hf列出它们;ibgamer library pull <game…>下载(--replace覆盖你自己的训练)。每个文件都会对照仓库index.json中的 sha256 进行校验,一次拉取的所有内容都读取自同一个提交。ibgamer library push [game…]通过 Hugging Face CLI 分享游戏(默认分享每一个)(pip install -U huggingface_hub,然后hf auth login):在一个提交中放入游戏的文件夹,同时移除它不再持有的内容,然后是索引。不存在的仓库会被设为私有(要设为公开则用--public)。你机器的路径会从分享内容中移除;若某个文件仍提及你的主目录,推送会中止。
该库以 Elastic License 2.0 分享,名称为 IronBee Gamer(推送会上传 LICENSE 及 README,其中会写明该名称);Laya 的检查点是从 jhu-clsp/mmBERT-base(MIT)微调而来。私有仓库用 HF_TOKEN 读取,否则保留令牌 hf auth login。
添加游戏
在 UI 中,按 + Add game。一个引导式对话框分四步获取可玩游戏的 URL:
- 页面。 页面被打开并查看。判定结果说明是什么吸引游戏以及它将如何被读取:2D 画布、Phaser、PixiJS 或 Cocos 通过通用适配器。任何其他画布(来自其他引擎的 WebGL、捆绑的)可以通过其像素读取——每一步一个小颜色网格,适用于任何内容但不够精确。更好的是,让训练器读取游戏代码:它读取页面的脚本并编写一个返回游戏自身状态的表达式,在提供之前会在页面上检查。这需要几分钟,并且适用于 JavaScript 可以访问其状态的任何游戏。在编写时,对话框会显示当前步骤和已用时间;你可以同时进行其他步骤,添加游戏 会等待它(或 停止读取 放弃它)。
- 开始。 它自行启动,通过按键,或通过你在页面截图上放置的点击。
- 游戏。 它的名称,如何用游戏自己的话玩——需要:这是训练器和引擎被告知的游戏内容;训练器在读取游戏代码时提出建议,供你检查——以及分数:由训练器从它感知到的内容读取,或你知道的页面表达式。保持灰色的 下一步 说明它在等待什么。
- 引擎。 Laya(本地,快速;训练器将规则编写为代码,Laya 学习它们)或 Jev(托管;将规则作为文本读取)。训练可以立即开始。
然后游戏页面显示一个清单(已添加 → 规则已训练 → Laya 已教会 → 可玩),并带有下一步的按钮。正在进行的训练或蒸馏显示其阶段、进度条、剩余时间和目前的结果。第一次训练运行会采样页面,向训练器请求提取器和动作,将第一个配置文件保存为 v1,然后对其进行调整。
游戏定义(game.json)仅包含数据:
{
"id": "dino",
"name": "Chrome Dino",
"url": "https://wayou.github.io/t-rex-runner/",
"goal": "Chrome Dino (T-Rex runner). Press Space or ArrowUp to jump, ArrowDown to duck. …",
"perception": { "adapter": "canvas2d" },
"viewport": { "width": 800, "height": 400 },
"start": [{ "press": ["Space"], "advanceMs": 700 }, { "waitMs": 1000 }, { "advanceMs": 800 }],
"score": { "expression": "(() => { const r = Runner.instance_; return { over: r.crashed, score: … }; })()", "label": "distance" },
"budgets": { "gameSeconds": 90, "episodes": 1, "trainSeconds": 90 },
"trainSeeds": [101, 202, 303]
}start 步骤是玩家开始时要做的:按键、点击、游戏时间(advanceMs),以及时钟冻结时的真实时间(waitMs);holdFrom,一个页面表达式,命名要按住的键,走过菜单或地图,其路径取决于种子。Dino 仅在 CSS 动画结束时开始,而 CSS 动画实时运行;在游戏时钟停止的情况下等待它,使每个游戏在同一游戏时间开始。
在关卡之间停止并等待玩家的游戏(“点击继续”)从其分数表达式报告 waiting: true,其 resume 步骤(与 start 形状相同)将其带到下一关。
游戏如何被提供也是数据:configs 列出了它使用的引擎和时钟对({"engine": "laya", "live": true, "version": 11, "lagMs": 50}:Laya,时钟从不暂停,版本 11,其输入在帧后不早于 50 毫秒到达)以及 preferredConfig 它打开时使用的那个。UI 仅提供这些。一个没有列出任何内容的游戏——你添加的——会获得其版本所赢得的内容:每个引擎在时钟暂停时,以及一旦为实时训练过的版本在那里播放几乎与暂停时一样好,Laya 就会实时运行。无需手动编写任何内容。
分数表达式读取游戏自身状态,并仅用于测量。引擎和训练器都不会将其视为状态字段。没有分数表达式的游戏可以使用 "score": { "fromState": true },其中配置文件自身的 state.score / state.over 就是度量。该度量是自我报告的,因此调优器可能会夸大它。
游戏如何被玩
-
冻结的时钟。 Playwright 的时钟在页面加载前就已安装。启动后,时间停止,每一步恰好运行
tickMs的游戏时间(至少 32 毫秒:本地引擎的一次决策大约需要这么长时间,而按自身速度观看的游戏不能落后于它)。引擎的延迟不消耗游戏时间,因此实时游戏变成了回合制。 -
每次决策一步。 每一步都是一次到浏览器的往返:输入,然后游戏时间,然后原始输入和得分。
decideOn: "change"保持一个动作直到状态改变(迷宫游戏的回合窗口)。askWhen在无事发生时跳过引擎,最后一个决策保持生效。 -
输入。 再次选择的按键保持按住;松开再按会缩短跳跃。点击动作点击游戏中心(或某个点)一次。指针动作在生效期间保持鼠标按钮按下,适用于按住蓄力、松开触发的游戏。
-
种子。 每一局都是全新的页面加载,
Math.random已播种(以及 Phaser 游戏自己的 RNG,它从时间播种,也从它播种),时钟冻结在动画帧边界上。因此相同的种子逐帧播放相同的游戏,两个配置版本(或 Laya 学生与其老师)在完全相同的游戏上进行比较。 -
游戏时钟(UI 的设置,CLI 中为
--watch/--realtime):- 每次决策暂停,按真实速度播放(UI 的默认值):一步永远不会快于游戏自身的速度,慢的决策就是暂停。
- 每次决策暂停,尽可能快:训练和测量以这种方式播放。
- 从不暂停:时钟像对人一样实时运行,游戏不会等待决策。
在实时模式下,引擎基于游戏已经离开的状态做决策,而不允许任何延迟的游戏——必须在落地后立即进行的跳跃、必须落在圆点上的点击——就会失败。解决它的是一个为实时训练的配置:它的提取器描述动作将要遇到的世界(
info.lagMs),玩家保持每个输入以在该延迟处落地,因此时间变化的引擎表现得像固定延迟的引擎。只有当某个版本在实时下的表现几乎与暂停时钟时一样好,游戏才会提供实时模式。ibgamer play <game> --lag 45在暂停时钟上模拟实时,每次运行都相同。 -
计划,针对比游戏慢的引擎。 一个在数百毫秒内响应的托管引擎可以在一次请求中被要求给出接下来几个时刻(
train --no-check --realtime --latency 250-600 --plan 8x50):提取器预测每个时刻,玩家在每个时刻落地每个输入,而下一个请求已经发出。它可玩,但不好,所以还没有游戏提供 Jev 实时模式(docs/design/realtime-plans.md)。
训练
play the best version on fixed seeds (several games at once)
→ evidence: scores, the last decisions before each end, end screens,
the raw frames before each failure (saved as windows), things perceived for the first time
→ the trainer writes a new version + regression tests that pin its fix down
→ it must pass every regression test offline (one repair round)
→ it plays the same seeds → kept only if its mean is higher
在 UI 中,训练 使用上方选择的引擎和时钟让游戏玩得更好——Laya 或 Jev,时钟暂停或运行(仅 Jev 暂停);CLI 中为 ibgamer train <game> --engine laya。无论游戏玩得差还是好,这都是唯一需要按的按钮:
- 它会检查。 引擎在版本规则作为代码的旁边玩这个游戏,使用相同的种子和相同的时钟,检查会指出引擎在哪里玩得更差以及这是谁的损失:版本的(引擎及其规则都输)还是仅引擎的。
- 它修复会输的地方。版本在它输的地方接受训练。Laya 在只有它自己输的地方被教得更多:它玩,规则纠正它,它从中学习。Jev 的指令在 Jev 做决定的情况下接受训练。在没有任何东西玩得更差的情况下,版本为更高的分数接受训练。
- 它会再次检查,只有当游戏玩得更好时才保留改动;没有变好的改动会被撤销。
在还没有可检查的东西时——一个新游戏,或者 Laya 还没有它所玩版本的模型——Train 就从那里开始:训练器写出规则(给 Jev 用文字,给 Laya 用代码),Laya 学习保留的版本。
ibgamer train <game> --check-only 只说明它会修复什么;--no-check 在没有检查的情况下训练,使用训练器自己的选项(--decider、--seeds、--realtime --simulated、--plan、……)。ibgamer laya distill 手工教 Laya。
为实时而训练。不等玩家的游戏要为此接受训练(设置清单中的 Train for real time、向导中的复选框、ibgamer train <game> --realtime):规则很晚才做决定,因为 Laya 要在安静的机器和繁忙的机器上实时作答,而一个版本必须在这整个范围内都玩得好。只有当它在时钟暂停时玩得不更差,它才会被保留,这样一个版本就能同时服务两种时钟;一个实时玩得更好但暂停时玩得更差的版本,只保留给实时时钟。
给训练器的备注(在 Train 旁边以及向导中;CLI 中为 train --note "…")告诉训练器你看到游戏做了什么,或者想让它做什么。版本仍然只有在得分更高时才会被保留:度量不奖励的备注会被尝试然后被拒绝,日志会说明这一点。
一个版本只有在同样的种子上击败最好的版本、在从未展示过的种子上玩得不更差,并通过每一项回归测试时,才会被保留。连续两次尝试都没有保留任何东西后,或者一旦某个版本达到游戏的最高分(score.max),训练就会停止。“训练一次就完事”是错的:一个配置只覆盖它见过的游戏阶段,所以一个在后期会变化的游戏——夜间模式、更快的关卡——需要在更长的游戏上训练。这背后的研究,连同每一个陷阱,都在 research/KNOW-HOW.md 中。
训练器
训练器是一个 LLM,通过编码代理 CLI 以它自己的登录运行——不读取任何 API 密钥。整个应用只有一个选择,而不是每个游戏一个:UI 顶部的 Trainer 胶囊显示正在使用的模型,并打开该选择。
| CLI | 模型 | 它能读取什么 |
|---|---|---|
Claude Code(claude),默认 |
Haiku、Sonnet、Opus(默认)、Fable——各自家族的最新版;UI 显示是哪一个(Opus 5.5),直接向 CLI 本身询问 |
仅训练运行自己的文件夹 |
Codex(codex) |
它在这台机器上列出的那些;其最新的 Sol 为默认 | 该用户的任何文件:它的沙箱是只读的(不写入、无网络),但不受运行文件夹的限制 |
除了模型,对话框还设置训练器的 effort——它在回答前思考的力度(Claude Code:从 low 到 max;Codex:它为模型列出的级别)。除非训练超时,否则保持 CLI 自己的设置:训练器的一次调用在 30 分钟后被切断(IBGAMER_TRAINER_TIMEOUT_MINUTES),此时会整个丢失。训练器在每个提示中都会被告知它的限制,一次超时的调优会告诉下一次尝试这一点;更低的 effort 会更快作答。
无论哪种方式,CLI 都拿不到这个应用的任何密钥,不写入任何内容,也不保存会话。它们能读取的内容的差异很重要,因为提示中带有游戏页面绘制的文本:使用 Codex 时,页面可能说服训练器在它写的规则中重复你的某个文件。该选择保存在 ~/.ibgamer/settings.json 中,所以 ibgamer train 也会使用它;IBGAMER_TRAINER_PROVIDER / IBGAMER_TRAINER_MODEL 优先于它。
决策引擎:Jev、Laya 和规则即代码
| Jev(TypeSafe,托管) | Laya(开放,本地) | 规则(代码) | |
|---|---|---|---|
| 一个决策 | 几百毫秒,其中大部分是往返时间 | 本地 GPU 上几十毫秒 | 即时 |
| 规则 | 在每次决策的指令中读取它们 | 学习它们:针对每个游戏和配置文件版本进行微调 | 是配置文件的 teach(state),由训练器编写 |
| 设置 | TYPESAFE_API_KEY |
ibgamer laya setup;游戏的模型被下载,或通过训练学习 |
无:为 Laya 训练的版本携带它们 |
规则即代码的表现与编写时一样好,从训练写入它们的那一刻起:它们就是 Laya 学习的内容,也是检查引擎所依据的标准。UI 不将它们作为引擎提供(ibgamer play <game> --engine rules 仍然玩它们)。它们只知道它们被编写的目的;一个将规则作为文本读取的引擎可以推理代码未预见的情况。
Laya 是一个小模型:它无法读取长规则,未经训练时玩游戏几乎靠运气。因此,游戏的 Laya 是蒸馏的——规则通过标记状态学习到其权重中:
- 教师。训练器将配置文件的规则写两次:作为指令(给 Jev 和人)和作为
teach(state),即相同的规则作为代码。教师在教导之前会接受检查:它必须与引擎记录的决策一致,并且在自行游戏时得分与配置文件相符。 - 标记状态。教师在几分钟内玩许多游戏,偶尔随机移动,以便数据偏离其自身路径。状态仍然只是特征;教师的答案是训练目标。
- 微调。
laya/finetune.py在这些行上微调 Laya 的副本。 - DAgger。Laya 玩游戏,教师标记 Laya 访问过的状态,训练从最后一个检查点继续:它学习自己的错误导致的状态,而教师的游戏从未到达这些状态。
- 服务。
laya/serve.py通过相同的/v1/systemone协议为每个游戏的检查点提供服务。当为游戏选择 Laya 时,UI 启动它。
ibgamer laya setup # a Python environment with Laya (~/.ibgamer/laya-venv) ibgamer laya distill dino # teacher → data → fine-tune → DAgger → Laya plays the seeds ibgamer laya distill dino --resume --rounds 2 # more DAgger rounds from the checkpoint there ibgamer play dino --engine laya --watch # or in the UI: Engine "Laya (local)"
检查点属于其学习状态的配置文件版本。新的提取器意味着重新蒸馏。新的教师意味着重新标记,这是即时的,并再次微调。每个配置文件版本保留一个检查点:只有当新检查点在配置文件的种子上表现更好时,才会替换旧的。一个游戏可以保留多个版本的检查点——一个用于暂停时钟,另一个来自为实时训练的版本(laya distill <game> --profile-version <n>;配置固定版本)——Laya 玩版本自己的检查点,当未指定时玩活动版本的检查点。
配置
| 变量 | 默认值 | 含义 |
|---|---|---|
TYPESAFE_API_KEY / JEV_API_KEY |
– | Jev 的密钥 |
IBGAMER_ENGINE |
jev |
jev 或 laya |
IBGAMER_LAYA_PYTHON / IBGAMER_LAYA_PORT |
~/.ibgamer/laya-venv(否则 python3)/ 8601 |
带有 Laya 的 Python,以及应用启动的服务器的端口 |
LAYA_URL / LAYA_MODEL |
http://127.0.0.1:8000 / – |
你自己运行的 Laya 服务器,以及要请求的检查点名称 |
IBGAMER_UI_PORT / IBGAMER_UI_HOST |
1986 / 127.0.0.1 |
Web UI |
IBGAMER_HEADLESS |
true |
false 显示浏览器窗口 |
IBGAMER_HOME |
~/.ibgamer |
用户库(library/)和运行(runs/) |
IBGAMER_DAEMON_URL |
– | 要使用的 IronBee DevTools 守护进程,使用此包的 TOOL_PLUGINS 启动 |
IRONBEE_DEVTOOLS_DAEMON_SCRIPT |
已安装的包 | 要启动的 DevTools 守护进程 |
IBGAMER_TRAINER_PROVIDER / IBGAMER_TRAINER_MODEL / IBGAMER_TRAINER_EFFORT |
claude-code / 其默认值(opus)/ CLI 自己的 |
训练器的 CLI(claude-code 或 codex),其模型和其努力(low、medium、high、…);设置后,它们优先于在 UI 中选择的(训练器药丸,保存在 ~/.ibgamer/settings.json 中) |
CLAUDE_CODE_CLI / CODEX_CLI |
claude / codex |
训练器 CLI 的可执行文件 |
IBGAMER_TRAINER_TIMEOUT_MINUTES |
30 |
训练器的一次调用可能花费多长时间:一次截断会整体丢失(训练器被告知其限制) |
IBGAMER_HF_REPO / IBGAMER_HF_REVISION |
ironbee-ai/ironbee-gamer-library / main |
Hugging Face 库,以及拉取的分支或提交 |
HF_TOKEN / HF_ENDPOINT / IBGAMER_HF_CLI |
令牌 hf auth login 保留 / https://huggingface.co / hf |
读取私有仓库;Hub;推送上传所用的 CLI |
底层原理
浏览器属于一个 IronBee DevTools
守护进程,与 IronBee Express 使用的是同一个。游戏工具作为工具插件在其中运行
(dist/devtools-plugin/game-tools.mjs,通过 TOOL_PLUGINS 加载):
game_open安装感知适配器、种子和时钟,然后加载并启动 页面。game_step应用输入、运行游戏时间,并读取原始输入和分数。game_probe报告页面使用哪种渲染技术。game_sprite-crops返回录制精灵的外观。
DevTools 的屏幕录制为实时视图和视频提供画面。
许可证
来源:Hacker News · github.com

