Map mods explains how to create, export, install and test your own world as an additional ship destination, keeping the original planets. It also covers standalone maps and updates to an installed world.
The editor turns the levels on your own disc of Ratchet & Clank (PAL,
SCES_509.16 v2.00, or NTSC-U, SCUS_971.99 v1.00) into a Godot 4 project.
Godot is the map editor: open a level, move, add or delete objects, and save.
For RAC1 PAL and NTSC-U, extract.py pack turns supported scene edits back
into mod files without writing to the disc. The launcher's Mods page can
extract, open, pack, and run those edits. Later games are still early access:
their levels can be exported for inspection, but packing edits back into those
games is not supported yet.
It sits at the top of OpenRAC because it serves all four games. It reads Ratchet & Clank's PAL and NTSC-U discs in full, and Going Commando's, Up Your Arsenal's and Deadlocked's NTSC-U discs in early access: their levels, textures and models, to look at and to make texture and model packs from (The later games).
Never commit or share what it produces (see docs/policy/SOURCING.md).
It only writes to new directories under OpenRAC's ignored assets/ or build/.
You need:
- Python 3.10 or newer (standard library only).
- Godot 4, tested with 4.7.2. On macOS its command is
/Applications/Godot.app/Contents/MacOS/Godot; below it isgodot. - An ISO image of your own disc, placed at the top of OpenRAC as
baserom/SCES_509.16.iso(PAL) orbaserom/SCUS_971.99.iso(NTSC-U) (baserom/README.md).baserom/is ignored by git. The commands below name the PAL image; every one of them reads the NTSC-U image the same way.
The RAC1 PAL build only needs the executable from that image
(games/rac1/README.md).
The extractor reads the whole image, and it stops unless the executable
inside is PAL v2.00's or NTSC-U v1.00's (its name in SYSTEM.CNF, its size
and SHA-1, which it reads from the version's entry in
games/rac1/game.json, as it does for the later games'
discs from theirs).
Both discs keep their levels the same way, so every reader serves both;
what differs is one table per version in disc.py (VERSIONS;
the executable's name, size and SHA-1 come from games/rac1/game.json):
| PAL v2.00 | NTSC-U v1.00 | |
|---|---|---|
| Executable | SCES_509.16 |
SCUS_971.99 |
| Table of contents | sector 1500, 0x2960 bytes, levels at 0x28c8 | the same |
| Gameplay copy the native port loads at 60 Hz (each level header has both) | gameplay_ntsc on the PAL disc |
gameplay_ntsc on the NTSC-U disc |
| (cos, sin) table the lighting reads | 0x165600 | 0x165500 (the same values) |
The NTSC-U side follows ReRAC's disc layout (docs/formats/disc_layout.md)
and the NTSC decomp's load_level_gameplay, which loads the NTSC copy
unless the game runs in PAL mode. The two discs' levels are the same
levels with small differences of their builds: on 2026-10-16 an export of
each had the same tie, shrub and moby placements and terrain in every
level, while NTSC-U's moby class 1138 (loaded in every level) has three
animations fewer, most levels have one moby texture fewer, a few collision
cells and surfaces differ, and level 17 loads four moby classes fewer
(Drek's and Qwark's among them).
python3 editor/extract.py godot baserom/SCES_509.16.iso assets/levels/rac1/pal --split
godot --path assets/levels/rac1/pal/level_00 -e--split writes a Godot project per level, level_00 to level_18, each
named after its planet. The second command opens Veldin in the editor. A
project holds one level, so Godot imports it in about five seconds the
first time it is opened, and only that level is loaded. This is what the
launcher's Mods page uses.
Without --split the 19 levels go into one project (assets/godot in
earlier versions of this page): the other levels are then in the FileSystem
dock under levels/, and Godot's first import takes about a minute and a
half.
All 19 levels take about 20 seconds and 800 MB to extract, several levels at a time, below normal priority so the computer stays usable. Options:
--level N(repeatable) exports only some levels.--jobs Nlimits how many levels are exported at once: by default all but an eighth of the CPU's threads (at least two kept free), orOPENRAC_EXTRACT_JOBSwhen it is set (a number, orall). The launcher's Get the levels ready reads the variable too: set it to2or3on a computer that struggles while the levels are read.--terrain-lod 2uses the coarsest terrain.
Nothing the extractor writes is kept in git, so a fresh checkout has none
of it: run the command above to make it. The same disc always gives
byte-identical files. To rebuild a project, delete its directory and run
the command again (in the launcher: Delete the level project, below, or Export the levels again, which keeps your edits); the extractor never overwrites an existing directory.
Godot rebuilds its import cache (.godot/) the next time it opens the
project.
Regenerating replaces the project, and with it any edit that has not been packed (below). To keep an edited level across a regeneration:
- Copy its scene,
levels/level_NN/level_NN.tscn, somewhere safe. - Regenerate.
- Copy the scene back. The meshes and textures it refers to come out identical, so its paths still resolve.
That only works when the extractor hasn't changed since the scene was made. A newer extractor can write scenes with other nodes (the sky's WorldEnvironment, for instance), so after updating it, move your changes into the new scene instead. Godot saves a scene as text, so a diff against a fresh export shows each edit.
The launcher's Mods page, Level editor tab, has a status panel that
says where you are (no levels yet, ready to edit, changes to pack, packed, or
needs an update) and offers the next step as its main button. While a step
runs, a bar says which one and how far along it is, by level ("Reading levels,
Kerwan, 3 of 18"), from the tools' openrac-progress: lines
(progress_line.py). Under it, always open, Your levels
holds what can be done with the levels there. Each button but Pack again asks
first, saying what goes or is replaced and what stays. None runs while the game
or another task does. Close Godot first.
- Pack again packs what you saved in Godot now, even when the launcher saw no change.
- Export the levels again (
extract.py godot … --again) reads every level from your disc into its Godot project again, even an up-to-date one: the projects there move to<disc>-backups/<time>/first, with your edits, and your moby edits are carried onto the fresh ones as an update does (Ratchet & Clank's; carry.py). What cannot be carried is listed, to redo by hand. Your packed levels, edits, packs and saves are kept. - Read the game's levels again deletes the game's copy of the levels (what Play
and Models and textures read, with its
levels-<format>.jsonandverified-<format>.jsonstamps and its visibility data) and the enhanced lighting baked from it, then reads the levels from the disc again straight away. The Godot projects, your packed levels, edits, packs, exports and saves are kept. - Delete the level project deletes the Godot projects of that disc (with Godot's import cache, and any change saved in Godot but not packed) and the models and textures exported for the game. Open in Godot then reads fresh copies from the disc. A tick box, off at first, also deletes your own work: the packed levels and the mods put together from them, your imported edits and added packs (the game's, for all its discs), and the older copies of your levels the extractor kept aside.
Where those are:
| In a checkout | In an installed launcher | |
|---|---|---|
| The game's levels | assets/port/<game>/<disc> |
<data>/port/<disc> |
| Enhanced lighting | <data>/probes/<disc> |
the same |
| Godot projects | assets/levels/<game>/<disc> |
<data>/levels/<disc> |
| Older copies of them | assets/levels/<game>/<disc>-backups |
<data>/levels/<disc>-backups |
| Exported models and textures | <data>/exports |
the same |
| Packed levels | assets/packed/<game>/<disc> |
<data>/packed/<disc> |
| Mods put together | <data>/mods/<disc> |
the same |
| Imported edits and packs | <data>/overrides |
the same |
<data> is the game's set-up folder, <install folder>/active/<game>/data.
The hidden .<disc>-* folders a stopped extraction leaves beside them go too.
Nothing else is deleted: never the disc's files, the saves, the gameplay mods
(<data>/plugins), the built game or anything outside these folders. A folder
that is a link, or that leads outside OpenRAC's folders, stops the reset before
anything is deleted (launcher/core/src/reset.rs).
A placed moby's node is named after its class: Vendor_0012, Bolt_0003,
BlargHeavyInterceptor_0040 (568 classes have a name, from
moby_classes.tsv; the rest show as Class809_0001).
Select one and the Inspector shows its values as rc1_* metadata (spawn id,
group, colour, draw distance...), which the packer writes back.
Its class scene (levels/level_NN/mobys/moby_<class>.tscn) says what the
class is and what runs it, in its tooltip in the Scene dock and as metadata:
| Metadata | |
|---|---|
rc1_class, rc1_class_name |
the class number and name |
rc1_update |
the function the game calls for each moby of the class, every frame |
rc1_source |
where that function is in the decompilation (games/rac1), and the tooltip says whether it is C there yet |
That function is the moby's behaviour: an enemy's attack pattern, a crate
breaking, a vendor's menu. It is C in the decompilation, and the native port
is built from that C. Behaviour is not edited in Godot: the level editor edits
levels and their data, and nothing in a project runs game logic. A class's
behaviour is changed by a mod through the mod SDK, on the port's hook points
(docs/port/MODS.md); the SDK's behaviour catalogue
(BEHAVIOUR.md, sdk/catalogue/rac1/) names each class's update
function, its hook point and its pvar fields. The table comes from the
decompilation's config/overlays/names.tsv (its UpdateMoby_<class> roles),
per level, so it is only written when games/rac1 is in the checkout.
The modder's guide to the whole round trip (extract, edit in Godot, pack, play from the launcher) is LEVEL_MODDING.md.
python3 editor/extract.py pack baserom/SCES_509.16.iso assets/levels/rac1/pal assets/packed/rac1/pal
python3 editor/extract.py pack baserom/SCUS_971.99.iso assets/levels/rac1/ntsc assets/packed/rac1/ntscTurns what was edited in Godot back into the game's level data (the folder of per-level projects, or a single project with every level), from the disc the project was extracted from. For each level whose scene differs from the disc it writes that level's gameplay file as the game stores it, compressed, and the list of disc sectors it replaces:
assets/packed/rac1/pal/
level_NN/gameplay_<version>.wad one per level whose gameplay data changed; padded to its disc slot or virtual range
level_NN/placements.json one per level whose ties or shrubs were edited, for the native port
level_NN/core_<version>.bin one per level whose collision (the level's or a moby class's) was edited: the level data's sectors of its core data
level_NN/index_<version>.bin with it, when the core data grew (a collision moved to its end): the core index's sector
level_NN/data_<version>.bin expanded level-data container when the compressed core outgrows its disc slot
patches.tsv first sector, sector count, file
virtual.tsv expanded streams: virtual first sector, sector count, file, level range and original range
changes.txt what changed, in words
packed.json the disc it was packed from
Each level header has separate 50 Hz and 60 Hz gameplay copies. Both native
ports run at 60 Hz, including PAL, so extraction and packing use gameplay_ntsc
on the selected disc. The sectors in patches.tsv are still that disc's own;
this does not make PAL and NTSC-U packs interchangeable. A packed folder serves
the disc it was packed from only; the
Mods page stacks it for that disc and leaves it out for the other
(overrides.py).
When an edited gameplay stream or level-data core no longer fits its original
sector range, the packer writes a padded file under level_NN/, gives it a
virtual sector range in virtual.tsv, and patches that level's header to point
there. OpenRAC serves those sectors only while this mod stack is active. The
disc image and no-mod read path stay unchanged. A stack with virtual streams
uses a 256 MiB guest RAM arena; without virtual streams, the original 32 MiB
arena remains. Larger streams can still meet RAC1's own object-table, level
format and collision limits.
A PAL project made before this read the 50 Hz copy. The launcher says so
("Your levels need an update") and its Update project runs Extract the
levels again: each old project moves to <disc>-backups/<time>/ and a 60 Hz
one takes its place, then carry.py carries your edits over. It
compares the old scene with the 50 Hz copy it was read from, the way the packer
does, and makes the same changes in the new scene: mobys moved, turned, scaled,
given another class or other values (pvars and links included), added or
deleted. It never copies the whole old scene, whose unedited gameplay values
are the 50 Hz ones. What it cannot carry (cameras, volumes, sounds, paths, moby
groups, level settings, ties and shrubs, collision, imported static models) is
listed per level in carried.json beside the new scene, and on the launcher's
panel, to redo by hand. Packing an old PAL project is refused with the code
project-outdated-gameplay. Renaming an old WAD does not update its sector
mapping. NTSC-U projects do not need this migration.
Every tool says why it stopped as openrac-error: <code> on a line of its own
before its message (errors.py); the launcher shows the sentence it
has for that code (launcher/src/lib/toolErrors.ts) and keeps the message for
"Show details".
The disc image is never written. The native port reads the folder with
--mods assets/packed/rac1/pal and serves those sectors from the files
(port/game/common/lib/cdvd.c); the
launcher's Mods page does all three steps (edit, pack, play).
What is packed today: mobys that were moved, turned, scaled, given another
class, given other values (the rc1_* metadata in the Inspector), added
or deleted, and ties and shrubs the same way (moved, turned, scaled
each axis its own, another class of the level, other values, added, deleted),
the gameplay data: pvars, links between objects, moby groups, volumes,
paths, grind paths, cameras, sound instances and the level settings
(Gameplay data), the collision: tiles and hero walls
deleted, moved, edited or added (Collision), and moby class
collision: a class's spheres, cylinders, capsules and triangles, for every
moby of the class in that level (Object collision). A moby keeps one scale for all three axes.
Not yet: terrain, and anything of a class the level does not load (the
port draws a denser or reshaped terrain from an overrides pack instead:
MODDING.md).
Ties and shrubs are packed for the native port only (scenery.py):
the port draws them from the extractor's files, so the pack writes the
level's placements.json with the lists that changed, each tie and shrub lit
again where it stands, and --mods reads it in place of the level's own.
The gameplay file's own tie and shrub tables stay the disc's, and the level's
collision is baked from the ties and shrubs: a tie that was moved is drawn in
its new place and still walked on in its old one until its collision is moved
too (the tiles, below), and a new one is walked through until collision is
added for it.
An added moby is a copy made in Godot (Ctrl+D) or one of the level's class
scenes dropped under Game/Mobys. It takes after the moby it copies (or the
level's first moby of its class): the same values, a spawn number of its own,
a copy of that moby's pvars, and a place in its group. A deleted moby leaves
the table, and the groups and pvar links that name mobys by their place in
it follow (gameplay.py). With an expanded stream, OpenRAC can
raise the runtime spawn pool up to the remaining 32,768 moby-index slots; each
spare slot reserves 0x180 bytes for a moby and its pvars. The packer cannot
predict a level's maximum simultaneous scripted spawns or remaining runtime
heap, so spawn-heavy edits still need in-game stress testing. A full pool can
make CreateMoby fail, and some original game callers do not handle that
failure safely.
How it stays exact (pack.py, wadpack.py):
- A moby the scene has as the extractor wrote it keeps its bytes from the
disc. A project nobody edited packs to nothing. Edits preserve untouched
moby records and change the tables, links, and data blocks needed to express
the saved scene. Godot's own way of saving a scene (other float rounding,
unique_id,rc1_classon every instance) is not an edit. - The compressor writes streams the way the retail ones are laid out, measured on all 19 gameplay files: 0x2000-byte blocks that no packet crosses, each ending with a skip and unused room after it, and no literal run after a literal run. Re-encoding the original files is smaller than retail's for every level. Edits can make a stream larger than its disc slot; the packer then stores it in a virtual sector range in the mod. It also uses virtual sectors when decoded gameplay or core data grows beyond 32 MiB even if compression still fits the disc slot, so the launcher selects the expanded arena the stream requires. The disc and vanilla read path stay untouched (see Pack). Expanded streams use a bounded 256 MiB guest RAM arena and remain subject to RAC1's game-owned table, address, and heap limits. The packer rejects edited gameplay or core data whose decoded stream alone exceeds 256 MiB; a level's other allocations can leave less room, so this is an absolute ceiling, not a promise that every stream below it will fit.
Checked on 2026-10-12: all 19 gameplay files round-trip through the
compressor; a scene re-saved by Godot 4.7.2 with one moby moved packs to a
file five bytes from the disc's, all in that moby's record; the port ran
level 0 for 400 frames from it. Whether the moved moby shows where it was
put has not been looked at in the running game yet. Checked on 2026-10-16
on the NTSC-U disc: level 0 extracted from it, one bolt moved in the scene,
packed to level_00/gameplay_ntsc.wad at the NTSC copy's sectors, which
reads back as the disc's copy but for two bytes of that bolt's record; all
19 NTSC gameplay copies round-trip through the compressor too.
python3 editor/assets.py export assets/port/rac1/pal build/my-export --level 0 --what mobys
python3 editor/assets.py import build/my-export --levels assets/port/rac1/pal --out build/my-packExports a level's moby, tie and shrub models as glTF (mesh, UVs, vertex
colours, materials, skeleton, inverse bind matrices, weights, animations), its
terrain (--what terrain: one node per fragment, any vertex count coming
back) and its textures as PNG, each with a manifest; reads the edited files back as an
overrides pack (overrides.py) that the port loads with
--mods. Either disc's levels (assets/port/rac1/ntsc the same way), and a
pack made from one disc's applies to the other's where it matches. MODDING.md is the guide: editing in Blender,
upscaling, re-importing, the pack format. The launcher's Mods page does it
all.
python3 editor/extract.py survey baserom/SCES_509.16.iso # disc layout, as JSON
python3 editor/extract.py raw baserom/SCES_509.16.iso build/level-00 --level 0
python3 editor/extract.py port baserom/SCES_509.16.iso build/port-levels --level 0port writes each level for the native port's level viewer
(port/viewer): the same meshes and textures as
the Godot export, the sky as a mesh, and the placements as JSON. Its
manifests say which disc they came from ("version": "pal" or "ntsc");
both use the same format. Export copies matching level_NN/probe-bake.json and
probe-emission.json profiles for shared runtime/bake settings, emission colours and static
scenery selection. The regions section controls placement, coarse bounds and padding.
Profiles live in port/enhanced/lighting/profiles/<game>-<version>/level_NN/; missing profiles use
defaults. Re-export/rebake after tracked settings change. Each version's export has a folder of its
own under one root: assets/port/rac1/pal and assets/port/rac1/ntsc in a
checkout, <data>/port/pal and <data>/port/ntsc in an installed launcher
(the game reads <data>/port/<version> without --levels). An export made
before that layout, straight in assets/port/rac1 or <data>/port, moves
into pal/ the next time the PAL levels are extracted with --replace.
python3 editor/extract.py port baserom/SCUS_971.99.iso assets/port/rac1/ntsc # NTSC-U, every levelraw writes one level's sections as stored on the disc and decompressed,
with a manifest of their sizes and hashes.
project.godot enables the Collision dock ([editor_plugins])
rc1/level.gd fly camera when a level is run
rc1/check.gd headless check against level.json
addons/openrac_collision/ the Collision dock, an editor plugin (Collision, below)
levels/level_NN/
level_NN.tscn the level
level.json what was extracted: counts, bounds, code overlay
terrain.glb one node per terrain fragment
collision/tile_XX_YY.glb the collision of a 64-unit square, a material per type byte
collision/hero_NNN.glb a hero wall (a hero group)
collision/collision.json what was written: each file's name, SHA-1 and triangles
ties/tie_<class>.glb one mesh per tie class
shrubs/shrub_<class>.glb one mesh per shrub class
mobys/moby_<class>.tscn a moby class: its mesh (or a box), a label and its collision (hidden)
mobys/moby_<class>.glb a moby class's mesh, skeleton and animations
mobys/collision/moby_<class>_XX.glb a class's collision triangles of type byte XX
mobys/collision.json the class scenes and collision files as written (SHA-1)
gameplay/<kind>.tscn a volume's unit shape (cuboid, sphere, cylinder, pill), or a
camera's, sound's or the ship's marker
sky.png the sky as a panorama, used by the WorldEnvironment
textures/*.png shared by the level's meshes
Each level scene looks like this:
Level_NN
WorldEnvironment the sky (sky.png) and flat ambient light
Sun a directional light, so shapes read in the editor
Game turns the game's Z-up axes into Godot's Y-up
Terrain fragments Terrain_000... (editable children)
Collision the collision, by 64-unit tile, hidden until you show it (Collision, below)
HeroWalls the walls only the player meets, hidden too
Ties/Tie_NNNN one node per placed tie
Shrubs/Shrub_NNNN one node per placed shrub
Mobys/Vendor_0012 one node per placed moby (crates, enemies, NPCs...), named
after its class and its place in the level's table
LevelSettings the gameplay data (Gameplay data, below): fog, death height,
MobyGroups, Cameras, ship; groups; cameras; sounds; volumes; paths
Sounds, Volumes,
Paths, GrindPaths
To edit a level:
- Move, rotate or scale objects as usual. Transforms under
Gameare in game units and axes. - To add an object, drag a class
.glbfrom the FileSystem dock ontoGame/TiesorGame/Shrubs. The file is the object's class. - Per-object game fields are in the Inspector's Metadata section:
rc1_index: the object's record in the original level (new objects have none);rc1_draw_distance,rc1_directional_lights;- ties only:
rc1_occlusion_index,rc1_uid; - shrubs only:
rc1_colour, raw integers; rc1_matrix_w: only on objects that store 0.0 instead of the usual 0.01.
- Mobys show as their class's mesh in its bind pose, with a label giving
the class number and name (
11 vendor) that fades out beyond 60 units. Classes without a mesh (spawn points, triggers, particle spawners...) show as a coloured box. Each class scene,mobys/moby_<class>.tscn, has one for every class the level loads, placed or not; drag one ontoGame/Mobysto add a moby. ItsModelnode carries the class scale. A moby's rotation in the Inspector is the game's own Euler angles (rotation order XYZ). - Animated classes have a
Skeleton3Din the bind pose and anAnimationPlayerwith one animation per sequence:seq_NNby slot, and Ratchet'sratchet_seq_NNN. Sequences that loop in the game loop here. To preview one, double-clickmobys/moby_<class>.glbin the FileSystem dock: Godot's import settings window plays each animation. In a scene, enable Editable Children on theModelnode to reach theAnimationPlayer. - A moby's metadata:
- always
rc1_index,rc1_spawn_id,rc1_group,rc1_pvar_indexandrc1_colour(the ambient colour, 128 = 1.0); - the other fields only when they differ from what most mobys store:
rc1_spawn_flags(0),rc1_draw_distanceandrc1_update_distance(64),rc1_mode_bits(32),rc1_occlusion(1),rc1_light(0),rc1_unknown_74(-1), and a few unknown fields (ASSETS.md).
- always
- Textures are ordinary PNGs. Editing one changes every mesh that uses it.
Run a level (F6) to fly around it:
| Key | Action |
|---|---|
| Right mouse | Look |
| WASD | Move |
| Q / E | Down / up |
| Shift | Faster |
| Mouse wheel | Change speed |
| F | Frame the terrain |
| C | Show or hide the collision layer |
RAC1 moby behaviour is C in the port, but each level's gameplay file says what every object starts with, and those values are in the scene, editable, and packed back like the mobys (gamedata.py; the formats are in LEVEL.md):
| Node | What | Packed |
|---|---|---|
Game/LevelSettings |
rc1_background, rc1_fog_colour (r, g, b), rc1_fog_near, rc1_fog_far and their _intensity, rc1_death_height, rc1_ship_path, rc1_ship_camera_cuboids (first, last) |
values |
Game/LevelSettings/Ship |
where the player's ship lands; turn it about Z only | moved |
Game/MobyGroups |
rc1_group_<n>: each group's members, by node name |
members |
Game/Cameras/Camera_NNNN |
position and rotation (the game's Euler angles), rc1_class, the common camera header and known class-specific pvars |
edited in place; cameras may be added by copying a same-class record and pvars, up to 48 |
Game/Sounds/Sound_NNNN |
the record's matrix, rc1_class, rc1_m_class, rc1_range, pvars (hidden) |
edited in place |
Game/Volumes/Cuboids/Cuboid_NNNN, Spheres, Cylinders, Pills |
the unit shape through the record's matrix, translucent (hidden until you show Volumes) |
moved, turned, scaled, added |
Game/Paths/Path_NNNN |
a Path3D: the points in its curve, rc1_w each point's fourth float |
points moved, added or removed; paths added; records cannot be deleted |
Game/GrindPaths/GrindPath_NNNN |
as paths, with rc1_bounding_sphere (x, y, z, r) and rc1_flag |
the same |
rc1_background is the colour the game clears the frame to before it draws the sky (SetBackgroundColor). It is not a sky tint: it shows only where the sky's shells leave gaps or are see-through. In 15 of the 19 levels the sky's first shell is an opaque dome coloured per vertex (Novalis, Kerwan, Pokitaru and most others), which hides the background completely; there the sky's colour is that dome's, part of the sky model. The background shows in Rilgar and Umbris, and below the horizon in Orxon and Quartu. rc1_background and rc1_fog_colour take red, green and blue from 0 to 255. The editor's sky panorama is baked over the background when the level is extracted, so an edit shows in the game, not in the editor's sky until the next extraction.
On a moby, camera or sound with a pvar block (its class's private
variables, rc1_pvar_index):
rc1_pvar: the block as 32-bit words. A number with a decimal point or an exponent packs as a float, any other as an integer; the list keeps its length (blocks are not resized).rc1_pv_<field>: the fields the decompilation names, typed (the classes'struct update<class>in games/rac1's moby_pvars.h, 19 classes, and the header every camera block starts with:rc1_pv_sphere,_cuboid,_cylinder,_pathname the volumes and path the camera's region test uses,rc1_pv_activationwhen it switches on). Camera classes 3, 14, 17, 18 and 23 also expose their named fields fromport/assets/world/cameras.h; runtime-owned fields are shown for inspection but Pack refuses edits to them. A field and the word under it are the same bytes: edit either; when both change, the field wins, except runtime-owned words which cannot be changed.rc1_link_<offset>: a moby link in the block (the gameplay file's +0x50 list), as the name of the moby node it names, or"-1"for none. Name another moby node to point it there; a link follows its moby when mobys are added or deleted.- The words the level loader rewrites (moby links, pointers into the block,
addresses of shared data) are not packed from
rc1_pvar: links throughrc1_link_*, the others not at all.
Installed launchers carry generated pvar_layouts.json field definitions, so typed
edits do not require a decompilation checkout. A pack log saying no such value: rc1_pv_... can indicate older tools without those definitions; update the launcher
and pack again. Existing typed edits in a saved scene can then be applied. Do not
copy an old PAL 50 Hz scene over a newly extracted 60 Hz project.
For the Novalis elevator (class 726), the historical field name rc1_pv_moveSpeed
actually stores the path's travel duration in seconds: 1 is faster than 4, while
10 is slower. The packer preserves the field name for existing projects. This is
separate from selecting the PAL disc's 60 Hz gameplay copy.
A copy of a moby (Ctrl+D) gets a copy of its pvar block, and its own
rc1_pv* values pack into the copy. Moby groups: removing a name from
rc1_group_<n> or adding one moves that moby (its rc1_group), and each
group then lists the mobys whose rc1_group names it, so setting a moby's
rc1_group moves it between the lists too (in a project exported with
MobyGroups; an older one packs its groups as before).
Volumes, paths and grind paths are named by their place in their table by
other data (camera blocks, moby pvars, the camera collision grid, the level
settings), so they are never deleted or renumbered: Pack refuses a project
with one of these nodes deleted and explains which index it kept. A new one (a copy, or
gameplay/<kind>.tscn dropped under its group, or a new Path3D under
Game/Paths) goes after the table's own; when the section no longer fits
where it was it moves to the end of the file. Cameras and sound instances
are not deleted or renumbered. Duplicate a camera node in Godot to add one. A
copied camera gets its own pvar block, including
the loader's pointer, shared-data and moby-link fixups. A camera can use the
level's existing collision volumes, and camera-region volumes can be moved or
added: Pack rebuilds the 64-by-64 camera collision grid. On a volume, edit
rc1_camera_collision_enabled to include it in that grid; its copied
rc1_camera_collision_flags, rc1_camera_collision_i_value and
rc1_camera_collision_f_value metadata preserve or set the game's collision
parameters. The game has 48 camera slots. New sound instances are not
supported yet.
Changing a camera class while it has a pvar block is refused, since the block
has the original class's layout. An edited volume's inverse matrix and Euler angles
are computed again; an edited grind path keeps its bounding sphere when it
still holds every point, and grows it just enough otherwise (the disc's own
rule is not known). Curve handles and tilts are not the game's: paths are
polylines, so they are ignored, and a point added in Godot gets the fourth
float -1, which nearly every path point on the disc has (on grind paths it
varies per point; its meaning is not established).
"Triggers" in RAC1 are mostly these volumes: cameras switch by the cuboid, sphere or cylinder of their block's header, many moby classes keep a cuboid or path index in their pvars (shown as words, or as a typed field where the layout is known), the level settings name the ship's camera cuboids, and the camera collision grid (not exported) lists volumes per 16-unit cell. Environment transitions (fog zones) have matrices of their own and are not exported yet.
As with mobys, what the scene has as the extractor wrote it keeps its bytes:
an untouched project packs to nothing, and Godot's own saving is not an
edit. Values that cannot be packed (not a number, out of the field's range,
a link to no moby, an index past its table) are listed as not packed: in
changes.txt and leave the bytes as they were.
The pack command fails instead of publishing a partial result when any edit is
listed as not packed:; an earlier packed folder is kept intact so you can fix
the scene and pack again.
Checked on 2026-10-21 on the PAL disc: every section above, read and written again unchanged, gives its bytes back in all 19 levels; each level exported and packed untouched gives the disc's gameplay file; and one edit of each kind (a pvar word, a typed field, a link, a group, a camera, a sound's range, a cuboid moved and one added, a path point and its fourth float, a grind path, the death height, the ship) on levels 3 and 16 changed only that record's bytes (or, for an added cuboid, the table moved to the end of the file).
Game/Collision is the level's baked collision: the triangles the game tests
Ratchet, bolts and shots against, with ties and shrubs already included.
Game/HeroWalls holds the walls only the player meets (fences, invisible
walls: the block's hero groups). Both are hidden by default (visible is off)
so they do not hide the terrain. In the editor, click the eye icon in the Scene
dock; in a running level press C. They are translucent and unshaded, with a
colour per surface, so they read over the terrain. Triangles are double-sided
here, but the game treats them as one-sided: a triangle faces the side its
corners turn counter-clockwise from, as in any glTF file.
The colour is the surface id, the low five bits of each face's type byte (bits 5-6 choose a footstep sound class and bit 7 excludes the face from some queries). The game has no table of surface types, so the names are inferences from how its code tests them, and most ids are only colours:
| Id | Colour | What is known |
|---|---|---|
| 31 (0x1f) | grey #8c8c8c |
no special surface; most of every level |
| 0 | blue #3373ff |
water surface (the ground probe looks for the floor below it) |
| 8, 9, 10, 12 | orange #f27130, #30f2aa, #e230f2, blue #3091f2 |
common; the game's wall and ledge checks test for these ids |
| 11 (0x0b) | #caf230 |
the ground snap is skipped (some special floor) |
| 13 (0x0d) | #f23058 |
liquid-like (level 12 only) |
| 1-7, 14 | other hues | used in many levels; meaning unknown |
The other ids are coloured by hue from the id. Each level's level.json
lists its surfaces and how many triangles use each.
The Collision dock (addons/openrac_collision, an editor plugin every
export enables; LEVEL_MODDING.md is the
modder's guide) edits the collision face by face: Show collision draws it over
the level with a legend of its surfaces; Pick faces selects faces in the 3D
view (click, Shift-click, a box, connected faces of one type byte, the faces
baked inside a selected tie or shrub); the selection is repainted (surface id,
sound class, exclusion bit), deleted, moved by an offset, or faces are added
(a quad, a box, or the triangles of a selected mesh snapped to the game's
collision grid). Each edit is undoable and saved with the scene. The dock leaves the
tiles alone and keeps its edits as metadata of Game/Collision, which
collision_scene.scene_collision applies on top of the nodes' triangles:
| Metadata | Ten ints per face |
|---|---|
rc1_removed |
the corners x0 y0 z0 x1 y1 z1 x2 y2 z2 in 1/64 game units (the tiles' positions times 64, glTF's winding), then the type byte |
rc1_added |
the same, on the game's grid; a repaint or a move is a face removed and added again |
rc1_hero_removed |
the hero wall's number (HeroWall_NNN), then the corners |
A face is named by its corners and type byte, whichever corner comes first.
A removed face no node holds any more (a tile edited in Blender since) is
reported in changes.txt, not guessed at. The dock's logic is in plain
scripts (level_faces.gd, class_collision.gd), which
test_collision_dock.gd runs headless on a real level.
The collision also comes as tiles, one per 64 x 64-unit square of the level
(Game/Collision/Tile_XX_YY, an instance of collision/tile_XX_YY.glb), and
one node per hero wall (Game/HeroWalls/HeroWall_NNN, collision/hero_NNN.glb).
A tile has a material per type byte named rc1_surface_XX (the byte in
hexadecimal: rc1_surface_1f, rc1_surface_0c). What you can do:
| In Godot or Blender | What the packer does |
|---|---|
| Delete a tile or a hero wall | its triangles leave the level |
| Move, turn or scale a tile | its triangles move |
Edit a tile's .glb in Blender and export it to the same file (keep the materials; give a face another rc1_surface_XX material to change its type) |
the triangles that differ are taken out and put in |
Set rc1_surface (an integer, the type byte) on a tile |
every face of it takes that byte |
Add a node under Game/Collision: a MeshInstance3D with a BoxMesh, PlaneMesh or QuadMesh, or an instance of any .glb/.gltf, with rc1_surface metadata (0x1f, 31, without it; a material named rc1_surface_XX in a glTF wins) |
its triangles are added, in the node's place |
The same under Game/HeroWalls |
a new hero wall (each game group has at most 255 triangles and 255 unique vertices; larger meshes become several groups) |
How it is packed (collision_scene.py, collision.py):
- The packer compares the scene's triangles with the disc's. A scene with the collision as the export wrote it (re-saved by Godot or not) packs nothing, and read back from the tiles it is exactly the disc's triangles (checked on all 19 levels of both discs).
- The game keeps its collision in a grid of 4-unit cells, each with its own copy of every face that touches it (COLLISION.md). A removed triangle leaves every cell that has it (a quad that loses one half keeps the other); an added one goes into every cell it touches; every other cell keeps its bytes, and the block keeps the disc's layout, so an untouched block is written back byte for byte.
- Positions snap to the game's grid: 1/16 unit across, 1/64 up. A triangle
that becomes a line on it is left out (
changes.txtcounts them). A large triangle is halved until each cell it touches can hold its corners (a vertex is at most 32 units from its cell's centre). - Checked against the collision format before anything is written, or the
level's collision is left as the disc has it and
changes.txtsays why: at most 2,147,483,647 unique vertices in an expanded cell (the editor writes a mod-only extended leaf and selects expanded RAM when a retail cell no longer fits), an aligned leaf length that fits its 32-bit header, at most 0xFFFFFF indexed leaves (their 32-bit directory removes the retail 24-bit leaf-start bound), everything inside 0-131,072 units on each positive axis in expanded mod collision (vanilla stays at 0-1024 units), and hero walls split into groups of at most 255 triangles and 255 unique vertices. Retail leaves remain byte-for-byte when they fit; their 4,080-byte size and 16-bit face count remain unchanged. - The block goes back into the level's core data, in its own place when it fits; a larger one moves to the end of the core data (the core index says where), and the level's memory after the core moves up by the block's size.
- If the recompressed core still fits its original slot, the packer writes the
changed sectors there. If it outgrows that slot, the packer appends the core
stream to an expanded level-data container and redirects the level header
through
virtual.tsv; the original disc image is not changed. The expanded gameplay and level-data loading paths have run in the real game on PAL and NTSC-U. A 9,500-moby gameplay stream and a Godot collision edit adding 95,000 triangles both loaded for 1,200 frames on both versions. That collision edit grew the compressed core beyond its original slot, so the packer moved the collision block to the core tail and expanded the level-data container. - The expanded stream path uses a bounded 256 MiB guest RAM arena. RAC1 still
has format and game-owned limits: vanilla collision is queryable only inside
the 0-1024-unit world, marked mod collision reaches 131,072 units, each cell
has bounded vertex/face storage, and
gameplay objects still depend on their per-level tables and heap. The
NTSC-U's old moby-index remap held only 64 entries; when the expanded 256 MiB
arena is active, both PAL and NTSC-U now allocate remap storage from level
memory for tables above 64 records. The vanilla 32 MiB path keeps its
original allocation. Signed-short remap values still limit a source gameplay
table to 32,768 mobys. RAC1's killed-state bitsets hold 2,048 spawn IDs;
additions use unused IDs in that range, then become non-persistent (spawn ID
-1, spawn flags0) with a note inchanges.txt. Expanded streams remove the original disc-slot ceiling. With the expanded 256 MiB arena, the larger pool can use the remaining 15-bit moby indices for spawned objects; the vanilla 32 MiB pool size and its three crowded levels are unchanged. Expanded mode now uses a sparse moby grid across the positive 131,072-unit collision world, with 16-unit cells, 262,144 hash slots, and a 14.5 MiB pool of 32-byte blocks; vanilla retains the original 1,536-block grid. A single moby's grid bounds must fit the expanded grid's cell capacity; empty expanded cells release their hash slots for later reuse. If grid allocation fails, that moby keeps its previous collision coverage and OpenRAC warns. The expanded target pointer table can hold 32,768 entries; vanilla keeps the original 511-target limit. Host moby bookkeeping and HUD markers now cover all 32,768 signed-index slots. Remaining game-format bounds include 32,768 source mobys, the 2,048 persistent spawn IDs, collision's positive 131,072-unit world, and per-cell vertex/face storage. Large-world moby collision and levels using the upper moby-index range still need live-game validation. These are game-structure limits, not disc-slot limits.
What the decoded collision misses: the cells' quads are split into triangles here, and a face shared by cells appears once, so the tiles show the level's surfaces, not the cells.
Crates, doors, enemies and the other mobys collide through their class:
a class in the level's core can carry a collision blob
(MOBY.md, section 8) of
primitives (spheres, spheres on a joint, upright cylinders, capsules
between two joints) and triangles with type bytes, which the game's
queries test against each moby of the class where it stands, turned and
scaled with it. A bolt crate, for one, is a sphere and a 12-triangle box.
Each class scene (mobys/moby_<class>.tscn) has it as a hidden Collision
child of its root, in the class's model units (the node carries the class
scale), coloured like the level's surfaces:
Moby_<class>
Collision hidden; rc1_collision_format = 1
Sphere_NN a SphereMesh: a sphere
JointSphere_NN a SphereMesh on joint rc1_joint, drawn in the bind pose
Cylinder_NN a CylinderMesh, upright (on a tilted moby the game keeps it upright too)
Capsule_NN a CapsuleMesh between joints rc1_joints, drawn in the bind pose
Mesh_XX collision/moby_<class>_XX.glb: the triangles of type byte XX
Every child has rc1_collision_kind (sphere, joint_sphere, cylinder,
capsule, mesh) and rc1_surface (a mesh's type byte; 31 on the
primitives, which have no surface: a hit on one reports none). A primitive
also has rc1_index, its place in the blob, and rc1_queries: bit 0 for the
queries that pass flag 0x4 (the player's movement among them), bit 1 for the others
(shots, bolts, explosions; the ground probe tests no primitives). Triangles are tested by every
query. Joint primitives follow their joints in the running game; the editor
shows them in the bind pose. Open the class scene and click the Collision
eye to see it (the Collision dock shows it on a selected moby, and edits its
parts' sizes, positions, queries and type bytes, or makes it from the class's
model: the same rules as below).
| In Godot or Blender | What the packer does |
|---|---|
| Move or scale a sphere or cylinder (the cylinder's height and radius), or a joint sphere (its offset from the joint) | the primitive moves or resizes |
| Scale a capsule | its radius; it runs between rc1_joints, which you change instead of moving it |
Change rc1_queries, rc1_joint or rc1_joints |
the primitive takes them (joints must be the class's) |
| Delete a child | the primitive or those triangles go; with every child gone the class has no collision |
Add a MeshInstance3D with a SphereMesh (with rc1_joint on a joint), CylinderMesh or CapsuleMesh (with rc1_joints) |
a new primitive |
Add a BoxMesh, PlaneMesh, QuadMesh or a .glb/.gltf instance, or edit a Mesh_XX glTF in Blender (a material rc1_surface_YY gives faces that type byte) |
the triangles change |
What a class edit affects. The collision belongs to the class, so an edit
is every moby of that class in that level. Each level has its own core,
so the same class in another level keeps its own collision: edit it there too.
One moby alone cannot get other collision from the level data: nothing in a
moby's record turns it on or off. What works is giving that moby another class
the level has (which changes its model too), or behaviour code
(docs/port/MODS.md): the game sets a moby's own
collision pointer (moby +0x94, copied from the class) to 0 to make it
intangible and sets bits of collision_disable (+0x98: bit i turns off
primitive i, the first 32) on objects being carried, and a mod can do the same.
How it is packed (moby_collision_scene.py, moby_collision.py):
- What is as the export wrote it keeps the disc's bytes: an untouched project packs nothing (checked on every class of both discs: 2,972 PAL and 2,968 NTSC-U class scenes read back as the disc's), and an edited class keeps the records of the primitives you left alone.
- Checked first, or that class is left as the disc has it and
changes.txtsays why: kinds, radii and heights above 0, joints within the class's skeleton, vertices within 32 model units of the class's origin (16 bits in 1/1024), and at most 65,536 vertices per class collision mesh. Retail blobs keep their byte-indexed faces; when an edit references vertex index 256 or higher, OpenRAC marks that blob and stores 16-bit face indices. Vanilla blobs keep their original layout and bytes. - Class collision is local to a moby and uses signed 16-bit vertex coordinates.
For large environment structures or map-scale collision, add the geometry to
the level's
Game/Collisioninstead; that path uses the level collision format and its mod-only extended coordinates, independently of moby classes. - The blob goes back where the class has it when it fits (its place is padded to 16 bytes), else to the end of the core data: the class header's collision offset is relative to the class and the loader adds the class's address, so it can point anywhere in the core. Like a moved level collision block, that has not been tried in the running game yet.
- The port reads it as the game does: the core data, served from the packed
sectors by
--mods, is in the game's memory, and the collision kernels read the class blob there.
Custom models. A model you replace with an overrides pack (MODDING.md) keeps the class's collision, so a bigger crate is still hit as the old one. Make collision for it from its mesh:
python3 editor/moby_collision.py generate my_crate.glb --scene assets/levels/rac1/pal/level_03/levels/level_03/mobys/moby_500.tscn
python3 editor/moby_collision.py generate my_crate.glb --mode box --surface 0x7f --scene ...--mode mesh (the default) takes the triangles themselves, merged until at
most 65,536 vertices are left; box the box around them; sphere and
cylinder one primitive around them. The model is read as assets.py
exports models (glTF's Y up, at the drawn size: the class scale is taken
from the class scene); --axes game reads one in the game's axes, --scale
gives the size. The class scene's Collision children are replaced, and
extract.py pack packs it. --out FILE writes the blob instead, for tools.
A class the level does not have cannot be added yet, so neither can its
collision.
python3 -m unittest discover -s editor # synthetic data only
godot --headless --path assets/godot --import
godot --headless --path assets/godot --script res://rc1/check.gd
godot --headless --editor --quit --path assets/godot # loads the Collision dock: no errors
OPENRAC_GODOT=godot OPENRAC_DOCK_DISC=baserom/SCES_509.16.iso OPENRAC_DOCK_PROJECT=assets/levels/rac1/pal/level_00 \
python3 -m unittest discover -s editor -p test_collision_dock.py # the dock's edits, packedThe last runs the dock's logic headless on a copy of an exported, imported level (picking by ray, selecting, repainting, deleting, moving, adding, a mesh made into collision, a class's collision made from its model) and checks the packed block is the disc's with exactly those edits, every other cell byte for byte.
The Godot check loads every level scene and compares its meshes, triangles,
textures, bounds, sky panorama and moby count with what the extractor wrote.
The collision layer is not counted with the textured meshes; its triangles
are compared with level.json on their own, and it must be hidden.
It also loads every moby class scene and compares their models, triangles,
textures, skeletons, bones and animations, and the meshes the placed mobys
show, and each class scene's collision (classes, primitives, triangles; it
must be hidden).
Extracted:
- terrain (tfrags);
- ties and shrubs, with their placements;
- moby placements, and each moby class's high-detail mesh, skeleton and animations;
- the sky;
- the collision and the hero walls, as hidden layers, editable;
- the gameplay data: pvars, moby links and groups, volumes, paths, grind paths, cameras, sound instances and level settings;
- the textures they use.
Approximations:
- Normals are flat, and Godot lights the scene. The game's own lighting, baked into vertex colours, is not decoded yet. Mobys use their stored vertex normals; the game lights them at run time.
- Alpha-tested textures become cutouts.
- Moby faces the game draws untextured get a flat grey texture, which is what the game binds for them. Some classes are made only of such faces (rings, force fields, barriers); the game may draw those translucent, glowing or not at all. Glow parts are lit like the rest, and the chrome and glass passes and the low-detail meshes are not extracted.
- Moby animations run at the game's 60 Hz tick, as ReRAC infers it. Godot interpolates rotation keys spherically where the game lerps, and a joint channel that stays within 1e-5 of the bind pose (1e-4 for rotations) is left out of an animation. The two helper classes 1 and 2, which animate joints of another skeleton, stay static. The gadget classes (the wrench and weapons in Ratchet's hand) are not extracted.
- The sky is baked into a 2048×1024 panorama, over the level settings' background colour (what the game clears to). The game centres its sky shells on the camera and never moves them, so a panorama loses nothing. Their blending follows the PS2's usual texture and alpha modes, which is inferred rather than traced in the game's code. Sprites the game adds to the sky at run time are not included.
Not yet extracted: low-detail moby meshes, audio, video and each level's code overlay. ASSETS.md describes the formats and the evidence for them.
Going Commando (SCUS_972.68 v1.01), Up Your Arsenal (SCUS_973.53) and
Deadlocked (SCUS_974.65), NTSC-U. Their levels are the first game's
format with a few differences, read by the same readers through a table of
what each game changes (games.py; the formats, and what was
checked on each disc, are in
docs/engine/formats/LATER_GAMES.md).
None of these games is playable in OpenRAC yet: this is for modders to get
ready, looking at the levels and making texture and model packs that will
apply once they are.
python3 editor/extract.py survey baserom/SCUS_972.68.iso # the levels on the disc, as JSON
python3 editor/extract.py godot baserom/SCUS_972.68.iso assets/levels/rac2/ntsc --split
godot --path assets/levels/rac2/ntsc/level_01 -e # Oozla
python3 editor/extract.py port baserom/SCUS_973.53.iso assets/port/rac3/ntsc # for texture and model packs
python3 editor/assets.py export assets/port/rac3/ntsc build/uya-export --level 2 --what textures
python3 editor/assets.py import build/uya-export --levels assets/port/rac3/ntsc --out build/uya-packThe disc is told apart by the executable SYSTEM.CNF boots, as for the
first game. A level is numbered as the game numbers it (its level header's
number), so the folders have gaps: Going Commando 0–20, 22–26 and 30 (the
Insomniac Museum); Up Your Arsenal 1–55 but for 15, 25, 37 and 38;
Deadlocked 0–74 with its multiplayer maps and split-screen copies. The
launcher's Mods page lists them by name.
| What | Going Commando | Up Your Arsenal | Deadlocked |
|---|---|---|---|
| Levels | 27 | 51 | 47 |
| Terrain, with the chunks large levels stream | yes | yes | yes |
| Ties and shrubs, with placements | yes | yes | yes |
| Moby class meshes, with placements | yes | yes | yes |
| Moby skeletons | yes | yes | yes |
| Moby animations | yes, but some classes | yes, but some classes | not yet |
| Textures | yes | yes | yes (Deadlocked's swizzled pixels are put back in order) |
| Chrome map for metal parts | not found: grey | not found: grey | not found: grey |
| Sky | yes | yes | yes |
| Collision layer | yes | yes | yes |
| Lighting baked as the game does it | not yet | not yet | not yet |
| Moby class names and update functions | not yet | not yet | not yet |
| Deadlocked's mission instances and classes | not yet | ||
| Packing edits back into the game | not yet | not yet | not yet |
| Playing | not yet | not yet | not yet |
What does not decode yet is left out rather than stopping the level, and
each level says what: level.json's problems in a Godot project,
stats.problems in the port layout's manifest.json (a class without its
animations, a terrain fragment, a texture drawn grey).
In Godot a later game's level opens like the first game's, with the same
nodes and rc1_* metadata. Its level node carries rc1_game and
rc1_packable = false, and its description in the Scene dock says that the
level is for looking and making packs: you can move things, but the packer
reads Ratchet & Clank's levels only and refuses another game's project
(extract.py pack stops with a message), so nothing you change in the scene
reaches the game yet. Moby nodes are named Class<N>_NNNN: the class names
are the first game's only.
Texture and model packs work as for the first game (MODDING.md):
an export made from a later game's levels says its game, its import makes a
pack for that game ("game": "rac2", "rac3" or "rac4", "version": "ntsc"), and overrides.py stacks a pack only for levels of its own game.
Such a pack holds textures and models; a level edit in it is ignored and
said so. The launcher's Mods page has all of it: Read the levels, then
Models and textures, and the level editor; its Pack and Play steps say
they are not there yet for these games.
The GDScript files follow GDSCRIPT_CONVENTIONS.md.
The editor's files carry no licence lines, as the maintainers decided: the editor's MIT LICENSE covers every one of them.
| File | Contents |
|---|---|
extract.py |
Command line and safe output |
disc.py |
ISO files, the two discs' versions (what identifies each from games/rac1/game.json), sector table of contents, level headers |
wads.py |
Going Commando's, Up Your Arsenal's and Deadlocked's discs (identified from their game.json): table of contents, level table, level headers |
games.py |
What the four games' level formats share and what each changes, for the shared readers; the versions each game's levels are read from (game.json) |
level.py |
A level's sections, core blocks and textures (each game's) |
formats.py |
FormatError and check, bounded reads, WAD decompression, code overlays, textures |
util.py |
The helpers several modules share, one copy each: SHA-256 of bytes, reading JSON with the caller's own error, 32-bit floats, the cross product, the disc identity in games/<game>/game.json. What the repository's openrac_common already does (a file's digest, reading a manifest, the ISO 9660 records) the editor takes from there |
terrain.py, ties.py, shrubs.py, sky.py |
Geometry and placements |
collision.py |
The collision block: decoded for display, and read, edited and written back byte-exact |
mobys.py, moby_class.py, moby_anim.py |
Moby placements, class meshes, skeletons and animations |
mesh.py, gltf.py |
The mesh type and the GLB writer |
gltf_read.py, pngio.py |
Reading glTF and PNG files back (a modder's edits) |
assets.py, overrides.py |
Export for modding and re-import; overrides packs (MODDING.md) |
godot.py |
Project, scenes and level.json |
collision_scene.py |
The collision as tiles and hero walls in the scene, read back and packed into the level's core data |
moby_collision.py |
Moby class collision: read, checked and written back byte-exact; generated from a mesh (also a command) |
moby_collision_scene.py |
A class's collision in its class scene, read back and packed into the level's core data |
gamedata.py |
The gameplay data (pvars, links, groups, volumes, paths, cameras, sounds, level settings) in the scene and packed back |
pack.py, gameplay.py, scenery.py, wadpack.py |
Packing edited scenes back into the game's level data |
addons/openrac_collision/ |
The Collision dock (GDScript, editor only): plugin.gd the plugin, dock.gd and help.gd its panel, level_faces.gd the level's faces and their edits, class_collision.gd a class scene's collision, surfaces.gd the type bytes |
The decoders accept only the layouts found on these discs and raise
FormatError on anything else. Nothing is skipped silently.
- Wrench by chaoticgd and
contributors (GPL-3.0-or-later). Most of what the extractor knows about
terrain, ties, shrubs, the sky, textures and placements comes from
reading its source. ASSETS.md
lists which files each format came from. What the later games change
(their table of contents, level headers and level data, the instance
records, the tie and moby headers, Deadlocked's swizzle, skeletons and
sky, the streamed chunks) comes from reading it too, and their level
names are its
data/underlay/*_levels.asset(LATER_GAMES.md). - Replanetizer by RatchetModding contributors, consulted for what fields mean.
- ReRAC (ISC): the moby instance record and what each field does in the game's level loader, the moby class format (packets, vertex cache, skinning slots, normals, the untextured faces, skeletons and animation sequences), and the collision block and the meaning of its surface bytes (ASSETS.md).
- Lombyte (GPL-3.0): the moby
class names on the labels (
moby_classes.tsv, see its header). - OpenGOAL's jak-project, whose extractor was the model for extracting from your own disc.
- Godot Engine and the Khronos Group's glTF 2.0, which the output is built for.
No code from these projects is included; the extractor is newly written.