First person body log¶
The dated record of the first person body: the faults reported against it, what caused each and what was changed, oldest first. It is a log, so an entry describes the code as it was on its date, and the passages are kept as they were written on the body's page, so "above" and "below" in them point at that page as it was then. How the body works today is in The first person body.
New entries go at the end, under a ## heading that ends with the date.
Five reported faults, four causes, 2026-08-22¶
Observed against a local Mineclonia server (Luanti 5.16.1, world
goanna_fpbody, port 30821) with Godot 4.5.1 on Linux, on branch
first-person-body. Screenshots were framed by script so the before and
after halves are comparable.
The body filled the screen, and you were inside it¶
Reported as a wall of skin at eye height in ordinary screenshots. It is not
the head, and it is not terrain. The cause is that
GoannaClient::set_player_pose is handed the camera position and stored it
as the local player's position:
p->setPosition(luanti * BS);
Every other reader of that value takes it for the feet. The collision box
stands on it, step_interact starts the dig ray an eye offset above it, and
the first person body is drawn from it. Setting it to the eye put the body's
feet at eye height, so the legs and waist rose through the lens.
It only shows in fly mode, because that is the only mode that calls
set_player_pose every frame; walking goes through step_player, which
returns an eye position the camera is set from and leaves the player's feet
alone. That is exactly why every agent hit it: the control channel's pose
command turns fly mode on by default, so every scripted screenshot is taken
in the one mode where it breaks.
The fix is to put the feet an eye height below the camera the caller gave us. Switching fly off no longer jumps the camera up by an eye height either, and the dig ray in fly mode now starts at the eye rather than 1.6 nodes above it. What Goanna reports to the server is unchanged, so block streaming still centres on the camera.
The old workaround, nudging the body 1.2 BS units (0.12 nodes) backwards along the look yaw to keep the shoulders out of the lens, is gone with it. It was too small to hide a head and large enough to put your legs and your shadow twelve centimetres behind your feet.
The shadow had no head¶
To keep the head out of the camera, ModelAnimator collapses the model's
Head joint to a hundredth of its size each step. One skinned mesh cannot
be headless to the camera and whole to the light, so the head left the
shadow pass with it.
The player model is a single skinned MeshInstance3D with several surfaces
under one Skeleton3D, so per surface cast_shadow cannot separate the
head, and camera cull masks cannot either: the head is a bone, not an
object. So the local player now gets a second copy of the same mesh on its
own skeleton. ModelAnimator::step takes an optional second skeleton and
writes the same pose into it without the shrink; that copy is
SHADOW_CASTING_SETTING_SHADOWS_ONLY and the copy the camera sees is
SHADOW_CASTING_SETTING_OFF. The cost is one extra skinned draw for one
entity.
"Show own body" now hides only the copy the camera sees, so turning it off leaves you a complete shadow to judge the sun by, which is what a player turning it off actually wants.
The arm pointed backwards, and was not where the tool was¶
Two causes, and the second is the interesting one.
Goanna posed the arm with an absolute euler of its own on
Arm_Right_Pitch_Control. Mineclonia's character model bakes a half turn
into that joint's rest and expresses it as a scale of (1, -1, -1), which is
why the game's own animation code carries a table of "bone workaround
scales". Replacing the joint's rotation while leaving that scale alone
mirrors the arm. It also threw away the pitch the game itself sets on that
bone for the held item, every step, which was already correct.
So Goanna no longer replaces the arm's rotation. It adds to it, in the same euler degrees and the same inverted storage a Luanti bone override uses, and only while the swing is running, so at rest the arm is exactly where the model and the server left it. Which arm is yours is now decided by the bone the game hangs the wield item from rather than by which arm projects to the camera's right, which was a guess that only held for models laid out the way Luanti's character is.
The second cause is that Goanna never told the server it was digging.
GoannaSession::writePlayerPosTo sent the keypress field of
TOSERVER_PLAYERPOS as a hardcoded zero, so no game ever saw the local
player's dig button down, and no game ever played its mining animation on
the body. Mineclonia switches to its mine animation and aims the arm down
the look direction while that button is held, and none of that reached us.
Goanna now reports the dig and place bits, which is what a vanilla client
sends and what player:get_player_control() reads. Movement and sneak are
still not reported.
With the game driving the arm during a dig, Goanna's own swing would only overshoot, so it stands down whenever the server has a bone override on the arm bone. It stays as the fallback for a game that never touches it.
Confirmed by reading the animation frame while digging: the local player's frame goes from about 3 (standing) to about 193 (mining) while the button is held and back to 3 when it is released.
The magenta shape at the hand¶
Reported separately, and not reproduced here: a large flat magenta shape where the hand and wield item are drawn, in one case filling the upper half of the frame after a teleport. It was not seen in any of the forty odd frames captured for this work, before or after, including at (3000, 90, 3000) on a freshly generated Mineclonia world, and a pixel scan of all of them found no magenta at all.
What can be said from the code is that Goanna has exactly one flat magenta,
Color(0.8, 0.3, 0.8) in EntityRenderer::materialForTexture, used when a
texture name does not resolve. It is an entity colour, so the shape is an
entity, and the two entities at your hand are your own body and the wield
item attached to its Wield_Item bone. Luanti's own unknown_node.png is a
checkerboard, so a clean shaded magenta face is not that.
The most likely path to it is the placeholder in
EntityRenderer::rebuildVisual: a model that fails to load falls back to a
capsule sized by the entity's collision box, tinted with its first texture,
and if that texture does not resolve either the capsule is magenta. For the
local player that capsule is your own collision box, so the camera is inside
it, and before the placement fix above its feet were at the eye in fly mode,
which would put a magenta wall across the upper half of the frame. That is
the third report exactly.
Two guards are in place now. EntityRenderer::modelFor no longer caches a
failed load, so one lookup that ran before the model's media arrived cannot
condemn that model to the placeholder for the whole session. And the local
player no longer falls back to the placeholder at all: nothing is better
than a magenta capsule around the camera, and the body returns on the next
visual version once the model loads.
If it recurs, tools/goanna-control set show_body 0 decides it: if the
shape goes, it is the body, and if it survives it is the wield item, which
is the same fallback one entity along. The client log names the cause
either way, with "Goanna: could not load model" from ModelLoader::load or
"Goanna: mesh not found in media" from ModelCache::getMesh.
The game's own local animations, 2026-09-19¶
A game can give the player's own model animations that the client plays
from its own controls: player:set_local_animation(idle, walk, dig,
walk_while_dig, speed), which arrives as TOCLIENT_LOCAL_PLAYER_ANIMATIONS.
The vanilla client plays them whenever it shows the local player's model,
which for it means third person, and does not interrupt them with a server
animation on the first track that uses one of the same frame ranges. Goanna
ignored the packet, so the body only ever played what the server sent.
Goanna now handles it, and the body plays those animations whenever it is
drawn, through upstream's own setLocalPlayerAnimation and the local player
part of GenericCAO::step in src/transplant/client/content_cao.cpp. The
dig and place buttons are recorded in the player's controls for it, as the
vanilla client records them.
This mattered because Goanna reported no movement keys to the server until
2026-09-29 (see below), so a game that animates walking from
get_player_control() never told the body to walk. Minetest Game's player_api
sets local animations, so on a fresh Minetest Game world (Luanti 5.17.0 Flatpak,
Godot 4.5.1) the body now plays its walk range, frames 168 to 187, while W is
held, although the server's own animation is still standing, and its dig range,
189 to 198, while digging; the same held against a Luanti 5.10.0 server.
Mineclonia sets no local animations, so nothing changes there: the body stands
while walking, and digging plays the server's mining range, 189 to 198, as
before.
What was still wrong, 2026-09-29¶
The body page's list of what was still wrong, as it stood after the work of 2026-09-29 (the lean over the body looking down, third person, and the whole keypress field).
Looking straight down used to fill the lower half of the frame with the top
of your own torso and an open neck, because the camera pitched about the
eye, and Mineclonia's eye height (1.5) is the model's shoulder line. Since
2026-09-29 the camera leans out over the body as the gaze drops, 0.45 nodes
forward and a little up when looking straight down (_body_lean in
main.gd), so it shows the front of the shirt and an arm rather than the
inside of the neck. The legs are still mostly hidden by the chest at that
height. The dig ray follows the camera (set_view_offset), as the vanilla
client's shootline follows its camera, so the crosshair still covers what
is pointed at.
The third person views (F7) draw the whole body, head included, whatever
"Show own body" says: EntityRenderer::setThirdPerson turns the head
shrink off.
The swing is smaller than a vanilla client's, because a vanilla client also gets the server's mining animation from the moment the button goes down, while Goanna's own swing only fires when the game leaves the arm bone alone. On Mineclonia the game does drive it, so this only shows on games that do not.
Since 2026-09-29 Goanna reports the whole keypress field, as the vanilla
client does, from PlayerControl::getKeysPressed: movement, jump, aux1,
sneak, dig and place. Before, it sent dig and place only, and Mineclonia,
which picks the walk animation from those keys, showed a body standing still
while it walked. Checked in third person on test_world: the legs and arms
swing while W is held. Sneak now reaches the server too, which on Mineclonia
changes the eye height and the model's pose, as it does for a vanilla
player.
Whether the body should be drawn at all under the free flying debug camera is open. You are not in your body in that mode: the camera has detached from the physics and the body follows it around like a ghost. Hiding it there would want to be conditional on the camera having detached rather than on the "Show own body" setting, and would want to keep the shadow, so that the frame still tells you where the player is.
The dig swing checked, 11 October 2026¶
The owner checked the dig in play on 11 October 2026 and found it fine. That closes the 29 September note that the swing was smaller than a vanilla client's, which was written before the mining clock drove a stroke of Goanna's own while digging.