icoa-cli 2.19.577 → 2.19.579

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{spawnSync as e}from"node:child_process";import{copyFileSync as o,existsSync as n,mkdirSync as t,readFileSync as r,writeFileSync as s}from"node:fs";import{homedir as c}from"node:os";import{join as i}from"node:path";import chalk from"chalk";import{getConfig as a}from"../lib/config.js";import{runInteractive as l}from"../lib/interactive-spawn.js";import{lazyTarball as u}from"../lib/lazy-assets.js";import{contestStart as d,contestSubmit as g}from"../lib/learn-contest.js";import{listLearnSessions as p}from"../lib/learn-state.js";import{logCommand as m}from"../lib/logger.js";import{mujocoPaths as y,probeMujoco as k}from"../lib/mujoco-setup.js";import{openFile as h}from"../lib/open-file.js";import{makeReplAsker as f}from"../lib/repl-asker.js";import{ARENA_VI_FLAGS as b}from"../lib/vi-flags.js";const $=()=>(a().language||"en").toLowerCase().startsWith("zh"),w=(e,o)=>$()?e:o,v=i(c(),".icoa","duck"),x=i(v,"control.py"),_=i(v,"state.json"),C=i(v,"last.png"),j={edit:"edit",e:"edit",vi:"edit",code:"edit",syntax:"syntax",api:"syntax",doc:"syntax",docs:"syntax",run:"run",r:"run",play:"run",sim:"run",go:"run",replay:"replay",show:"replay",picture:"replay",open:"replay",submit:"submit",s:"submit",judge:"submit",ac:"submit",status:"status",st:"status",progress:"status",scenarios:"scenarios",scenario:"scenarios",list:"scenarios",checkpoints:"scenarios",help:"help","?":"help",h:"help",back:"back",quit:"back",q:"back",exit:"back",menu:"menu"};export function normalizeDuckVerb(e){const o=e.trim().replace(/^\//,"").split(/\s+/).filter(Boolean);return 0===o.length?{verb:"unknown",args:[]}:{verb:j[o[0].toLowerCase()]??"unknown",args:o.slice(1)}}export function pickCheckpoint(e,o){if(!o)return null;const n=o.toLowerCase(),t=e.find(e=>e.scenario===n);if(t)return t;const r=/^c?(\d+)$/.exec(n);if(!r)return null;const s=Number(r[1]);return e.find(e=>e.scenario?.startsWith(`c${s}_`))??e.find(e=>e.card===s)??null}function A(o,n,t,r=18e4){const s=e(o,[i(n,"duck_cli.py"),...t],{encoding:"utf-8",timeout:r,env:{...process.env,PYTHONDONTWRITEBYTECODE:"1",OMP_NUM_THREADS:"1",MUJOCO_GL:"disable"}}),c=(s.stdout||"").trim().split("\n").pop()||"";try{return JSON.parse(c)}catch{return{error:"engine",message:(s.stderr||"").trim().split("\n").slice(-3).join(" ")||`exit ${s.status}`}}}function N(e){t(v,{recursive:!0}),s(_,JSON.stringify(e,null,1))}function S(e,o){return e.find(e=>!o.ac.includes(e.card))??null}function D(){try{return p().find(e=>"mduck-48"===e.curriculumId)?.token??null}catch{return null}}const T=e=>`C${e.slot+3*(e.phase-1)} · ${w("卡","card")} ${e.card} · ${e.title}`;function O(e){h(e)?console.log(chalk.gray(w(" 正在打开图片…"," opening the picture…"))):console.log(chalk.gray(w(` 图在这里,用文件浏览器打开:${e}`,` The picture is here — open it from your file browser: ${e}`)))}function B(e,o,n){const t=S(e,o);console.log(chalk.gray(t?w(" 当前检查点:"," Checkpoint: ")+T(t)+chalk.gray(` — ${t.pass}`):w(" 12/12 全部 AC"," 12/12 AC"))),n||console.log(chalk.gray(w(" 🦆 本机没有仿真 — "," 🦆 No sim on this machine — ")+chalk.bold.green("mujoco setup")+w(" 装上后 run 才出图;submit 照样能交,判分机替你跑。"," to run here; submit still works, the judge runs it for you.")));const r=D();console.log(chalk.gray(r?w(` ☁ submit 走服务端判分 · token ${r.slice(0,2)}••••••${r.slice(-2)}`,` ☁ submit is judged on the server · token ${r.slice(0,2)}••••••${r.slice(-2)}`):w(" (没有 mduck-48 token:run 可用,submit 不可用)"," (no mduck-48 token on this machine: run works, submit does not)"))),console.log(chalk.gray(w(" edit · syntax · run [c4] · replay · submit · scenarios · status · back(回上一层)· menu"," edit · syntax · run [c4] · replay · submit · scenarios · status · back (one level up) · menu")))}function E(e,o){console.log(chalk.bold.white(w(" Duck ABC · mduck-48"," Duck ABC · mduck-48")));const n=S(e,o);for(const t of e){const e=o.ac.includes(t.card),r=o.attempts[String(t.card)]??[],s=!e&&r.length?chalk.gray(w(` (跑了 ${r.length} 次)`,` (${r.length} runs)`)):"",c=t===n?chalk.cyan(w(" ← 当前"," ← now")):"";console.log(` ${e?"✅":"⬜"} ${chalk.white(T(t))}${s}${c}`)}}function M(e){const o=e.line;return"syntax"===e.error?(console.log(chalk.yellow(w(" ✗ 语法不对"," ✗ Syntax error")+(o?w(`,第 ${o} 行:`,`, line ${o}:`):":"))+chalk.yellow(` ${String(e.message??"")}`)),e.text&&console.log(chalk.yellow(` ${String(e.text)}`)),void console.log(chalk.gray(w(" ▸ edit 改完再 run"," ▸ edit, then run again")))):"control"===e.error?(console.log(chalk.yellow(w(" ✗ control() 返回的东西套件不认:"," ✗ control() returned something the kit does not accept:"))),console.log(chalk.yellow(` ${String(e.message??"")}`)),void console.log(chalk.gray(w(" ▸ syntax 看每个字段 ▸ edit"," ▸ syntax lists every field ▸ edit")))):"runtime"===e.error?(console.log(chalk.yellow(w(" ✗ control.py 跑到一半出错"," ✗ control.py crashed")+(o?w(`(第 ${o} 行)`,` (line ${o})`):"")+":")),console.log(chalk.yellow(` ${String(e.message??"")}`)),void console.log(chalk.gray(w(" ▸ edit 改一改再 run"," ▸ edit, then run again")))):void console.log(chalk.yellow(w(" 引擎出错:"," Engine error: ")+String(e.message??e.error??"")))}export async function runDuck(e=[]){m(["duck",...e].join(" "));const a=Number((e.find(e=>e.startsWith("from-card:"))??"").slice(10))||0;e=e.filter(e=>!e.startsWith("from-card:"));const p=await async function(){const e=process.env.ICOA_DUCK_ENGINE_DIR;if(e&&n(i(e,"duck_cli.py")))return e;const o=await u("duck-engine.tar.gz","duck-engine",6e4);return o&&n(i(o,"duck_cli.py"))?o:null}();if(!p)return console.log(chalk.yellow(w(" 机器人引擎还没到本机(需要联网拉一次,约 11 MB)。"," The robot engine is not on this machine yet (one online fetch, ~11 MB)."))),console.log(chalk.gray(w(" 联网后再敲 duck;联不上找组织者。"," Try `duck` again when online; ask the organiser if it keeps failing."))),"back";const h=k().ok,j=y(c()).python,q=(()=>{try{return JSON.parse(r(i(p,"checkpoints.json"),"utf-8"))}catch{return null}})();if(!Array.isArray(q))return console.log(chalk.yellow(w(" 引擎包不完整(没有 checkpoints.json)。"," The engine package is incomplete (no checkpoints.json)."))),"back";const z=q,J=function(){try{const e=JSON.parse(r(_,"utf-8"));return{ac:Array.isArray(e.ac)?e.ac:[],attempts:e.attempts??{}}}catch{return{ac:[],attempts:{}}}}();if(t(v,{recursive:!0}),!n(x)){const e=i(p,"control_template.py");if(h){const e=A(j,p,["template"]);"string"==typeof e.path&&n(e.path)&&o(e.path,x)}n(x)||s(x,n(e)?r(e,"utf-8"):'from duckkit import dist, heading_to, wrap_angle\n\n\ndef control(t, sense):\n return None # None = stand still. Try {"vx": 0.2}.\n'),console.log(chalk.gray(w(` (已把模板 control.py 放到 ${x})`,` (template control.py placed at ${x})`)))}console.log(),console.log(chalk.bold.cyan(w("🦆 鸭子仿真台","🦆 Duck sim bench"))+chalk.gray(w(" — 在这里改 control.py、本机跑仿真看图、提交检查点。 ↩ back 回上一层 · menu 回首页"," — edit control.py, run the sim here and look at the picture, submit checkpoints. ↩ back · menu"))),B(z,J,h);let I=a?z.find(e=>e.card===a)??null:null;const W=f(),L=W.ask;let R=0;try{for(;;){const e=await L(chalk.bold.cyan("icoa duck> "));if(null===e)return"back";if(!e.trim())continue;const{verb:o,args:t}=normalizeDuckVerb(e);switch("unknown"!==o&&(R=0),o){case"back":return"back";case"menu":return"menu";case"help":B(z,J,h);break;case"status":E(z,J);break;case"scenarios":for(const e of z)console.log(` ${J.ac.includes(e.card)?"✅":"⬜"} ${chalk.white(`run ${e.scenario?e.scenario.split("_")[0]:"c40"}`.padEnd(9))} ${chalk.gray(T(e))}`);console.log(chalk.gray(w(" run c4 = 跑 c4_walk;不带参数 = 当前检查点。"," `run c4` runs c4_walk; bare `run` = the current checkpoint.")));break;case"syntax":{const e=(()=>{try{return $()&&h?String(A(j,p,["cheatsheet","--lang","zh"]).text??""):r(i(p,"syntax.txt"),"utf-8")}catch{return""}})();console.log(chalk.gray(e||w(" (速查表不在引擎包里)"," (no syntax sheet in the engine package)")));break}case"edit":console.log(chalk.gray(w(" vi 三键: i 进入编辑 · Esc 退出编辑 · :wq 存盘离开 · :q! 放弃"," vi survival: i = edit · Esc = stop editing · :wq = save and leave · :q! = discard"))),await l("vi",[...b,x]);break;case"replay":if(!n(C)){console.log(chalk.gray(w(" 还没有图。先 run 一次。"," No picture yet. `run` first.")));break}O(C);break;case"run":{const e=pickCheckpoint(z,t[0]);if(t[0]&&!e){console.log(chalk.yellow(w(` 不认识「${t[0]}」。`,` Don't know "${t[0]}".`))+chalk.gray(w(" scenarios 列出所有名字。"," `scenarios` lists the names.")));break}const o=e??I??S(z,J);if(!o){console.log(chalk.green(w(" 12 个检查点全部 AC。"," All 12 checkpoints AC.")));break}if(e&&(I=e),!o.scenario){console.log(chalk.gray(w(" 这一关是打印题,不在仿真里:写好程序直接 submit。"," This checkpoint is a print-a-number task, not a sim run: write the program and `submit`.")));break}if(!h){console.log(chalk.gray(w(" 本机没有仿真 — "," No sim on this machine — ")+chalk.bold.green("mujoco setup")+w("(~45 MB,免 sudo)。submit 照样能交。"," (~45 MB, no sudo). `submit` still works.")));break}process.stdout.write(chalk.gray(w(` ${T(o)} … `,` ${T(o)} … `)));const n=A(j,p,["run","--file",x,"--scenario",o.scenario,"--png",C]);if(console.log(),!0!==n.ok){M(n);break}const r=n.prints??[];for(const e of r.slice(0,12))console.log(chalk.white(` ${e}`));Number(n.prints_total)>12&&console.log(chalk.gray(w(` … 共 ${n.prints_total} 行输出`,` … ${n.prints_total} lines printed`)));const s=Boolean(n.pass);console.log((s?chalk.green(" ✓ "):chalk.yellow(" ✗ "))+chalk.white(String(n.detail??""))+chalk.gray(null!=n.fallen_at?w(` · 摔倒于 ${n.fallen_at} s`,` · fell at ${n.fallen_at} s`):"")+chalk.gray(` (${n.seconds} s ${w("仿真","sim")}, ${n.time_ms} ms)`));const c=J.attempts[String(o.card)]??[];c.push(s),J.attempts[String(o.card)]=c,N(J),console.log(chalk.gray(s?w(" ▸ submit 提交检查点 ▸ replay 再看图 ▸ edit 继续改"," ▸ submit ▸ replay the picture ▸ edit to keep improving"):w(" ▸ replay 看图找原因 ▸ edit 改一改再 run"," ▸ replay to see what happened ▸ edit, then run"))),"string"==typeof n.png&&process.stdout.isTTY&&O(n.png);break}case"submit":{const e=I??S(z,J);if(!e){console.log(chalk.green(w(" 全部 AC。"," All AC.")));break}if(h){const e=A(j,p,["syntax","--file",x]);if(!1===e.ok){M(e);break}}const o=D();if(!o){console.log(chalk.yellow(w(" 这台机器上没有 mduck-48 token,没法交。"," No mduck-48 token on this machine — nothing to submit with."))),console.log(chalk.gray(w(" ▸ learn 粘 token 后再来。"," ▸ paste your token in `learn`, then come back.")));break}const n=await d(o,e.phase,e.slot);if(!n.ok||!n.data){console.log(chalk.yellow(w(" 服务端没开门:"," Server did not open the checkpoint: ")+String(n.message??n.code??"")));break}const t=n.data.problems[0]?.id??`duck-c${e.card}`;console.log(chalk.gray(w(" ⚖ 判分中(服务端 · "," ⚖ judging (server · ")+T(e)+w(")… 约 5–10 秒",")… 5–10 s")));const s=await g(o,e.phase,t,r(x,"utf-8"),e.slot);if(!s.ok||!s.data){console.log(chalk.yellow(w(" 判分没回来:"," No verdict came back: ")+String(s.message??s.code??"")));break}const c=s.data;for(const e of(c.report??[]).slice(0,3))console.log(chalk.gray(` ${e}`));if(c.note&&console.log(chalk.gray(` ${c.note}`)),"AC"===c.verdict||!0===c.already_ac){J.ac.includes(e.card)||J.ac.push(e.card),N(J);const o=J.ac.length;console.log(chalk.green(` ✅ AC · ${o}/12`+(1===o?w(" 🎉 第一个检查点!"," 🎉 first checkpoint!"):"")));const n=z.find(o=>!J.ac.includes(o.card)&&o.card>e.card)??null,t=z.find(o=>!J.ac.includes(o.card)&&o.card<e.card)??null;I=null,console.log(chalk.green(n?w(` ▸ back 回卡片,读第 ${e.card+1} 张 —— 下一个检查点在卡 ${n.card}`,` ▸ back to the cards, read card ${e.card+1} — next checkpoint is card ${n.card}`):t?w(` ▸ 前面还有没做的检查点:卡 ${t.card}`,` ▸ an earlier checkpoint is still open: card ${t.card}`):w(" 🎓 12/12 —— 鸭子听你的了。"," 🎓 12/12 — the duck does what you say."))),n&&t&&console.log(chalk.gray(w(` (卡 ${t.card} 的检查点也还没做)`,` (card ${t.card}'s checkpoint is still open too)`)))}else console.log(chalk.yellow(w(` ❌ ${c.verdict}。`,` ❌ ${c.verdict}.`))+chalk.gray(w(" ▸ run 本机看图 ▸ edit"," ▸ run to see the picture ▸ edit")));break}default:if(R+=1,console.log(chalk.yellow(w(` 不认识「${e.trim()}」。`,` Don't know "${e.trim()}".`))+chalk.gray(w(" 可用:edit · syntax · run · replay · submit · scenarios · status · back"," Try: edit · syntax · run · replay · submit · scenarios · status · back"))),R>=3)return console.log(chalk.gray(w(" 连续三次没对上 —— 回上一层。"," Three misses — going back one level."))),"back"}}}finally{W.close()}}export function registerDuckCommand(e){e.command("duck [args...]").description("Duck ABC sim bench — edit control.py, run the biped in MuJoCo on this machine, submit checkpoints").action(async e=>{await runDuck(e??[])})}
1
+ import{spawnSync as e}from"node:child_process";import{copyFileSync as o,existsSync as t,mkdirSync as n,readFileSync as r,writeFileSync as s}from"node:fs";import{homedir as c}from"node:os";import{join as i}from"node:path";import chalk from"chalk";import{getConfig as a}from"../lib/config.js";import{runInteractive as l}from"../lib/interactive-spawn.js";import{lazyTarball as u}from"../lib/lazy-assets.js";import{contestStart as d,contestSubmit as g}from"../lib/learn-contest.js";import{listLearnSessions as p}from"../lib/learn-state.js";import{logCommand as m}from"../lib/logger.js";import{mujocoPaths as y,probeMujoco as k}from"../lib/mujoco-setup.js";import{openFile as h}from"../lib/open-file.js";import{makeReplAsker as f}from"../lib/repl-asker.js";import{ARENA_VI_FLAGS as b}from"../lib/vi-flags.js";const $=()=>(a().language||"en").toLowerCase().startsWith("zh"),w=(e,o)=>$()?e:o,v=i(c(),".icoa","duck"),x=i(v,"control.py"),_=i(v,"state.json"),C=i(v,"last.png"),j={edit:"edit",e:"edit",vi:"edit",code:"edit",syntax:"syntax",api:"syntax",doc:"syntax",docs:"syntax",run:"run",r:"run",play:"run",sim:"run",go:"run",replay:"replay",show:"replay",picture:"replay",open:"replay",submit:"submit",s:"submit",judge:"submit",ac:"submit",status:"status",st:"status",progress:"status",scenarios:"scenarios",scenario:"scenarios",list:"scenarios",checkpoints:"scenarios",help:"help","?":"help",h:"help",back:"back",quit:"back",q:"back",exit:"back",menu:"menu"};export function normalizeDuckVerb(e){const o=e.trim().replace(/^\//,"").split(/\s+/).filter(Boolean);return 0===o.length?{verb:"unknown",args:[]}:{verb:j[o[0].toLowerCase()]??"unknown",args:o.slice(1)}}export function pickCheckpoint(e,o){if(!o)return null;const t=o.toLowerCase(),n=e.find(e=>e.scenario===t);if(n)return n;const r=/^c?(\d+)$/.exec(t);if(!r)return null;const s=Number(r[1]);return e.find(e=>e.scenario?.startsWith(`c${s}_`))??e.find(e=>e.card===s)??null}function A(o,t,n,r=18e4){const s=e(o,[i(t,"duck_cli.py"),...n],{encoding:"utf-8",timeout:r,env:{...process.env,PYTHONDONTWRITEBYTECODE:"1",OMP_NUM_THREADS:"1",MUJOCO_GL:"disable"}}),c=(s.stdout||"").trim().split("\n").pop()||"";try{return JSON.parse(c)}catch{return{error:"engine",message:(s.stderr||"").trim().split("\n").slice(-3).join(" ")||`exit ${s.status}`}}}function N(e){n(v,{recursive:!0}),s(_,JSON.stringify(e,null,1))}function S(e,o){return e.find(e=>!o.ac.includes(e.card))??null}function D(){try{return p().find(e=>"mduck-48"===e.curriculumId)?.token??null}catch{return null}}const T=e=>`C${e.slot+3*(e.phase-1)} · ${w("卡","card")} ${e.card} · ${(e=>$()&&e.title_zh?e.title_zh:e.title)(e)}`;function O(e){h(e)?console.log(chalk.gray(w(" 正在打开图片…"," opening the picture…"))):console.log(chalk.gray(w(` 图在这里,用文件浏览器打开:${e}`,` The picture is here — open it from your file browser: ${e}`)))}function z(e,o,t){const n=S(e,o);var r;console.log(chalk.gray(n?w(" 当前检查点:"," Checkpoint: ")+T(n)+chalk.gray(` — ${r=n,$()&&r.pass_zh?r.pass_zh:r.pass}`):w(" 12/12 全部 AC"," 12/12 AC"))),t||console.log(chalk.gray(w(" 🦆 本机没有仿真 — "," 🦆 No sim on this machine — ")+chalk.bold.green("mujoco setup")+w(" 装上后 run 才出图;submit 照样能交,判分机替你跑。"," to run here; submit still works, the judge runs it for you.")));const s=D();console.log(chalk.gray(s?w(` ☁ submit 走服务端判分 · token ${s.slice(0,2)}••••••${s.slice(-2)}`,` ☁ submit is judged on the server · token ${s.slice(0,2)}••••••${s.slice(-2)}`):w(" (没有 mduck-48 token:run 可用,submit 不可用)"," (no mduck-48 token on this machine: run works, submit does not)"))),console.log(chalk.gray(w(" edit · syntax · run [c4] · replay · submit · scenarios · status · back(回上一层)· menu"," edit · syntax · run [c4] · replay · submit · scenarios · status · back (one level up) · menu")))}function B(e,o){console.log(chalk.bold.white(w(" Duck ABC · mduck-48"," Duck ABC · mduck-48")));const t=S(e,o);for(const n of e){const e=o.ac.includes(n.card),r=o.attempts[String(n.card)]??[],s=!e&&r.length?chalk.gray(w(` (跑了 ${r.length} 次)`,` (${r.length} runs)`)):"",c=n===t?chalk.cyan(w(" ← 当前"," ← now")):"";console.log(` ${e?"✅":"⬜"} ${chalk.white(T(n))}${s}${c}`)}}function E(e){const o=e.line;return"syntax"===e.error?(console.log(chalk.yellow(w(" ✗ 语法不对"," ✗ Syntax error")+(o?w(`,第 ${o} 行:`,`, line ${o}:`):":"))+chalk.yellow(` ${String(e.message??"")}`)),e.text&&console.log(chalk.yellow(` ${String(e.text)}`)),void console.log(chalk.gray(w(" ▸ edit 改完再 run"," ▸ edit, then run again")))):"control"===e.error?(console.log(chalk.yellow(w(" ✗ control() 返回的东西套件不认:"," ✗ control() returned something the kit does not accept:"))),console.log(chalk.yellow(` ${String(e.message??"")}`)),void console.log(chalk.gray(w(" ▸ syntax 看每个字段 ▸ edit"," ▸ syntax lists every field ▸ edit")))):"runtime"===e.error?(console.log(chalk.yellow(w(" ✗ control.py 跑到一半出错"," ✗ control.py crashed")+(o?w(`(第 ${o} 行)`,` (line ${o})`):"")+":")),console.log(chalk.yellow(` ${String(e.message??"")}`)),void console.log(chalk.gray(w(" ▸ edit 改一改再 run"," ▸ edit, then run again")))):void console.log(chalk.yellow(w(" 引擎出错:"," Engine error: ")+String(e.message??e.error??"")))}export async function runDuck(e=[]){m(["duck",...e].join(" "));const a=Number((e.find(e=>e.startsWith("from-card:"))??"").slice(10))||0;e=e.filter(e=>!e.startsWith("from-card:"));const p=await async function(){const e=process.env.ICOA_DUCK_ENGINE_DIR;if(e&&t(i(e,"duck_cli.py")))return e;const o=await u("duck-engine.tar.gz","duck-engine",6e4);return o&&t(i(o,"duck_cli.py"))?o:null}();if(!p)return console.log(chalk.yellow(w(" 机器人引擎还没到本机(需要联网拉一次,约 11 MB)。"," The robot engine is not on this machine yet (one online fetch, ~11 MB)."))),console.log(chalk.gray(w(" 联网后再敲 duck;联不上找组织者。"," Try `duck` again when online; ask the organiser if it keeps failing."))),"back";const h=k().ok,j=y(c()).python,M=(()=>{try{return JSON.parse(r(i(p,"checkpoints.json"),"utf-8"))}catch{return null}})();if(!Array.isArray(M))return console.log(chalk.yellow(w(" 引擎包不完整(没有 checkpoints.json)。"," The engine package is incomplete (no checkpoints.json)."))),"back";const q=M,J=function(){try{const e=JSON.parse(r(_,"utf-8"));return{ac:Array.isArray(e.ac)?e.ac:[],attempts:e.attempts??{}}}catch{return{ac:[],attempts:{}}}}();if(n(v,{recursive:!0}),!t(x)){const e=i(p,"control_template.py");if(h){const e=A(j,p,["template"]);"string"==typeof e.path&&t(e.path)&&o(e.path,x)}t(x)||s(x,t(e)?r(e,"utf-8"):'from duckkit import dist, heading_to, wrap_angle\n\n\ndef control(t, sense):\n return None # None = stand still. Try {"vx": 0.2}.\n'),console.log(chalk.gray(w(` (已把模板 control.py 放到 ${x})`,` (template control.py placed at ${x})`)))}console.log(),console.log(chalk.bold.cyan(w("🦆 鸭子仿真台","🦆 Duck sim bench"))+chalk.gray(w(" — 在这里改 control.py、本机跑仿真看图、提交检查点。 ↩ back 回上一层 · menu 回首页"," — edit control.py, run the sim here and look at the picture, submit checkpoints. ↩ back · menu"))),z(q,J,h);let I=a?q.find(e=>e.card===a)??null:null;const W=f(),L=W.ask;let R=0;try{for(;;){const e=await L(chalk.bold.cyan("icoa duck> "));if(null===e)return"back";if(!e.trim())continue;const{verb:o,args:n}=normalizeDuckVerb(e);switch("unknown"!==o&&(R=0),o){case"back":return"back";case"menu":return"menu";case"help":z(q,J,h);break;case"status":B(q,J);break;case"scenarios":for(const e of q)console.log(` ${J.ac.includes(e.card)?"✅":"⬜"} ${chalk.white(`run ${e.scenario?e.scenario.split("_")[0]:"c40"}`.padEnd(9))} ${chalk.gray(T(e))}`);console.log(chalk.gray(w(" run c4 = 跑 c4_walk;不带参数 = 当前检查点。"," `run c4` runs c4_walk; bare `run` = the current checkpoint.")));break;case"syntax":{const e=(()=>{try{return $()&&h?String(A(j,p,["cheatsheet","--lang","zh"]).text??""):r(i(p,"syntax.txt"),"utf-8")}catch{return""}})();console.log(chalk.gray(e||w(" (速查表不在引擎包里)"," (no syntax sheet in the engine package)")));break}case"edit":console.log(chalk.gray(w(" vi 三键: i 进入编辑 · Esc 退出编辑 · :wq 存盘离开 · :q! 放弃"," vi survival: i = edit · Esc = stop editing · :wq = save and leave · :q! = discard"))),await l("vi",[...b,x]);break;case"replay":if(!t(C)){console.log(chalk.gray(w(" 还没有图。先 run 一次。"," No picture yet. `run` first.")));break}O(C);break;case"run":{const e=pickCheckpoint(q,n[0]);if(n[0]&&!e){console.log(chalk.yellow(w(` 不认识「${n[0]}」。`,` Don't know "${n[0]}".`))+chalk.gray(w(" scenarios 列出所有名字。"," `scenarios` lists the names.")));break}const o=e??I??S(q,J);if(!o){console.log(chalk.green(w(" 12 个检查点全部 AC。"," All 12 checkpoints AC.")));break}if(e&&(I=e),!o.scenario){console.log(chalk.gray(w(" 这一关是打印题,不在仿真里:写好程序直接 submit。"," This checkpoint is a print-a-number task, not a sim run: write the program and `submit`.")));break}if(!h){console.log(chalk.gray(w(" 本机没有仿真 — "," No sim on this machine — ")+chalk.bold.green("mujoco setup")+w("(~45 MB,免 sudo)。submit 照样能交。"," (~45 MB, no sudo). `submit` still works.")));break}process.stdout.write(chalk.gray(w(` ${T(o)} … `,` ${T(o)} … `)));const t=A(j,p,["run","--file",x,"--scenario",o.scenario,"--png",C]);if(console.log(),!0!==t.ok){E(t);break}const r=t.prints??[];for(const e of r.slice(0,12))console.log(chalk.white(` ${e}`));Number(t.prints_total)>12&&console.log(chalk.gray(w(` … 共 ${t.prints_total} 行输出`,` … ${t.prints_total} lines printed`)));const s=Boolean(t.pass);console.log((s?chalk.green(" ✓ "):chalk.yellow(" ✗ "))+chalk.white(String(t.detail??""))+chalk.gray(null!=t.fallen_at?w(` · 摔倒于 ${t.fallen_at} s`,` · fell at ${t.fallen_at} s`):"")+chalk.gray(` (${t.seconds} s ${w("仿真","sim")}, ${t.time_ms} ms)`));const c=J.attempts[String(o.card)]??[];c.push(s),J.attempts[String(o.card)]=c,N(J),console.log(chalk.gray(s?w(" ▸ submit 提交检查点 ▸ replay 再看图 ▸ edit 继续改"," ▸ submit ▸ replay the picture ▸ edit to keep improving"):w(" ▸ replay 看图找原因 ▸ edit 改一改再 run"," ▸ replay to see what happened ▸ edit, then run"))),"string"==typeof t.png&&process.stdout.isTTY&&O(t.png);break}case"submit":{const e=I??S(q,J);if(!e){console.log(chalk.green(w(" 全部 AC。"," All AC.")));break}if(h){const e=A(j,p,["syntax","--file",x]);if(!1===e.ok){E(e);break}}const o=D();if(!o){console.log(chalk.yellow(w(" 这台机器上没有 mduck-48 token,没法交。"," No mduck-48 token on this machine — nothing to submit with."))),console.log(chalk.gray(w(" ▸ learn 粘 token 后再来。"," ▸ paste your token in `learn`, then come back.")));break}const t=await d(o,e.phase,e.slot);if(!t.ok||!t.data){console.log(chalk.yellow(w(" 服务端没开门:"," Server did not open the checkpoint: ")+String(t.message??t.code??"")));break}const n=t.data.problems[0]?.id??`duck-c${e.card}`;console.log(chalk.gray(w(" ⚖ 判分中(服务端 · "," ⚖ judging (server · ")+T(e)+w(")… 约 5–10 秒",")… 5–10 s")));const s=await g(o,e.phase,n,r(x,"utf-8"),e.slot);if(!s.ok||!s.data){console.log(chalk.yellow(w(" 判分没回来:"," No verdict came back: ")+String(s.message??s.code??"")));break}const c=s.data;for(const e of(c.report??[]).slice(0,3))console.log(chalk.gray(` ${e}`));if(c.note&&console.log(chalk.gray(` ${c.note}`)),"AC"===c.verdict||!0===c.already_ac){J.ac.includes(e.card)||J.ac.push(e.card),N(J);const o=J.ac.length;console.log(chalk.green(` ✅ AC · ${o}/12`+(1===o?w(" 🎉 第一个检查点!"," 🎉 first checkpoint!"):"")));const t=q.find(o=>!J.ac.includes(o.card)&&o.card>e.card)??null,n=q.find(o=>!J.ac.includes(o.card)&&o.card<e.card)??null;I=null,console.log(chalk.green(t?w(` ▸ back 回卡片,读第 ${e.card+1} 张 —— 下一个检查点在卡 ${t.card}`,` ▸ back to the cards, read card ${e.card+1} — next checkpoint is card ${t.card}`):n?w(` ▸ 前面还有没做的检查点:卡 ${n.card}`,` ▸ an earlier checkpoint is still open: card ${n.card}`):w(" 🎓 12/12 —— 鸭子听你的了。"," 🎓 12/12 — the duck does what you say."))),t&&n&&console.log(chalk.gray(w(` (卡 ${n.card} 的检查点也还没做)`,` (card ${n.card}'s checkpoint is still open too)`)))}else console.log(chalk.yellow(w(` ❌ ${c.verdict}。`,` ❌ ${c.verdict}.`))+chalk.gray(w(" ▸ run 本机看图 ▸ edit"," ▸ run to see the picture ▸ edit")));break}default:if(R+=1,console.log(chalk.yellow(w(` 不认识「${e.trim()}」。`,` Don't know "${e.trim()}".`))+chalk.gray(w(" 可用:edit · syntax · run · replay · submit · scenarios · status · back"," Try: edit · syntax · run · replay · submit · scenarios · status · back"))),R>=3)return console.log(chalk.gray(w(" 连续三次没对上 —— 回上一层。"," Three misses — going back one level."))),"back"}}}finally{W.close()}}export function registerDuckCommand(e){e.command("duck [args...]").description("Duck ABC sim bench — edit control.py, run the biped in MuJoCo on this machine, submit checkpoints").action(async e=>{await runDuck(e??[])})}