Skip to content

timing precision & adds aurora borealis effect#101

Merged
rbergen merged 7 commits into
PlummersSoftwareLLC:mainfrom
robertlipe:timing-precision
Jun 21, 2026
Merged

timing precision & adds aurora borealis effect#101
rbergen merged 7 commits into
PlummersSoftwareLLC:mainfrom
robertlipe:timing-precision

Conversation

@robertlipe

Copy link
Copy Markdown
Contributor

Improve timing precision to sub-millisecond accuracy and fix accumulative drift

The current implementation suffers from cumulative truncation errors because it calculates frame durations in microseconds using integer division (e.g., 1,000,000 / 30 = 33,333µs instead of 33,333.333µs). This leads to a persistent drift of approximately 36ms per hour at 30 FPS, and potentially more at other frame rates.

  • Using Nanosecond Precision: Upgrading all internal timing calculations to use nanoseconds to minimize truncation errors.
  • Absolute Time Calculation: Computing the "ideal" target time for each frame relative to the loop's start time, eliminating cumulative drift.
  • Measured Delta Time: Providing effects with the actual measured time elapsed since the last frame for smoother animations.
  • Microsecond Drift Correction: Updating the drift detection and synchronization logic to use microsecond precision.

robertlipe and others added 3 commits April 13, 2026 21:49
…tive drift

- Refactored EffectsManager to use an absolute nanosecond timeline for rendering.
- Updated interfaces.h and LEDEffectBase to use microsecond precision for delta time.
- Updated all LED effects to utilize the high-precision delta time.
- Standardized timing across SpeedTracker, monitor UI, and connection delays to microseconds.
- Improved global millis() accuracy and added delayMicros utility.
@rbergen

rbergen commented May 15, 2026

Copy link
Copy Markdown
Collaborator

I'm going to leave this comment on this PR only, but it applies to #102, #103 and #104 as well. Before writing this comment I synced all 4 PRs with the current state of main, on which CI does pass, so I feel comfortable concluding the problem I'm describing in this comment is caused by the changes the PRs are proposing.

On all PRs, CI fails because the API test suite does, comprehensively (9 out of 9 tests), because of an unexpected response code. What I think is particularly relevant is the response code reported to be returned, which is 0 - that's clearly not a valid HTTP response code in any scenario.

The tests can be executed by running tests/tests after a full build.

I noticed that the 4 PRs have one commit in common (1bb2390), which makes me think the root cause is likely to be embedded in that one.

@robertlipe

Copy link
Copy Markdown
Contributor Author

All four have been updated and pushed. (I outsourced this.) PTAL

Fixes Pushed to PRs 101-104: Recapping the pushed fixes for PRs 101-104.

✦ I have ensured that all needed fixes are pushed to PRs 101 through 104 on the
PlummersSoftwareLLC/NDSCPP repository.

Summary of Actions:

  1. PR 101 (timing-precision): Rebased and pushed the fix(api): restore
    backward compatibility for color JSON keys commit.
  2. PR 102 (performance-and-color): Rebased and pushed the same color
    compatibility fix.
  3. PR 103 (schedule-and-robustness): Rebased and pushed the same color
    compatibility fix.
  4. PR 104 (webserver-enhancements):
    • Pushed the fix(api): restore schedule support when adding effects
      commit.
    • Applied and pushed the same color compatibility fix as the other
      branches to ensure consistency.

All branches are now up to date with origin and include the latest cumulative
improvements and bug fixes.

@robertlipe robertlipe changed the title timing precision timing precision & adds aurora borealis effect May 16, 2026
@rbergen

rbergen commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

@robertlipe The test suite still fails across all four PRs. Have you established they do pass successfully?

@robertlipe

Copy link
Copy Markdown
Contributor Author

Multi-week turnaround (I know, I know...) to get build results is worse than my days of turning in assignments on punched cards and then waiting for a printout with a syntax error.

 ndscpp  git checkout timing-precision
Switched to branch 'timing-precision'
Your branch is up to date with 'origin/timing-precision'.

 ndscpp  make && tests/tests
make: Nothing to be done for `all'.

Running main() from /private/tmp/googletest-20250808-4780-aa0gxy/googletest-1.17.0/googletest/src/gtest_main.cc
[==========] Running 11 tests from 2 test suites.
[----------] Global test environment set-up.
[----------] 9 tests from APITest
[ RUN ] APITest.GetController
[ OK ] APITest.GetController (2 ms)
[ RUN ] APITest.GetSockets
[ OK ] APITest.GetSockets (0 ms)
[ RUN ] APITest.GetSpecificSocket
[ OK ] APITest.GetSpecificSocket (0 ms)
[ RUN ] APITest.CanvasCRUD
[ OK ] APITest.CanvasCRUD (1 ms)
[ RUN ] APITest.CanvasFeatureOperations
[ OK ] APITest.CanvasFeatureOperations (1 ms)
[ RUN ] APITest.MultipleCanvasOperations
[ OK ] APITest.MultipleCanvasOperations (74 ms)
[ RUN ] APITest.MultipleFeatureOperations
[ OK ] APITest.MultipleFeatureOperations (78 ms)
[ RUN ] APITest.RapidCreationDeletion
[ OK ] APITest.RapidCreationDeletion (1403 ms)
[ RUN ] APITest.CanvasFeatureEffectWithSchedule
[ OK ] APITest.CanvasFeatureEffectWithSchedule (3 ms)
[----------] 9 tests from APITest (1566 ms total)

[----------] 2 tests from SolarTest
[ RUN ] SolarTest.TestSolarMathUTC
[ OK ] SolarTest.TestSolarMathUTC (0 ms)
[ RUN ] SolarTest.TestScheduleSunriseSunset
[ OK ] SolarTest.TestScheduleSunriseSunset (0 ms)
[----------] 2 tests from SolarTest (0 ms total)

[----------] Global test environment tear-down
[==========] 11 tests from 2 test suites ran. (1567 ms total)
[ PASSED ] 11 tests.

I'll dig into fresh checkouts or something, but if it's not something pretty obvious, I'll probably just withdraw the changes. My tree works fine for me.

@rbergen

rbergen commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

You can actually hold for now. I believe the problem may be at the github actions side. I'll investigate and comment when I gave a conclusion.

@robertlipe

Copy link
Copy Markdown
Contributor Author

Reading that more carefully, the top-level Makefile never called the child Makefile. (It caught my eye that the output tested solar when that code isn't even in this branch.) Perhaps intentional, but doesn't exactly encourage devs to track tests. But even with that...

 ndscpp  make -C tests && tests/tests
Compiling tests.cpp...
Linking tests...
Running main() from /private/tmp/googletest-20250808-4780-aa0gxy/googletest-1.17.0/googletest/src/gtest_main.cc
[==========] Running 9 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 9 tests from APITest
[ RUN ] APITest.GetController
[ OK ] APITest.GetController (1 ms)
[ RUN ] APITest.GetSockets
[ OK ] APITest.GetSockets (0 ms)
[ RUN ] APITest.GetSpecificSocket
[ OK ] APITest.GetSpecificSocket (0 ms)
[ RUN ] APITest.CanvasCRUD
[ OK ] APITest.CanvasCRUD (1 ms)
[ RUN ] APITest.CanvasFeatureOperations
[ OK ] APITest.CanvasFeatureOperations (0 ms)
[ RUN ] APITest.MultipleCanvasOperations
[ OK ] APITest.MultipleCanvasOperations (76 ms)
[ RUN ] APITest.MultipleFeatureOperations
[ OK ] APITest.MultipleFeatureOperations (69 ms)
[ RUN ] APITest.RapidCreationDeletion
[ OK ] APITest.RapidCreationDeletion (1406 ms)
[ RUN ] APITest.CanvasFeatureEffectWithSchedule
[ OK ] APITest.CanvasFeatureEffectWithSchedule (1 ms)
[----------] 9 tests from APITest (1559 ms total)

[----------] Global test environment tear-down
[==========] 9 tests from 1 test suite ran. (1559 ms total)
[ PASSED ] 9 tests.

The reason these are failing in CI is because the tests are buggy. They assume ndscpp is running and apparently nobody is starting one. Look carefully.

[ RUN ] APITest.GetController
tests.cpp:42: Failure
Expected equality of these values:
response.status_code
Which is: 0
200

It's failing (but not displaying any errors, alas) because it can't open a socket on 7777

This isn't a problem in the PR. It's a problem that (only) external contributions are expected to pass a test suite that has clearly never been run before.

I just went to a completely different machine.
➜ blah gh repo clone robertlipe/NDSCPP
Cloning into 'NDSCPP'...

➜ blah cd NDSCPP
➜ NDSCPP git:(main) git checkout timing-precision
branch 'timing-precision' set up to track 'origin/timing-precision'.
Switched to a new branch 'timing-precision'

➜ NDSCPP git:(timing-precision) make -C tests && tests/tests
Compiling tests.cpp...

➜ NDSCPP git:(timing-precision) make -C tests && tests/tests
Compiling tests.cpp...
Linking tests...
Running main() from /private/tmp/googletest-20250430-4705-6kzjyz/googletest-1.17.0/googletest/src/gtest_main.cc
[==========] Running 9 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 9 tests from APITest
[ RUN ] APITest.GetController
tests.cpp:42: Failure
Expected equality of these values:
response.status_code
Which is: 0
200

[ FAILED ] APITest.GetController (6 ms)

[ RUN ] APITest.GetSockets
tests.cpp:51: Failure
Expected equality of these values:
response.status_code
Which is: 0
200

[ FAILED ] APITest.GetSockets (0 ms)
[ RUN ] APITest.GetSpecificSocket
tests.cpp:63: Failure
Expected equality of these values:
response.status_code
Which is: 0
200

[ FAILED ] APITest.GetSpecificSocket (0 ms)
[ RUN ] APITest.CanvasCRUD
tests.cpp:122: Failure
Expected equality of these values:
createResponse.status_code
Which is: 0
201

[ FAILED ] APITest.CanvasCRUD (0 ms)
[ RUN ] APITest.CanvasFeatureOperations
tests.cpp:171: Failure
Expected equality of these values:
createCanvasResponse.status_code
Which is: 0
201

[ FAILED ] APITest.CanvasFeatureOperations (0 ms)
[ RUN ] APITest.MultipleCanvasOperations
tests.cpp:287: Failure
Expected equality of these values:
response.status_code
Which is: 0
201

[ FAILED ] APITest.MultipleCanvasOperations (62 ms)
[ RUN ] APITest.MultipleFeatureOperations
tests.cpp:333: Failure
Expected equality of these values:
createCanvasResponse.status_code
Which is: 0
201

[ FAILED ] APITest.MultipleFeatureOperations (0 ms)
[ RUN ] APITest.RapidCreationDeletion
tests.cpp:426: Failure
Expected equality of these values:
response.status_code
Which is: 0
201

[ FAILED ] APITest.RapidCreationDeletion (6 ms)
[ RUN ] APITest.CanvasFeatureEffectWithSchedule
tests.cpp:478: Failure
Expected equality of these values:
createCanvasResponse.status_code
Which is: 0
201

[ FAILED ] APITest.CanvasFeatureEffectWithSchedule (0 ms)
[----------] 9 tests from APITest (77 ms total)

[----------] Global test environment tear-down
[==========] 9 tests from 1 test suite ran. (77 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 9 tests, listed below:
[ FAILED ] APITest.GetController
[ FAILED ] APITest.GetSockets
[ FAILED ] APITest.GetSpecificSocket
[ FAILED ] APITest.CanvasCRUD
[ FAILED ] APITest.CanvasFeatureOperations
[ FAILED ] APITest.MultipleCanvasOperations
[ FAILED ] APITest.MultipleFeatureOperations
[ FAILED ] APITest.RapidCreationDeletion
[ FAILED ] APITest.CanvasFeatureEffectWithSchedule

9 FAILED TESTS

So that carnage exactly matches what's in reported by CI. Let's start ndscpp.

➜ NDSCPP git:(timing-precision) cp config.led.example config.led
➜ NDSCPP git:(timing-precision) ./ndscpp
zsh: no such file or directory: ./ndscpp
➜ NDSCPP git:(timing-precision) make ndscpp
Copying secrets.h.example to secrets.h...
Compiling main.cpp...
In file included from main.cpp:28:
./global.h:10:10: fatal error: 'spdlog/spdlog.h' file not found
10 | #include <spdlog/spdlog.h>
| ^~~~~~~~~~~~~~~~~
1 error generated.
make: *** [main.o] Error 1
➜ NDSCPP git:(timing-precision) brew install spdlog
✔︎ JSON API packages.arm64_sequoia.jws.json Downloaded 15.2MB/ 15.2MB
==> Downloading https://ghcr.io/v2/homebrew/core/spdlog/manifests/1.17.0
==> Fetching downloads for: spdlog
✔︎ Bottle spdlog (1.17.0) Downloaded 330.6KB/330.6KB
==> Pouring spdlog--1.17.0.arm64_sequoia.bottle.tar.gz
🍺 /opt/homebrew/Cellar/spdlog/1.17.0: 104 files, 1.6MB
==> Running brew cleanup spdlog...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP=1.
Hide these hints with HOMEBREW_NO_ENV_HINTS=1 (see man brew).
➜ NDSCPP git:(timing-precision) make ndscpp
Compiling main.cpp...
Linking ndscpp...

➜ NDSCPP git:(timing-precision) ./ndscpp
^Z
[1] + 67820 suspended ./ndscpp
➜ NDSCPP git:(timing-precision) bg
[1] + 67820 continued ./ndscpp
➜ NDSCPP git:(timing-precision) tests/tests

Running main() from /private/tmp/googletest-20250430-4705-6kzjyz/googletest-1.17.0/googletest/src/gtest_main.cc
[==========] Running 9 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 9 tests from APITest
[ RUN ] APITest.GetController
[ OK ] APITest.GetController (6 ms)
[ RUN ] APITest.GetSockets
[ OK ] APITest.GetSockets (0 ms)
[ RUN ] APITest.GetSpecificSocket
[ OK ] APITest.GetSpecificSocket (1 ms)
[ RUN ] APITest.CanvasCRUD
[2026-06-10 18:31:09.265] [console] [error] Error in /api/canvases/20: Canvas not found: 20
[ OK ] APITest.CanvasCRUD (2 ms)
[ RUN ] APITest.CanvasFeatureOperations
[ OK ] APITest.CanvasFeatureOperations (1 ms)
[ RUN ] APITest.MultipleCanvasOperations
[ OK ] APITest.MultipleCanvasOperations (65 ms)
[ RUN ] APITest.MultipleFeatureOperations
[ OK ] APITest.MultipleFeatureOperations (86 ms)
[ RUN ] APITest.RapidCreationDeletion
[2026-06-10 18:31:10.109] [console] [warning] Queue is full at 192.168.8.60 [Ceiling] dropping frame and resetting socket
[2026-06-10 18:31:10.110] [console] [warning] Queue is full at 192.168.8.167 [Tree] dropping frame and resetting socket
[ OK ] APITest.RapidCreationDeletion (1508 ms)
[ RUN ] APITest.CanvasFeatureEffectWithSchedule
[2026-06-10 18:31:10.929] [console] [error] Unknown effect type for deserialization: SolidColorFill, replacing with magenta fill
[2026-06-10 18:31:10.929] [console] [error] Unknown effect type for deserialization: SolidColorFill, replacing with magenta fill
[ OK ] APITest.CanvasFeatureEffectWithSchedule (3 ms)
[----------] 9 tests from APITest (1677 ms total)

[----------] Global test environment tear-down
[==========] 9 tests from 1 test suite ran. (1677 ms total)
[ PASSED ] 9 tests.

All tests pass.
The "errors" above that are visible aren't errors tested or incurred by the test suite; it's the server fussing about the test suite testing for the ability of the server to respond safely to dumb requests. This seems like intended behavior.

In short, I posit that everything passes. No, I didn't go through all three; I've spent quite enough time debugging buggy tests already.

If there's an actual problem/regression exposed in the PRs, I'll look into it, but since my workhorse branch on my workhorse machine (that's been dutifully delivering blinkies now for months) has evolved way beyond the April submits and is passing all the tests, I really think this is broken CI.

Consider it a feature request that a 'make check' or 'make tests' or whatever target should be present that does whatever the project considers a requirement for submissions and CI should use that. No developer will know, for example, that tests/tests doesn't require a setup with twenty canvases to not incur
[2026-06-10 18:31:09.265] [console] [error] Error in /api/canvases/20: Canvas not found: 20
or if those are expected or what.

@rbergen

rbergen commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

I found it. Your split of build-time and runtime config breaks CI, because config.led is no longer generated as part of make all. That makes ndscpp fail on startup when the test suite is executed.

I'll push an update to robertlipe:timing-precision, which you can then propagate to the other PR head branches.

@rbergen

rbergen commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

I made and pushed the change, and CI now passes.

@rbergen

rbergen commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

I now see some of our messages crossed while we were both analyzing. For purposes of clarity:

  • Before this PR, it was enough to build ndcspp and the test suite using make all && make -C tests, start ndscpp using ./ndscpp and then run the tests with tests/tests once ndscpp had had a chance to settle down. One of the reasons this worked was that in the Makefile, the build of ndcspp was marked as dependent on both secrets.h and config.led, which made make generate those files from the examples if they didn't exist.
  • Due to a change introduced in this PR, config.led is no longer a build-time dependency, and hence won't be generated with make all if it doesn't already exist. That makes ndscpp fail at startup, and hence the tests too.
  • The fact that the embedded configuration points to systems that will only exist at those addresses in one place - and I actually doubt they still do, considering how long ago the example config was put together - is irrelevant for success or failure of the tests; dealing with that is part of what should work.
  • I've now updated the CI script, which is part of the repo, to explicitly execute a make config.led before executing the test suite. I pushed this update to robertlipe:timing-precision which makes CI pass.
  • You can pick up this change and apply it to the head branches for performance and color #102, schedule and robustness #103 and webserver enhancements #104 as well; I expect that will fix the CI failures in those.

@robertlipe

robertlipe commented Jun 11, 2026 via email

Copy link
Copy Markdown
Contributor Author

@rbergen

rbergen commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Yeah, I saw you do that. :) That won't be enough, but I cherry-picked the commit and applied it to the other PR branches too. That should work.

It just means you'll have to pull those branches to get the fix locally as well - which is functionally irrelevant unless you're GitHub.

Resolved conflicts keeping robertlipe's sub-millisecond timing changes
(GetDataFrame with targetTime, microseconds deltaTime for effects) while
taking upstream bug fixes (overflow-safe buffer size cast, resilient
invalid-byte handling in socketchannel, SetId preservation in from_json,
clientBufferCount default 500).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rbergen rbergen merged commit 9f80181 into PlummersSoftwareLLC:main Jun 21, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants