⚔ RenYing
An ink-wash martial-arts duel game that runs in a browser tab
By Even, Luffy, Opus 5.5, and Gemini 4 Argon. Plain JavaScript and canvas, serverless WebRTC multiplayer, and an optional zero-dependency Python server.
🌐 Select Language / 选择语言
English •
简体中文
🧭 Table Of Contents
⚡ Overview And Core Value
RenYing (刃影) is a fast ink-wash martial-arts duel game. Twelve characters fight on one stage, each with their own reach, weight and tricks. It is built to be played right away — at school or at home: two browsers connect directly to each other over WebRTC, with nothing to host and nothing to install.
🚀 Key Features
- Plays from a URL — open the GitHub Pages link and start. No install, no clone, no hosting.
- Serverless P2P multiplayer — share a 4-character room code and the two browsers connect directly over a WebRTC DataChannel, with rollback netcode to keep the fight responsive.
- Armory & Marketplace (武库与集市) — open weapon cases with ink, trade collectibles with other players, use trade-up contracts, or equip skins tiered from Standard to Legendary with automatic highest-tier equipping.
- Bilingual Interface — seamless English and Simplified Chinese support with persistent preferences and in-game toggle.
- Drawn in code, not sprites — brushstroke trails, scarf motion, hit-stop, screen shake and the whole ink-wash look are drawn procedurally on a 2D canvas.
- Single-file offline build —
renying-offline.html opens by double-click, with the music and the Qingfengxing mini-game packed inside it.
🎮 Game Controls And Mechanics
Default Keyboard And Mouse Controls
| Action | Input |
|---|
| Move left / right | A / D |
| Jump / double jump | W or Space |
| Block | Hold S |
| Attack (5-hit combo) | Left mouse button |
| Charged slash | Hold the left mouse button, then release |
| Ranged attack | Hold the left mouse button, then right-click (or R) |
| Escape (dodge upward) | Right-click (or Left Shift) |
| Skill / ultimate | Q / E (the ultimate needs a full meter) |
| Pause / mute | P / M (single-player) |
On a phone or tablet the game switches to its own touch controls — a virtual stick on the left, action buttons on the right — and asks you to turn the device sideways.
📦 How To Play And Multiplayer Modes
Option 1: Direct WebRTC (Best For School — No Budget Needed)
Nothing to install and no server to run:
- Both players open https://amfi-disable.github.io/RenYing/.
- Player 1 goes to 联机对战, clicks 建房, and shares the 4-character room code it shows (for example
K7PQ).
- Player 2 types that code into 加入 and clicks 加入.
- From then on the two browsers talk straight to each other over a WebRTC DataChannel; PeerJS is only used to introduce them.
Some networks — campus Wi-Fi in particular — block peer-to-peer traffic. If joining times out, try another network.
Option 2: Standalone Offline Play
Open renying-offline.html directly — double-click it, with no server and no internet connection. The music and the Qingfengxing mini-game are inside that single file.
🏗 Architecture And Technical Anatomy
flowchart TD
P1["Player 1 Browser (GitHub Pages)"] <-->|"WebRTC DataChannel (Direct UDP)"| P2["Player 2 Browser (GitHub Pages)"]
- Client runtime — vanilla JavaScript drawing to a single HTML5 canvas. No framework, no bundler.
- Rollback netcode — both sides simulate the whole match deterministically and exchange only their inputs. If an input arrives late, that side reloads the stored frame, replays it with the correct input and catches back up; the two sides also compare a state checksum periodically to catch any drift.
- P2P networking — WebRTC DataChannels, with PeerJS for the handshake and public STUN servers for NAT traversal (
src/net-p2p.js).
- Persistence —
localStorage in the browser, plus cloud accounts and saves on a Cloudflare Worker with D1 (cloudflare/).
🧱 Project Structure And Building
Edit the sources; never edit the generated pages.
| Path | Purpose |
|---|
src/page.html | Page markup with build markers |
src/style.css | All styles |
src/i18n.js | Bilingual internationalization engine and vocabulary dictionary |
src/game/ | Game engine, 12-character roster, menus, combat states and rollback netcode, split into 25 numbered files (02-heroes.js, 03-skills.js, 10-hits.js …) that build.py concatenates in order |
src/armory-core.js | Weapon cases, rarity tiers, skins catalog, and inventory calculations |
src/armory.js | Armory UI, unboxing animations, trade-up contracts, and marketplace views |
src/anticheat.js | Anti-cheat state integrity, rate limiting, and damage verification |
src/net-p2p.js | Serverless WebRTC direct connection |
src/cloud.js | Cloud save synchronization, account merge, and signed local backup/restore |
src/social.js | Serverless WebRTC P2P community hub, friends, and live spectating |
src/qfx/ | Modular source files for the Qingfengxing mini-game |
cloudflare/ | Cloudflare Worker backend with D1 database for account sync, economy, and admin console |
assets/music/ | Background music (.mp3) |
assets/qingfengxing.html | Generated Qingfengxing mini-game page |
tools/build.py | Generates the pages below (Python standard library only) |
tools/combat-test.mjs |
After changing anything in src/ or assets/, rebuild and commit the generated pages together with the sources:
python3 tools/build.py
CI runs python3 tools/build.py --check and fails if the generated pages are out of date. It also runs the economy tests, the combat and AI regression suite, and the panel, HUD and touch audits.
Entry Points
| Entry | What it is |
|---|
index.html | Online edition served by GitHub Pages (https://amfi-disable.github.io/RenYing/) |
renying-offline.html | Single-file offline edition: download and open, no network needed |
renying.html | Old address, forwards to index.html |
assets/qingfengxing.html | Qingfengxing mini-game page |
cloudflare/worker.bundle.js | Cloudflare Worker (accounts, cloud saves, armory economy, open world); cloudflare/wrangler.toml points at it |
Worker /admin | Admin console, served by the Worker itself |
Build Commands
| Command | When |
|---|
python3 tools/build.py | After any change in src/, assets/ or cloudflare/ (regenerates every page and the Worker bundle) |
python3 tools/build.py --check | Before committing: fails if a generated file is stale |
node tools/predeploy-check.mjs | Before deploying the Worker: wrangler.toml, admin CSP and key API smoke tests |
npx wrangler deploy --dry-run --env="" | Run inside cloudflare/: bundles the Worker as a deploy would, without uploading |
Deploying the Worker, D1 migrations (run by the Worker itself, never with wrangler d1 migrations apply), backups, restores and rollbacks: see docs/deploy-runbook.md (Chinese).
Test Matrix
Everything runs with plain Node 22 (no npm install). The browser audits need a local server first: python3 -m http.server 8791.
| Area | Command | What it covers | CI workflow |
|---|
| combat | node tools/combat-test.mjs --sim | Every hero, combat states, 2v2 AI | CI, Combat |
| netplay | node tools/netplay-test.mjs | Two game copies over a laggy link end every match identically | Rollback |
| rollback | node tools/rollback-test.mjs --rollback | Snapshot, restore and resimulation determinism | Rollback |
| econ | node tools/econ-test.mjs (add --bundle for the built Worker) | Armory, market, drop rates, anti-abuse; D1 migrations on an empty and an old database must match cloudflare/schema.sql | CI, Armory |
| cloud | node tools/cloud-test.mjs | Cloud save client: per-account isolation, retries | CI |
| sync | node tools/sync-test.mjs (add --bundle) | Real Worker with several devices: merges never double count | CI |
| world | node tools/world-test.mjs | Open world server: caps, view range, world boss | CI |
| ai-lingque | node tools/ai-lingque-test.mjs | Lingque AI combo checks | CI |
| ui | node tools/ui-audit.mjs ci-ui 1440 900 | Every panel fits, nothing clipped or too small | CI, UI |
| touch | node tools/touch-audit.mjs ci-touch | Phone and tablet touch controls | CI, Mobile |
🛡 Privacy And Security First
- No telemetry — no analytics, no trackers, no crash reporting.
- Peer-to-peer stays peer-to-peer — in WebRTC mode your inputs travel straight to the other player's browser and never pass through a server.
- Your data stays on your machine — match history, ladder statistics and replays live in your browser's local storage, or in
data/renying.db on the machine running the server.
⚖ Educational And Fair-Play Disclaimer
[!CAUTION]
Notice:
This codebase is meant for learning, research and non-commercial play.
Please keep multiplayer matches and community chat friendly.
The authors are not responsible for third-party hosting or for servers other people run.
📄 License And Legal Attribution
Created by Even, Luffy, Opus 5.5, and Gemini 4 Argon. Licensed under the MIT License. Copyright (c) 2026 AMFI-Disable, Even, Luffy, Opus 5.5, and Gemini 4 Argon.