尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗A browser game after a ZX Spectrum platformer of the 1980s, built by reading the game's room layouts out of the published screen map at maps.speccy.cz.
Open index.html in a browser. No build step, no dependencies, no server.
O / P or ← → run Q / ↑ jump, and ride grav-lifts
A / ↓ kneel Space fire
Enter start Escape skip the intro or the ending
On a tablet or a phone there are no keys, so the screen itself is the
control (js/touch.js). It is there on every machine, a desktop included:
the target button stands beside the game screen wherever the game is
played, because no browser can be asked whether there are fingers on the
glass - a tablet with a trackpad plugged in answers like a desktop, and a
laptop with a touch screen answers like a tablet. In play the screen
is read as places: a finger near a side wall runs Ai that way, near the
ceiling jumps him, near the floor kneels him - and up and down ride the
grav-lifts, as the keys do. Corners answer both at once, so a jump to the
left is one touch on the top left, and the middle answers nothing, so a
finger can rest there.
Beside the screen stand five buttons, for anyone who would rather aim at a control than at the picture, spread to the corners so that no two want the same thumb: the way buttons left and right at the top, one at each end of the screen; the target, which fires, halfway down its own end; and up and down halfway down and at the foot of the other end, clear of it. One thumb runs while the other jumps. A finger, a stylus or a mouse presses any of them. The target takes the left end the way the game comes, and the fourth line of the options page sends it to the right instead; up and down go to whichever end it does not, and left and right keep the ends they are named for. On the splash and the menus there is nothing to aim at but the words: tap the line the game asks for, and it counts as its key. A stylus draws where a finger would, and the screen reads it the same way. Nothing else on the menus answers: the best scores are a joke to read, not a button, so the game starts on the target, on the space bar, or on the line that says so.
The menus can also be worked without aiming at them: a cursor rests on one of the lines the screen listens for, the wheel and the up and down buttons walk it, and the target picks the line it rests on. It is up from the moment the page is, on the page's first line, so a player who has never touched the wheel can see what the target would pick; while it is being walked the title page stops taking turns with the scores, so the line it rests on does not roll away. A page with no lines to pick - the joke scores, the rolling credits - has nothing to rest on, and there it stays away.
?touch=1 in the address has the mouse stand in for a finger, for a look
at the tablet's game from a desktop: press and drag on the game screen
where a finger would land. ?touch=0 takes the screen controls away and
leaves the keyboard and the mouse.
The mouse has buttons of its own and does not aim at the picture at all: the left button runs Ai left, the right button runs him right and the middle one fires. The wheel is up and down: a notch forward jumps, a notch back kneels, and both ride the grav-lifts. Those never change. On the splash and the menus any button picks the line under the pointer.
1 on the title page opens the options, laid out as the original's: the
control keys, a fourth line for which end the target stands at, and a
third the original never had - the story. Chosen,
every message, the panel and the title tell another tale: Ai is now the AI
that flies the mail rocket to Mars, and Dare is a policeman. A skeleton forces
the rocket down on his cosmodrome, Ai calls the police, and Dare drives out
to gather the five boxes of mail and load them, so that Ai can fly on
(js/story.js; the font gains Cyrillic for it). The original's words
are the default, and the choice is remembered by the browser.
Cheat codes, typed on the title page (or during play): DOORS opens every
door, PARTS fits all five parts and starts the eleven-minute countdown,
TIME stops the clock. They last until the page is reloaded.
The alien boss's hollowed-out asteroid is on a collision course with Earth. Ai has two hours to find the five parts of its self-destruct mechanism — one in each colour-coded sector — and carry them to the self-destruct room.
As in the original there are no lives: running out of energy gets Ai captured and dumped in a cell, costing ten minutes off the clock. His guards patrol and shoot; clear a room and it stays safe. Kneeling ducks their fire and gets Ai under low headers. Grav-lift shafts carry him between floors — press left or right to step off one.
Two sources, kept apart.
Which rooms exist and how they join comes from the original itself. The
game was run in a headless emulator and surveyed by playing it: from the
start, in every room reached, Ai walks off either edge and tries up and down
on each of the room's thirty cells. Every move that changed the room, or the
floor he stood on, is recorded as a link (data/emu/graph.json), and every
room's screen is dumped. That survey found what no reading of the map could:
the surface is three screens with holes in the floor of the third, a grav-lift
answers only when Ai stands a cell or two left of its rails and holds down or
up, holding on carries him through the next room and beyond, and letting go
leaves him hanging in the field to step off onto an upper floor. The scripts
are in tools/emu/, with what they need and where the numbers come from.
What each room looks like comes from the published map at
maps.speccy.cz, which is a montage
of the game's screens. Measured from it: a room is 240×144 px (30×18 cells),
rooms tile at x = 31 + 240·col, y = 39 + 144·row, and that origin sits on
the Spectrum's attribute grid (99.7% of cells hold at most two colours).
tools/extract_level.py classifies every cell - floor and ceiling courses,
blocks and steps whose tops are ledges, scenery, lift rails (matched by
their exact 8×8 dot pattern, since the sectors recolour them) - and
tools/match_rooms.py says which map room each surveyed screen is, by
comparing them cell for cell after reducing both to the Spectrum's hues, with
rooms of one corridor placed together.
tools/build_level.py joins the two into level.json: only rooms the original
let Ai reach, linked only as the original let him move. Nothing is generated.
The surveys, resumed from each state the fitted parts open up, reach 121
rooms across all five sectors — the surface, every sector below it and its
lifts, the hologram room and the way back to the ship. level.json is built
from them, and tools/emu/route_follow.js walks the whole recorded
walkthrough through the recreation: 293 moves of 293 with every part fitted.
A play-test harness walks the recreation the same way the survey walked the original, so a room the survey reached but the recreation cannot is caught before it ships.
The rooms are drawn from the original's own screens. Every room was dumped
many times by the emulator surveys, and again with Ai stepped and jumped
about in it; tools/make_rooms.py takes, cell by cell, the value most dumps
agree on, so Ai, the guards and the message boxes fall away, wipes the parts
of the mechanism, writes the cleaned screens back to data/emu and packs the
play areas (the original's rows 0-17, so its floor courses meet the level's)
into assets/rooms.png. The game lays its own things over them:
the lifts' marks, the doors, the parts, the pickups, the mechanism's lights.
--door cuts the door slabs out of shut-and-open pairs of screens,
--objects takes the original's gun tables, lifts the floor guns off the
backdrops and records every gun's place, span and wall colour, --solid
packs the original's own flag map: the cells it draws in front of the
figures (walls, walkways, shafts, doorways), so the room hides Ai's feet on
a walkway and hides him in a doorway or a shaft as the original does, and
the cells that stop him walking (walls, steps, the lifts' stations - checked
in the emulator: he cannot walk into 118's step or past 120's posts), --arrow
finds the lifts' scrolling arrow cells (in any of their eight phases) so the
game can animate them, and --button finds the call buttons beside them,
which blink while a lift is moving as the original's do: the two take turns,
magenta and red, swapping every four frames, both blue as the ride begins.
The game itself keeps no picture of a room. tools/make_tiles.py takes those
cleaned screens apart into the 8x8 cells the original builds its rooms from -
291 of them for all 121 rooms, assets/tiles.png, 3.3 kB - and writes each
room as a layout over that set (js/rooms_tiles.js); js/tiles.js lays the
layouts out at load. Redrawing the world means redrawing the tiles. What each
tile is - floor, ledge, column, pipe, rail, lamp, panel, gun, door, mechanism -
is in docs/tiles.md and docs/tile-chart.png.
Dare, the guards and the alien boss are the project's own figures, drawn as
vectors (js/figures.js), with Dare's head from the project's own renders. The
boss's hologram rides his dish over the bare room 63: the original's own
hologram is taken out of that room's screen.
python3 tools/extract_level.py -o level_map.json # map -> geometry of all 106 rooms
python3 tools/validate_level.py MAP.png level_map.json 1,5 # draw a room's geometry over the map
python3 tools/match_rooms.py MAP.png data/emu --graph data/emu/graph.json --level level_map.json -o data/emu/match.json
python3 tools/build_level.py data/emu/graph.json data/emu/graph2.json data/emu/graph3.json \
data/emu/graph4.json data/emu/graph5.json data/emu/graph6.json --match data/emu/match.json \
--geometry level_map.json --parts 83:4,148:5,185:25,255:22,56:11 --slot 143 --from-screen 117 \
--prisons 50,53,241,192 --gate 185:186:3,159:158:4 --label 4:186:185,217 --boss 63:22:16 --clear 143:13:22:6:14 \
--fake-lifts data/emu/phantom_lifts.json -o level.json # -> level.json + js/level.js
python3 tools/make_sprites.py # the pose renders (kept out of the repository) -> assets/ai.png
python3 tools/make_title.py # the render -> assets/title.png
python3 tools/make_rooms.py DUMPDIR... --prefer MOVEDDIRS --parts-from data/emu/masks/part_148.scr \
--erase data/emu/masks/ai_14.scr:11:16:3:6,data/emu/masks/guard_212.scr:10:15:1:4,data/emu/masks/guard_89.scr:6:10:20:23 \
--door 84:right:data/emu/doors/room_84_shut.scr:data/emu/doors/room_84_open.scr,209:right:...,159:left:...,185:right:... \
--objects data/emu/guns.json --solid data/emu/solid.json --button data/emu/room_146.scr:4:16 --arrow data/emu/masks/arrow_83.scr:12:23 \
-o assets/rooms.png # the rooms from the original's screens, cleaned
python3 tools/apply_rzx_fixes.py # what the surveys missed, put back from the walkthrough
python3 tools/make_tiles.py --check # -> assets/tiles.png + js/rooms_tiles.js, checked against the screens
python3 tools/inventory_tiles.py # what the rooms are made of -> docs/tile-chart.png
NODE_PATH=... node tools/check_tiles.js out.png # the rooms the game builds from the tiles, saved to compare
The surveys that walked the original recorded a lift wherever Ai's y
changed after Q or A - and a fall through a gap looks the same to them. Every
recorded call was retried in the emulator (tools/emu/emu_liftcheck.js): Ai
is walked, or put straight down (--place), on the call's cell and floor, the
key is pressed, and a ride moves him a steady 3-5 px a frame the way the key
says. The calls that did anything else - a jump, a fall through a gap, a call
made in the air with no floor under it - are data/emu/phantom_lifts.json,
and the level is built without them (--fake-lifts; a call made mid-ride over
a real lift is moved down to the floor the ride starts from).
That check tried each call from one cell, the first of its span, and a lift's span often begins with the cell off the end of its floor, where the key only drops Ai off the ledge. So a phantom takes out that cell and not the lift: a ride down that is left with two cells or more on solid floor beside a shaft is kept (pressing down on a floor only kneels him). This brought back the rides down from the upper floors of 55, 59, 88, 111, 189, 210 and 221, and from 89 down into 121. The check also took the ride up from 113's upper floor into 81 for a jump, though its trace climbs four pixels every three frames - the lift's own pace - so that entry is gone. A ride from a room's floor into the room its hole drops into is a fall, unless the call is beside a shaft that runs down through that floor: 212's lift down into 244 is one.
The title is framed to the screen's foot, as the original's is, with a line running along the bottom that tells of the mission - or, with the story switched to the policeman's, of Ai's mail rocket and Dare's call-out. It goes at two thirds of the original's four pixels a frame, to be read.
Backspace puts the game back as it was five seconds ago: the whole of play (Ai, the guards, the guns and their shots, the cups and parts, the clock, the score, the messages due) is kept as a snapshot every half second for the last ten, and the newest one at least five seconds old is restored. Pressing again goes back further, to the oldest kept. It is there to try a spot again without the walk back.
The original keeps a table of each room's guns, and the game reads it from
the emulator (data/emu/guns.json, by tools/emu/emu_guns.js and the
snapshots) into the room sheet's index. Three kinds, each doing what the
original was filmed doing with its gun routine put back:
A shot moves a cell every three frames and ends at the screen's edge, in a floor or wall, or in Ai. Each frame the original rolls one chance in four of a shot and fires a gun picked at random. A gun destroyed flashes the screen inverted for a frame and plays the beeper burst the original plays; the bursts are synthesised from its sound records.
assets/title.png is laid out as the original's loading screen - the plaque
top left, Ai under it, the alien boss beside him over a wall of blue panels - but
drawn from the hi-res render in the repository root (tools/make_title.py
cuts the two heads off the render's checkerboard). The game letters the
plaque itself in a bold slab serif (assets/plaque.ttf, DejaVu Serif Bold,
licence beside it), so the story renames it, and shows the picture until
space, enter or escape is pressed.
The original game and its characters are someone else's property; this is a personal recreation of a thirty-year-old game's design, not an attempt to reissue it.
tools/extract_level.py takes it as an argument.