Fix ref-count underflow, PSP timezone units, thread restart race, color codegen

- util/ref.c: refUnlock's assert(count >= 0) on an unsigned count was
  tautological, so a double-unlock silently underflowed to UINT32_MAX
  instead of asserting. Now asserts count > 0 before decrementing.
- duskpsp/time/timepsp.c: timeGetRealTimeZonePSP returned hours while
  every other platform (and timeepoch.c's math) expects seconds.
- thread.c: threadHandler reset threadId outside the mutex, after
  signaling STOPPED, letting a caller's immediate threadStart() race
  threadStartRequest()'s "thread id not 0" assert. threadId is now
  reset inside the same locked section.
- tools/color.py: whole-number CSV channels (0, 1) produced invalid C
  float literals (0f, 1f) for the generated COLOR_*_3F/_4F macros.
  Route through float() so they always stringify with a decimal point.

Also adds .claude/code-check.md: a full review pass over every
subsystem in src/dusk/, with the above (plus several other findings
not yet acted on) written up in detail.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 15:02:17 -05:00
parent df9fdf26c8
commit 56230dd340
8 changed files with 927 additions and 23 deletions
+34
View File
@@ -75,6 +75,38 @@ static void test_colorHex_create(void **state) {
assert_int_equal(color.a, COLOR_WHITE.a);
}
// Exercises the color.csv-generated macros directly (COLOR_<NAME>_3F/_4F
// in particular) -- whole-number CSV channels like black/white's 0/1 once
// produced invalid C float literals ("0f"/"1f") in these, so referencing
// them here means a regression fails to compile instead of staying dead
// code no one notices.
static void test_generatedColor_wholeNumberChannels(void **state) {
color3f_t black3f = COLOR_BLACK_3F;
assert_float_equal(black3f.r, 0.0f, 0.0001f);
assert_float_equal(black3f.g, 0.0f, 0.0001f);
assert_float_equal(black3f.b, 0.0f, 0.0001f);
color4f_t white4f = COLOR_WHITE_4F;
assert_float_equal(white4f.r, 1.0f, 0.0001f);
assert_float_equal(white4f.g, 1.0f, 0.0001f);
assert_float_equal(white4f.b, 1.0f, 0.0001f);
assert_float_equal(white4f.a, 1.0f, 0.0001f);
color3b_t red3b = COLOR_RED_3B;
assert_int_equal(red3b.r, 255);
assert_int_equal(red3b.g, 0);
assert_int_equal(red3b.b, 0);
}
// A fractional channel (gray = 0.5) should generate the same way whole
// numbers do -- confirms the float()-based fix didn't just special-case 0/1.
static void test_generatedColor_fractionalChannel(void **state) {
color3f_t gray3f = COLOR_GRAY_3F;
assert_float_equal(gray3f.r, 0.5f, 0.0001f);
assert_float_equal(gray3f.g, 0.5f, 0.0001f);
assert_float_equal(gray3f.b, 0.5f, 0.0001f);
}
int main(int argc, char **argv) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_color3f_create),
@@ -83,6 +115,8 @@ int main(int argc, char **argv) {
cmocka_unit_test(test_color4b_create),
cmocka_unit_test(test_color_create),
cmocka_unit_test(test_colorHex_create),
cmocka_unit_test(test_generatedColor_wholeNumberChannels),
cmocka_unit_test(test_generatedColor_fractionalChannel),
};
return cmocka_run_group_tests(tests, NULL, NULL);
+11 -10
View File
@@ -69,19 +69,20 @@ static void test_thread_data(void **state) {
}
static void test_thread_restart(void **state) {
// A thread can be started, stopped, and started again.
// A thread can be started, stopped, and started again on the same
// thread_t -- without re-calling threadInit() -- because threadId is
// reset under stateMutex before threadHandler() signals STOPPED, so
// threadStop() returning guarantees threadStartRequest()'s "thread id
// not 0" assert won't race against it. Looping a few times gives any
// regression of that ordering repeated chances to hit the window.
thread_t thread;
threadInit(&thread, helper_noop);
threadStart(&thread);
threadStop(&thread);
assert_int_equal(thread.state, THREAD_STATE_STOPPED);
// Re-initialise so threadId / state are reset, then start again.
threadInit(&thread, helper_noop);
threadStart(&thread);
threadStop(&thread);
assert_int_equal(thread.state, THREAD_STATE_STOPPED);
for(int32_t i = 0; i < 20; i++) {
threadStart(&thread);
threadStop(&thread);
assert_int_equal(thread.state, THREAD_STATE_STOPPED);
}
}
// --- threadmutex_t tests ---