跳到正文

StarKnightt

operation-ironhold

A complete first-person shooter in a single 290 KB HTML file. No build step, no assets, no dependencies beyond three.js from a CDN.

README 已保存到本站,可直接阅读

Documentation snapshot

README 快照

本页保存的是公开项目资料快照,阅读过程不需要连接 GitHub。

Operation Ironhold

A complete first-person shooter that runs in a browser tab. One HTML file, no build step, no package manager, and not a single image, audio or model file on disk.

Play it here

图片:Gameplay

Ten hostiles hold a container yard in Sector 7. You have three minutes to clear it.

The entire game — renderer setup, world generation, weapon handling, enemy AI, audio synthesis, post-processing and HUD — is about 6,500 lines of JavaScript and CSS inlined into a single 290 KB index.html. The only external resource is three.js r128, pulled from a CDN. Everything else is generated at runtime.

It was built by an AI agent from five prompts, recorded verbatim in PROMPTS.md.

Running it

Open index.html in any desktop browser. That is the whole installation process.

If you would rather serve it:

python -m http.server 8123

Then visit http://127.0.0.1:8123/index.html.

Click the page to start. The click is what grants pointer lock, so the game cannot capture your mouse until you ask it to. Press Esc to release the pointer and pause, then click the overlay to resume.

Requires a desktop browser with WebGL and Pointer Lock. There is no touch input, so it will not play on a phone.

Controls

InputAction
W A S DMove
MouseLook
Left clickFire
Right clickToggle aim down sights
RReload
ShiftSprint, or hold breath while scoped
CtrlCrouch
SpaceJump; tap again in the air to double jump; hold near a ledge to mantle
1 2 3 4M4 carbine / KS-12 pump / P-9 sidearm / SR-7 Longbow
VToggle the rifle between full auto and semi
Mouse wheelCycle weapons
EscPause

The round

Eliminate all ten hostiles inside 3:00. You start with 100 health and 50 armor, where armor absorbs half of incoming damage until it is gone.

Hostiles hold fire for the first three seconds so you can get your bearings, and a combat director caps the number shooting at you at any one moment to two. The rest keep manoeuvring. That single constraint is what keeps a firefight readable instead of collapsing into crossfire you cannot answer.

Dying or running out the clock ends the round. The report screen shows eliminations, headshots, accuracy and time survived.

Weapons

SlotWeaponMagazineReserveBehaviour
1M4 Carbine30210760 rpm full auto, V switches to semi
2KS-12 Pump848Nine pellets per shell, pump between shots
3P-9 Sidearm1590430 rpm semi automatic, fastest draw
4SR-7 Longbow525Bolt action, a body hit kills

Each carries a viewmodel built from primitives with gloved hands, sways against mouse movement, bobs in time with footsteps, dips off screen to reload and ejects brass that bounces off the concrete. Muzzle flash is a sprite backed by a real point light that the bloom pass picks up. The rifle climbs on a fixed recoil pattern rather than random scatter, so the spray is something you can learn to hold down.

Aiming

图片:Sniper scope

Right click toggles ADS. Every weapon takes exactly 0.2s to settle in or out, tightens its spread, and costs 40% of your movement speed while held.

WeaponFOVZoomSight
M4 Carbine75 to 382.2xRed dot on tinted glass
KS-12 Pump75 to 601.3xBead raised clear of the heat shield
P-9 Sidearm75 to 501.7xThree-dot irons with an open notch
SR-7 Longbow75 to 155.8xScope overlay, mil-dot reticle

The sniper is the one that changes the screen. Scoping swaps the crosshair for a full overlay and hides the weapon behind the glass, which the composite shader veils the way real optics do. The view drifts on a slow two-sine sway; holding Shift steadies it for three seconds, after which you have to release and take another breath. Mouse sensitivity scales with the zoom so your aim stays consistent on screen. The bolt takes 1.5s to work, which makes a miss genuinely expensive.

Movement

Container tops, crates, barrel stacks, the wrecked flatbed and every other solid prop are walkable. A jump clears roughly 1.2m and the double jump adds another 0.9m. Holding Space while airborne near a ledge up to about 1.95m above your feet pulls you up over it, which is what gets you onto a container roof from flat ground and onto the sniper deck without the ramp.

Enemies

Ten soldiers patrol waypoints, routing around cover with a grid pathfinder over the same colliders the player obeys. Spotting you takes a human 300 to 800ms before they react, and they call it over the radio when they do.

In a fight they break for cover, strafe, flank, reload, and refuse to fire when their muzzle is blocked even if they can see you over it. Line of sight is traced from the player’s end as well as theirs, which guarantees the two agree: if you cannot shoot them, they cannot shoot you. Headshots do double damage, hits produce a flinch, and death drops them in a ragdoll fall with the weapon coming loose.

Everything is generated at runtime

There are no image, audio or model files anywhere in this repository.

Concrete, corrugated steel, plywood, rust, brick and chain link are painted into canvases at load, down to the stencilled shipping marks on the container flanks. Weapons, hands and soldiers are assembled from boxes, cylinders and spheres. The sky is evaluated per fragment and the industrial skyline on the horizon is geometry.

Every sound is synthesised through the Web Audio API: per-weapon gunshots layered from a crack, a body and a chest thump, reloads, footsteps, impact sounds that differ between wall and flesh, the low-health heartbeat, and an ambient industrial hum with distant metal creaks on a random timer. There is a convolution reverb built from a generated impulse response.

图片:Start screen

Performance

Dynamic resolution scaling watches frame times and adjusts render scale to hold the target frame rate. Repeated props are drawn with InstancedMesh grouped by colour. Static scenery is merged into single draw calls. Tracers, decals, shell casings and particles are pooled. Shadow casting is trimmed by bounding-box size, which cut roughly two thirds of the shadow pass for objects that only ever contributed a sub-pixel smudge.

Implementation notes

Six things are worth knowing before modifying index.html.

No environment map. three.js renders a metal with nothing to reflect as near-black, so every material stays close to dielectric (metalness under about 0.2) and fakes metal through colour and roughness instead. Raising metalness on the containers or weapons will make any surface turned away from the sun collapse to black.

Colour space is handled manually. The renderer stays in linear output and the composite shader does its own gamma encode, so material colours are pushed through convertSRGBToLinear() at startup by linearizeMats(). New materials need the same treatment or they will read oversaturated.

Post-processing is hand-rolled. The r128 CDN build has no EffectComposer, so bloom, chromatic aberration, vignette, colour grading, damage tint and the low-health pulse all live in one composite pass over a pair of render targets. Shadow tinting there is multiplicative on purpose; an additive lift in linear space swamps near-black pixels and turns the whole frame blue.

The sky is a shader, and it has to be. It began as a 4096x2048 equirectangular canvas, which sounds like plenty until you do the arithmetic: 4096 texels over 360 degrees is 11 texels per degree, and the sniper’s 15 degree field of view puts about 170 of them across a 1920 pixel screen. Eleven screen pixels per texel. Doubling the sheet buys one stop and costs 64MB, and it does nothing about the other half of the problem, which is that an equirect projection collapses every column onto the zenith and smears whatever is painted up there into a pinwheel. The replacement evaluates a gradient, a cloud deck and a cirrus layer per fragment, so the scope magnifies ray directions instead of a bitmap. Clouds are projected onto a flat slab overhead (dir.xz / dir.y), which bunches them towards the horizon the way perspective actually does and makes the zenith an ordinary point of the plane rather than a singularity. The fbm is band-limited against fwidth of that projection: octaves finer than the fragment footprint are dropped rather than sampled, which is what stops the deck from aliasing into a shimmer near the horizon where the slab stretches a degree of sky across hundreds of noise cells. It measures free against a flat colour, because the dome is drawn last of the opaque queue and early-z kills every sky fragment behind a wall.

Sight alignment is data, not guesswork. Each viewmodel returns an adsPos that puts its sight on the viewmodel camera’s -Z axis: x is zero and y is the negative of the sight’s height in model space. Move a sight and that number has to move with it. Optics you look through need open-ended geometry with a double-sided material, since a capped cylinder renders as a solid disc.

The viewmodel is deliberately telephoto. VM_FOV is 41.9 against the world’s 75, and every weapon sits proportionally further from the eye to hold its screen size. The two numbers are a pair: narrowing the lens without pushing the gun out shrinks it, and pushing it out without narrowing the lens makes it tiny. What the pairing buys is a flatter depth gradient, so the buttstock stops ballooning over the receiver. adsRef is the sight’s distance from the eye and feeds the ADS dolly, so it scales with adsPos.z; adsPos.x and .y do not, because sight alignment is independent of range.

Two smaller conventions: walkable surfaces are opt-out, since box() pushes anything solid into groundMesh and groundAt() only considers surfaces at or below the probe height. And the crosshair is drawn to a canvas rather than built from HTML elements, because at two pixels thick every edge has to land on a whole device pixel or it smears.

Repository layout

index.html              the entire game
PROMPTS.md              the prompts that specify it, verbatim
screenshots/            images used by this README
tools/test-harness.js   collision, weapon and AI test suite
tools/autoplay-bot.js   pathfinding bot for unattended regression runs

The two files in tools/ are development-only. Neither is referenced by index.html; they are pasted into the console or injected over the DevTools protocol. The harness exposes collision audits, weapon accuracy and hit-fidelity tests, map boundary probes and an enemy watchdog. The bot plays full rounds on its own with configurable aim error and reaction delay, which is how the collision and AI fixes were regression tested.

License

MIT. See LICENSE.

Official distribution

获取与安装

暂未发现可确认的官方软件包地址

当前 README 快照没有出现 npm、PyPI、Crates.io、pub.dev 等官方包页链接。本站不会根据仓库名称猜测下载地址。

本站不托管项目文件;需要安装时,请以项目维护者发布的官方文档为准。

使用前核验

本站保存公开资料用于阅读,不代表安全审计或功能背书。安装前请核对许可证、依赖来源和发布签名,不要直接运行来源不明的二进制文件或高权限脚本。