Skip to content

Commit 35251ab

Browse files
r1tsuuclaude
andcommitted
docs: add first-person hand polish plan
Add plans/0040-first-person-hand-polish.md outlining the work needed to improve the first-person hand rendering. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b79f6ce commit 35251ab

1 file changed

Lines changed: 157 additions & 0 deletions

File tree

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# First-Person Hand Polish
2+
3+
## Summary
4+
5+
The current first-person hand looks bad: it uses a sand block texture for the
6+
arm, is too large, sits at an unnatural angle, always renders at maximum
7+
brightness regardless of the environment, and lacks the proportioned
8+
Minecraft-style corner placement that makes a viewmodel feel grounded. This plan
9+
fixes all of that without introducing a full animation framework.
10+
11+
## Problems To Fix
12+
13+
### Wrong texture on the arm
14+
15+
The arm/hand cuboid samples the sand block texture (`BLOCK_IDS.sand`). It needs
16+
a dedicated skin-tone texture tile added to the voxel atlas so the arm looks
17+
like a player arm instead of a sand block.
18+
19+
### Bad proportions and placement
20+
21+
The arm is too wide and too centred. A Minecraft-style hand sits in the
22+
lower-right corner at a visible tilt so it reads clearly as an arm receding into
23+
the screen. Current constants put it roughly centred with minimal inward tilt,
24+
which is why it looks pasted on. The size and offset constants in
25+
`player-model.ts` need to be tuned to:
26+
27+
- Thinner cross-section (roughly 0.14 × 0.14 wide/deep, 0.60 tall)
28+
- Pushed further right and down so only the lower-right corner is visible
29+
- Tilted inward (positive roll on the arm mesh so the face angle points slightly
30+
toward the camera) to give the receding-perspective feel
31+
32+
### No separate viewmodel projection
33+
34+
The hand currently renders in the same world-space projection as terrain, with
35+
depth test disabled as a workaround. This causes it to scale with world FOV,
36+
making it look enormous at wider FOV settings and causing the size to jump when
37+
FOV is changed in settings. The fix is to render the viewmodel with a fixed
38+
narrower FOV (e.g. 70°) independent of the world FOV, the standard approach for
39+
FPS viewmodels. This requires building a second projection matrix during the
40+
viewmodel pass.
41+
42+
### Flat max-brightness lighting
43+
44+
The hand always draws at full brightness because the sky/block light values
45+
passed to the voxel shader are hardcoded to max. It should sample the light level
46+
at the player's current block position so the hand dims appropriately in caves,
47+
under overhangs, and at night.
48+
49+
### Held item floats disconnected from the hand
50+
51+
The held item uses separate position and rotation offsets that don't visually
52+
attach it to the hand. The offsets need to be tuned so the item appears to be
53+
gripped at the top of the visible arm segment rather than floating nearby.
54+
55+
### No movement bob
56+
57+
There is a swing animation but no idle or movement bob. A subtle vertical bob
58+
tied to horizontal movement speed makes the hand feel alive and grounded.
59+
60+
## Key Changes
61+
62+
### Add an arm skin tile to the atlas
63+
64+
- Add a new `arm` tile to the source tile set (a simple skin-tone flat colour, or
65+
a small hand/arm texture consistent with the blocky art style).
66+
- Register it in the content registry as a non-placeable internal block ID
67+
(`PLAYER_ARM_BLOCK_ID`), replacing the current sand fallback.
68+
- Re-run atlas generation (`bun run build:native` or the atlas preprocessing
69+
step) so the tile appears in `voxel-atlas.png`.
70+
71+
### Tune proportions and corner placement
72+
73+
In `player-model.ts`:
74+
75+
- Reduce `FIRST_PERSON_ARM_PART.size` to approximately `[0.14, 0.60, 0.14]`.
76+
- Adjust the position offsets in `player-renderer.ts` so the arm is anchored in
77+
the lower-right corner: increase the right offset, increase the downward
78+
offset, and pull slightly closer (reduce the forward offset).
79+
- Add a roll component to the arm's model matrix so the arm face tilts inward
80+
~25–35°, giving the receding-perspective look.
81+
82+
### Render the viewmodel with a fixed FOV
83+
84+
- In `renderer.ts`, before the viewmodel draw call, compute a secondary
85+
projection matrix using a fixed FOV (70° recommended) instead of the world
86+
camera FOV.
87+
- Pass this alternate projection to the voxel shader uniform for the viewmodel
88+
pass only, then restore the world projection for the next frame.
89+
- Keep near/far planes tight for the viewmodel (near ~0.01, far ~5.0) to avoid
90+
z-fighting with nearby geometry.
91+
92+
### Sample environment lighting at the player position
93+
94+
- In `player-renderer.ts`, read the sky light and block light at the player's
95+
foot or eye block from `ClientWorldRuntime` (the same chunk/lighting data
96+
already used for chunk meshing).
97+
- Pass those values as the `aSkyLight` and `aBlockLight` vertex attributes when
98+
building the arm and held-item mesh each frame, replacing the current hardcoded
99+
max values.
100+
- The existing daylight-aware brightness formula in the fragment shader then
101+
handles dimming automatically.
102+
103+
### Attach the held item to the arm
104+
105+
- Revise the held-item position and rotation offsets in `player-renderer.ts` so
106+
the item renders at the top of the visible arm, as if gripped.
107+
- A good reference: held item should sit slightly left of arm centre, at arm-top
108+
height, and share nearly the same yaw/pitch tilt as the arm rather than having
109+
an independent rotation.
110+
111+
### Add a movement bob
112+
113+
- In `player-renderer.ts`, track horizontal movement speed each frame (derive
114+
from the local player's velocity or position delta).
115+
- Compute a bob phase that advances while the player is moving:
116+
`bobPhase += movementSpeed * dt * bobFrequency`
117+
- Apply a small vertical sine offset to the arm and held-item position:
118+
`bobOffset = sin(bobPhase) * bobAmplitude` where amplitude is ~0.012.
119+
- Keep bob amplitude small enough that it is not nauseating. It should be barely
120+
perceptible but present.
121+
- Blend bob offset smoothly to zero when the player stops moving.
122+
123+
## Important Files
124+
125+
- `plans/0040-first-person-hand-polish.md`
126+
- `apps/client/src/render/player-renderer.ts` — position, rotation, lighting
127+
- `apps/client/src/render/player-model.ts` — geometry size and offset constants
128+
- `apps/client/src/render/renderer.ts` — viewmodel projection and draw order
129+
- `apps/client/assets/shaders/voxel.vert` / `voxel.frag` — lighting uniforms
130+
- `apps/client/assets/textures/tiles/` — source tiles for atlas
131+
- `packages/core/src/world/content-registry.ts` — arm block ID registration
132+
- `packages/core/src/world/item-render.ts` — face geometry helpers
133+
134+
## Out Of Scope
135+
136+
- Left-hand rendering
137+
- Tool-specific animations (mining swing, eating, etc.)
138+
- Offhand slot
139+
- Player skin customisation
140+
- Shadow casting from the viewmodel
141+
142+
## Test Plan
143+
144+
- Manual smoke tests:
145+
- Hand is visibly skin-toned and no longer looks like a sand block
146+
- Hand sits in the lower-right corner at a natural inward tilt
147+
- Changing FOV in settings does not change the apparent hand size
148+
- Walking causes a subtle vertical bob; stopping smoothly ends the bob
149+
- Entering a dark cave causes the hand to dim with the environment
150+
- Night cycle dims the hand consistently with the world lighting
151+
- Held item visually attaches to the top of the arm, not floating nearby
152+
- Swing animation still plays correctly on break/place
153+
- Regression:
154+
- Remote player arm appearance unchanged (shares the same arm block ID but
155+
renders through the world-space player pass, not the viewmodel pass)
156+
- FOV slider still affects world geometry FOV as expected
157+
- No z-fighting between hand and near-clipped world geometry

0 commit comments

Comments
 (0)