Agent Skills › felixrieseberg/windows95

felixrieseberg/windows95

GitHub

用于在 Electron 中自动引导和测试 Windows 95 VM,支持 v86 更新、SMB 变更及键盘输入等场景。通过环境变量配置探针,自动采样 CPU/VGA/屏幕状态并输出 SUCCESS/FAIL 判定结果及日志截图。

2 个 Skill 24,188

安装全部 Skills

npx skills add felixrieseberg/windows95 --all -g -y
更多选项

预览集合内 Skills

npx skills add felixrieseberg/windows95 --list

集合内 Skills (2)

用于在 Electron 中自动引导和测试 Windows 95 VM,支持 v86 更新、SMB 变更及键盘输入等场景。通过环境变量配置探针,自动采样 CPU/VGA/屏幕状态并输出 SUCCESS/FAIL 判定结果及日志截图。
测试 v86 更新 测试 SMB 变更 测试键盘输入 检查启动稳定性 二分法排查回归问题
.claude/skills/probe-win95/SKILL.md
npx skills add felixrieseberg/windows95 --skill probe-win95 -g -y
SKILL.md
Frontmatter
{
    "name": "probe-win95",
    "description": "Boot Windows 95 in Electron under Claude's control, without a human clicking anything. Use when testing v86 updates, SMB changes, keyboard input, boot stability, or bisecting regressions."
}

Probing Windows 95 autonomously

You can run and test the Win95 VM yourself. The harness is already wired up — three pieces:

File Role
src/renderer/debug-harness.ts Activated by WIN95_PROBE=1. Boots fresh automatically, samples CPU + VGA + text screen every 5s, writes /tmp/win95-probe.json + /tmp/win95-screen.png, detects SUCCESS vs FAIL modes, optionally drives keyboard input.
src/renderer/smb/index.ts Wraps console.log so [smb] and [nbns] lines tee to $TMPDIR/windows95-smb.log (outside Electron, readable by any polling script — no CDP needed).
tools/probe-boot.sh One-shot: kill leftovers → parcel build → launch Electron → poll /tmp/win95-probe.done → report → kill.

Running from a git worktree

images/ is gitignored, so a fresh worktree has no disk image or default state and every probe will fail at boot. Clone them from the main checkout first (APFS clonefile — instant, no extra disk space):

mkdir -p images
cp -c "$(git rev-parse --git-common-dir)/.."/images/*.{img,bin} images/

One-shot boot test

tools/probe-boot.sh

Prints SUCCESS or a FAIL verdict. ~40s on a clean run.

Boot + type into Run

pkill -9 -f "windows95.*electron"; sleep 2
rm -f "$HOME/Library/Application Support/windows95/"state-v*.bin
rm -f /tmp/win95-probe.json /tmp/win95-probe.done \
      "$TMPDIR/windows95-smb.log"

WIN95_PROBE=1 \
WIN95_PROBE_SCRIPT='HOST/HOST' \
WIN95_SMB_SHARE="$HOME/Downloads" \
  ./node_modules/.bin/electron . > /tmp/win95-electron.log 2>&1 &

WIN95_PROBE_SCRIPT='HOST/HOST' types \\HOST\HOST into Start → Run on desktop. WIN95_PROBE_DOSBOX=1 instead opens command, types dir, and (with WIN95_PROBE_DOSBOX_ALTENTER=1) toggles fullscreen — this is the regression scenario for the windowed-DOS-box VBE leak. WIN95_PROBE_CDROM=/path/to.iso mounts an ISO on the secondary-IDE ATAPI drive (bypasses the settings UI). WIN95_PROBE_CDTRACE=1 logs every secondary-channel ATA/ATAPI command to /tmp/win95-cdtrace.log. WIN95_PROBE_VGATRACE=1 wraps the VGA I/O ports at the io.ports[] layer and writes [port, op, value, "eip VMPE cplN"] tuples to /tmp/win95-vgatrace.json every tick (heavy — can hit 1M entries during boot). /\ substitution (env var / shell quoting, pragmatism). The harness drives it via XT scancodes — Win95 doesn't have Win+R (Win98+ only), so the sequence is Esc, Esc, Ctrl+Esc, R, backslashes + text, Enter.

Reading results

File What
/tmp/win95-probe.json Live status: phase (init/text-mode/splash/desktop), gfxW/H, textScreen, instructionDelta, verdict
/tmp/win95-probe.done Written once when verdict is decided
/tmp/win95-screen.png Canvas screenshot, refreshed each tick
$TMPDIR/windows95-smb.log SMB/NBNS protocol trace
/tmp/win95-electron.log Electron stderr

Verdicts

Verdict Meaning Action
SUCCESS Canvas ≥640×480, CPU active, uptime >30s desktop reached
FAIL_VXDLINK "Invalid VxD dynamic link call" flaky — retry
FAIL_IOS / FAIL_PROTECTION IOS subsystem protection error usually driver/BIOS mismatch
FAIL_KRNL386 "Cannot find KRNL386.EXE" in safe mode disk reads returning garbage — wasm/BIOS drift
FAIL_SPLASH_HANG Canvas stuck 320×400 for >70s IRQ starvation — if you're on v86 master, check the IDE register fix
FAIL_HUNG CPU stopped advancing or text screen frozen 40s hard hang

Rules of the road

  • Sporadic bluescreens are normal on all v86 versions. One FAIL_VXDLINK or FAIL_HUNG doesn't prove anything — retry up to 3×.
  • Always clean state (state-v*.bin — the suffix tracks STATE_VERSION in src/constants.ts, so never hardcode a version) before a probe. pkill on a wedged Electron triggers onbeforeunload, saving the corrupted state. Deleting it forces fallback to images/default-state.bin.
  • Don't trust the text buffer in graphics mode. After desktop (≥640×480) the stale BIOS text lingers in the buffer. The harness's phase field accounts for this; don't re-read textScreen in a desktop phase and think you hit a BSOD.
  • Kill Electron when done. Background processes pile up, each holding the disk image lock. pkill -f "windows95.*electron" on every path out.

Bisecting v86

tools/bisect-v86.sh <commit> handles one step. The harness retries 3× per commit. Hard-won lessons:

  1. Validate bounds against a known-good binary. Source-built wasm can drift from prod due to cargo/rustc version differences. We hit this: the "GOOD" bound produced a wasm that couldn't read the disk at all.
  2. JS-only when toolchain drifts. Keep the prod wasm, rebuild only libv86.js at each commit. Closure is deterministic enough; cargo isn't always. Works until you cross a commit that changes the JS↔wasm ABI (for v86, the APIC→Rust port in Aug 2025).
  3. Retry on FAIL, never on SUCCESS. One SUCCESS = commit is good. Three different FAILs at the same commit = commit is bad.
  4. State cleanup between runs (see above). Skipping this is the #1 cause of spurious "bad" verdicts during bisect.

Extending the harness

  • New verdicts: add to the chain in collectStatus in debug-harness.ts
  • New keyboard actions: extend runScript (current types: keys, chord, text, wait)
  • New probe signals: add to ProbeStatus interface

Gate everything new on process.env.WIN95_PROBE === "1" so it stays out of the normal app.

Common failure diagnostics

Symptom Check
No SMB traffic at all $TMPDIR/windows95-smb.log should have hooked adapter line. If absent, v86 API changed — see src/renderer/smb/README.md
SMB hooks fire, no connection Win95's "NetBIOS over TCP/IP" checkbox — bake into default-state.bin
Boot hangs on 2996c087 or older v86 You probably have a ABI-mismatched wasm/JS pair. Prod wasm is the ground truth; rebuild JS against it.

VXDLINK: flake vs. real bug

Two different things produce FAIL_VXDLINK:

  1. Sporadic flake (~1 in 2–3 runs even on known-good images): passes on retry. This is why the retry-3× rule exists.
  2. Deterministic failure (same address every run, e.g. VMM(01)+000036E5 → device "C000" service E3E4): the image's disk layout triggers a real v86 disk-path bug. Known triggers: zeroing free space via the "mcopy a giant zero file, then delete it" trick, and offline (mtools) mass-deletion of recently-written file trees. The same image boots fine in QEMU and passes fsck. Retrying never helps; the image content must change.

This is the canonical verdict-interpretation policy (other docs link here):

  • One SUCCESS = the image can boot. Good — same rule as bisecting.
  • Three identical failures (same VxD address) = the image is in a bad state. Stop retrying; the content must change.
  • Anything in between = keep retrying, you are looking at flakes.

Never conclude anything from a single FAIL.

Probing the state-restore path

WIN95_PROBE_RESTORE=1 tools/probe-boot.sh makes the probe restore state (user state → images/default-state.bin fallback) instead of cold booting. Use it to verify a freshly generated default-state.bin actually resumes to the desktop — required after every image change.

用于在windows95项目中构建和安装v86(wasm+libv86.js+BIOS)。适用于拉取上游更新、修复构建错误、验证分支同步或初始化新签出的场景,通过脚本自动执行构建、复制及完整性检查。
拉取v86上游变更 修复v86构建失败 验证fork分支与上游同步状态 设置新的v86代码签出环境
.claude/skills/update-v86/SKILL.md
npx skills add felixrieseberg/windows95 --skill update-v86 -g -y
SKILL.md
Frontmatter
{
    "name": "update-v86",
    "description": "Build and install v86 (wasm + libv86.js + BIOS) into windows95. Use when pulling upstream v86 changes, fixing a broken build, verifying the fork branches are still in sync, or setting up a fresh v86 checkout."
}

Updating v86

windows95 builds v86 from source — not from copy.sh. Two small bugfix patches ride along on a fork branch until the upstream PRs land.

Sources

File Built from
src/renderer/lib/libv86.js make build/libv86.js in ../v86
src/renderer/lib/build/v86.wasm make build/v86.wasm
bios/seabios.bin, bios/vgabios.bin copied from ../v86/bios/

tools/update-v86.js runs those targets, copies the artifacts, runs 5 sanity checks, and fails loudly if any prerequisite is missing. No fallbacks, no fetching from copy.sh.

The fork branch

v86 should be checked out on felixrieseberg/v86:windows95-base. That branch merges the feature branches tracked in docs/v86-patches.md — keep that table in sync with this list. Each is upstreamable on its own:

  • electron-renderer-fs-loader (PR #1540) — src/lib.js uses require("fs") instead of await import("node:fs/promises"). Dynamic import of node: URLs doesn't work in an Electron renderer.
  • ide-shared-registers (PR #1541) — src/ide.js writes ATA Command Block registers (Features, Sector Count, LBA Low/Mid/High) to both master and slave. Without this, Win95/98 hang at the splash screen on any disk >~535MiB. Root cause: v86 commit 1b90d2e7 changed those writes to target only current_interface, but per ATA spec they're channel-shared (one register file on the IDE cable; both drives latch the same value).
  • vmware-abspointersrc/vmware.js implements the VMware backdoor (port 0x5658): GETVERSION + ABSPOINTER_* so a guest driver (VBADOS VBMOUSE) can read absolute cursor position and track the host cursor 1:1 without pointer lock, and the legacy text-clipboard commands 6–9 so W95TOOLS.EXE (guest-tools/agent) can sync CF_TEXT with the host. Consumes mouse-absolute and vmware-clipboard-host bus events; emits vmware-absolute-mouse and vmware-clipboard-guest.
  • vmware-gettime (stacked on the clipboard branch)src/vmware.js adds backdoor command GETTIME (23): EAX = host UTC seconds, EBX = microseconds, ECX = max time lag, EDX = host UTC offset in minutes. W95TOOLS.EXE polls it and calls SetLocalTime, so a resumed guest (which never re-reads the RTC) snaps back to host time.
  • fake-network-copy-tcp-addrssrc/browser/fake_network.js copies the four address subarrays (hsrc/hdest/psrc/pdest) when a TCPConnection is created from an inbound SYN. Upstream stores them as zero-copy views into the NE2000 TX ring; once the guest's 12-slot TX ring wraps (any concurrent traffic — SMB, NBNS, ping), pump() builds segments with whatever IP now occupies that slot, the guest RSTs them as belonging to no TCB, and recv() blocks forever. Exercised by tools/probe-tcp.sh.
  • vga-defer-vbe-disable-v86src/vga.js defers dispi[4]=0 written from V86 mode until a legacy attribute-mode write reaches the hardware. Win9x's VDD virtualises ports 3B0–3DF for a windowed DOS VM but not 1CE/1CF, so vgabios's VBE-disable leaks through while the rest of its mode-set is captured into the VM's virtual register file — without this the screen turns to planar garbage the moment you open a DOS box.

Prerequisites

rustup target add wasm32-unknown-unknown
brew install openjdk
# one-time: fetch the Closure compiler v86's Makefile pins to
curl -sL https://repo1.maven.org/maven2/com/google/javascript/closure-compiler/v20210601/closure-compiler-v20210601.jar \
  -o ../v86/closure-compiler/compiler.jar

Closure must be v20210601 — newer versions hit closure-compiler#3972 on v86's source. The pin is in v86's Makefile.

Steps

cd ../v86
git fetch fork origin
git checkout windows95-base
git rebase fork/windows95-base   # in case fork was updated elsewhere
cd ../windows95
node tools/update-v86.js

That's it. Script runs both make targets, copies, verifies.

Sanity-check WARNs

The 5 checks assert invariants src/renderer/smb/index.ts and tools/parcel-build.js depend on. A WARN means upstream changed something load-bearing — don't ignore it:

  1. await import("node:...") still present → PR #1540 was reverted or the pattern moved. Electron renderer will fail to load disk images.
  2. master.features_reg= missing in minified → PR #1541 was reverted or windows95-base lost the commit. Win95 will hang at splash on disks >535MiB. Check cd ../v86 && git log --oneline windows95-base.
  3. Export pattern changedtools/parcel-build.js shim needs updating. Look for module.exports.V86= and window.V86=.
  4. tcp-connection event gone → SMB falls back to the old-API theft hack in src/renderer/smb/index.ts — still works, but surprising.
  5. on_tcp_connection gone → old-API fallback is dead. SMB integration only works via the tcp-connection bus event now. Harmless; update the comment in index.ts and retire the theft code.

After updating, probe-test

node tools/update-v86.js && tools/probe-boot.sh

Should land SUCCESS in ~40s. If FAIL_SPLASH_HANG, the IDE fix didn't take — check grep master.features_reg src/renderer/lib/libv86.js. If FAIL_VXDLINK, retry — sporadic bluescreens are normal (see the probe-win95 skill).

When a PR merges upstream

Rebase windows95-base to drop the now-redundant commit:

cd ../v86
git fetch origin
git checkout windows95-base
git rebase origin/master              # drops the merged commit cleanly
git push fork windows95-base --force-with-lease

If both PRs are upstream, retire the fork branch entirely:

  1. Point tools/update-v86.js default at origin/master (it already uses ../v86, so just git checkout master there)
  2. Delete fork/windows95-base
  3. Remove this skill's "The fork branch" section
  4. Confirm the 5 sanity checks still pass — they're version-agnostic

Integration contract with SMB

The SMB server sits on top of v86's network adapter. Details in src/renderer/smb/README.md. Short version: the new path uses the tcp-connection bus event; the fallback path uses adapter.on_tcp_connection callback + connection-theft (stealing a TCPConnection the HTTP probe builds for us). Both use .on_data on the conn, not .on("data"), because Closure dead-code-eliminates the event emitter plumbing.

If any v86 update breaks these assumptions, src/renderer/smb/index.ts needs updating, not just tools/update-v86.js.

首页 - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-07-21 20:05
浙ICP备14020137号-1 $访客地图$