The COG Factory / COG Factory Experiments

Scuba Tank

2026-09-22
DOWNLOAD ZIP · 432 KBBUILD 62026-09-30
Scuba Tank

A dry room with a square pool sunk into its floor and an air tank leaning against the wall -- the tank off the diver's own back. Press Use on it and you are a diver in mask, tank and fins on the spot, in or out of the water, and the air is unlimited: dive, and after two seconds under you breathe like Vader, for as long as you like. Press the tank a second time and the gear comes off at once. One number in the level's COGS row gives a finite tank instead -- the screen then says 75, 50, 25 and 5 percent and empty, and when it runs out the suit comes off where you float and drowning resumes exactly where it left off -- and another picks when the suit is worn: whenever the tank is held (default), only in the water, or only under it. Built to be lifted into another level: three scb files, one template and one placed cog, with the steps in the readme.

A level cog can hand the player scuba gear by owning one actor flag. SITH_AF_BREATHEUNDERWATER (0x40) gates the whole drowning block in sithActor_Update, and the engine reads it in three places and writes it in none, so death, respawn and normal play all leave a cog-set bit alone; only a level reload puts the template's value back. With the bit SET the drown counter is frozen, not reset — a tank that runs out resumes the count rather than restarting the 20-second grace — which is exactly the behaviour a tank should have. The cog uses the engine's own two-term test for submerged (sector flag 0x2 set, physics flag 0x100000 on the water surface clear) on a 0.25 s pulse, spends air only on a submerged pulse, and clears the flag on four exits: empty, surfaced, dead, shutdown. Measured at 20 fps: an unaided player warped into the pool takes his first 10 drown damage at frame 451; the same player with the tank sits 30 seconds under at 100 health, and the control build with the one SetActorFlags line deleted fails at exactly the 50 percent marker, which reads HURT instead of FULL. Shields do not soak drowning (item_shields.cog only subtracts for damage types 2 and 4), so GetThingHealth is a sound oracle.

The trap was the tank, not the water. _structure inherits collide=3, and with face collision on, the thing's size sphere is only a broad phase: the 0.165 Use ray from the eye has to land on a real face. bact.3do declares a 0.084 radius around a mesh 0.031 by 0.036 by 0.058, so ten stand-offs from 0.30 down to 0.12 all ended inside the radius and short of the mesh, silently. collide=1 makes the sphere the whole test, and Use then works at 0.22 and closer. A second, smaller one: probe sector reports the runtime flags with SITH_SECTOR_SEEN (0x4000) ORed in, so a pool written 0x1002 reads 0x5002 from inside it. Phase 1 alone: gate 5 of 5, GOB standalone.

Phase 2: the suit is one verb, and the stock animations drive it. SetThingModel(player, suit) swaps the 3DO and keeps the puppet (sithThing_SetThingModel saves and restores renderData.puppet), and keyframes bind to hierarchy nodes by INDEX, so the handed-off scubakatarn.3do -- ky.3do's 18 nodes in ky.3do's order -- walks, swims and punches on Kyle's keys. Its zero pivots are baked into the vertices (exact) and its zero rest rotations do not matter, because a key is absolute: the puppet subtracts the rest pose, blends and adds it back. Retail swaps the player's model the same way in CTF. The assertion is the engine's own GetThingModel read back at every transition plus a watch that reports each change of model, and the control with the one SetThingModel line deleted reads KYLE where the cog meant SUIT. Two traps: SetThingModel is not void -- it pushes the old id, so a bare call leaks a slot of retail's 64-entry COG stack per swap -- and the camera key is F1, not MINUS, which is bound to screen size as well. Five of the suit's eight materials exist nowhere on this machine and ship as flat-colour placeholders. Gate 7 of 7; retail JKDF2 has not seen either phase.

Phase 3: in the water, the tank off his back, and the breath. The default suit mode now dresses him the moment his sector is water -- the engine computes the on-the-surface bit only while the player's sector is underwater, so a swimmer floating at the top is in the pool sector with that bit set, and the mask (flag 0x40) still goes on only below it. The tank he presses is cut out of the suit's own K_TORSO mesh (444 faces: body, band and valve, axis exactly vertical) and leans 6 degrees on the wall with pitch -6, its pose solved on the rotated vertices so the foot's rim sits on the floor and the shoulder touches the wall; a camera parked in the wall/floor corner shows both contacts as the two lines through the image centre. The breath is the owner's WAV, downmixed and resampled from 48 kHz stereo to the 22050 Hz mono every positional stock sound uses, looped on the player with LOOP | FOLLOWSTHING after four seconds suited under water: the mixer reads peak 0.0000 before it, 0.2241 during and 0.0000 after, and a control that starts the same loop at volume 0 prints every marker and fails the audio check. And a harness bug: look was a silent no-op on its second use, like warp and cam before p54 -- a stand-off sweep read one head pitch at fourteen distances. Fixed.

Phase 4: the suit comes off two seconds late. Whenever it stops being wanted -- out of the water, the tank given back or empty -- a countdown starts, and the suit comes off 40 frames (2.00 s) later; the air and the breath do not wait. Get back into the water inside those two seconds and the countdown is dropped, so the next one is fresh: a control that keeps the dropped countdown's time takes the suit off a second early and fails.

Phase 5, and the end of the experiment: handing the tank back is immediate. The second Use takes the suit off in the same message and drops any countdown that was running -- pressed 0.45 s into one, the suit is off on that frame, and getting back into the water inside the old two seconds brings no stale countdown back. The breath now starts after two seconds under (43 frames). Seven controls, each located: gate 15 of 15, GOB build 5; retail JKDF2 has not seen it.

Phase 6: made shareable, for the man whose suit it is. No behaviour changed; the names, the defaults and the text did. Everything the kit mints is scb-prefixed and 8.3 -- scbscuba.cog, scbtank.3do and its template, scbbreth.wav -- while Gnarly's scubakatarn.3do ships byte-for-byte as he made it, under his name, with every material under the name his model asks for; the rewritten copy phases 2-5 shipped is gone, and the generator deletes its old files because export packs whatever is on disk. The defaults are now the suit whenever the tank is held and an unlimited tank (capacity 0), each one number in the COGS row. Every one of the cog's 102 debug prints sits behind an int dbg=1 local symbol, the readback subroutine and the model watch are not even entered without it, and export switches that one declaration to 0 on the cog's way into the GOB -- a new generic step that leaves any cog without the symbol byte-for-byte alone -- so the package prints nothing at all on the default tank and only five player-facing lines on a finite one. Proving a silent cog works needed a witness that is not a marker: the mixer, silent before the breath, peak 0.2241 once it loops, silent at the surface, with not one debug line in the log. The readme now has an ADDING IT TO YOUR OWN LEVEL section -- what to copy, the one template line, where the tank goes, the COGS row, and honest limits -- and the GOB checker refuses a readme that quotes the template or the row differently from the level it ships. Gate 21 of 21 with eight located controls, GOB build 6; retail JKDF2 has not seen it.
projectp56-scuba
date2026-09-22
packageP56.GOB, readme.txt, screenshot.jpg
build6 — 2026-09-30 (first packaged 2026-09-22)
sha256e4f095470ef9658e575b302b9fb42bbbab8c693773ee6d729de740bfe15da50e

Installing it

  1. Unzip it.
  2. Drop the .GOB into the episode folder of a Jedi Knight install — the one that already has JK1.GOB in it.
  3. Pick it from the episode list. On a libretro frontend, load the .GOB itself as content.

The GOB carries its own level, cogs, models, materials, and sounds. Everything else is read from the stock resource/, and there is no retail game data in the zip.

These are living projects, not finished releases. A level is rebuilt whenever something is learned — a lighting rule, a texture orientation, a whole detail pass — and it keeps its filename through all of it. The build number is how to tell which one you have: it is printed in the readme inside every zip, and it only moves when the level's bytes actually change.

Screenshots

The readme that ships with it

========================================================================
 Jedi Knight Level

 P56.GOB

 A dry room with a pool in the floor and an air tank leaning on the
 wall. Press Use on the tank and you are a diver: in the suit at once,
 breathing like Vader after two seconds under water, and safe from
 drowning for as long as you hold it. It is built to be lifted into
 another level: three files, one template and one placed cog.

 [cogfactory / p56-scuba]

 BUILD 6  --  2026-09-30   (first packaged 2026-09-22)
 sha256 e4f095470ef9658e575b302b9fb42bbb

 This level is not made or distributed by LucasArts
 Entertainment Co.
========================================================================

THIS PACKAGE
------------------------------------------------------------------------
  P56.GOB          the level
  readme.txt       this file
  screenshot.jpg   what it looks like

INSTALLATION
------------------------------------------------------------------------
Drop P56.GOB into the `episode` folder of a Jedi Knight install (the one
that already has JK1.GOB in it) and pick it from the episode list.
Nothing else needs to change: the GOB carries its own level, cogs,
models, materials and sounds, and reads the stock resource/ for
everything else.

On a libretro frontend such as RetroArch, unzip first and put P56.GOB in
the episode folder of your Jedi Knight folder, then load it from there.
The core treats the folder above the GOB's folder as the game folder,
reads the episode from episode/P56.GOB inside it, and will not start
unless that folder also holds resource/ (Res2.gob) from a real install.

CONTROLS
------------------------------------------------------------------------
  Use (Space) on the tank takes the gear -- look down at it, it is small
  Use (Space) on the tank again to hand it back
  F1 toggles the external camera, to see the suit

NOTES
------------------------------------------------------------------------
  the suit is on from the moment the tank is taken until it is handed
  back, in or out of the water, and the air is unlimited. Both are the
  cog's defaults; one number each in the level's COGS row changes them
  (see ADDING IT TO YOUR OWN LEVEL)

  the tank is a locker, not an inventory item: it stays on the wall,
  and every take is a full tank

  the breathing loop starts after 2 seconds under water and stops at
  the surface

  with the default unlimited tank there is no text on screen at all. A
  finite tank says Scuba tank: 75% air, then 50%, 25% and 5%, then
  Scuba tank empty! -- and when it empties the suit comes off 2
  seconds later and drowning resumes

  everything named scb* is the kit; everything named p56*, and
  scuba.jkl, is this demo room and is not meant to be copied into
  another level

  five of the diving suit's seven textures in this GOB are flat-colour
  stand-ins (divetank, wetsuit, divereg, divelense, cressifins);
  darker.mat and bluesuede.mat are the real ones

  tested in OpenJKDF2 only; retail JKDF2 has not run it

ADDING IT TO YOUR OWN LEVEL
------------------------------------------------------------------------
  This GOB is a small kit inside a demo room. Everything named scb* is
  the kit, and is what you copy. Everything named p56*, and scuba.jkl,
  is the demo room and stays here. The diving suit is neither: it is
  Gnarly's scubakatarn.3do, and your level brings its own copy of it.

  1. WHAT TO COPY

  Open P56.GOB with any GOB tool (ConMan, for one) and extract three
  files into your level's project folder, or add them to its GOB,
  keeping the folder names:

      cog\scbscuba.cog
      3do\scbtank.3do
      sound\scbbreth.wav

  Your level must ALREADY have the suit, from its author:

      3do\scubakatarn.3do
      mat\darker.mat        mat\divetank.mat      mat\bluesuede.mat
      mat\wetsuit.mat       mat\divereg.mat       mat\divelense.mat
      mat\cressifins.mat

  (kyhand.mat, the eighth material the suit names, is stock.) The
  scubakatarn.3do in P56.GOB is the author's file, byte for byte, and
  darker.mat and bluesuede.mat are the real Club OC files. The other
  five materials in P56.GOB -- divetank, wetsuit, divereg, divelense
  and cressifins -- are FLAT-COLOUR STAND-INS made for this demo. Do
  NOT copy them over the real ones.

  scbtank.3do is the tank cut off the suit's back. It uses three of
  the suit's materials, by the suit's names: bluesuede.mat,
  divetank.mat and darker.mat.

  2. THE TEMPLATE

  Exactly one new template is needed, for the tank. It is ONE line:

scbtank          _structure       model3d=scbtank.3do collide=1 size=0.040000 movesize=0.040000 thingflags=0x408

  Its parent, _structure, is stock. Put the line in the template list
  your editor reads (master.tpl for JED and ZED), or straight into the
  JKL's TEMPLATES section with "World templates" raised by one. In
  master.tpl, these two comment lines above it give the editor a name
  to show and a box to draw:

# DESC:   Scuba tank (scbscuba.cog)
# BBOX: -0.0078 -0.0077 -0.0275 0.0078 0.0077 0.0275

  collide=1 matters. _structure alone would give the tank collide=3,
  face collision, and then the player's Use ray has to land on a face
  of a mesh 0.055 tall and 0.016 across -- and it misses, without a
  word. With collide=1 the 0.04 sphere around the tank's middle is
  what gets pressed.

  The SUIT needs NO template. The cog puts it on the player with
  SetThingModel; the level only has to be able to load
  3do\scubakatarn.3do.

  3. PLACING THE TANK

  Place one thing of template scbtank. Its origin is the middle of the
  tank, and the tank is 0.055 tall, so standing upright on a floor it
  sits at z = floor + 0.0275. The demo leans it 6 degrees against a
  wall instead: pitch -6, z = floor + 0.0281, its middle 0.0093 out
  from the wall. The player presses Use on it from within about 0.2
  units, looking AT it -- it is small and low, so that means looking
  down.

  The tank stays where you put it. It is a locker, not a pickup: Use
  takes the gear, Use again hands it back.

  4. THE COG

  Add scbscuba.cog to the level's placed cogs, ONCE. It takes three
  values, in this order:

      tank       the thing number of the tank you placed
      capacity   seconds of air; 0 = unlimited (the default)
      suitmode   1 = the suit is worn whenever the tank is held
                     (the default)
                 0 = only while the player is in water
                 2 = only while he is submerged

  In a JKL that is one row of the COGS section. This is the demo's,
  where the tank is thing 1:

0:	scbscuba.cog	1	0.000000	1

  A 60 second tank whose suit goes on only in the water would be this
  instead:

0:	scbscuba.cog	1	60.000000	0

  5. WATER

  Nothing to mark. Any sector with the water flag (0x2) works; the cog
  finds out for itself whether the player is in it and whether he is
  under the surface. Drowning is the game's own, and it comes back the
  moment the gear is handed back or a finite tank runs dry.

  6. WHAT THE PLAYER GETS

  Use on the tank takes the gear; Use on it again hands it back. While
  he holds a tank with air in it he does not drown. The breathing loop
  starts after 2 seconds under water and stops when he surfaces. In
  suitmode 0 and 2 the suit comes off 2 seconds after it stops being
  wanted (get back in the water inside that and it stays on); handing
  the tank back takes it off at once, in every mode. The suit is a
  third-person model: he sees it on the external camera (F1).

  7. TEXT ON SCREEN

  With an unlimited tank: none. With a finite one, these five lines,
  once each per tank:

      Scuba tank: 75% air
      Scuba tank: 50% air
      Scuba tank: 25% air
      Scuba tank: 5% air
      Scuba tank empty!

  The cog also has a debug trace, every line of which starts with SCB.
  It sits behind "int dbg" in the cog's symbols, which is 0 in this
  package; set it to 1 to watch the cog think.

  8. LIMITS

  One tank and one instance of the cog per level. A second instance
  would be a second state machine editing the same player.

  Single-player is what has been tested. The cog is flags=0x240: it
  runs locally, is not synchronised, and edits only the local player.
  Read from the engine source and NOT tested: in a multiplayer game
  each machine would run its own copy for its own player, and the suit
  model, the air flag and the breathing would change on that machine
  only -- nothing is sent -- so other players would go on seeing the
  ordinary player model.

  Tested in OpenJKDF2 only. Retail JKDF2 has not run it.

  If you edit the JKL by hand, it must have room for what this adds:
  two entries in "World models" (scbtank.3do, and scubakatarn.3do if
  nothing else in the level uses it), the suit's seven materials in
  "World materials", one entry in "World sounds", and one each in
  "World templates", "World things", "World cogs" and "World scripts".
  An editor works these out when it saves.

WHAT IS IN THE GOB
------------------------------------------------------------------------
        88  episode.jk
      7848  jkl\scuba.jkl
     24845  cog\scbscuba.cog
     55304  3do\scbtank.3do
    105893  3do\scubakatarn.3do
    172172  mat\bluesuede.mat
      2188  mat\cressifins.mat
    131212  mat\darker.mat
      2188  mat\divelense.mat
      2188  mat\divereg.mat
      2188  mat\divetank.mat
      2188  mat\p56ceil.mat
      2188  mat\p56floor.mat
      2188  mat\p56pool.mat
      2188  mat\p56wall.mat
      2188  mat\wetsuit.mat
    225020  sound\scbbreth.wav

    742074  bytes in 17 entries

CREDITS
------------------------------------------------------------------------
  The diving suit, scubakatarn.3do, is by Gnarly, from his level Club
  OC. It is in this GOB exactly as he made it, under his name.
  darker.mat and bluesuede.mat are his textures from the same level.
  scbtank.3do is the tank cut off that suit's back. It, the cog, the
  demo room and the five stand-in textures were generated by
  tools/cogfactory in the openjkdf2_libretro tree. The breathing loop,
  scbbreth.wav, is vaderbreath.wav converted to 22050 Hz mono.

  The readme format is styled after SM Sith Lord's cog headers,
  because his are the ones everybody read.

========================================================================