Skip to content

Commit e2e6506

Browse files
committed
feat(api)!: consistency sweep — colors 0-255 everywhere, degrees everywhere, Texture.handle
Three silent inconsistencies that forced users to read engine source (audit Tier-3), fixed as one coordinated breaking pass with all engine examples migrated and docs/migration-0.5.md as the guide: - setSceneNodeColor / setOutlineColor / setSceneNodeWaterMaterial took 0-1 floats — the only color params in the API that did; passing a Colors preset silently rendered white. Now 0-255 like every draw* call (wrappers divide before the FFI; native stays 0-1). Light colors deliberately stay 0-1 float + intensity (radiometric, like Unity/Unreal) and are documented as such. World-format tints stay 0-1 (versioned serialized data); the loader converts. - drawModelRotated took radians while Camera2D.rotation was degrees; now degrees everywhere user-facing (raylib convention). - Texture.id -> Texture.handle (was the lone outlier among resource types). Plus documentation that existed nowhere user-facing: coordinate system + SI units at the top of the physics and scene modules, immediate-vs- retained-mode guidance, loadTexture failure semantics, light-color convention, @internal markers on the *Raw compiler-workaround variants. All examples + src/index.ts pass perry check.
1 parent 576c02f commit e2e6506

14 files changed

Lines changed: 186 additions & 35 deletions

File tree

docs/migration-0.5.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Migrating to Bloom 0.5
2+
3+
0.5 makes the API consistent in three places where conventions silently
4+
diverged. Each change is breaking on purpose — the old inconsistencies
5+
caused invisible bugs (colors that rendered white, rotations that were
6+
60× too fast). All engine examples are already migrated and serve as
7+
references.
8+
9+
## Surface colors are 0–255 everywhere
10+
11+
`setSceneNodeColor`, `setOutlineColor`, and the color part of
12+
`setSceneNodeWaterMaterial` previously took 0–1 floats — the only places
13+
in the API that did. They now take 0–255 like every `draw*` call and the
14+
`Colors` presets.
15+
16+
```ts
17+
// before // after
18+
setSceneNodeColor(node, 0.75, 0.75, 0.7); setSceneNodeColor(node, 191, 191, 179);
19+
setSceneNodeColor(node, c.r/255, c.g/255, …) setSceneNodeColor(node, c.r, c.g, c.b, c.a); // Colors presets now just work
20+
```
21+
22+
Symptom of unmigrated code: scene nodes render almost black (values
23+
divided twice).
24+
25+
**Unchanged:** light colors (`addDirectionalLight`, `addPointLight`)
26+
stay 0–1 floats with a separate intensity — that's the radiometric
27+
convention (Unity and Unreal do the same), and light color × intensity
28+
can meaningfully exceed 1.0. The `*.world.json` format also keeps 0–1
29+
tints (serialized data is versioned separately); the loader converts.
30+
31+
## Angles are degrees everywhere
32+
33+
`drawModelRotated`'s `rotY` was radians; `Camera2D.rotation` was degrees.
34+
Everything user-facing is now degrees (the raylib convention).
35+
36+
```ts
37+
// before // after
38+
drawModelRotated(m, p, 1.0, Math.PI / 2, t); drawModelRotated(m, p, 1.0, 90, t);
39+
```
40+
41+
Symptom of unmigrated code: models spin ~57× faster than intended.
42+
43+
**Unchanged:** physics angular velocity stays radians/sec (SI, matches
44+
Jolt), and quaternions are quaternions.
45+
46+
## `Texture.handle` (was `Texture.id`)
47+
48+
`Texture` was the only resource type whose handle field was named `id`;
49+
`Sound`, `Music`, `Font`, and `Model` all use `handle`.
50+
51+
```ts
52+
// before // after
53+
myAtlas.id myAtlas.handle
54+
```
55+
56+
## Also in 0.5 (non-breaking)
57+
58+
- `physics.step(world, dt)` is now fixed-timestep with an accumulator
59+
and returns the interpolation alpha; `physics.stepVariable` is the
60+
old exact-dt behavior. See docs/physics.md "Stepping".
61+
- Stale handles (use-after-free/destroy) now fail lookups instead of
62+
aliasing whatever object reused the slot.
63+
- `*Raw` function variants are documented `@internal` — they exist only
64+
as a compiler workaround and will be removed.
65+
- Coordinate system is now documented at the top of the physics and
66+
scene modules: right-handed, Y-up, meters, SI units.

examples/pbr-spheres/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ for (let row = 0; row < GRID_N; row = row + 1) {
195195

196196
const node = createSceneNode();
197197
attachModelToNode(node, sphereHandle, 0);
198-
setSceneNodeColor(node, BASE_R, BASE_G, BASE_B);
198+
setSceneNodeColor(node, BASE_R * 255, BASE_G * 255, BASE_B * 255);
199199
setSceneNodePbr(node, roughness, metallic);
200200
setSceneNodeCastShadow(node, false);
201201
setSceneNodeReceiveShadow(node, false);

examples/renderer-test/main.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ function placeSphere(
231231
roughness: number, metalness: number,
232232
): number {
233233
const node = placeNode(sphereHandle, 0, px, py, pz, scale, scale, scale);
234-
setSceneNodeColor(node, cr, cg, cb);
234+
setSceneNodeColor(node, cr * 255, cg * 255, cb * 255);
235235
setSceneNodePbr(node, roughness, metalness);
236236
return node;
237237
}
@@ -243,7 +243,7 @@ function placeCube(
243243
roughness: number, metalness: number,
244244
): number {
245245
const node = placeNode(cubeHandle, 0, px, py, pz, sx, sy, sz);
246-
setSceneNodeColor(node, cr, cg, cb);
246+
setSceneNodeColor(node, cr * 255, cg * 255, cb * 255);
247247
setSceneNodePbr(node, roughness, metalness);
248248
// Thin horizontal slabs (floors) should receive but not cast
249249
// shadows — otherwise they fill the shadow map with their own
@@ -374,7 +374,7 @@ function setupWater(): void {
374374
updateSceneNodeGeometry(waterNode, wv, wi);
375375
const wm = mat4Translate(mat4Identity(), { x: 0, y: 0.2, z: cz });
376376
setSceneNodeTransform(waterNode, wm);
377-
setSceneNodeWaterMaterial(waterNode, 0.15, 1.5, 0.1, 0.3, 0.5, 0.6);
377+
setSceneNodeWaterMaterial(waterNode, 0.15, 1.5, 26, 77, 128, 153);
378378
setSceneNodeReceiveShadow(waterNode, true);
379379

380380
// Rocks / objects sticking out of water
@@ -457,7 +457,7 @@ function setupThinGeometry(): void {
457457
const x = cx - 5.5 + i * 1.0;
458458
const node = createSceneNode();
459459
attachModelToNode(node, cubeHandle, 0);
460-
setSceneNodeColor(node, 0.6, 0.6, 0.62);
460+
setSceneNodeColor(node, 153, 153, 158);
461461
setSceneNodePbr(node, 0.3, 1.0);
462462
setSceneNodeCastShadow(node, true);
463463
setSceneNodeReceiveShadow(node, true);

examples/scene-graph/interactive.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ let selectedWallId: string | null = null;
5252
const floorHandle = createSceneNode();
5353
const floorPolygon = [-10, -10, 10, -10, 10, 10, -10, 10];
5454
extrudePolygon(floorHandle, floorPolygon, 0.02);
55-
setSceneNodeColor(floorHandle, 0.85, 0.85, 0.82, 1.0);
55+
setSceneNodeColor(floorHandle, 217, 217, 209, 255);
5656
setSceneNodePbr(floorHandle, 0.7, 0.0);
5757

5858
// Handle → wall ID lookup (for picking)
@@ -112,9 +112,9 @@ function wallSystem(dt: number): void {
112112

113113
// Color based on selection
114114
if (wall.id === selectedWallId) {
115-
setSceneNodeColor(wall.handle, 0.3, 0.6, 1.0, 1.0);
115+
setSceneNodeColor(wall.handle, 77, 153, 255, 255);
116116
} else {
117-
setSceneNodeColor(wall.handle, 0.95, 0.95, 0.92, 1.0);
117+
setSceneNodeColor(wall.handle, 242, 242, 235, 255);
118118
}
119119
setSceneNodePbr(wall.handle, 0.8, 0.0);
120120

examples/scene-graph/main.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,10 @@ const floorIdx: number[] = [0, 1, 2, 0, 2, 3];
149149
updateSceneNodeGeometry(floor, floorVerts, floorIdx);
150150

151151
// Set materials
152-
setSceneNodeColor(wall1, 0.95, 0.95, 0.92, 1.0);
153-
setSceneNodeColor(wall2, 0.92, 0.92, 0.88, 1.0);
154-
setSceneNodeColor(wall3, 0.90, 0.90, 0.86, 1.0);
155-
setSceneNodeColor(floor, 0.7, 0.7, 0.65, 1.0);
152+
setSceneNodeColor(wall1, 242, 242, 235, 255);
153+
setSceneNodeColor(wall2, 235, 235, 224, 255);
154+
setSceneNodeColor(wall3, 230, 230, 219, 255);
155+
setSceneNodeColor(floor, 179, 179, 166, 255);
156156

157157
// Set PBR properties
158158
setSceneNodePbr(wall1, 0.8, 0.0);

examples/scene-graph/room.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ function slabSystem(dt: number): void {
151151
extrudePolygon(handle, flat, slab.elevation);
152152

153153
// Gray floor material
154-
setSceneNodeColor(handle, 0.75, 0.75, 0.70, 1.0);
154+
setSceneNodeColor(handle, 191, 191, 179, 255);
155155
setSceneNodePbr(handle, 0.6, 0.0);
156156

157157
clearDirty(id);
@@ -203,7 +203,7 @@ function wallSystem(dt: number): void {
203203
}
204204

205205
// White wall material
206-
setSceneNodeColor(handle, 0.95, 0.95, 0.92, 1.0);
206+
setSceneNodeColor(handle, 242, 242, 235, 255);
207207
setSceneNodePbr(handle, 0.8, 0.0);
208208

209209
clearDirty(id);

examples/scene-graph/shadows.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ setDirectionalLight(0.5, 1.0, 0.3, 255, 240, 220, 0.7);
5151
const floor = createSceneNode();
5252
const floorPoly = [-5, -5, 5, -5, 5, 5, -5, 5];
5353
extrudePolygon(floor, floorPoly, 0.05);
54-
setSceneNodeColor(floor, 0.8, 0.78, 0.72, 1.0);
54+
setSceneNodeColor(floor, 204, 199, 184, 255);
5555
setSceneNodePbr(floor, 0.7, 0.0);
5656

5757
// Walls
@@ -69,7 +69,7 @@ function makeWall(sx: number, sz: number, ex: number, ez: number): void {
6969
sx - nx, sz - nz,
7070
];
7171
extrudePolygon(node, poly, 3.0);
72-
setSceneNodeColor(node, 0.95, 0.93, 0.88, 1.0);
72+
setSceneNodeColor(node, 242, 237, 224, 255);
7373
setSceneNodePbr(node, 0.85, 0.0);
7474
}
7575

@@ -88,7 +88,7 @@ function makeBox(cx: number, cy: number, cz: number, w: number, h: number, d: nu
8888
// Offset Y via transform
8989
const t = mat4Translate(mat4Identity(), 0, cy, 0);
9090
setSceneNodeTransform(node, t);
91-
setSceneNodeColor(node, r, g, b, 1.0);
91+
setSceneNodeColor(node, r * 255, g * 255, b * 255, 255);
9292
setSceneNodePbr(node, 0.6, 0.0);
9393
}
9494

src/audio/index.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ export function playMusic(music: Music): void {
6969
bloom_play_music(music.handle);
7070
}
7171

72+
/**
73+
* @internal Compiler workaround — not part of the public API.
74+
* Identical to the non-Raw version but takes primitives instead of
75+
* reading object fields (aarch64 Android Perry miscompilation where
76+
* obj.field reads feeding f64 FFI args arrive as NaN). Use the
77+
* non-Raw version; these disappear when the Perry fix ships.
78+
*/
7279
export function playMusicRaw(handle: number): void {
7380
bloom_play_music(handle);
7481
}
@@ -77,6 +84,13 @@ export function stopMusic(music: Music): void {
7784
bloom_stop_music(music.handle);
7885
}
7986

87+
/**
88+
* @internal Compiler workaround — not part of the public API.
89+
* Identical to the non-Raw version but takes primitives instead of
90+
* reading object fields (aarch64 Android Perry miscompilation where
91+
* obj.field reads feeding f64 FFI args arrive as NaN). Use the
92+
* non-Raw version; these disappear when the Perry fix ships.
93+
*/
8094
export function stopMusicRaw(handle: number): void {
8195
bloom_stop_music(handle);
8296
}
@@ -85,6 +99,13 @@ export function updateMusicStream(music: Music): void {
8599
bloom_update_music_stream(music.handle);
86100
}
87101

102+
/**
103+
* @internal Compiler workaround — not part of the public API.
104+
* Identical to the non-Raw version but takes primitives instead of
105+
* reading object fields (aarch64 Android Perry miscompilation where
106+
* obj.field reads feeding f64 FFI args arrive as NaN). Use the
107+
* non-Raw version; these disappear when the Perry fix ships.
108+
*/
88109
export function updateMusicStreamRaw(handle: number): void {
89110
bloom_update_music_stream(handle);
90111
}
@@ -96,6 +117,13 @@ export function setMusicVolume(music: Music, volume: number): void {
96117
bloom_set_music_volume(music.handle, volume);
97118
}
98119

120+
/**
121+
* @internal Compiler workaround — not part of the public API.
122+
* Identical to the non-Raw version but takes primitives instead of
123+
* reading object fields (aarch64 Android Perry miscompilation where
124+
* obj.field reads feeding f64 FFI args arrive as NaN). Use the
125+
* non-Raw version; these disappear when the Perry fix ships.
126+
*/
99127
export function setMusicVolumeRaw(handle: number, volume: number): void {
100128
bloom_set_music_volume(handle, volume);
101129
}
@@ -104,6 +132,13 @@ export function isMusicPlaying(music: Music): boolean {
104132
return bloom_is_music_playing(music.handle) !== 0;
105133
}
106134

135+
/**
136+
* @internal Compiler workaround — not part of the public API.
137+
* Identical to the non-Raw version but takes primitives instead of
138+
* reading object fields (aarch64 Android Perry miscompilation where
139+
* obj.field reads feeding f64 FFI args arrive as NaN). Use the
140+
* non-Raw version; these disappear when the Perry fix ships.
141+
*/
107142
export function isMusicPlayingRaw(handle: number): boolean {
108143
return bloom_is_music_playing(handle) !== 0;
109144
}

src/core/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ export interface Camera3D {
4646
}
4747

4848
export interface Texture {
49-
id: number;
49+
/** Engine handle — same field name as Sound/Music/Font/Model. (Was
50+
* `id` before v0.5, the lone outlier among resource types.) */
51+
handle: number;
5052
width: number;
5153
height: number;
5254
}

src/models/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ export function drawModel(model: Model, position: Vec3, scale: number, tint: Col
158158
/// Draw a model with a Y-axis rotation (radians). RGBA is packed into
159159
/// a single f64 (ARGB byte order) to keep the FFI to 7 args, dodging
160160
/// the Perry-ARM64 9th-arg quirk.
161+
/**
162+
* Draw a model with a Y-axis rotation in DEGREES (engine-wide angle
163+
* convention, matching Camera2D.rotation and raylib; was radians before
164+
* v0.5). Tint components are 0-255.
165+
*/
161166
export function drawModelRotated(
162167
model: Model, position: Vec3, scale: number, rotY: number, tint: Color,
163168
): void {
@@ -168,7 +173,7 @@ export function drawModelRotated(
168173
const b = tint.b & 0xff;
169174
// Use unsigned-shift-zero to keep the value positive when stored as f64.
170175
const packed = (a | r | g | b) >>> 0;
171-
bloom_draw_model_rotated(model.handle, position.x, position.y, position.z, scale, rotY, packed);
176+
bloom_draw_model_rotated(model.handle, position.x, position.y, position.z, scale, rotY * Math.PI / 180, packed);
172177
}
173178

174179
export function unloadModel(model: Model): void {

0 commit comments

Comments
 (0)