Direct a Shot: Take the Camera and Fly It
This is the one where you get to be the cinematographer.
Everything so far has been about making a ship do something. This time we’re going to make the camera do something: take it away from the game, put it 40 metres off the nose of a vessel, aim it at a point on the hull, glide it in over six seconds, and then give it politely back.
And: this is the good bit: there is no camera API to learn. The camera is a folder of files under
/sim/camera. echo a number into one and the view changes. Everything below is cat and echo.
Before you start
Section titled “Before you start”-
Reference Frames: the camera is placed relative to something, in some frame, so the vocabulary matters. You don’t need any math from that page, just the idea.
-
A vessel to point at. Teleport a Fleet into Orbit gets you one in a clean orbit in a single command if you don’t already have a good subject.
If /sim/camera doesn’t exist, the camera_enabled config gate is off: it’s on by
default. cat /sim/camera/info prints the caps and the exact token vocabularies your build accepts.
The idea in one picture: anchor, frame, offset
Section titled “The idea in one picture: anchor, frame, offset”A camera position is only meaningful once you say what it’s measured about and which way the axes point. gatOS splits that into three files, and that split is the whole mental model:
anchor what the pose is measured about vessel:apollo11 frame which way the axes point bodyfixed position where in that frame -40 0 -6 ← 40 m aft, 6 m upChange the anchor and the same numbers follow a different ship. Change the frame and the same numbers mean a different direction. The camera is re-resolved every rendered frame, so if the anchor is a moving vessel, the camera moves with it for free.
The six frame tokens are ecl, cce, bodyfixed, enu, lvlh and chase. We’ll use
bodyfixed: the ship’s own axes, laid out aircraft-style exactly like the RCS controls:
+X nose ↑ │ −Y ←───┼───→ +Y left ← • → right │ ↓ −X tail
third axis, through the belly: +Z = down , −Z = upSo -40 0 -6 is 40 metres behind the nose and 6 metres above the hull: and, as ever, “up” is
negative Z.
Aim is a target, not a rotation
Section titled “Aim is a target, not a rotation”You could write a quaternion into camera/pose/rotation, but you almost never want to. Instead you
name a target and an offset, and gatOS re-resolves it every frame:
camera/pose/aim←<target> [off <x> <y> <z>] [frame <frame>] [up <up>] [roll <deg>]
The offset is measured on the subject, in the subject’s own frame: which is what makes
off 0 0 -1.2 stay glued 1.2 m above a moving ship’s origin instead of drifting off into space.
Borrowing, not owning
Section titled “Borrowing, not owning”One more idea and we can fly. camera/enabled 1 takes the camera: gatOS snapshots where the
game had it, unfollows it, and becomes the only writer. camera/enabled 0 gives it back, eased,
exactly where it was. Everything you write in between is an override layered on that snapshot :
so an override you never set simply falls through to how the game had it.
Step 1: take it, place it, aim it
Section titled “Step 1: take it, place it, aim it”Six writes. Pick a transport and the rest of the page follows your choice.
echo 1 > /sim/camera/enabled # take the cameraecho "vessel:apollo11" > /sim/camera/pose/anchor # measure everything about this shipecho "bodyfixed" > /sim/camera/pose/frame # in the ship's own axesecho "-40 0 -6" > /sim/camera/pose/position # 40 m aft, 6 m aboveecho 42 > /sim/camera/pose/fov # degreesecho "vessel:apollo11 off 0 0 -1.2 up world" > /sim/camera/pose/aimRead it back: every leaf reports the composed effective value, not the last thing you wrote, so a read-then-write-it-straight-back is a no-op:
cat /sim/camera/status # one "key value…" line per channelcat /sim/camera/target # the follow target's bare id, or "-"Every /sim leaf is mirrored field-by-field at /v1/fs/<path>: the path after /v1/fs/ is
exactly the /sim-relative path, and a POST body is exactly the text you’d have echoed:
H=http://127.0.0.1:4242/v1/fscurl -X POST --data '1' $H/camera/enabledcurl -X POST --data 'vessel:apollo11' $H/camera/pose/anchorcurl -X POST --data 'bodyfixed' $H/camera/pose/framecurl -X POST --data '-40 0 -6' $H/camera/pose/positioncurl -X POST --data '42' $H/camera/pose/fovcurl -X POST --data 'vessel:apollo11 off 0 0 -1.2 up world' $H/camera/pose/aimRead it back with a plain GET: same paths, raw text out:
curl -s $H/camera/statuscurl -s $H/camera/targetStep 2: fly it, route A: a timed batch
Section titled “Step 2: fly it, route A: a timed batch”A static shot is a screenshot. To make it a move, we drive those same leaves along a timeline.
/sim/ctl/timed_batch takes a little script of <offsetMs> <path> <payload> lines: offsets are
absolute milliseconds from the schedule’s start: followed by commit:
cat > /tmp/push-in.tb <<'EOF'@id push-in@clock render
0 camera/pose/position -40 0 -6 bodyfixed0 camera/pose/fov 421500 camera/pose/position -33 0 -5.2 bodyfixed1500 camera/pose/fov 383000 camera/pose/position -25 0 -4.4 bodyfixed3000 camera/pose/fov 324500 camera/pose/position -18 0 -3.6 bodyfixed4500 camera/pose/fov 276000 camera/pose/position -12 0 -3 bodyfixed6000 camera/pose/fov 24commitEOF
cat /tmp/push-in.tb > /sim/ctl/timed_batch # validates, then startscat /sim/ctl/schedules/push-in/state # pending | running | paused | done | failedThe batch file is an ordinary writable leaf, so the whole script goes in one POST body:
curl -X POST --data-binary @push-in.tb \ http://127.0.0.1:4242/v1/fs/ctl/timed_batch
curl -s http://127.0.0.1:4242/v1/fs/ctl/schedules/push-in/stateFive keyframes at 1.5-second intervals is coarse: it will step rather than glide. Two things fix that, and neither costs you anything:
- Write more lines. The scheduler coalesces to the last write per leaf per tick, so a 20 Hz
script costs one write per leaf per frame no matter how finely you sample it. Generating those
lines is exactly what
examples/shotbuilderdoes. - Ask for smoothing.
echo 0.35 > /sim/camera/pose/smoothing(seconds, 0–10) puts a critically damped filter on the pose, which turns coarse steps into a glide.
Step 3: fly it, route B: a JSON track
Section titled “Step 3: fly it, route B: a JSON track”The same move can be handed over as a track: a small JSON document of shots and keys that gatOS
interpolates itself, at render rate, with real easing and splines. Upload it as a file under
/sim/camera/track/ and play it by name.
{ "shots": [ { "name": "push-in", "t": 0, "duration": 6, "anchor": "vessel:apollo11", "position": { "mode": "cartesian", "curve": "linear", "frame": "bodyfixed", "keys": [ { "t": 0, "v": [-40, 0, -6], "ease": "out", "ease_power": 3 }, { "t": 6, "v": [-12, 0, -3] } ] }, "aim": { "target": "vessel:apollo11", "offset": [0, 0, -1.2], "up": "world" }, "fov": { "keys": [ { "t": 0, "v": 42, "ease": "out" }, { "t": 6, "v": 24 } ] } } ]}Two keys and an ease replace the whole ladder of batch lines: and notice that every channel name in
there (position, frame, aim, fov) is the same channel you wrote by hand in step 1. It really
is one surface.
A host folder shows up in the guest at /mnt/<name>, so you can author on your own machine and
copy the file straight in. The upload is parsed and validated on close:
cp /mnt/shots/push-in.json /sim/camera/track/push-inecho "push-in" > /sim/camera/play # optionally: "push-in at 2 rate 0.5 loop 1"While it plays, it appears in the schedule registry as /sim/ctl/schedules/camera/, and you can
scrub, re-rate, pause or stop it live:
cat /sim/camera/playback # <state> <t_ms> <duration_ms> <shot> <index> <rate> <loop>echo "t 4 rate 0.25" > /sim/camera/set # scrub to 4 s and go quarter-speedecho 1 > /sim/camera/stopThe track file is a leaf like any other, so a POST uploads it:
H=http://127.0.0.1:4242/v1/fscurl -X POST --data-binary @push-in.json $H/camera/track/push-incurl -X POST --data 'push-in' $H/camera/play
curl -s $H/camera/playbackcurl -X POST --data 't 4 rate 0.25' $H/camera/setcurl -X POST --data '1' $H/camera/stopStep 4: hand it back
Section titled “Step 4: hand it back”Two verbs, and the difference is the approach, not the result:
echo 0 > /sim/camera/enabled # eased blend back to where the game had it (~0.6 s)echo 1 > /sim/camera/release # hard cut: the "give it back NOW" buttonAnd if you just want to undo your own writes without giving up the camera:
echo 1 > /sim/camera/pose/reset # clears your overrides; an active track keeps playingH=http://127.0.0.1:4242/v1/fscurl -X POST --data '0' $H/camera/enabled # eased blend backcurl -X POST --data '1' $H/camera/release # hard cutcurl -X POST --data '1' $H/camera/pose/reset # clear your overrides onlyEither way the camera lands back on its original mode, follow target, position, rotation and field of view: and the blend recomputes its destination every frame, so handing back onto a moving follow target lands on the target, not on where it used to be.
Run it
Section titled “Run it”# take it and park itecho 1 > /sim/camera/enabledecho "vessel:apollo11" > /sim/camera/pose/anchorecho "bodyfixed" > /sim/camera/pose/frameecho "-40 0 -6" > /sim/camera/pose/positionecho "vessel:apollo11 off 0 0 -1.2 up world" > /sim/camera/pose/aimecho 0.35 > /sim/camera/pose/smoothing
# poke it by hand and watch the view moveecho 24 > /sim/camera/pose/fovecho 120 > /sim/camera/pose/orbit/radiusecho 90 > /sim/camera/pose/orbit/azimuth
# give it backecho 0 > /sim/camera/enabledYou should see the view snap to a quarter-behind, slightly-high shot of the ship, hold its aim as the ship rotates, tighten to a 24° telephoto, then swing around to the side as you write the orbit channels: and finally glide back to whatever the game was showing before you started.
What’s next
Section titled “What’s next”You now have the third kind of thing gatOS lets you drive: not the ship, not the world, but the view. The natural follow-ons:
- Cut between subjects.
camera/pose/anchorandcamera/pose/aim_targetboth takevessel:<id>,body:<id>andpart:<vessel-id>/<instance-id>: so you can aim at a specific engine bell, or at an EVA kitten’s head, and the offset stays glued to it as it moves. - Cue a shot off a maneuver. A timed batch is just a schedule; start one at the same moment you fire an impulse or a burn and you have a cut, on the beat.
- Chase something you flew there yourself. Point the camera at the target of the Searchlight tracker and watch the tracker work from the outside.