239 Commits

Author SHA1 Message Date
YourWishes ed5c60ac30 Add claude docs 2026-06-16 10:15:59 -05:00
YourWishes 8131bcd4d4 Build on github tag 2026-06-09 15:43:31 -05:00
YourWishes 4ba11e3363 Test
Build Dusk / run-tests (push) Failing after 15m50s
2026-06-08 18:01:19 -05:00
YourWishes acf2be3f66 Try set
Build Dusk / run-tests (push) Failing after 7s
2026-06-08 17:04:07 -05:00
YourWishes f5df0195e2 Docker compose
Build Dusk / run-tests (push) Failing after 7s
2026-06-08 17:02:58 -05:00
YourWishes d26995b48d pwd
Build Dusk / run-tests (push) Failing after 6s
2026-06-08 16:59:39 -05:00
YourWishes 617f8120ae Docker exec
Build Dusk / run-tests (push) Failing after 14s
2026-06-08 16:48:17 -05:00
YourWishes 06c517c9aa chmod
Build Dusk / run-tests (push) Failing after 7s
2026-06-08 16:47:45 -05:00
YourWishes 593ed6408c test2
Build Dusk / run-tests (push) Successful in 7s
2026-06-08 16:47:03 -05:00
YourWishes fb7d3ed122 LS stuff
Build Dusk / run-tests (push) Successful in 7s
2026-06-08 16:45:59 -05:00
YourWishes 19b88ec858 Test linux?
Build Dusk / run-tests (push) Failing after 6s
2026-06-08 16:08:24 -05:00
YourWishes 160e65be7f Run on ubuntu-latest
Build Dusk / run-tests (push) Failing after 16s
2026-06-08 15:34:31 -05:00
YourWishes 079b0d2cf6 Update test script back to what it was
Build Dusk / run-tests (push) Has been cancelled
2026-06-08 15:18:40 -05:00
YourWishes 78f1310f41 Update test script
Build Dusk / run-tests (push) Failing after 7s
2026-06-08 15:14:53 -05:00
YourWishes eb1974c113 Update to docker-host
Build Dusk / run-tests (push) Failing after 6m15s
2026-06-08 14:55:31 -05:00
YourWishes 551409a023 Force run again
Build Dusk / run-tests (push) Failing after 9m15s
2026-06-08 13:58:11 -05:00
YourWishes a11e14daac Try without setup docker
Build Dusk / run-tests (push) Failing after 8m28s
2026-06-08 13:15:15 -05:00
YourWishes 7441e15e76 Run on ubuntu-latest
Build Dusk / run-tests (push) Failing after 3m15s
2026-06-08 13:09:53 -05:00
YourWishes 46506228a6 One thing at a time
Build Dusk / run-tests (push) Has been cancelled
2026-06-08 12:46:38 -05:00
YourWishes 17c49c74cf Try tests updated
Build Dusk / build-linux (push) Failing after 29s
Build Dusk / build-psp (push) Failing after 1m13s
Build Dusk / run-tests (push) Has been cancelled
Build Dusk / build-gamecube (push) Has been cancelled
Build Dusk / build-gamecube-iso (push) Has been cancelled
Build Dusk / build-knulli (push) Has been cancelled
Build Dusk / build-wii (push) Has been cancelled
Build Dusk / build-wii-iso (push) Has been cancelled
2026-06-08 12:44:22 -05:00
YourWishes 3f8024d4db Workflows
Build Dusk / run-tests (push) Failing after 58s
Build Dusk / build-linux (push) Failing after 1m11s
Build Dusk / build-psp (push) Failing after 46s
Build Dusk / build-knulli (push) Failing after 53s
Build Dusk / build-gamecube (push) Failing after 47s
Build Dusk / build-gamecube-iso (push) Failing after 49s
Build Dusk / build-wii (push) Failing after 49s
Build Dusk / build-wii-iso (push) Failing after 1m14s
2026-06-08 12:25:48 -05:00
YourWishes 8675e44d28 GitTea actions 2026-06-08 12:24:09 -05:00
YourWishes 1301d9a718 idk why I btoher with github actions 2026-06-08 12:23:18 -05:00
YourWishes da3db50ca8 Want to test this in PSP 2026-06-08 11:32:59 -05:00
YourWishes 2ca6780305 Just trying to fix things now 2026-06-08 09:39:09 -05:00
YourWishes be68fe5a35 Emdashless 2026-06-07 21:27:59 -05:00
YourWishes dc41c0e302 Cleanup 2026-06-07 21:16:46 -05:00
YourWishes 51388c90d5 we ball I guess 2026-06-07 19:51:54 -05:00
YourWishes f8c9d33df2 Fix some script bugs 2026-06-07 18:47:34 -05:00
YourWishes ed0420fdce Cleanup of script modules. 2026-06-07 12:59:17 -05:00
YourWishes 47a6f396fa Fix typedefs 2026-06-06 19:06:52 -05:00
YourWishes 9edb2aa0c1 Example scene working 2026-06-06 18:46:08 -05:00
YourWishes 003b647d83 Fix tests 2026-06-06 17:36:13 -05:00
YourWishes 2849ff8844 Fix linux? 2026-06-06 17:14:42 -05:00
YourWishes 9f3089742a Fixed ISO builds. 2026-06-06 17:07:30 -05:00
YourWishes b286a9bbcd Fixed wii build 2026-06-06 16:53:06 -05:00
YourWishes 6204e745ba Fixed gamecube building. 2026-06-06 16:47:07 -05:00
YourWishes bbe0e48d23 Builds again? 2026-06-06 16:42:12 -05:00
YourWishes 79054080c0 Cleanup a bit. 2026-06-06 16:39:27 -05:00
YourWishes 81024c4c09 Require async 2026-06-06 10:55:10 -05:00
YourWishes 9068d96130 Add timeouts 2026-06-06 10:38:10 -05:00
YourWishes 6f47543720 Update jerryscript each frame. 2026-06-06 10:30:22 -05:00
YourWishes 5a08384ae1 start async 2026-06-05 19:42:24 -05:00
YourWishes 45d8fda0e4 require() working as I like 2026-06-05 18:49:10 -05:00
YourWishes a9e664492f First round of asset refactoring 2026-06-05 13:18:08 -05:00
YourWishes 3c8b6cb2cc Scene script code 2026-06-04 13:36:35 -05:00
YourWishes 2b3abbe13b ABout to try scene and script merger 2026-06-02 16:46:39 -05:00
YourWishes 241a52b94a nuke unused overworld code 2026-06-02 13:23:11 -05:00
YourWishes 82c300b077 Add some script modules 2026-06-02 12:55:32 -05:00
YourWishes 0f8b629e20 Add logging to Wii 2026-06-02 11:01:54 -05:00
YourWishes 36f6ac65f2 Builds and works on Gamecube 2026-06-02 09:53:56 -05:00
YourWishes a25871a849 Test sprite from script 2026-06-02 09:32:07 -05:00
YourWishes 57766a9104 Merge branch 'main' into scriptentity 2026-06-02 07:36:44 -05:00
YourWishes 3770ae1645 Fix tests? 2026-06-02 07:35:28 -05:00
YourWishes d73edb403f Example Camera 2026-06-01 23:04:55 -05:00
YourWishes b14196ff0d Basic entity script 2026-06-01 22:56:37 -05:00
YourWishes 88903fee94 No need for asset batching on text.c 2026-06-01 22:36:02 -05:00
YourWishes 1e8311fc04 Add asset batch 2026-06-01 22:34:44 -05:00
YourWishes 2b78370cb8 Add asset reaping 2026-06-01 22:20:57 -05:00
YourWishes 8f78bba9e9 Restoring JerryScript a bit cleaner 2026-06-01 21:52:36 -05:00
YourWishes 41a4be678e Added a tiny sleep on assets to stop pegging the CPU 2026-06-01 15:48:10 -05:00
YourWishes 8b2b4b7c3d Fixed JSON loader, added some tests 2026-06-01 15:31:22 -05:00
YourWishes 1f3a29f89d Asyncify other loaders 2026-06-01 15:10:58 -05:00
YourWishes c4c93097cd Add async texture loading 2026-06-01 14:53:18 -05:00
YourWishes eedb7769e6 Add some extra tests 2026-06-01 13:48:29 -05:00
YourWishes 98db62a4bc Add some more tests, prepping for asset testing 2026-06-01 13:37:14 -05:00
YourWishes df48c8e500 Consistency and fixing thread unit tests 2026-06-01 11:33:27 -05:00
YourWishes db9cc0f4c6 Add thread tests 2026-06-01 10:59:56 -05:00
YourWishes a79ee429b4 Prepping for async 2026-06-01 10:57:40 -05:00
YourWishes 6acfca6d48 Consistent 2026-05-30 20:30:13 -05:00
YourWishes 1cd6f4cb72 First refactor of new asset system 2026-05-30 08:21:58 -05:00
YourWishes 3271e8c7d6 FInished porting last asset loader types 2026-05-30 07:59:06 -05:00
YourWishes 0bcde064af Asset refactor, phase one. 2026-05-29 14:27:40 -05:00
YourWishes 957980b3c5 Updating event handler 2026-05-28 14:22:13 -05:00
YourWishes 03eb328d81 Allow dynamic trace on any platform that can support it. 2026-05-28 11:21:36 -05:00
YourWishes e1716a741f Trigger test 2026-05-26 22:18:41 -05:00
YourWishes e24707c847 Scene loading example 2026-05-26 21:42:37 -05:00
YourWishes 7c4b8c307f Fix flocking bug 2026-05-26 20:24:34 -05:00
YourWishes 109318aeaf Remove useless void checks 2026-05-26 19:24:17 -05:00
YourWishes 1f2657cea0 Spritebatch cleanup 2026-05-26 19:07:07 -05:00
YourWishes 382c435bac Entity refactoring 2026-05-22 23:01:45 -05:00
YourWishes 130fe4ca5d Delete JS assets 2026-05-22 00:00:23 -05:00
YourWishes 31ba3fe127 add build to corner of screen 2026-05-21 23:59:26 -05:00
YourWishes f68b31158f Asset refactor 2026-05-21 23:42:56 -05:00
YourWishes 653ca9a72d PSP rendering fix 2026-05-21 22:07:56 -05:00
YourWishes ba7857f4df Fix rendering 2026-05-21 18:24:18 -05:00
YourWishes 23e617ea21 Optimizing entityposition as much as possible. 2026-05-21 13:17:48 -05:00
YourWishes cdf5a5229c Refactor cleaned a few things 2026-05-21 12:52:23 -05:00
YourWishes f841a35a53 Revert "Disable old ent code"
This reverts commit efd31237be.
2026-05-21 11:07:21 -05:00
YourWishes efd31237be Disable old ent code 2026-05-21 10:18:20 -05:00
YourWishes 6502822583 Update render, spritebatch and input stuffs. 2026-05-21 09:51:56 -05:00
YourWishes a9e6f2b2a5 Scene rendering native. 2026-05-20 23:45:27 -05:00
YourWishes 510a94b42c Remove Jerryscript further 2026-05-20 21:34:00 -05:00
YourWishes d805be47ce No script 2026-05-20 20:58:56 -05:00
YourWishes f9ea8e380a Temporarily disable save code 2026-05-20 09:52:07 -05:00
YourWishes 5cb05beb30 Fix dolphin compile 2026-05-19 23:24:27 -05:00
YourWishes 677768e6ab Map Base 2026-05-19 23:13:41 -05:00
YourWishes ed6c951783 Script improvements 2026-05-17 23:40:42 -05:00
YourWishes 54254348b8 Add parent/child 2026-05-17 21:46:08 -05:00
YourWishes 782fd07a8d Savestream update 2026-05-16 17:51:00 -05:00
YourWishes a8fd55cb38 Save file update (incomplete) 2026-05-10 11:20:09 -05:00
YourWishes d7f515575a Working on burned DVD for gamecube 2026-05-09 00:14:28 -05:00
YourWishes bafbf2ec2f Fix compile error 2026-05-08 23:11:20 -05:00
YourWishes 7415944e0a scripting improvements 2026-05-08 22:46:24 -05:00
YourWishes 1ff990ff44 Add strided memory pushing and improved spritebatching 2026-05-08 20:53:05 -05:00
YourWishes 73e73d8772 Cleanup animation 2026-05-08 15:44:55 -05:00
YourWishes 6d876bb767 Anim tweak 2026-05-07 19:37:30 -05:00
YourWishes 2be0fe9f06 Fix wii build 2026-05-07 17:54:10 -05:00
YourWishes e1fb082927 Increase spritebatch flushing count 2026-05-07 17:47:39 -05:00
YourWishes 65ca5ae4c4 Dolphin Bootable ISO working! 2026-05-07 17:38:41 -05:00
YourWishes 2cea43dc70 Switch to ogc2 2026-05-07 17:21:52 -05:00
YourWishes 44a0700800 Fixed memalign again 2026-05-07 14:11:59 -05:00
YourWishes 1613a378f1 Added memalign 2026-05-07 12:39:07 -05:00
YourWishes deed98a27d 2026-05-07 12:31:22 -05:00
YourWishes 9d0cb8fb46 ISO build (partial) 2026-05-07 12:18:30 -05:00
YourWishes d8fe0f6923 textbox 2026-05-06 22:42:28 -05:00
YourWishes 581dbc2b3c Update linux docker 2026-05-06 20:31:50 -05:00
YourWishes 7301d2ad76 luce bree 2026-05-06 20:24:16 -05:00
YourWishes 3232a14d1d Consistent build 2026-05-06 14:40:06 -05:00
YourWishes 84c1f88d42 Fix PSP blending issues 2026-05-06 11:17:34 -05:00
YourWishes 3695b10e4b Easing test 2026-05-05 22:24:25 -05:00
YourWishes 6da02b25fa Testing cutscenes 2026-05-05 22:10:47 -05:00
YourWishes 3bc544fba1 Re-enable build pulling fetched modules 2026-05-05 19:29:48 -05:00
YourWishes 368d370f49 Cleanup modules 2026-05-05 19:29:29 -05:00
YourWishes bb29c0edef Working on some script modules 2026-05-05 16:22:04 -05:00
YourWishes 6edcf75a0c add display state 2026-05-04 22:16:30 -05:00
YourWishes 31cc186424 Fixed small compile bugs 2026-05-04 08:39:47 -05:00
YourWishes 0e94c1fa6d Fixed a bunch of messy over 80 char lines 2026-05-04 08:29:43 -05:00
YourWishes 6d9e2dd3e1 UI first pass 2026-05-03 21:52:12 -05:00
YourWishes 4a4adeb3c8 Finally fixed linux asset weirdness 2026-05-02 15:18:49 -05:00
YourWishes ff77f8cfa0 Fix dolphin rendering 2026-05-01 23:11:59 -05:00
YourWishes 36db89c36e Nuke the old input system, use the new UI system 2026-05-01 15:21:46 -05:00
YourWishes a9948142ad Fix linux warning 2026-05-01 14:00:24 -05:00
YourWishes 8d05510584 Fix linux building 2026-05-01 13:58:05 -05:00
YourWishes d373de7a29 Some adjustments 2026-05-01 13:44:51 -05:00
YourWishes 1efa9a9f7b More cleanup 2026-05-01 09:43:50 -05:00
YourWishes 0fb3ba2f91 Cleanup, prepping for example game stuff 2026-04-30 23:43:49 -05:00
YourWishes 3b4c5b5153 Added FPS meter 2026-04-30 23:34:32 -05:00
YourWishes 9293aeeec8 Fix position 2026-04-30 23:18:36 -05:00
YourWishes 03ae83b119 More cleanup? 2026-04-30 23:07:17 -05:00
YourWishes abd63cc6cf More cleanup 2026-04-30 22:40:32 -05:00
YourWishes 2e43aa2c44 Bit more cleanup 2026-04-30 20:03:44 -05:00
YourWishes 3d984e13c2 Module input improvements 2026-04-29 23:40:01 -05:00
YourWishes 010900fe21 Better again. 2026-04-29 23:26:21 -05:00
YourWishes ffed626447 More cleanup 2026-04-29 22:39:47 -05:00
YourWishes 61f69af35a Refactor pass 1 2026-04-29 14:53:35 -05:00
YourWishes bd248ee91c Build script on PSP, Dolphin and Engine. 2026-04-28 21:34:09 -05:00
YourWishes 194255bffe Fix merge conflcits 2026-04-28 14:02:59 -05:00
YourWishes 52ee627079 Merge branch 'jerryscript' into playertest 2026-04-28 14:02:53 -05:00
YourWishes bd4200e707 Finished getting JerryScript on all the platforms. 2026-04-28 13:59:46 -05:00
YourWishes 73e7d6c7f3 Add epoch 2026-04-28 10:33:23 -05:00
YourWishes a41b0e916b prog 2026-04-28 08:04:01 -05:00
YourWishes 19f2a2c616 Bit more consistent but still far from perfect 2026-04-27 09:14:14 -05:00
YourWishes 998601f722 Playertest: scene/script system refactor and Wii ABI fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 23:30:08 -05:00
YourWishes 7c3386cf3e Add entity scripting 2026-04-20 17:01:12 -05:00
YourWishes d161182997 Entity modules 2026-04-20 16:50:16 -05:00
YourWishes 1646dc2dbd Fixed build 2026-04-20 15:43:18 -05:00
YourWishes b640295be2 Scene script 2026-04-20 15:34:24 -05:00
YourWishes b89ae2391b Reg console. 2026-04-20 14:31:22 -05:00
YourWishes a0fad441d0 Updated bind command 2026-04-20 12:59:25 -05:00
YourWishes 340084dac3 Removed console aliases 2026-04-20 12:49:06 -05:00
YourWishes c78135aa09 Fixed bugs with console 2026-04-20 12:05:35 -05:00
YourWishes d19f8bbd30 Restored console, has a bug 2026-04-20 09:26:25 -05:00
YourWishes 4205899f5a No idea why gamecube is crashing, disabling this for now 2026-04-18 21:57:57 -05:00
YourWishes 7dd3940770 Moved code to dolphin for network 2026-04-18 17:41:30 -05:00
YourWishes 00d94e3015 Slight wii improvements 2026-04-18 16:01:53 -05:00
YourWishes 7bacb3ee2b Testing on real wii hardware some more 2026-04-18 15:59:25 -05:00
YourWishes 8e49be5ac4 Testing some wii rendering bugs 2026-04-18 15:29:40 -05:00
YourWishes 3b94598d2c Fixed dolphin matricies the ugly way 2026-04-18 00:36:35 -05:00
YourWishes bddc9af3b6 "Improved" Dolphin matricies slightly 2026-04-18 00:32:50 -05:00
YourWishes 2451d73a7c Improved Wii aspect ratio significantly 2026-04-17 23:49:39 -05:00
YourWishes 1dd2efa182 Dolphin compiles, network untested 2026-04-17 22:53:49 -05:00
YourWishes acea610773 Disable curl on linux 2026-04-17 22:53:29 -05:00
YourWishes 8f2f1fd496 Added network info 2026-04-17 17:00:03 -05:00
YourWishes 39c775872a PSP networking matches linux now. 2026-04-17 16:32:45 -05:00
YourWishes bdb3cbd109 Fixed crash for cross/cancel logic 2026-04-17 16:02:45 -05:00
YourWishes ff84ce2b04 Added PSP Accept/Cance 2026-04-17 15:28:03 -05:00
YourWishes 225f405592 PSP Networking refactor 2026-04-17 14:57:10 -05:00
YourWishes 715ecffa18 Taking a break on net 2026-04-16 06:38:56 -05:00
YourWishes e51cdc8992 PSP net code first pass 2026-04-15 15:50:43 -05:00
YourWishes 133685ea37 Linux HTTP implementation 2026-04-15 15:11:44 -05:00
YourWishes 6aff98d555 Fix PSP build 2026-04-15 06:10:38 -05:00
YourWishes acdc524284 bit more accurate 2026-04-15 06:04:30 -05:00
YourWishes 1ee5ec7b43 Vita builds for the first time 2026-04-15 05:52:30 -05:00
YourWishes 46a5403511 Optimized entity memory. 2026-04-14 14:48:26 -05:00
YourWishes 87bfb92576 Physics position optimization 2026-04-14 14:46:07 -05:00
YourWishes c7a3e5601c De-ugifying 2026-04-14 14:29:48 -05:00
YourWishes 4009130f6e Refactor Physics further 2026-04-14 13:55:48 -05:00
YourWishes c91243f6e9 Physics refactor 2026-04-14 13:45:16 -05:00
YourWishes 0e3871ac26 Reset position 2026-04-14 09:47:17 -05:00
YourWishes 55baafec8a Fixed some of the rendering problems on Dolphin, things still look wrong though. 2026-04-14 09:40:58 -05:00
YourWishes b5a66993ca Phyiscs engine first pass 2026-04-14 09:34:57 -05:00
YourWishes 0b570b5fd6 Add a few more mesh types 2026-04-14 08:38:50 -05:00
YourWishes 378227c377 Fixed more memory tests 2026-04-13 22:55:59 -05:00
YourWishes 650645eaff Fixing memory tests 2026-04-13 22:42:39 -05:00
YourWishes 62c71f3fe6 Remove malloc log 2026-04-13 20:36:48 -05:00
YourWishes 041ec3d710 Add texture padder tool 2026-04-13 20:34:54 -05:00
YourWishes 5f2d871bad Cleaned a bit more 2026-04-13 20:05:34 -05:00
YourWishes a30b151e4d Bit of cleanup 2026-04-13 20:03:02 -05:00
YourWishes 5a651d2d1f Dusk texture creator 2026-04-13 19:51:11 -05:00
YourWishes 4b3826edd9 Cleanup the test mesh 2026-04-13 13:05:39 -05:00
YourWishes bae1ff3759 Allow reaxising mesh 2026-04-13 12:58:54 -05:00
YourWishes fd82486431 Fix dolphin color-less 2026-04-13 12:37:54 -05:00
YourWishes c9cd91cbd8 Make color optional 2026-04-13 12:29:06 -05:00
YourWishes c8abd374fe STL Loader 2026-04-13 11:41:51 -05:00
YourWishes 2b9ee8f721 Entity does not own mesh. 2026-04-13 09:40:40 -05:00
YourWishes d02673e04a 3D OBJ loading 2026-04-10 22:09:01 -05:00
YourWishes f0117b8e6e Renders on PSP but it's inconsistent 2026-04-10 20:59:38 -05:00
YourWishes bb7c41c754 Rotation 2026-04-10 18:47:46 -05:00
YourWishes efa583c154 Fixed Dolphin culling 2026-04-10 18:37:27 -05:00
YourWishes d16ea13c14 Dolphin shader handler 2026-04-10 18:34:58 -05:00
YourWishes 673d8e0a18 Shader material ECS example 2026-04-10 12:48:05 -05:00
YourWishes 37cfdde1ee Mesh component 2026-04-10 10:19:44 -05:00
YourWishes 0778ffb57a ECS POC 2026-04-10 07:31:31 -05:00
YourWishes 42099f7241 ECS Enhancements 2026-04-10 07:09:25 -05:00
YourWishes c52e1d22b7 Basic ECS 2026-04-09 22:07:17 -05:00
YourWishes 0d7b0aadd1 ECS 2026-04-09 11:53:11 -05:00
YourWishes 4cd3355ef1 Fix memory tests 2026-04-04 19:45:29 -05:00
YourWishes 98d70b96d1 Added proper plural support 2026-04-04 15:21:27 -05:00
YourWishes 64735bdf43 Implemented lua locale gettext 2026-04-04 11:32:46 -05:00
YourWishes 7b87347b77 Fixed small bug with parsing plurals 2026-04-04 10:19:07 -05:00
YourWishes b5b29d7061 locale parsing done 2026-04-04 10:11:46 -05:00
YourWishes 9ec21f85a0 Asset moved some code around 2026-04-03 14:41:38 -05:00
YourWishes da1a5a3f1b Asset refactor 2026-04-03 12:56:04 -05:00
YourWishes 0885da8d44 Fixed dynamic updates on scene rendering 2026-03-29 19:08:58 -05:00
YourWishes 8af961c6d3 Fixed knulli rendering 2026-03-29 18:53:42 -05:00
YourWishes ef5febdde3 Fixed dolphin rendering. 2026-03-29 18:42:59 -05:00
YourWishes 6d7fbd3926 Change to square only 2026-03-29 17:58:13 -05:00
YourWishes 2680d373d8 Fixed boot.dol in wii 2026-03-29 16:51:54 -05:00
YourWishes 2b2ddb3cf2 Fixed spritebatch flickering on Dolphin 2026-03-29 16:10:39 -05:00
YourWishes 85ff95296b Fix Linux again 2026-03-29 15:19:15 -05:00
YourWishes 314a2de41a Fixed text on PSP 2026-03-29 14:45:40 -05:00
YourWishes 26fafab47a Fix copy issues 2026-03-29 14:25:10 -05:00
YourWishes e56ff20e2d Attempting to fix PSP alpha textures 2026-03-29 13:38:55 -05:00
YourWishes 55d44f229d Fixed crash on PSP 2026-03-29 10:35:57 -05:00
YourWishes 1c5e50cc4d Test text rendering 2026-03-29 10:15:22 -05:00
YourWishes ea898da6c2 Fix compile 2026-03-28 21:52:52 -05:00
YourWishes dbb7e9f53c Getting shaders working with lua. 2026-03-28 21:50:59 -05:00
YourWishes cbb68a399d Fix compile error 2026-03-28 15:43:38 -05:00
684 changed files with 37359 additions and 8960 deletions
+88
View File
@@ -0,0 +1,88 @@
# Animation System
Source: `src/dusk/animation/`
## Overview
The animation system provides time-based keyframe interpolation with
pluggable easing functions. It is intentionally minimal -- no skeleton,
no blending, no state machine. Animations produce a single `float_t`
value at a given time, which callers apply to whatever property they
are animating.
## Keyframes (`keyframe.h`)
```c
typedef struct {
float_t time; // time in seconds this keyframe is at
float_t value; // the value at this keyframe
easingtype_t easing; // easing applied between this frame and the next
} keyframe_t;
```
## Animation (`animation.h`)
```c
typedef struct {
keyframe_t *keyframes; // caller-owned array
uint16_t keyframeCount;
} animation_t;
void animationInit(
animation_t *anim,
keyframe_t *keyframes,
uint16_t keyframeCount
);
float_t animationGetValue(animation_t *anim, float_t time);
// Returns the interpolated value at the given time.
// Before the first keyframe: returns the first keyframe's value.
// After the last keyframe: returns the last keyframe's value.
```
## Easing functions (`easing.h`)
```c
typedef float_t (*easingfn_t)(float_t t); // t in [0, 1], out in [0, 1]
extern const easingfn_t EASING_FUNCTIONS[EASING_COUNT];
float_t easingApply(easingtype_t type, float_t t);
```
Available easing types:
```
EASING_LINEAR
EASING_IN_SINE EASING_OUT_SINE EASING_IN_OUT_SINE
EASING_IN_QUAD EASING_OUT_QUAD EASING_IN_OUT_QUAD
EASING_IN_CUBIC EASING_OUT_CUBIC EASING_IN_OUT_CUBIC
EASING_IN_QUART EASING_OUT_QUART EASING_IN_OUT_QUART
EASING_IN_BACK EASING_OUT_BACK EASING_IN_OUT_BACK
```
## Usage pattern
```c
// Declare keyframes statically (no allocation):
static keyframe_t kfs[] = {
{ .time = 0.0f, .value = 0.0f, .easing = EASING_OUT_CUBIC },
{ .time = 1.0f, .value = 1.0f, .easing = EASING_LINEAR },
};
animation_t anim;
animationInit(&anim, kfs, 2);
// In update loop:
float_t alpha = animationGetValue(&anim, TIME.time);
// Apply alpha to whatever is being animated.
```
## Design notes
- Keyframe arrays are caller-owned and not copied. Use static or
long-lived arrays; do not allocate per-frame.
- The system has no notion of looping -- wrap `time` with `fmodf` if
you need a repeating animation.
- For multi-property animations, use multiple `animation_t` instances
sharing the same time source.
+125
View File
@@ -0,0 +1,125 @@
# Asset System
Source: `src/dusk/asset/`
## Overview
All game assets are packed into a single ZIP archive named `dusk.dsk`
(`ASSET_FILE_NAME`). The asset system loads entries from this archive
asynchronously on a background thread, caches them, and provides
synchronous blocking access when an asset is required immediately.
## Key limits
| Constant | Value | Meaning |
|----------|-------|---------|
| `ASSET_LOADING_COUNT_MAX` | 4 | Concurrent in-flight loads |
| `ASSET_ENTRY_COUNT_MAX` | 128 | Cached entries |
## Top-level API (`asset.h`)
```c
errorret_t assetInit(); // Open dusk.dsk, start background thread
void assetUpdate(); // Dispatch completed-load callbacks (main thread)
errorret_t assetDispose(); // Wait for loads, close archive
assetentry_t *assetGetEntry(
const char_t *path,
assetloadertype_t type,
assetloaderinput_t *input
); // Get (or create) a cache entry; does NOT start loading
errorret_t assetRequireLoaded(assetentry_t *entry);
// Block the calling thread until this entry is fully loaded.
// Only safe to call from the main thread.
void assetLock(assetentry_t *entry);
void assetUnlock(assetentry_t *entry);
// Reference counting. Lock before using loaded data; unlock when done.
// The entry will not be evicted while locked.
```
## Asset entry states
Each cache entry goes through a state machine:
```
IDLE -> QUEUED -> READING (async) -> PROCESSING (sync, main thread) -> LOADED
-> ERROR
```
- **READING** runs on the background loader thread (file I/O).
- **PROCESSING** runs on the main thread (GPU uploads, parsing finalization).
- Once LOADED, data is available in `entry->data`.
## Loader types
Loader types are registered in the `ASSET_LOADER_CALLBACKS[]` table.
Each type implements three callbacks: `loadSync`, `loadAsync`, `dispose`.
| `assetloadertype_t` | Data read | Description |
|---------------------|-----------|-------------|
| `ASSET_LOADER_TYPE_TEXTURE` | STB image | Loads image bytes async, creates GPU texture sync |
| `ASSET_LOADER_TYPE_TILESET` | `.dtf` binary | Custom tile format (magic, version, grid, UVs) |
| `ASSET_LOADER_TYPE_MESH` | `.stl` | STL mesh with configurable axis orientation |
| `ASSET_LOADER_TYPE_JSON` | yyjson | Up to 256 KB; parsed async |
| `ASSET_LOADER_TYPE_LOCALE` | Gettext `.po` | PO parser with plural-form expression evaluation |
| `ASSET_LOADER_TYPE_SCRIPT` | JS source | JerryScript module |
## Adding a new loader type
1. Add an enum value before `_COUNT` in `assetloadertype_t`
(`src/dusk/asset/loader/assetloader.h`).
2. Add fields to the input/loading/output unions in `assetloader.h`.
3. Implement `assetXxxLoaderSync`, `assetXxxLoaderAsync`, and
`assetXxxDispose` in `src/dusk/asset/loader/xxx/`.
4. Register the three callbacks in `ASSET_LOADER_CALLBACKS[]` in
`src/dusk/asset/loader/assetloader.c`.
5. If user-facing, create a JS module and a `.d.ts` file (see `CLAUDE.md`).
## Asset batch (`assetbatch.h`)
`assetbatch_t` groups multiple asset requests into a single logical
load. All entries in the batch start loading concurrently. The batch
fires a completion callback once every entry has reached LOADED (or
ERROR).
```c
assetbatch_t batch;
assetBatchInit(&batch, entries, count, onComplete, user);
assetBatchStart(&batch);
// ... later, after assetUpdate() fires the callback ...
assetBatchDispose(&batch);
```
## Usage pattern
```c
// 1. Get or create the cache entry (no I/O yet).
assetentry_t *tex = assetGetEntry(
"textures/hero.png",
ASSET_LOADER_TYPE_TEXTURE,
NULL
);
assetLock(tex);
// 2. Option A -- non-blocking: check tex->state each frame.
// Option B -- blocking (main thread only):
errorChain(assetRequireLoaded(tex));
// 3. Use the loaded data.
texture_t *t = &tex->data.texture;
// 4. Release when done.
assetUnlock(tex);
```
## Error macros (inside loader implementations)
```c
assetLoaderErrorThrow("msg %d", val); // errorThrow equivalent
assetLoaderErrorChain(someCall()); // errorChain equivalent
```
Use these instead of the bare error macros inside loader callbacks so
that failures include the loader context in the stack trace.
+85
View File
@@ -0,0 +1,85 @@
# Build System
Dusk uses CMake exclusively. Every source subdirectory owns its own
`CMakeLists.txt`; the root file only wires them together.
## Golden rule
**Never add source files to the root `CMakeLists.txt` directly.**
Every `.c` file is registered in the `CMakeLists.txt` that lives in
the same directory (or a direct parent within the same module):
```cmake
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
myfile.c
)
```
## Configuration variables
| Variable | Purpose |
|------------------------|----------------------------------------------|
| `DUSK_TARGET_SYSTEM` | Selects the platform (see `.claude/platforms.md`) |
| `DUSK_BUILD_TESTS` | Enables the test suite (`ON` / `OFF`) |
| `CMAKE_TOOLCHAIN_FILE` | Cross-compiler toolchain for console targets |
| `CMAKE_BUILD_TYPE` | `Debug` / `Release` / `RelWithDebInfo` |
## Typical configure + build
```sh
# Linux debug build
cmake -B build -DDUSK_TARGET_SYSTEM=linux -DCMAKE_BUILD_TYPE=Debug
cmake --build build
# Linux with tests
cmake -B build \
-DDUSK_TARGET_SYSTEM=linux \
-DDUSK_BUILD_TESTS=ON \
-DCMAKE_BUILD_TYPE=Debug
cmake --build build
ctest --test-dir build
```
## Module layout convention
Each logical module under `src/` gets its own directory:
```
src/dusk/ Platform-agnostic core
src/dusk<platform>/ Platform-specific impl (one dir per target)
```
Within a module, subdirectories mirror subsystem boundaries
(`asset/`, `entity/`, `script/`, etc.). Each subdirectory has its own
`CMakeLists.txt` that is `add_subdirectory()`-included by its parent.
## Adding a new source file
1. Create `src/.../myfile.c` (and `myfile.h` if needed).
2. Open the `CMakeLists.txt` in the same directory.
3. Add `myfile.c` to the `target_sources(...)` block.
4. Do **not** touch any parent or root `CMakeLists.txt`.
## Platform-conditional sources
Wrap platform-only files in a generator expression or `if()` block:
```cmake
if(DUSK_TARGET_SYSTEM STREQUAL "psp")
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
mypspfile.c
)
endif()
```
## Tests
- Test files live in `test/` mirroring the `src/dusk/` structure.
- Enable with `-DDUSK_BUILD_TESTS=ON`.
- Uses cmocka; include `dusktest.h` in every test file.
- Every test must assert `memoryGetAllocatedCount() == 0` at teardown
to catch allocator leaks.
- Test function signature: `static void test_something(void **state)`
+81
View File
@@ -0,0 +1,81 @@
# Display -- Screen, Framebuffer, and Size Modes
Source: `src/dusk/display/`
See also: `.claude/display-texture.md`, `.claude/display-shader.md`
---
## Display size modes
Two compile-time configurations exist:
### Fixed size (`DUSK_DISPLAY_WIDTH` + `DUSK_DISPLAY_HEIGHT`)
The render resolution is constant. Set both defines at CMake configure
time. `SCREEN.width` and `SCREEN.height` are compile-time constants.
### Dynamic size (`DUSK_DISPLAY_SIZE_DYNAMIC`)
The window can be resized (desktop targets). Instead of fixed defines,
set `DUSK_DISPLAY_WIDTH_DEFAULT` and `DUSK_DISPLAY_HEIGHT_DEFAULT`.
The screen system renders to an internal framebuffer at a logical
resolution and scales/letterboxes to the actual window.
Screen modes available only with `DUSK_DISPLAY_SIZE_DYNAMIC`:
| Mode | Behaviour |
|------|-----------|
| `SCREEN_MODE_BACKBUFFER` | Render directly to the window backbuffer |
| `SCREEN_MODE_FIXED_SIZE` | Fixed pixel dimensions; letterboxed |
| `SCREEN_MODE_ASPECT_RATIO` | Maintain aspect ratio at all cost |
| `SCREEN_MODE_FIXED_HEIGHT` | Fixed height; width expands/contracts |
| `SCREEN_MODE_FIXED_WIDTH` | Fixed width; height expands/contracts |
| `SCREEN_MODE_FIXED_VIEWPORT_HEIGHT` | Fixed height at higher resolution |
Configure via `SCREEN.mode` and the corresponding union field before
calling `screenInit()`.
---
## Framebuffer (`framebuffer.h`)
```c
extern framebuffer_t FRAMEBUFFER_BACKBUFFER;
extern const framebuffer_t *FRAMEBUFFER_BOUND;
// Bind/unbind:
frameBufferBind(fb);
frameBufferUnbind();
// Clear (pass flag combination):
frameBufferClear(fb, FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH);
// Dimensions of the currently bound framebuffer:
int32_t w = frameBufferGetWidth();
int32_t h = frameBufferGetHeight();
```
`FRAMEBUFFER_BACKBUFFER` is the window surface. Off-screen framebuffers
are used by the screen system when `DUSK_DISPLAY_SIZE_DYNAMIC` is on.
---
## Screen (`screen.h`)
```c
extern screen_t SCREEN;
// SCREEN.width, SCREEN.height -- logical render dimensions
// SCREEN.aspect -- width / height
// SCREEN.background -- clear colour
errorret_t screenInit();
errorret_t screenBind(); // call before rendering game content
errorret_t screenUnbind(); // call after game content, before UI
errorret_t screenRender(); // blit the internal framebuffer to the window
errorret_t screenDispose();
```
`screenBind` / `screenUnbind` / `screenRender` are called by the scene
system automatically each frame. Game code normally does not call them
directly -- use the JS `render()` hook instead.
+73
View File
@@ -0,0 +1,73 @@
# Display -- Shader, Material, and Display State
Source: `src/dusk/display/`
See also: `.claude/display-core.md`, `.claude/display-texture.md`
---
## Shader (`shader.h` + `shaderlist.h`)
Shaders are platform-abstracted. The current shader list is defined
in `shaderlist.h`. Currently only one shader is implemented:
| Enum | Description |
|------|-------------|
| `SHADER_LIST_SHADER_UNLIT` | Unlit / flat colour + texture shader |
```c
extern shaderlistdef_t SHADER_LIST_DEFS[SHADER_LIST_SHADER_COUNT];
// SHADER_LIST_DEFS[n].shader is the platform shader object.
// Bind a shader before drawing:
errorret_t shaderBind(shader_t *shader);
// Upload a mat4 uniform by name:
errorret_t shaderSetMatrix(shader_t *shader, const char_t *name, mat4 m);
```
Adding a new shader means adding an entry to `shaderlist.h`, providing
platform-specific vertex/fragment sources, and implementing the
corresponding material type in `shadermaterial_t`.
---
## Shader material (`shadermaterial.h`)
`shadermaterial_t` is a union over per-shader material structs.
Currently contains only the unlit material:
```c
typedef union shadermaterial_u {
shaderunlitmaterial_t unlit;
} shadermaterial_t;
```
`shaderunlitmaterial_t` typically holds a `texture_t *` and a
tint colour. Check `shaderunlitmaterial.h` for the exact fields.
To use a shader material on a renderable entity:
1. Set `renderable.type = ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL`.
2. Set `renderable.data.material.shaderType` to the desired
`shaderlistshadertype_t` value (e.g. `SHADER_LIST_SHADER_UNLIT`).
3. Fill in the corresponding union field:
`renderable.data.material.material.unlit`.
4. Set `renderable.data.material.state.flags` for rasterizer state.
---
## Display state (`displaystate.h`)
`displaystate_t` controls per-draw rasterizer state flags:
```c
DISPLAY_STATE_FLAG_CULL // back-face culling
DISPLAY_STATE_FLAG_DEPTH_TEST // depth testing
DISPLAY_STATE_FLAG_BLEND // alpha blending
```
Set flags via `data.material.state.flags` on the renderable's material.
The default for an uninitialised state is all flags clear (no culling,
no depth test, no blending). Most opaque geometry should set at least
`CULL | DEPTH_TEST`.
+86
View File
@@ -0,0 +1,86 @@
# Display -- Texture, Tileset, and Font
Source: `src/dusk/display/`
See also: `.claude/display-core.md`, `.claude/display-shader.md`
---
## Texture (`texture.h`)
```c
extern texture_t TEXTURE_WHITE; // 4x4 opaque white; always available
errorret_t textureInit(
texture_t *texture,
int32_t width,
int32_t height,
textureformat_t format,
texturedata_t data
);
errorret_t textureDispose(texture_t *texture);
```
`textureformat_t` and `texture_t` are platform aliases
(`textureformatplatform_t`, `textureplatform_t`). On OpenGL targets
the format maps to GL texture format constants.
`texturedata_t` is a union:
- `.paletted.indices` + `.paletted.palette` -- for paletted formats
- `.rgbaColors` -- for RGBA formats
### Texture rules
- Dimensions must be powers of two on PSP and GameCube/Wii. Use
`mathNextPowTwo` from `util/math.h` if needed.
- Texture upload must happen on the main thread. In the asset loader,
this means `loadSync` (not `loadAsync`).
- `TEXTURE_WHITE` is always available without loading; use it as a
placeholder or for untextured geometry.
---
## Tileset (`tileset.h`)
A tileset subdivides a texture into a uniform grid of tiles.
```c
typedef struct {
uint16_t tileWidth, tileHeight;
uint16_t tileCount;
uint16_t columns, rows;
vec2 uv; // UV size per tile (pre-computed from grid dimensions)
} tileset_t;
// Get UV rect for a tile by index:
void tilesetTileGetUV(
const tileset_t *ts, uint16_t tileIndex, vec4 outUV
);
// Get UV rect for a tile by grid position:
void tilesetPositionGetUV(
const tileset_t *ts, uint16_t column, uint16_t row, vec4 outUV
);
```
`outUV` is `{u, v, u2, v2}` in normalised [0, 1] texture space.
Tilesets are loaded from `.dtf` binary files via
`ASSET_LOADER_TYPE_TILESET`. The DTF format stores tile width/height,
grid dimensions, and per-tile UV offsets (magic + version header).
---
## Font (`font.h`)
```c
typedef struct {
texture_t *texture;
tileset_t *tileset;
} font_t;
```
A font is a tileset-backed texture atlas where each tile is a character
glyph. No heap allocation -- both pointers are owned by the caller.
Character lookup is by glyph index into the tileset grid. Rendering is
handled by the spritebatch system using the tileset UV helpers.
+19
View File
@@ -0,0 +1,19 @@
# Display System
Source: `src/dusk/display/`
## Overview
The display system is a platform-abstracted rendering layer. Each
subsystem (texture, shader, framebuffer, screen) is defined by a core
header that requires the platform layer to provide concrete types and
hook macros. The OpenGL implementation lives in `src/duskgl/`; the
Dolphin (GX) implementation in `src/duskdolphin/`.
## Subsystem documentation
| Subsystem | Reference |
|-----------|-----------|
| Screen size modes, framebuffer, screen | `.claude/display-core.md` |
| Texture, tileset, font | `.claude/display-texture.md` |
| Shader, shader material, display state | `.claude/display-shader.md` |
+179
View File
@@ -0,0 +1,179 @@
# Entity Component System (ECS)
Source: `src/dusk/entity/`
## Core concepts
- **Entity** (`entityid_t` = `uint8_t`) -- a numeric ID. No data of
its own; just an index into the entity manager pool.
- **Component** -- a plain data struct registered in `componentlist.h`.
Stores state; no behaviour.
- **System** -- functions that query all entities with a given component
type and act on them each tick.
## Hard limits
| Constant | Value |
|----------|-------|
| `ENTITY_COUNT_MAX` | 64 |
| `ENTITY_COMPONENT_COUNT_MAX` | 16 per entity |
| Total component slots | 1024 (64 x 16) |
| Update callbacks per entity | 5 |
| Dispose callbacks per entity | 5 |
`ENTITY_ID_INVALID = 0xFF`, `COMPONENT_ID_INVALID = 0xFF`.
## Global state
```c
extern entitymanager_t ENTITY_MANAGER;
// .entities[64] -- entity structs
// .components[1024] -- all component data (entity * 16 + comp)
// .entitiesWithComponent -- O(1) lookup indexed by [type * 64 + entityId]
```
## Entity lifecycle
```c
entityid_t id = entityManagerAdd(); // reserve first inactive slot
entityInit(id); // zero the entity, mark active
componentid_t posId = entityAddComponent(id, COMPONENT_TYPE_POSITION);
componentid_t rendId = entityAddComponent(id, COMPONENT_TYPE_RENDERABLE);
// Per-frame update (called by entityManagerUpdate):
entityUpdate(id);
// Cleanup:
entityDispose(id); // dispose components, mark inactive
entityDisposeDeep(id); // dispose self + entire position hierarchy
```
## Component registration (X-macro)
All component types are declared in a single table in
`src/dusk/entity/componentlist.h`:
```c
X(NAME, type_t, fieldName, initFn, disposeFn, renderFn)
```
This generates:
- `COMPONENT_TYPE_NAME` enum value
- Union field `fieldName` in `componentdata_t`
- Entry in `COMPONENT_DEFINITIONS[]` with `init` / `dispose` /
`render` function pointers (any may be `NULL`)
Current registered components:
| Enum suffix | Struct | Notes |
|-------------|--------|-------|
| `POSITION` | `entityposition_t` | Transform + parent/child hierarchy |
| `CAMERA` | `entitycamera_t` | View matrix setup |
| `RENDERABLE` | `entityrenderable_t` | Sprite batch, shader material, or custom draw |
| `PHYSICS` | `entityphysics_t` | Physics body (see `.claude/physics.md`) |
| `TRIGGER` | `entitytrigger_t` | Collision trigger zone |
## Accessing component data
```c
void *componentGetData(
entityid_t entityId,
componentid_t componentId,
componenttype_t type
);
// Returns pointer into the preallocated components pool.
// Never NULL for a valid (id, type) pair.
```
Querying all entities with a given type:
```c
entityid_t ids[ENTITY_COUNT_MAX];
componentid_t comps[ENTITY_COUNT_MAX];
entityid_t count = componentGetEntitiesWithComponent(
COMPONENT_TYPE_PHYSICS, ids, comps
);
```
## Adding a new component -- checklist
1. Create `src/dusk/entity/component/<category>/entity<Name>.h/.c`.
- Struct: `entity<Name>_t`
- `entity<Name>Init(entityid_t, componentid_t)` (required)
- `entity<Name>Dispose(entityid_t, componentid_t)` (if needed)
2. `#include` the new header in the header block of `componentlist.h`.
3. Add an `X(...)` row in `componentlist.h`.
4. If JS-facing, add a script module (see `CLAUDE.md`).
## Position component (`entityposition_t`)
The position component implements the transform hierarchy and uses lazy
evaluation with dirty flags to avoid redundant matrix rebuilds.
**Dirty flags:**
| Flag | Meaning |
|------|---------|
| `ENTITY_POSITION_FLAG_PRS_DIRTY` | Cached position/rotation/scale stale vs localTransform |
| `ENTITY_POSITION_FLAG_ROTATION_DIRTY` | Rotation columns of localTransform stale |
| `ENTITY_POSITION_FLAG_POSITION_DIRTY` | Position column of localTransform stale |
| `ENTITY_POSITION_FLAG_WORLD_DIRTY` | World matrix stale |
**Hierarchy:** up to 8 children per entity. `entityPositionSetParent()`
reparents and maintains the child list. `entityDisposeDeep()` /
`entityPositionDisposeDeep()` recursively disposes the entire subtree.
**Key functions:**
```c
// Local space getters/setters (mark local dirty):
entityPositionGetLocalPosition / SetLocalPosition
entityPositionGetLocalRotation / SetLocalRotation
entityPositionGetLocalScale / SetLocalScale
// World space getters/setters (ensure world updated):
entityPositionGetWorldPosition / SetWorldPosition
entityPositionGetWorldRotation / SetWorldRotation
entityPositionGetWorldScale / SetWorldScale
entityPositionSetParent(entityId, parentEntityId, parentComponentId);
entityPositionLookAt(entityId, componentId, eye, target, up);
entityPositionRebuild(pos); // force immediate matrix rebuild
```
## Renderable component (`entityrenderable_t`)
Three rendering modes selected via `entityrenderabletype_t`:
| Mode | Description |
|------|-------------|
| `ENTITY_RENDERABLE_TYPE_CUSTOM` | User-supplied `draw` callback |
| `ENTITY_RENDERABLE_TYPE_SPRITEBATCH` | Up to 64 sprites + texture |
| `ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL` | Up to 8 meshes, shader, material, display state |
Default on init: shader material (white, unlit, depth-tested cube).
`priority` (int8_t) controls render order; 0 = automatic.
## Callback hooks on entities
Entities support up to 5 registered update callbacks and 5 dispose
callbacks. These are used by systems that need per-entity ticks without
building a full query loop every frame:
```c
entityUpdateAdd(entityId, updateFn, componentId, user);
entityUpdateRemove(entityId, updateFn);
entityDisposeAdd(entityId, disposeFn, componentId, user);
entityDisposeRemove(entityId, disposeFn);
```
## Design rules
- Components store **data only**. No logic in a component struct or
its init beyond setting default values.
- Keep components small and focused.
- Cross-component access is fine from a system, but a component must
never hold a pointer to another component -- use entity IDs.
- Systems must not assume component ordering. Use `componentGetEntitiesWithComponent`.
+95
View File
@@ -0,0 +1,95 @@
# Engine, System, and Log
Sources: `src/dusk/engine/`, `src/dusk/system/`, `src/dusk/log/`
---
## Engine (`engine.h`)
The engine owns the top-level init / update / dispose loop. Every
platform's `main()` calls these three functions in order.
```c
extern engine_t ENGINE;
// ENGINE.running -- false causes the main loop to exit
// ENGINE.argc / ENGINE.argv -- passed from main()
// ENGINE.version -- version string
errorret_t engineInit(int32_t argc, const char_t **argv);
errorret_t engineUpdate(void); // called once per tick
errorret_t engineDispose(void);
```
`engineInit` initialises subsystems in order: system, log, assert,
display, time, asset, input, physics, script, etc.
`engineUpdate` steps each subsystem: time, input, physics, script, ECS
entities, rendering, audio, network, asset completion callbacks.
`engineDispose` shuts everything down in reverse order.
**To exit gracefully:** set `ENGINE.running = false` -- the platform
main loop checks this each tick and calls `engineDispose` before
returning.
---
## System (`system.h`)
The system module is initialised very early (before most other
subsystems) and provides two things:
### Platform identity
```c
typedef enum { SYSTEM_PLATFORM_LIST } systemplatform_t;
systemplatform_t systemGetPlatform(void);
```
Platform names come from `systemplatformlist.h` via an X-macro. This
lets game code query the runtime platform when compile-time guards are
not sufficient (e.g. serializing platform name to a log).
### Dialog blocking
Some platforms (PSP, Wii) show OS-level dialogs (Wi-Fi setup, save
management) that block the normal game loop. The system module exposes
the current dialog state so the engine main loop can adjust:
```c
typedef enum {
SYSTEM_DIALOG_TYPE_NONE,
SYSTEM_DIALOG_TYPE_RENDER_BLOCKING, // skip render but still tick
SYSTEM_DIALOG_TYPE_TICK_BLOCKING, // skip both render and tick
} systemdialogtype_t;
systemdialogtype_t systemGetActiveDialogType(void);
```
`engineUpdate` checks this before calling render / update code.
Most platforms always return `SYSTEM_DIALOG_TYPE_NONE`.
---
## Log (`log.h`)
Simple printf-style logging with two levels. Always use these instead
of `printf` / `fprintf`.
```c
void logDebug(const char_t *message, ...);
// Writes to the debug output (stdout on desktop, platform console
// on handhelds). No-op in release builds on some platforms.
void logError(const char_t *message, ...);
// Writes to the error output. On some platforms (PSP) this may
// pause execution to ensure the message is visible before continuing.
```
**Do not** use `logDebug` / `logError` for structured error handling --
that is what `errorThrow` / `errorChain` are for. Log calls are for
human-readable diagnostics only.
The error system calls `logError` internally when printing a caught
error via `errorPrint()`.
+133
View File
@@ -0,0 +1,133 @@
# Error Handling System
Source: `src/dusk/error/`
## Philosophy
Error handling is return-value based. Functions that can fail return
`errorret_t`. There are no exceptions, `errno`, `setjmp`, or global
error codes. Each thread has its own isolated error state.
## Types
```c
typedef uint8_t errorcode_t;
typedef struct {
errorcode_t code;
char_t *message; // allocated; freed by errorCatch
char_t *lines; // call-stack trace; allocated; freed by errorCatch
} errorstate_t;
typedef struct {
errorcode_t code;
errorstate_t *state; // NULL on success
} errorret_t;
```
**Constants:** `ERROR_OK = 0`, `ERROR_NOT_OK = 1`.
Error state is thread-local:
```c
extern THREAD_LOCAL errorstate_t ERROR_STATE;
```
Each thread has its own `ERROR_STATE` so concurrent errors never
interfere.
## Macros
### Throwing an error
```c
errorThrow("message %d", value);
// Throws with ERROR_NOT_OK, captures __FILE__ / __func__ / __LINE__.
errorThrowWithCode(code, "message %d", value);
// Same but with a specific error code.
```
Both macros **return from the current function** with an `errorret_t`.
Do not call them in void functions.
### Propagating up the call stack
```c
errorChain(someCall());
```
If `someCall()` returned an error, appends the current location to the
stack trace and **returns** that error from the current function.
If `someCall()` returned success, execution continues normally.
### Returning success
```c
errorOk();
```
Returns `errorret_t` with `code == ERROR_OK` and asserts the thread's
`ERROR_STATE` is clean (no leftover error). Must be the last statement
in a fallible function.
### Inspecting a result
```c
if(errorIsOk(ret)) { ... }
if(errorIsNotOk(ret)) { ... }
```
### Cleaning up
```c
errorCatch(ret);
```
Frees `ret.state->message` and `ret.state->lines`, resets the thread's
`ERROR_STATE.code` to `ERROR_OK`. Safe to call on a success return
(no-op). **Always call `errorCatch` on errors you are handling** --
otherwise the allocated message and stack-trace leak.
### Logging
```c
errorPrint(ret); // prints code + message + stack trace, returns ret
```
## Typical patterns
### Fallible function
```c
errorret_t myFunction(int_t x) {
if(x < 0) errorThrow("x must be non-negative, got %d", x);
errorChain(someOtherFallibleCall(x));
errorOk();
}
```
### Caller that handles errors
```c
errorret_t ret = myFunction(-1);
if(errorIsNotOk(ret)) {
errorCatch(errorPrint(ret));
// ... fallback logic ...
}
```
### Stack trace accumulation
Each `errorChain()` call appends a line to `ret.state->lines` in the
format ` at file:line in function\n`. A deeply chained error produces
a full call path readable from `ret.state->lines`.
## What NOT to do
- Do not use raw `errno` for in-engine errors.
- Do not return an error code integer -- always return `errorret_t`.
- Do not ignore an error return without calling `errorCatch` on it if
you are not propagating it.
- Do not mix assertions with error handling. Assertions are for
programmer mistakes; `errorThrow` is for expected failure paths.
+102
View File
@@ -0,0 +1,102 @@
# Event System
Source: `src/dusk/event/`
## Overview
The event system is a simple publish-subscribe mechanism backed by
caller-owned static arrays. There is no heap allocation in the event
system itself -- the caller provides the backing storage.
## API
```c
typedef void (*eventcallback_t)(void *params, void *user);
typedef struct {
eventcallback_t *callbacks;
void **users;
size_t size;
uint32_t count;
} event_t;
```
### Initialise
```c
void eventInit(
event_t *event,
eventcallback_t *callbacks,
void **users,
size_t size
);
```
`callbacks` and `users` are caller-owned arrays of length `size`. Both
are zeroed by `eventInit`. `users` may be `NULL` if no subscriber needs
a user pointer.
### Subscribe / unsubscribe
```c
void eventSubscribe(event_t *event, eventcallback_t callback, void *user);
void eventUnsubscribe(event_t *event, eventcallback_t callback);
```
The same `(callback, user)` pair may only be subscribed once --
`eventSubscribe` asserts on a duplicate. `eventUnsubscribe` is a no-op
if the pair is not found. Unsubscribing uses swap-with-last to keep the
array packed; ordering is not preserved.
### Fire
```c
void eventInvoke(const event_t *event, void *params);
```
Calls every subscriber in registration order, passing `params` and
each subscriber's `user` pointer.
## Usage pattern
Declare the backing arrays alongside the event struct, typically as
struct fields or static variables:
```c
#define MY_EVENT_CAPACITY 4
typedef struct {
event_t onComplete;
eventcallback_t _completeCbs[MY_EVENT_CAPACITY];
void *_completeUsers[MY_EVENT_CAPACITY];
} mystate_t;
// Init:
eventInit(
&state.onComplete,
state._completeCbs,
state._completeUsers,
MY_EVENT_CAPACITY
);
// Publish:
eventInvoke(&state.onComplete, &someParams);
// Subscribe from outside:
eventSubscribe(&state.onComplete, myHandler, myUserPtr);
```
## Constraints
- Capacity is fixed at init time. Exceeding it is a runtime assertion.
- Subscriber order is not stable after an unsubscribe.
- `eventInvoke` is synchronous -- all callbacks run on the calling
thread before it returns.
- Do not subscribe or unsubscribe from inside a callback -- the array
may shift during iteration.
## Where events are used
- `inputactiondata_t`: `onPressed`, `onReleased` per action
- Any engine subsystem that exposes hooks (network connect/disconnect,
asset batch completion, etc.)
+130
View File
@@ -0,0 +1,130 @@
# Input System
Source: `src/dusk/input/`, platform layers in `src/dusk<platform>/input/`
## Architecture
The input system has two layers:
1. **Action layer** (`inputaction_t`) -- named gameplay inputs, e.g.
UP, DOWN, ACCEPT, CANCEL. This is what game code reads.
2. **Button layer** (`inputbutton_t`) -- physical hardware inputs, e.g.
keyboard key, gamepad button, analog axis, mouse axis. Multiple
buttons can bind to the same action (the highest value wins).
The platform layer implements two hooks:
- `inputUpdatePlatform()` -- read hardware state once per frame
- `inputButtonGetValuePlatform()` -- return the analog value [0.0, 1.0]
for a given button
## Global state
```c
extern input_t INPUT;
// INPUT.actions[INPUT_ACTION_COUNT] -- all action states
// INPUT.platform -- platform-specific data
```
## Reading actions (game code)
```c
// Analog value this frame (0.0 - 1.0)
float_t inputGetCurrentValue(inputaction_t action);
// Analog value last frame
float_t inputGetLastValue(inputaction_t action);
// Boolean helpers (built on current/last values)
bool_t inputIsDown(inputaction_t action);
bool_t inputWasDown(inputaction_t action);
bool_t inputPressed(inputaction_t action); // was up, now down
bool_t inputReleased(inputaction_t action); // was down, now up
// 2D axis helpers
void inputAxis2D(
inputaction_t horiz,
inputaction_t vert,
vec2 out
);
float_t inputAngle2D(inputaction_t horiz, inputaction_t vert);
void inputAxis(inputaction_t action, float_t *out);
// Deadzone filter (applied to raw axis values)
float_t inputDeadzone(float_t value, float_t deadzone);
```
## Binding buttons to actions
```c
void inputBind(inputaction_t action, inputbutton_t button);
```
Each platform's init function calls `inputBind` to wire its hardware
buttons to the standard action IDs. Game code should never need to call
`inputBind` -- it is set up once during platform init.
## Button types
```c
INPUT_BUTTON_TYPE_KEYBOARD // SDL scancode (SDL2 targets only)
INPUT_BUTTON_TYPE_POINTER // Mouse axes: X, Y, Z, WHEEL_X, WHEEL_Y
INPUT_BUTTON_TYPE_TOUCH // Touch (defined, not fully implemented)
INPUT_BUTTON_TYPE_GAMEPAD // Digital gamepad buttons
INPUT_BUTTON_TYPE_GAMEPAD_AXIS // Analog axes (-1.0 to 1.0 internally)
```
## Events
Each action has `onPressed` and `onReleased` event callbacks. Subscribe
via the event system (see `.claude/events.md`):
```c
eventSubscribe(&INPUT.actions[ACTION_ACCEPT].onPressed, myCallback, NULL);
```
## Platform implementations
### SDL2 (`src/dusksdl2/input/`)
Handles Linux, Knulli, and PSP (PSP adds its own button mapping layer
on top of SDL2).
- Keyboard: SDL scancode array from `SDL_GetKeyboardState()`
- Pointer: normalized mouse position (0.0-1.0), scroll axes
- Gamepad: first available `SDL_GameController`; axis values normalized
to [-1.0, 1.0] with deadzone (default 0.2f via `inputGetDeadzoneSDL2`)
### Dolphin -- GameCube / Wii (`src/duskdolphin/input/`)
Uses `libogc` PAD API. No keyboard or pointer input -- trying to use
those button types is a compile-time `#error`.
- Gamepad: `PAD_ScanPads()` + `PAD_ButtonsHeld()` for pad 0
- Axes: left stick X/Y, C-stick X/Y, L/R triggers (6 total)
- Deadzone: hardcoded 0.2f
- Default bindings set at init: D-pad/L-stick = directional actions,
A = ACCEPT, B = CANCEL, X = CONSOLE, Start = RAGEQUIT
### PSP (`src/duskpsp/input/`)
Layered on top of SDL2. `inputInitPSP()` remaps SDL2 controller button
constants to PSP button names, then calls `inputBind` to wire them:
| PSP button | SDL2 constant |
|------------|---------------|
| Cross | `SDL_CONTROLLER_BUTTON_A` |
| Circle | `SDL_CONTROLLER_BUTTON_B` |
| Triangle | `SDL_CONTROLLER_BUTTON_Y` |
| Square | `SDL_CONTROLLER_BUTTON_X` |
| L / R | `SDL_CONTROLLER_BUTTON_LEFTSHOULDER` / `RIGHTSHOULDER` |
| L-Stick | `SDL_CONTROLLER_AXIS_LEFTX/Y` |
## Platform capability notes
| Feature | Linux/Knulli | PSP | GameCube/Wii |
|---------|-------------|-----|--------------|
| Keyboard | Yes (SDL2) | No | No |
| Pointer/Mouse | Yes (SDL2) | No | No |
| Gamepad | Yes (SDL2) | Yes (SDL2) | Yes (PAD) |
| Analog axes | Yes | L-Stick only | L-Stick, C-Stick, Triggers |
| Touch | Defined, not implemented | -- | -- |
+90
View File
@@ -0,0 +1,90 @@
# Locale System
Source: `src/dusk/locale/`, asset loader at
`src/dusk/asset/loader/locale/`
## Overview
The locale system loads Gettext PO files from the asset archive and
provides string lookup with plural-form support and printf-style
argument substitution. Locale files live in `locale/` inside `dusk.dsk`.
## Global state
```c
extern localemanager_t LOCALE;
// LOCALE.locale -- currently active localeinfo_t
// LOCALE.entry -- locked assetentry_t for the current PO file
```
## Initialise and switch locale
```c
errorret_t localeManagerInit();
// Defaults to LOCALE_EN_US (locale/en_US.po).
errorret_t localeManagerSetLocale(const localeinfo_t *locale);
// Unlocks the old entry, loads and locks the new one.
// Blocks until the new PO file is fully parsed.
void localeManagerDispose();
```
## Getting a localised string
```c
// Variadic (printf-style args):
localeManagerGetText(id, buffer, bufferSize, plural, ...);
// With a pre-built args array:
localeManagerGetTextArgs(id, buffer, bufferSize, plural, args, argCount);
```
Both are macros that delegate to `assetLocaleGetStringWithVA` /
`assetLocaleGetStringWithArgs`.
- `id` -- message ID string (the English key in the PO file)
- `plural` -- plural index (0 for singular, 1+ per PO plural rules)
- `buffer` -- destination `char_t` array
- `bufferSize` -- size of the destination buffer
- `...` -- format arguments matching `%s`, `%d`, `%f` placeholders
## Locale descriptors (`localeinfo_t`)
```c
typedef struct {
const char_t *name; // e.g. "en-US"
const char_t *file; // path inside dusk.dsk, e.g. "locale/en_US.po"
} localeinfo_t;
```
The built-in descriptor is:
```c
static const localeinfo_t LOCALE_EN_US = {
.name = "en-US",
.file = "locale/en_US.po",
};
```
Add new locales by declaring another `localeinfo_t` constant and
shipping the corresponding `.po` file in the asset archive.
## PO file format notes
The loader (`assetlocaleloader`) parses standard Gettext PO syntax:
- `msgid` / `msgstr` pairs
- `msgid_plural` / `msgstr[n]` for plural forms
- The `Plural-Forms:` header (e.g. `nplurals=2; plural=(n != 1);`)
is parsed and evaluated at lookup time
Argument substitution uses `%s`, `%d`, `%f` placeholders (not
standard Gettext `%1` positional args).
## Adding a new locale
1. Create `locale/<lang_COUNTRY>.po` with a valid `Plural-Forms:`
header and the translated `msgid`/`msgstr` entries.
2. Pack it into `dusk.dsk`.
3. Add a `localeinfo_t` constant in `localeinfo.h`.
4. Call `localeManagerSetLocale()` with the new descriptor to activate.
+132
View File
@@ -0,0 +1,132 @@
# Network System
Source: `src/dusk/network/`, platform layers in
`src/dusk<platform>/network/`
## Overview
The network system provides a platform-agnostic API for detecting and
managing a network connection. Higher-level functionality (HTTP, sockets)
is not yet implemented in any platform. The system handles the
connection lifecycle -- connect, detect disconnect, disconnect -- and
reports the current IP address.
## Implementation status by platform
| Platform | Connection | IP info | HTTP/Requests | Notes |
|----------|-----------|---------|--------------|-------|
| **Linux** | Auto (OS) | IPv4 + IPv6 | Not implemented | `getifaddrs()` |
| **Knulli** | Auto (OS) | IPv4 + IPv6 | Not implemented | same as Linux |
| **PSP** | Manual dialog | IPv4 only | Not implemented | `sceUtilityNetconf`; SSL/HTTP modules commented out |
| **GameCube** | Manual DHCP | IPv4 only | Not implemented | `if_config()` stubbed; `net_init()` commented out |
| **Wii** | Manual DHCP | IPv4 only | Not implemented | blocking `if_config()` via System Menu Wi-Fi settings |
## Connection states
```c
typedef enum {
NETWORK_STATE_DISCONNECTED,
NETWORK_STATE_CONNECTING,
NETWORK_STATE_CONNECTED,
NETWORK_STATE_DISCONNECTING,
} networkstate_t;
```
## Global state
```c
extern network_t NETWORK;
// NETWORK.state -- current connection state
// NETWORK.platform -- platform-specific data
// NETWORK.onDisconnect -- callback fired on unexpected disconnect
```
## Core API (`network.h`)
```c
errorret_t networkInit();
errorret_t networkUpdate(); // call each frame; detects dropped connections
errorret_t networkDispose();
bool_t networkIsConnected();
void networkRequestConnection(
void (*onConnected)(void *user),
void (*onFailed)(errorret_t error, void *user),
void (*onDisconnect)(errorret_t error, void *user),
void *user
);
void networkRequestDisconnection(
void (*onComplete)(void *user),
void *user
);
```
On platforms that manage their own connection (Linux, macOS, Windows),
`networkRequestConnection` immediately calls `onConnected` if a network
interface is up, or `onFailed` if not. No `networkPlatformRequestConnection`
macro is needed.
On platforms that require explicit connection (PSP, Wii), the platform
implements `networkPlatformRequestConnection` and
`networkPlatformRequestDisconnection`.
## Network info
```c
networkinfo_t networkGetInfo();
// Only valid when NETWORK.state == NETWORK_STATE_CONNECTED.
```
```c
typedef struct {
networktype_t type; // NETWORK_TYPE_IPV4 or (ifdef) NETWORK_TYPE_IPV6
union {
networkinfoipv4_t ipv4; // uint8_t ip[4]
networkinfoipv6_t ipv6; // uint8_t ip[16] (requires DUSK_NETWORK_IPV6)
};
} networkinfo_t;
```
IPv6 support requires the `DUSK_NETWORK_IPV6` compile-time define.
## Platform-specific notes
### Linux / Knulli
Fully functional for connection detection and IP querying. No explicit
connect/disconnect step needed -- the OS manages the interface. Uses
`getifaddrs()` to find the first non-loopback running interface.
### PSP
Connection is asynchronous and driven by a state machine in
`networkPSPUpdate()`. The PSP shows the built-in network configuration
dialog (`sceUtilityNetconfInitStart`) to let the user pick a Wi-Fi
access point.
HTTP/SSL modules (`psphttp`, `pspssl`) are loaded in commented-out code
-- the infrastructure for HTTP exists but is disabled.
### GameCube
`net_init()` is commented out. Networking on GameCube is non-functional
in the current build.
### Wii
Uses `if_config()` (DHCP via libogc) to connect using the Wi-Fi settings
stored in Wii System Menu. This call **blocks** the main thread. The
connection only works when `DUSK_WII` is defined; the GameCube path
always fails.
## Adding a new platform implementation
1. Create `src/dusk<platform>/network/network<platform>.h/.c`.
2. Implement the five required functions:
`Init`, `Update`, `Dispose`, `IsConnected`, `GetInfo`.
3. Optionally implement `RequestConnection` and `RequestDisconnection`
if the platform requires an explicit connection step.
4. Create `networkplatform.h` mapping each `networkPlatform*` macro to
your functions, and defining `networkplatform_t`.
+83
View File
@@ -0,0 +1,83 @@
# Optimization Guidelines
Dusk must run well on severely resource-constrained hardware. The PSP
has 32 MB of RAM and a 333 MHz MIPS CPU. The GameCube has 24 MB of RAM
and a 485 MHz PowerPC CPU with no FPU for integer paths. Optimization
is not an afterthought -- it is a first-class design constraint.
## General principles
- **Measure before optimizing.** Don't guess where bottlenecks are.
Profile on the actual target hardware when possible.
- **Data-oriented design.** The ECS exists to enable cache-friendly
iteration over components. Keep hot data tightly packed (SoA over
AoS where it matters).
- **Minimize allocations.** Dynamic allocation at runtime is expensive
and causes fragmentation. Prefer fixed-size pools, arenas, and
pre-allocated arrays.
- **Avoid per-frame allocations.** Anything allocated and freed every
tick is a red flag. Use scratch buffers or static pools.
- **Avoid recursion** on constrained targets -- stack is small.
## Memory
| Platform | Total RAM | Notes |
|------------|-------------|-----------------------------------|
| GameCube | 24 MB | 16 MB main + 8 MB "Aram" (audio) |
| Wii | 88 MB | 24 MB MEM1 + 64 MB MEM2 |
| PSP | 32 MB | 4 MB reserved for OS |
| Vita | 512 MB | Much more headroom |
| Linux | Host RAM | Effectively unlimited |
Treat the GameCube 16 MB main RAM as the worst-case constraint when
designing data structures and budgets.
Always use `memoryAllocate` / `memoryFree` -- never `malloc` / `free`.
The engine allocator tracks usage and can enforce budgets per platform.
## Math
- Prefer integer math over floating-point on platforms without an FPU.
- Use fixed-point arithmetic (`int32_t` with a known scale) for physics
and animation on PSP/GameCube where FPU throughput is limited.
- SIMD / VFPU (PSP) and paired-singles (GameCube) are available but
require platform-guarded code paths under `#ifdef DUSK_PSP` etc.
- Avoid `double` entirely -- use `float_t` (32-bit) throughout.
## Rendering
- Batch draw calls aggressively. Every draw call has overhead on all
platforms; consoles are especially sensitive.
- Minimize state changes (texture binds, shader switches, etc.).
- Use display lists (GameCube/Wii) and vertex buffer objects (OpenGL)
to offload geometry to GPU memory.
- Keep texture sizes powers of two. Non-PoT textures are unsupported
or have penalties on PSP and GameCube.
## Asset loading
- Assets are loaded asynchronously via the asset loader system. Do not
block the game loop waiting for assets.
- Compress textures to the native format for each platform at build
time, not at runtime.
- Stream large assets from the filesystem rather than loading them all
at startup.
## Platform-specific notes
### PSP
- The Media Engine (ME) is a second CPU core -- use it for audio and
background decompression, not general logic.
- VFPU gives 4-wide SIMD floats; use it for matrix and vector math.
- Keep the uncached scratchpad (4 KB at 0x00010000) in mind for hot
temporary data.
### GameCube / Wii
- The GX display list pipeline is the primary rendering path; avoid
immediate-mode GX calls in the hot path.
- Texture Compression (CMPR / S3TC equivalent) halves texture memory.
- Wii: prefer MEM1 for GPU-accessed data; MEM2 for CPU-only buffers.
### PSP / Vita
- OpenGL ES has a subset of desktop OpenGL. Avoid extensions and
features that are not in the ES 1.1 / ES 2.0 core.
+127
View File
@@ -0,0 +1,127 @@
# Physics System
Source: `src/dusk/physics/`, entity component at
`src/dusk/entity/component/physics/entityphysics.h/.c`
## Overview
Dusk uses a lightweight, custom 3D physics simulation with no external
library dependency. It is integrated with the ECS: only entities that
have both a `COMPONENT_TYPE_PHYSICS` and a `COMPONENT_TYPE_POSITION`
component participate in the simulation.
## Shapes
```c
typedef enum {
PHYSICS_SHAPE_CUBE, // Axis-aligned bounding box (AABB)
PHYSICS_SHAPE_SPHERE,
PHYSICS_SHAPE_CAPSULE, // Y-axis aligned; radius + halfHeight
PHYSICS_SHAPE_PLANE, // Infinite plane; normal + distance
} physicshapetype_t;
```
All shape pairs are supported by the collision dispatch
(`physicsTestShapeVsShape`). See `physicstest.h` for the individual
test functions.
## Body types
```c
typedef enum {
PHYSICS_BODY_STATIC, // Never moves; immovable collision surface
PHYSICS_BODY_DYNAMIC, // Driven by gravity, velocity, collisions
PHYSICS_BODY_KINEMATIC, // Moved programmatically; collides but not
// driven by the simulation (e.g. player)
} physicsbodytype_t;
```
## World and gravity
```c
extern physicsworld_t PHYSICS_WORLD;
// PHYSICS_WORLD.gravity -- default {0, -9.81, 0}
```
The simulation step is driven by `physicsManagerUpdate()`, which is
called each fixed-timestep game loop tick. It skips dynamic-timestep
sub-steps (`DUSK_TIME_DYNAMIC`).
## Simulation phases (each step)
1. **Integrate dynamics** -- apply gravity scaled by `gravityScale`,
advance velocity, update position.
2. **Dynamic vs static/kinematic** -- resolve penetration and cancel
the normal velocity component.
3. **Dynamic vs dynamic** -- split penetration 50/50; exchange
relative normal velocity.
4. **Rebuild transforms** -- call `entityPositionRebuild()` for all
affected dynamic bodies.
`PHYSICS_GROUND_THRESHOLD = 0.707f` -- a collision normal with a Y
component above this value sets `onGround = true` on the dynamic body.
## Entity component (`entityphysics_t`)
```c
typedef struct {
physicsbodytype_t type;
physicsshape_t shape;
vec3 velocity;
float_t gravityScale; // default 1.0
bool_t onGround; // set by the solver each step
} entityphysics_t;
```
Default on init: DYNAMIC body, 0.5m half-extents AABB cube,
`gravityScale = 1.0f`.
### Component API
```c
entityphysics_t *entityPhysicsGet(entityid_t, componentid_t);
void entityPhysicsSetShape(entityid_t, componentid_t, physicsshape_t);
physicsshape_t entityPhysicsGetShape(entityid_t, componentid_t);
void entityPhysicsSetVelocity(entityid_t, componentid_t, vec3);
void entityPhysicsGetVelocity(entityid_t, componentid_t, vec3 dest);
void entityPhysicsApplyImpulse(entityid_t, componentid_t, vec3);
// No-op on STATIC bodies.
bool_t entityPhysicsIsOnGround(entityid_t, componentid_t);
void entityPhysicsSetBodyType(entityid_t, componentid_t, physicsbodytype_t);
physicsbodytype_t entityPhysicsGetBodyType(entityid_t, componentid_t);
```
## Collision detection primitives (`physicstest.h`)
Each function returns `true` if overlapping and writes the push-out
normal (pointing from B toward A) and penetration depth.
| Function | Shapes |
|----------|--------|
| `physicsTestAabbVsAabb` | CUBE vs CUBE |
| `physicsTestSphereVsSphere` | SPHERE vs SPHERE |
| `physicsTestSphereVsAabb` | SPHERE vs CUBE |
| `physicsTestSphereVsPlane` | SPHERE vs PLANE |
| `physicsTestAabbVsPlane` | CUBE vs PLANE |
| `physicsTestCapsuleVsSphere` | CAPSULE vs SPHERE |
| `physicsTestCapsuleVsAabb` | CAPSULE vs CUBE |
| `physicsTestCapsuleVsPlane` | CAPSULE vs PLANE |
| `physicsTestCapsuleVsCapsule` | CAPSULE vs CAPSULE |
| `physicsTestShapeVsShape` | Any pair via dispatch |
Capsules are always Y-axis aligned. Planes are infinite (not half-spaces).
## Limitations and known gaps
- No rotation simulation -- bodies do not rotate from collisions.
- No friction or damping model yet.
- No sleeping / deactivation for resting bodies.
- No broad-phase culling: the solver is O(n^2) per phase.
This is acceptable up to the ECS entity limit (64 entities) but must
be revisited if the entity count grows.
- Capsule vs plane uses the bottom/top hemisphere centers as a
degenerate approximation -- accurate for large planes but
not for thin surfaces.
+288
View File
@@ -0,0 +1,288 @@
# Platform -- Dolphin (GameCube and Wii)
`DUSK_TARGET_SYSTEM`: `gamecube` / `wii`
Source layer: `src/duskdolphin/`
Renderer: libogc GX (native Nintendo hardware)
---
## Overview
GameCube and Wii are collectively called the **Dolphin** targets. They
share a single source layer (`src/duskdolphin/`) and a shared CMake base
(`cmake/targets/dolphin.cmake`). Individual targets add `DUSK_GAMECUBE`
or `DUSK_WII` on top.
Both are **big-endian** PowerPC platforms. They do **not** use SDL2 or
OpenGL -- rendering and input go through `libogc` (the open-source
GameCube/Wii SDK) and the GX hardware API directly.
---
## Hardware
| Attribute | GameCube | Wii |
|-----------|---------|-----|
| CPU | IBM PowerPC 750CL (Gekko), 485 MHz | IBM Broadway (Wii CPU), 729 MHz |
| RAM | 24 MB (16 MB MEM1 + 8 MB ARAM) | 88 MB (24 MB MEM1 + 64 MB MEM2) |
| GPU | ATI Flipper (GX) | ATI Hollywood (GX) |
| Display | 640x480 (480p max) | 640x480 (480p/576i), 480p/1080i via component |
| Storage | Memory Card (slots A/B), SD Gecko | SD card, USB, NAND |
| Endian | Big-endian | Big-endian |
Treat the **GameCube 16 MB MEM1** as the worst-case RAM budget for data
structures shared between both targets.
---
## Compile-time macros
| Macro | GameCube | Wii | Notes |
|-------|---------|-----|-------|
| `DUSK_DOLPHIN` | yes | yes | Set by `dolphin.cmake` |
| `DUSK_GAMECUBE` | yes | no | |
| `DUSK_WII` | no | yes | |
| `DUSK_INPUT_GAMEPAD` | yes | yes | |
| `DUSK_DISPLAY_WIDTH` | 640 | 640 | |
| `DUSK_DISPLAY_HEIGHT` | 480 | 480 | |
| `DUSK_THREAD_PTHREAD` | yes | yes | devkitPPC pthreads |
| `DUSK_PLATFORM_ENDIAN_BIG` | yes | yes | Not set by cmake -- apply manually |
| `DOL` | 1 | 1 | Build type token |
| `ISO` | 2 | 2 | Build type token |
| `DUSK_DOLPHIN_BUILD_TYPE` | `DOL` or `ISO` | `DOL` or `ISO` | |
| `DUSK_DOLPHIN_BUILD_ISO` | if ISO mode | if ISO mode | |
No `DUSK_SDL2`, no `DUSK_OPENGL`, no `DUSK_INPUT_KEYBOARD`,
no `DUSK_INPUT_POINTER`, no `DUSK_TIME_DYNAMIC`.
Attempting to use `DUSK_INPUT_KEYBOARD` or `DUSK_INPUT_POINTER` causes
a compile-time `#error` in `inputdolphin.h`.
---
## Endianness
**Both GameCube and Wii are big-endian.** This is the most critical
platform difference from all other targets.
- All binary asset data (`.dtf` tilesets, STL meshes, DTF headers, etc.)
must be byte-swapped when read on Dolphin.
- Use `endianLittleToHost32` / `endianLittleToHost16` etc. from
`util/endian.h` when reading any multi-byte value from a file.
- Save files are stored in little-endian order; the save stream handles
this transparently via the `saveFile*` macros.
- Network data likewise needs endian conversion.
See `.claude/util.md` (Endian section) for the full API.
---
## Display
- Fixed 640x480 resolution, driven by GX (the hardware rasteriser).
- Uses double-buffered framebuffers:
```c
typedef struct {
void *frameBuffer[2]; // double-buffered
int_t whichFrameBuffer;
GXRModeObj *screenMode;
void *fifoBuffer; // GX command FIFO, 256 KB
} displaydolphin_t;
```
- The GX pipeline uses display lists for efficient draw call batching --
avoid immediate-mode GX calls in the hot path.
- `CONF_GetAspectRatio()` returns `CONF_ASPECT_4_3` on GameCube (always)
and the user's setting on Wii. Use `systemGetAspectRatioDolphin()`.
---
## Asset loading
Two modes are selected at CMake configure time via
`DUSK_DOLPHIN_BUILD_TYPE`:
### DOL mode (default -- `DUSK_DOLPHIN_BUILD_TYPE=DOL`)
Assets are loaded from `dusk.dsk` on a FAT filesystem -- SD card on Wii
(via SD slot), or SD Gecko / SD adapter on GameCube. The loader searches
these paths in order:
```c
"/", "/Dusk", "/dusk", "/DUSK",
"/apps", "/apps/Dusk", "/apps/dusk", "/apps/DUSK",
".", "./Dusk", "./dusk", ...
```
Uses `libfat` for filesystem access.
### ISO mode (`DUSK_DOLPHIN_BUILD_TYPE=ISO`)
`dusk.dsk` is read directly off the DVD disc via the libogc DVD driver
(`assetdolphindvd.c`). Reads are 32-byte aligned:
```c
#define ASSET_DOLPHIN_DVD_ALIGN 32u
```
The DVD FST (file-system table) is parsed at init to locate the data
file. All reads go through `assetDolphinDVDRead(offset, size)` which
returns an aligned heap buffer that the caller must free.
Post-build in ISO mode, `makedolphiniso.py` produces **three disc
images** (NTSC-J, NTSC-U, PAL).
---
## Input
Uses libogc `PAD` API. Only GameCube controllers are supported (port 0
by default; up to 4 via `PAD_CHANMAX`).
Available axes (6 total per controller):
| Axis | Enum |
|------|------|
| Left stick X/Y | `INPUT_GAMEPAD_AXIS_LEFT_X/Y` |
| C-stick X/Y | `INPUT_GAMEPAD_AXIS_C_X/Y` |
| L trigger | `INPUT_GAMEPAD_AXIS_TRIGGER_LEFT` |
| R trigger | `INPUT_GAMEPAD_AXIS_TRIGGER_RIGHT` |
Axis values are normalised by dividing the raw 8-bit value by 128.0.
Deadzone: 0.2 (hardcoded).
Default bindings set at init: D-pad/left stick = directional actions,
A = ACCEPT, B = CANCEL, X = CONSOLE, Start = RAGEQUIT.
Wii Remote / Nunchuk / Classic Controller / Pro Controller are not yet
implemented (noted as TODO in `inputdolphin.h`).
---
## Save system
Uses the libogc Memory Card API (`CARD_*`) to read/write save slots.
```c
typedef struct {
card_file cardFile;
uint8_t cardBuffer[CARD_WORKAREA] __attribute__((aligned(32)));
bool_t mounted;
} savedolphin_t;
```
- Default channel: `CARD_SLOTA` (Memory Card slot A).
Override via `SAVE_DOLPHIN_CHANNEL`.
- Sector size: 8192 bytes (`SAVE_DOLPHIN_SECTOR_SIZE`).
- Buffers must be 32-byte aligned (enforced by `__attribute__((aligned(32)))`).
- Game code: `DUSK` (4 chars, override via `SAVE_DOLPHIN_GAME_CODE`).
- The card must be mounted before any read/write. `saveInitDolphin()`
mounts slot A; failures are treated as "no save present".
- Save stream handles little-endian encoding transparently -- all data
stored little-endian on the card even though the CPU is big-endian.
---
## Network
### GameCube
`net_init()` is commented out. Networking is **non-functional** on
GameCube in the current codebase. The BBA (Broadband Adapter) link
library is in a commented `# bba` in `gamecube.cmake`.
### Wii
Uses `if_config()` from libogc which reads Wi-Fi settings saved in the
Wii System Menu. The call **blocks** the main thread until DHCP
completes or fails. Wii network is available only when `DUSK_WII` is
defined; the GameCube path always fails immediately.
IPv6 is not supported on either Dolphin target.
---
## Time
- No `DUSK_TIME_DYNAMIC`. All ticks are fixed 16 ms steps.
- Tick source: `__SYS_GetSystemTime()` returns PowerPC bus ticks.
- Real time: ticks converted to microseconds via `ticks_to_microsecs()`,
then offset from the GameCube epoch (2000-01-01 00:00:00) to the UNIX
epoch (1970-01-01 00:00:00) by adding **946 684 800 seconds**.
- Timezone: always returned as 0 -- no timezone data without network time.
---
## System
Language and aspect ratio queries:
```c
// Language (used for locale selection):
systemGetLanguageDolphin();
// -> SYS_GetLanguage() on GameCube
// -> CONF_GetLanguage() on Wii
// Aspect ratio:
systemGetAspectRatioDolphin();
// -> CONF_ASPECT_4_3 always on GameCube
// -> CONF_GetAspectRatio() on Wii (4:3 or 16:9)
```
---
## Build and toolchain
Requires [devkitPro](https://devkitpro.org/) with `devkitPPC` and
`libogc` installed.
```sh
# GameCube (SD card / DOL mode)
cmake -B build \
-DDUSK_TARGET_SYSTEM=gamecube \
-DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/GameCube.cmake \
-DCMAKE_BUILD_TYPE=Release
cmake --build build
# Wii (SD card / DOL mode)
cmake -B build \
-DDUSK_TARGET_SYSTEM=wii \
-DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/Wii.cmake \
-DCMAKE_BUILD_TYPE=Release
cmake --build build
# Either target in ISO mode
cmake -B build \
-DDUSK_TARGET_SYSTEM=gamecube \
-DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/GameCube.cmake \
-DDUSK_DOLPHIN_BUILD_TYPE=ISO \
-DCMAKE_BUILD_TYPE=Release
cmake --build build
```
Post-build outputs (DOL mode): `Dusk.elf` + `Dusk.dol` (generated by
`elf2dol`). Copy `Dusk.dol` and `dusk.dsk` to the SD card.
Post-build outputs (ISO mode): `Dusk.dol` + disc images in
`NTSC-J/`, `NTSC-U/`, `PAL/` subdirectories.
Dependencies: libogc, devkitPPC, `fat` (DOL mode), cglm, zip, bz2,
zstd, z, lzma, m.
---
## Gotchas
- **Big-endian is the most common source of bugs** when porting code
from Linux. Always use `endian.h` utilities for file I/O and network.
- Memory is tight on GameCube -- 16 MB MEM1 must hold code, stack, heap,
framebuffers (2x 640x480x2 bytes), and the GX FIFO (256 KB).
- GX display lists are the correct rendering path; immediate-mode GX
calls carry heavy CPU overhead on the short FIFO pipeline.
- The GameCube has no FPU for integer paths. Avoid `double`; use
`float_t` throughout.
- `consoleInit` is shadowed to `consoleInitDolphin` to avoid conflicts
with the devkitPPC console API.
- On GameCube `CONF_GetAspectRatio()` is always 4:3; the macro is
defined to return `CONF_ASPECT_4_3` unconditionally.
- DVD reads must be 32-byte aligned and padded -- use
`ASSET_DOLPHIN_DVD_ALIGN_UP(n)` when computing read sizes in ISO mode.
+162
View File
@@ -0,0 +1,162 @@
# Platform -- Linux and Knulli
`DUSK_TARGET_SYSTEM`: `linux` / `knulli`
Source layer: `src/dusklinux/`
Renderer: OpenGL (Linux) / OpenGL ES via EGL (Knulli)
---
## Overview
Linux is the primary development target. Knulli is a Linux-based handheld
OS (e.g. Anbernic devices); it shares the `src/dusklinux/` layer entirely
and differs only in the CMake target (OpenGL ES instead of desktop OpenGL,
EGL instead of GLX, and no backtrace support).
Both targets use SDL2 for windowing and input. The window is resizable on
both (`DUSK_DISPLAY_SIZE_DYNAMIC`).
---
## Compile-time macros
| Macro | Linux | Knulli |
|-------|-------|--------|
| `DUSK_LINUX` | yes | yes |
| `DUSK_KNULLI` | no | yes |
| `DUSK_SDL2` | yes | yes |
| `DUSK_OPENGL` | yes | yes |
| `DUSK_OPENGL_ES` | no | yes |
| `DUSK_DISPLAY_SIZE_DYNAMIC` | yes | yes |
| `DUSK_INPUT_KEYBOARD` | yes | yes |
| `DUSK_INPUT_POINTER` | yes | yes |
| `DUSK_INPUT_GAMEPAD` | yes | yes |
| `DUSK_TIME_DYNAMIC` | yes | yes |
| `DUSK_NETWORK_IPV6` | yes | no |
| `DUSK_THREAD_PTHREAD` | yes | yes |
| `DUSK_CONSOLE_POSIX` | yes | no |
---
## Display
- Default logical resolution: **640x480** (`DUSK_DISPLAY_WIDTH_DEFAULT` /
`DUSK_DISPLAY_HEIGHT_DEFAULT`); game content renders at
`DUSK_DISPLAY_SCREEN_HEIGHT=240`.
- Dynamic resize: the window can be resized at any time; the engine
letterboxes/scales the logical framebuffer to fit.
- Screen mode is configurable via `SCREEN.mode` (see
`.claude/display-core.md`).
- Knulli uses OpenGL ES (GLES2) linked via EGL. Avoid any desktop
OpenGL extensions that are not in the ES2 core.
---
## Asset loading
`dusk.dsk` is located by searching a list of paths relative to the
current working directory:
```c
static const char_t *ASSET_LINUX_SEARCH_PATHS[] = {
"%s",
"../%s",
"../../%s",
"data/%s",
"../data/%s",
NULL
};
```
The first path where `dusk.dsk` is found wins. No packaging step is
required on Linux -- run from the build directory or the project root.
---
## Input
All three input types are supported:
- **Keyboard** -- SDL scancode array via `SDL_GetKeyboardState()`.
- **Pointer** -- mouse position normalized to [0, 1], scroll axes.
- **Gamepad** -- first available `SDL_GameController`; axes normalized
to [-1, 1] with a 0.2 deadzone.
See `.claude/input.md` for the full action/button API.
---
## Save system
Save files are plain files written to disk.
- Path: `./saves/save_N.dat` (override `SAVE_LINUX_PATH` to change the
directory at CMake configure time).
- Format: `SAVE_LINUX_FILE_FORMAT = "%s/save_%u.dat"` where `%u` is the
slot index.
- No OS-level dialog blocking -- saves are synchronous filesystem calls.
- Endian: host byte order (little-endian on x86/ARM).
---
## Network
- Connection is detected automatically via `getifaddrs()`. No explicit
connect step is needed.
- `networkRequestConnection` immediately calls `onConnected` if any
non-loopback interface is up, `onFailed` otherwise.
- IPv4 and IPv6 supported (`DUSK_NETWORK_IPV6`).
---
## Time
- Tick source: `SDL_GetTicks64()`.
- Real time: `clock_gettime(CLOCK_REALTIME)`.
- Dynamic timestep enabled (`DUSK_TIME_DYNAMIC`).
---
## Threading
pthreads (`DUSK_THREAD_PTHREAD`). Thread-local storage via `__thread`.
---
## Build and toolchain
No cross-compiler needed -- use the host GCC/Clang.
```sh
# Debug build
cmake -B build -DDUSK_TARGET_SYSTEM=linux -DCMAKE_BUILD_TYPE=Debug
cmake --build build
# Knulli (cross-compile to aarch64)
cmake -B build \
-DDUSK_TARGET_SYSTEM=knulli \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/aarch64-linux-gnu.cmake \
-DCMAKE_BUILD_TYPE=Release
cmake --build build
```
Dependencies: `SDL2`, `OpenGL` (Linux) or `GLES2` + `EGL` (Knulli),
`pthread`, `m`.
---
## Endianness
Little-endian. Detected at CMake configure time via `TestBigEndian` and
set as `DUSK_PLATFORM_ENDIAN_LITTLE` or `DUSK_PLATFORM_ENDIAN_BIG`.
---
## Gotchas
- `DUSK_CONSOLE_POSIX` enables POSIX-specific assert backtracing (Linux
only; Knulli does not set it).
- Knulli does not set `DUSK_NETWORK_IPV6` -- IPv6 may not be available
on handheld devices.
- `DUSK_TIME_DYNAMIC` is set, so physics/networking skip dynamic sub-steps
by checking `if(TIME.dynamicUpdate) return;`.
+46
View File
@@ -0,0 +1,46 @@
# Platform -- macOS
`DUSK_TARGET_SYSTEM`: `macos`
Source layer: `src/duskmacos/` (planned, does not exist yet)
Status: **Planned -- not yet implemented**
---
## Overview
macOS desktop is a planned target. No source layer, CMake target file,
or toolchain exists yet. The intended architecture mirrors Linux: SDL2
for windowing/input, OpenGL (or Metal via MoltenVK/SDL2) for rendering.
---
## Expected macros (when implemented)
| Macro | Expected |
|-------|---------|
| `DUSK_MACOS` | yes |
| `DUSK_SDL2` | yes |
| `DUSK_OPENGL` | yes |
| `DUSK_DISPLAY_SIZE_DYNAMIC` | yes |
| `DUSK_INPUT_KEYBOARD` | yes |
| `DUSK_INPUT_POINTER` | yes |
| `DUSK_INPUT_GAMEPAD` | yes |
| `DUSK_PLATFORM_ENDIAN_LITTLE` | yes |
| `DUSK_TIME_DYNAMIC` | yes |
| `DUSK_THREAD_PTHREAD` | yes |
---
## Notes
- Will be little-endian (Apple Silicon and Intel x86-64).
- Apple deprecated OpenGL on macOS in 10.14 (Mojave). The implementation
will need to either target the deprecated OpenGL path or use MoltenVK
(Vulkan-over-Metal) with an SDL2 OpenGL layer. This decision is
pending.
- Save files will likely live in `~/Library/Application Support/`.
- Expected to share `src/dusksdl2/` and `src/duskgl/` with Linux.
- Toolchain: native Clang via Xcode Command Line Tools, or a
cross-compile from Linux with osxcross.
Update this document when the macOS target is implemented.
+200
View File
@@ -0,0 +1,200 @@
# Platform -- Sony PSP
`DUSK_TARGET_SYSTEM`: `psp`
Source layer: `src/duskpsp/`
Renderer: OpenGL ES (legacy, via PSPGL/SDL2)
---
## Overview
The PSP is a 32 MB MIPS-based handheld console running at up to 333 MHz.
It uses SDL2 (ported to PSP) for windowing and OpenGL in legacy/fixed-
function mode. The game binary and all assets are packaged together inside
a `.pbp` file -- the PSP's native executable format.
---
## Hardware
| Attribute | Value |
|-----------|-------|
| CPU | MIPS R4000 (Allegrex), up to 333 MHz |
| RAM | 32 MB (4 MB reserved for OS) |
| Display | 480x272, 16/32-bit colour |
| Storage | Memory Stick (UMD for retail; MS for homebrew) |
| Endian | Little-endian |
---
## Compile-time macros
| Macro | Set |
|-------|-----|
| `DUSK_PSP` | yes |
| `DUSK_SDL2` | yes |
| `DUSK_OPENGL` | yes |
| `DUSK_OPENGL_LEGACY` | yes |
| `DUSK_INPUT_GAMEPAD` | yes |
| `DUSK_PLATFORM_ENDIAN_LITTLE` | yes |
| `DUSK_DISPLAY_WIDTH` | 480 |
| `DUSK_DISPLAY_HEIGHT` | 272 |
| `DUSK_THREAD_PTHREAD` | yes |
No `DUSK_DISPLAY_SIZE_DYNAMIC` -- the resolution is fixed.
No `DUSK_INPUT_KEYBOARD`, no `DUSK_INPUT_POINTER`.
---
## Display
- Fixed 480x272 resolution.
- OpenGL legacy (fixed-function pipeline, `DUSK_OPENGL_LEGACY`).
- Texture dimensions **must** be powers of two (use `mathNextPowTwo`).
- VFPU (4-wide float SIMD) is available -- use it for matrix/vector hot
paths under `#ifdef DUSK_PSP`.
---
## Asset loading
Assets are packed into the **PSAR** section of the `.pbp` file by the
post-build `create_pbp_file()` CMake command. At runtime,
`assetInitPBP()` locates and opens the PSAR from the running executable's
path.
The PBP format header:
```c
typedef struct {
char_t signature[4]; // "\0PBP"
uint32_t version;
uint32_t sfoOffset;
uint32_t icon0Offset;
uint32_t icon1Offset;
uint32_t pic0Offset;
uint32_t pic1Offset;
uint32_t snd0Offset;
uint32_t pspOffset;
uint32_t psarOffset; // dusk.dsk starts here
} assetpbpheader_t;
```
`assetpbp_t` holds the open file handle and parsed header. Asset paths
inside the PSAR are ZIP paths within `dusk.dsk`.
---
## Input
Layered on SDL2. `inputInitPSP()` maps PSP physical buttons to SDL2
`SDL_CONTROLLER_BUTTON_*` constants:
| PSP button | Action |
|------------|--------|
| Cross | Accept |
| Circle | Cancel |
| Triangle | - |
| Square | - |
| L / R | Shoulder buttons |
| D-pad | Directional |
| L-Stick | Analog axes |
No keyboard or pointer input available. Attempting to use
`INPUT_BUTTON_TYPE_KEYBOARD` on PSP is undefined behaviour.
The PSP system setting `PSP_UTILITY_ACCEPT_CROSS` / `ACCEPT_CIRCLE`
swaps the Cross and Circle button roles in OS dialogs -- read this via
`systemPSPGetCrossButtonSetting()` if you need to match the system
convention.
---
## Save system
- Path: `ms0:/PSP/SAVEDATA/<TITLE_ID><slot>/save.dat`
(default title ID `DUSK00001`, configurable via `SAVE_PSP_TITLE_ID`).
- Uses `sceIo` for file I/O -- no extra dialog required for raw reads.
- PSP OS-level save/load dialogs (via `sceUtility`) are separate and
block the main loop when open (`systemGetActiveDialogType()` returns
`SYSTEM_DIALOG_TYPE_TICK_BLOCKING`).
- Do not call save functions directly from game code during a dialog.
---
## Network
Connection requires an explicit user Wi-Fi selection step via the PSP
system network dialog (`sceUtilityNetconfInitStart`).
```
networkRequestConnection(onConnected, onFailed, onDisconnect, user);
// -> shows PSP Wi-Fi selection dialog (blocking dialog type)
// -> calls onConnected or onFailed when the dialog closes
```
HTTP and SSL modules (`psphttp`, `pspssl`, `pspnet_resolver`) are
linked in `psp.cmake` but the HTTP implementation code is commented out.
The infrastructure exists for future use.
---
## Time
- Tick source: `SDL_GetTicks64()`.
- Real time: `sceRtcGetCurrentTick()` (returns microseconds).
- Dynamic timestep is **not** enabled (`DUSK_TIME_DYNAMIC` not set).
Every tick is a fixed 16 ms step.
---
## System dialogs
PSP shows OS-level dialogs for:
- Wi-Fi configuration (`networkRequestConnection`)
- Save management (if using `sceUtility` save dialogs)
Check `systemGetActiveDialogTypePSP()` to know whether the main loop
should skip rendering or ticking.
---
## Build and toolchain
Requires the [PSPDEV toolchain](https://github.com/pspdev/pspdev).
Set `PSPDEV` in your environment before configuring.
```sh
cmake -B build \
-DDUSK_TARGET_SYSTEM=psp \
-DCMAKE_TOOLCHAIN_FILE=${PSPDEV}/lib/cmake/psp.cmake \
-DCMAKE_BUILD_TYPE=Release
cmake --build build
```
Post-build output: `Dusk.pbp` (executable + assets combined).
Dependencies: SDL2-PSP, OpenGL-PSP, pspgu, pspctrl, pspdisplay,
pspaudio, pspaudiolib, psputility, pspvfpu, pspvram, pspnet,
pspnet_inet, pspnet_apctl, psphttp, pspssl, pspdebug, psphprm,
mbedtls, mbedcrypto, lzma, zip, bz2, z.
---
## Endianness
Little-endian. `DUSK_PLATFORM_ENDIAN_LITTLE` is set at compile time.
No runtime endian check is needed.
---
## Gotchas
- The PSP has only 28 MB of usable RAM after the OS. Keep asset budgets
tight -- see `.claude/optimization.md`.
- VFPU instructions are not valid on threads other than the main thread
on some firmware versions. Use `assertIsMainThread` on any code that
calls VFPU intrinsics.
- OpenGL legacy mode means no vertex/fragment shaders; rendering uses
the fixed-function pipeline via `pspgl`.
- `DUSK_TIME_DYNAMIC` is absent -- physics always runs at exactly the
fixed step rate.
+173
View File
@@ -0,0 +1,173 @@
# Platform -- PlayStation Vita
`DUSK_TARGET_SYSTEM`: `vita`
Source layer: `src/duskvita/`
Renderer: vitaGL (OpenGL-over-GXM compatibility layer)
---
## Overview
The PlayStation Vita is an ARM-based handheld with 512 MB of RAM and a
960x544 OLED/LCD display. It uses SDL2 (ported to Vita) for input
abstraction, but the graphics layer is vitaGL -- an OpenGL compatibility
shim that translates OpenGL calls to Sony's native GXM API.
The distribution format is a `.vpk` (Vita Package) file containing the
signed executable and `dusk.dsk` bundled as `dusk.dsk` at the package
root, accessible at `app0:/dusk.dsk` at runtime.
---
## Hardware
| Attribute | Value |
|-----------|-------|
| CPU | ARM Cortex-A9 quad-core, ~444 MHz |
| RAM | 512 MB |
| Display | 960x544 |
| Storage | Vita game card / memory card / internal flash |
| Endian | Little-endian |
---
## Compile-time macros
| Macro | Set |
|-------|-----|
| `DUSK_VITA` | yes |
| `DUSK_SDL2` | yes |
| `DUSK_OPENGL` | yes |
| `DUSK_OPENGL_LEGACY` | yes |
| `DUSK_INPUT_GAMEPAD` | yes |
| `DUSK_PLATFORM_ENDIAN_LITTLE` | yes |
| `DUSK_DISPLAY_WIDTH` | 960 |
| `DUSK_DISPLAY_HEIGHT` | 544 |
No `DUSK_DISPLAY_SIZE_DYNAMIC`, no `DUSK_TIME_DYNAMIC`,
no `DUSK_INPUT_KEYBOARD`, no `DUSK_INPUT_POINTER`,
no `DUSK_NETWORK_IPV6`.
---
## Display
- Fixed 960x544 resolution.
- vitaGL translates OpenGL calls to GXM. Some OpenGL calls are stubbed
out in `duskplatform.h` where vitaGL does not support them:
```c
#define glDrawArrays(type, first, count) ((void)0)
#define glDepthFunc(func) ((void)0)
#define glBlendFunc(sfactor, dfactor) ((void)0)
#define glColorTableEXT(...) ((void)0)
```
These stubs mean the Vita uses the fixed-function pipeline through
vitaGL. Do not rely on `glDrawArrays` or depth/blend state changes
being applied -- use the engine's `displaystate_t` flags instead
(see `.claude/display-shader.md`).
- `DUSK_OPENGL_LEGACY` is set. Avoid shader-based features that are
not in the fixed-function ES1 subset.
- Texture dimensions **must** be powers of two.
---
## Asset loading
`dusk.dsk` is bundled inside the `.vpk` and mounted at `app0:/` by the
Vita OS. The asset system opens it at the fixed path:
```c
#define ASSET_VITA_DSK_PATH "app0:/" ASSET_FILE_NAME
```
No path search is needed -- the file is always at that location.
---
## Input
Uses SDL2 with Vita button mapping. Buttons map to SDL2 gamepad
constants:
| Vita button | SDL2 constant |
|-------------|---------------|
| Triangle | `SDL_CONTROLLER_BUTTON_Y` |
| Cross | `SDL_CONTROLLER_BUTTON_A` |
| Circle | `SDL_CONTROLLER_BUTTON_B` |
| Square | `SDL_CONTROLLER_BUTTON_X` |
| Start | `SDL_CONTROLLER_BUTTON_START` |
| Select | `SDL_CONTROLLER_BUTTON_BACK` |
| D-pad | `SDL_CONTROLLER_BUTTON_DPAD_*` |
| L / R | `SDL_CONTROLLER_BUTTON_LEFTSHOULDER / RIGHTSHOULDER` |
| L-Stick | `SDL_CONTROLLER_AXIS_LEFTX/Y` |
Vita also has L2, R2, L3, R3 and touch surfaces -- not currently wired
into the input system.
---
## Save system
Uses `SceIofilemgr` (Vita filesystem API) for file I/O. Save data lives
in the application's sandbox on the memory card. The stream API
(`savestream_t`) handles all serialization with automatic CRC32 and
little-endian encoding (see `.claude/save.md`).
---
## Network
No network implementation exists in `src/duskvita/`. Network
functionality is not currently available on Vita.
---
## Time
No `src/duskvita/time/` directory exists -- the Vita time implementation
falls back to the SDL2 time layer if available, or is not yet
implemented.
---
## Build and toolchain
Requires the [VITASDK](https://vitasdk.org/). Set `VITASDK` in your
environment before configuring.
```sh
cmake -B build \
-DDUSK_TARGET_SYSTEM=vita \
-DCMAKE_TOOLCHAIN_FILE=$VITASDK/share/vita.cmake \
-DCMAKE_BUILD_TYPE=Release
cmake --build build
```
Post-build output: `Dusk.self` (signed executable) and `Dusk.vpk`
(installable package containing the SELF + `dusk.dsk`).
Title ID: `DUSK00001` (configurable via `VITA_TITLEID`).
Dependencies: SDL2, vitaGL, mathneon, vitashark, kubridge, SceGxm,
SceCtrl, SceAudio, SceTouch, SceRtc, SceAppUtil, zip, bz2, z, lzma.
---
## Endianness
Little-endian. `DUSK_PLATFORM_ENDIAN_LITTLE` is set at compile time.
---
## Gotchas
- vitaGL stubs several OpenGL calls. Always use the engine display state
API rather than calling `glBlendFunc` / `glDepthFunc` directly.
- `DUSK_TIME_DYNAMIC` is not set -- all ticks are fixed-step 16 ms.
- Threading: `pthread` is linked but `DUSK_THREAD_PTHREAD` is not
explicitly defined in `vita.cmake`. Verify threading behaviour before
relying on it.
- The Vita implementation is less complete than Linux and PSP -- network
and time platform layers are absent. Contributions welcome.
+46
View File
@@ -0,0 +1,46 @@
# Platform -- Windows
`DUSK_TARGET_SYSTEM`: `windows`
Source layer: `src/duskwindows/` (planned, does not exist yet)
Status: **Planned -- not yet implemented**
---
## Overview
Windows desktop is a planned target. No source layer, CMake target file,
or toolchain exists yet. The intended architecture closely mirrors Linux:
SDL2 for windowing/input, desktop OpenGL for rendering, pthreads (via
MinGW or MSVC pthreads shim) for threading.
---
## Expected macros (when implemented)
| Macro | Expected |
|-------|---------|
| `DUSK_WINDOWS` | yes |
| `DUSK_SDL2` | yes |
| `DUSK_OPENGL` | yes |
| `DUSK_DISPLAY_SIZE_DYNAMIC` | yes |
| `DUSK_INPUT_KEYBOARD` | yes |
| `DUSK_INPUT_POINTER` | yes |
| `DUSK_INPUT_GAMEPAD` | yes |
| `DUSK_PLATFORM_ENDIAN_LITTLE` | yes |
| `DUSK_TIME_DYNAMIC` | yes |
| `DUSK_THREAD_PTHREAD` | yes |
---
## Notes
- Will be little-endian (x86-64 Windows).
- Expected to share `src/dusksdl2/` and `src/duskgl/` with Linux and
Knulli; only a thin `src/duskwindows/` layer for OS-specific
functionality (save paths, system dialogs) should be needed.
- Save files will likely live in `%APPDATA%` or a sibling `saves/`
directory.
- No cross-compiler needed; MSVC or MinGW-w64 on Windows or a
cross-compile from Linux.
Update this document when the Windows target is implemented.
+98
View File
@@ -0,0 +1,98 @@
# Platform Support
Dusk targets a wide range of platforms, from modern desktops to classic
handheld and home consoles. New platform targets will be added over time.
## Platform index
| Platform | `DUSK_TARGET_SYSTEM` | Status | Reference |
|----------|----------------------|--------|-----------|
| Linux | `linux` | Supported | `.claude/platform-linux.md` |
| Knulli | `knulli` | Supported | `.claude/platform-linux.md` |
| Windows | `windows` | Planned | `.claude/platform-windows.md` |
| macOS | `macos` | Planned | `.claude/platform-macos.md` |
| Sony PSP | `psp` | Supported | `.claude/platform-psp.md` |
| PlayStation Vita | `vita` | Supported | `.claude/platform-vita.md` |
| Nintendo GameCube | `gamecube` | Supported | `.claude/platform-dolphin.md` |
| Nintendo Wii | `wii` | Supported | `.claude/platform-dolphin.md` |
GameCube and Wii share the `src/duskdolphin/` layer and are collectively
referred to as **Dolphin** targets throughout the codebase.
---
## Layer structure
```
src/dusk/ Core -- platform-agnostic game logic and ECS
src/duskgl/ OpenGL abstraction (Linux, Knulli, PSP, Vita)
src/dusksdl2/ SDL2 window + input (Linux, Knulli, PSP, Vita)
src/dusklinux/ Linux + Knulli platform impl
src/duskpsp/ PSP platform impl
src/duskvita/ Vita platform impl
src/duskdolphin/ GameCube + Wii platform impl
```
Dolphin is the only target that bypasses SDL2 and OpenGL entirely --
it uses native libogc GX for rendering and PAD for input.
---
## Capability macros
Each target sets a combination of these macros. Do not assume a
capability is present without checking the appropriate macro.
| Macro | Meaning |
|-------|---------|
| `DUSK_SDL2` | SDL2 is available |
| `DUSK_OPENGL` | OpenGL is available |
| `DUSK_OPENGL_ES` | OpenGL ES variant (Knulli) |
| `DUSK_OPENGL_LEGACY` | Fixed-function OpenGL (PSP, Vita) |
| `DUSK_INPUT_GAMEPAD` | Gamepad / controller input |
| `DUSK_INPUT_KEYBOARD` | Keyboard input (Linux, Knulli only) |
| `DUSK_INPUT_POINTER` | Mouse / pointer input (Linux, Knulli only) |
| `DUSK_DISPLAY_SIZE_DYNAMIC` | Window is resizable (Linux, Knulli) |
| `DUSK_TIME_DYNAMIC` | Dynamic timestep available (Linux, Knulli) |
| `DUSK_THREAD_PTHREAD` | pthreads available |
| `DUSK_NETWORK_IPV6` | IPv6 supported (Linux only) |
| `DUSK_PLATFORM_ENDIAN_BIG` | Big-endian byte order |
| `DUSK_PLATFORM_ENDIAN_LITTLE` | Little-endian byte order |
| `DUSK_DOLPHIN` | Any Dolphin target |
| `DUSK_DOLPHIN_BUILD_ISO` | Dolphin DVD-ISO asset mode |
| `DUSK_CONSOLE_POSIX` | POSIX assert backtrace (Linux only) |
---
## Quick comparison
| | Linux | Knulli | PSP | Vita | GameCube | Wii |
|-|-------|--------|-----|------|----------|-----|
| SDL2 | yes | yes | yes | yes | no | no |
| OpenGL | desktop | ES2 | legacy | vitaGL | no | no |
| Endian | little | little | little | little | **big** | **big** |
| Dynamic resize | yes | yes | no | no | no | no |
| Dynamic timestep | yes | yes | no | no | no | no |
| Keyboard | yes | yes | no | no | no | no |
| Pointer/mouse | yes | yes | no | no | no | no |
| Network | full | full | partial | no | no | partial |
| Save storage | file | file | MS/SAVEDATA | SceIo | Mem Card | SD/NAND |
| Asset source | dsk file | dsk file | inside .pbp | inside .vpk | SD or DVD | SD or DVD |
---
## Abstraction pattern
Platform-specific implementations are wired in via `#define` macros in
each platform's `displayplatform.h`, `inputplatform.h`, etc., which the
core calls through. Functions that a platform does not support are
simply left undefined -- the core guards calls with `#ifdef`.
## Adding platform-specific code
- Put new code under `src/dusk<platform>/` in the matching subsystem
folder.
- Gate any core call-site with `#ifdef DUSK_<PLATFORM>` or the
relevant capability macro.
- Keep `src/dusk/` free of platform `#ifdef`s -- delegate through
the platform header macros instead.
+148
View File
@@ -0,0 +1,148 @@
# Save System
Source: `src/dusk/save/`, platform layers in `src/dusk<platform>/save/`
## Overview
The save system provides multi-slot persistent storage. Each slot
maps to one `savefile_t`. Platform implementations handle the actual
read/write (memory card on GameCube/Wii, EEPROM/flash on PSP,
filesystem on Linux).
## Global state
```c
extern save_t SAVE;
// SAVE.files[SAVE_FILE_COUNT_MAX] -- one per slot
// SAVE.platform -- platform-specific state
```
## API
```c
errorret_t saveInit(void);
errorret_t saveDispose(void);
errorret_t saveLoad(uint8_t slot); // read slot from storage -> SAVE.files[slot]
errorret_t saveWrite(uint8_t slot); // write SAVE.files[slot] -> storage
errorret_t saveDelete(uint8_t slot); // delete slot from storage
bool_t saveExists(uint8_t slot); // true if a save file is present
savefile_t *saveGet(uint8_t slot); // pointer to the in-memory slot data
```
Slot indices are 0-based, range `[0, SAVE_FILE_COUNT_MAX - 1]`.
## Save file structure (`savefile.h`)
`savefile_t` is a plain struct written verbatim to storage. Keep it
small and use fixed-width integer types (`uint8_t`, `int32_t`, etc.)
to ensure cross-platform binary compatibility.
**Endianness:** storage is always written in little-endian byte order.
Use the `endian.h` utilities when reading fields on big-endian targets
(GameCube, Wii). See `.claude/util.md`.
**Versioning:** include a version field at the start of `savefile_t`.
Check it on load and handle mismatches gracefully (reset to defaults
rather than crashing on corrupt data).
## Save stream (`savestream.h`)
`savestream_t` is a cursor-based reader/writer used to serialize
`savefile_t` to/from a raw byte buffer. Platform implementations
use it to abstract the I/O layer.
```c
typedef struct {
bool_t found;
uint32_t checksum;
uint32_t expectedChecksum;
saveplatformstream_t platform;
} savestream_t;
```
### Typed read/write macros
Use the `saveFile*` macros inside `saveFileLoad` and `saveFileWrite`.
All multi-byte values are stored in little-endian order; endian
conversion is handled automatically.
```c
saveFileReadHeader(stream, headerBuf)
saveFileWriteHeader(stream, headerBuf)
saveFileReadVersion(stream, &version)
saveFileWriteVersion(stream, &version)
saveFileReadBool(stream, &boolField)
saveFileWriteBool(stream, &boolField)
saveFileReadInt8(stream, &i8) saveFileWriteInt8(stream, &i8)
saveFileReadUInt8(stream, &u8) saveFileWriteUInt8(stream, &u8)
saveFileReadInt16(stream, &i16) saveFileWriteInt16(stream, &i16)
saveFileReadUInt16(stream, &u16) saveFileWriteUInt16(stream, &u16)
saveFileReadInt32(stream, &i32) saveFileWriteInt32(stream, &i32)
saveFileReadUInt32(stream, &u32) saveFileWriteUInt32(stream, &u32)
saveFileReadInt64(stream, &i64) saveFileWriteInt64(stream, &i64)
saveFileReadUInt64(stream, &u64) saveFileWriteUInt64(stream, &u64)
saveFileReadFloat(stream, &f) saveFileWriteFloat(stream, &f)
saveFileReadString(stream, buf, maxLen)
saveFileWriteString(stream, str, maxLen)
saveFileReadDate(stream, &epoch)
saveFileWriteDate(stream, &epoch)
```
Each macro expands to `errorChain(saveStreamRead/WriteXxxImpl(...))`.
A failing read/write propagates the error up from `saveFileLoad` /
`saveFileWrite`.
### Typical saveFileLoad / saveFileWrite pattern
```c
errorret_t saveFileLoad(savestream_t *stream, savefile_t *file) {
char_t header[SAVE_FILE_HEADER_SIZE];
saveFileReadHeader(stream, header);
saveFileReadVersion(stream, &file->version);
saveFileReadInt32(stream, &file->score);
// ... remaining fields ...
errorOk();
}
errorret_t saveFileWrite(savestream_t *stream, savefile_t *file) {
char_t header[SAVE_FILE_HEADER_SIZE] = SAVE_FILE_HEADER;
saveFileWriteHeader(stream, header);
saveFileWriteVersion(stream, &file->version);
saveFileWriteInt32(stream, &file->score);
// ... remaining fields ...
errorOk();
}
```
After `saveFileWrite` completes, the platform layer calls
`saveStreamFinalizeWriteImpl` which seeks back and writes the CRC32.
After `saveFileLoad`, the platform calls `saveStreamVerifyChecksumImpl`
to confirm the CRC matches.
## Platform notes
| Platform | Storage mechanism |
|----------|------------------|
| Linux | File in user home / working directory |
| Knulli | File on filesystem |
| PSP | EEPROM / memory stick via `sceIo` |
| GameCube | Memory Card via libogc `CARD_*` API |
| Wii | NAND filesystem via libogc or SD card |
Platform-specific save implementations go in `src/dusk<platform>/save/`
and are wired in via `save/saveplatform.h` macros.
## PSP note
PSP save dialogs are OS-level UI shown via `sceUtility`. When a dialog
is open, `systemGetActiveDialogType()` returns a blocking type so the
engine pauses the main loop. Never call save functions directly from
game code without going through the engine's dialog guard.
+110
View File
@@ -0,0 +1,110 @@
# Scene System
Source: `src/dusk/scene/`
## Overview
The scene system is the top-level coordinator for a running game state.
It manages one active scene at a time. Scenes are JS scripts -- each
scene is a `.js` asset file that exports an object with lifecycle hooks.
The scene system loads, ticks, and tears down these scripts, while the
C side runs the ECS and render pipeline on each tick.
## Scene lifecycle (C side)
```c
extern scene_t SCENE;
errorret_t sceneInit(void); // initialise the scene manager
errorret_t sceneUpdate(void); // process pending transition, tick active scene
errorret_t sceneRender(void); // render entities + render pipeline + UI
errorret_t sceneDispose(void); // dispose the active scene immediately
```
`sceneUpdate` each tick:
1. Checks for a pending scene transition and performs it (dispose old,
load and init new).
2. Calls the JS scene's `update()` hook.
3. Calls `entityManagerUpdate()` to fire all entity update callbacks.
`sceneRender` each tick:
1. Binds the screen.
2. Calls `sceneRenderPipeline()` -- renders all entities with a
`COMPONENT_TYPE_RENDERABLE` in priority order.
3. Renders UI.
4. Calls the JS scene's `render()` hook (for any custom drawing).
5. Unbinds the screen.
## Scene lifecycle (JS side)
A scene file exports a plain object with these optional hooks:
```js
var scene = {};
scene.init = async function() {
// Load assets, create entities, set up state.
// May be async -- await asset loads here.
};
scene.update = function() {
// Called each fixed-timestep tick.
};
scene.render = function() {
// Called each render tick, after ECS renderables.
};
scene.dispose = function() {
// Clean up entities and state.
};
module.exports = scene;
```
See `CLAUDE.md` -- "JavaScript (asset scripts)" for JS style rules.
## Render pipeline (`scenerenderpipeline.h`)
`sceneRenderPipeline(cameraEntityId)` gathers all active
`COMPONENT_TYPE_RENDERABLE` components, sorts them by effective
priority, and draws each one using its shader.
**Priority rules:**
- `renderable.priority != 0` -- use that value directly.
- `renderable.priority == 0` -- auto-derive: opaque geometry sorts
before transparent geometry; sprite batches sort before shader
materials; etc.
- Lower priority number = drawn first (behind); higher = drawn last
(on top).
The shader used for each renderable:
- `ENTITY_RENDERABLE_TYPE_SPRITEBATCH` and `CUSTOM` default to
`SHADER_LIST_SHADER_UNLIT`.
- `ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL` uses the shader indexed by
`renderable.data.material.shaderType` in `SHADER_LIST_DEFS`.
## Transitioning between scenes
To move to a new scene from JS, call the scene module's transition
function (exact API in the `scene` JS module). The C side defers the
actual switch to the start of the next `sceneUpdate` call so the
current tick completes cleanly before any dispose runs.
## Relationship to the engine loop
```
engineUpdate()
timeUpdate()
inputUpdate()
physicsManagerUpdate()
scriptUpdate() <- runs JS microjobs
sceneUpdate() <- JS update + ECS entity updates
engineUpdate() -> sceneRender()
screenBind()
sceneRenderPipeline() <- ECS renderables sorted by priority
uiRender()
sceneRender (JS hook)
screenUnbind / screenRender
```
+104
View File
@@ -0,0 +1,104 @@
# Script -- Async Promises (`scriptpromisepend_t`)
Source: `src/dusk/script/scriptpromisepend.h`
See also: `.claude/script.md`
---
## Overview
When a C module needs to resolve a JS `Promise` from an asynchronous
C event (e.g. an asset finishing loading, a network response arriving),
use `scriptpromisepend_t`. The pattern avoids heap allocation by using
a fixed-size pending slot array declared in the module.
---
## Declaring the pending array
```c
#define MY_MODULE_PENDING_MAX 8
static scriptpromisepend_t MY_PENDING[MY_MODULE_PENDING_MAX];
static uint32_t MY_PENDING_COUNT = 0;
```
---
## Add a pending promise
Called from the JS-facing function that returns the Promise:
```c
jerry_value_t promise = jerry_create_promise();
scriptPromisePendAdd(
MY_PENDING, &MY_PENDING_COUNT, MY_MODULE_PENDING_MAX,
key, // opaque void * used to match the resolve/reject later
promise
);
return jerry_acquire_value(promise); // return a copy to the caller
```
The `key` should be a stable pointer that uniquely identifies the
async operation -- e.g. an `assetentry_t *`, a network request handle,
or a pointer to a fixed-size slot in the module.
---
## Resolve or reject
Called when the C event fires, typically from `moduleUpdate` or an
event callback:
```c
// On success:
scriptPromisePendResolve(
MY_PENDING, &MY_PENDING_COUNT,
key, jerry_undefined() // or a result value
);
// On failure:
jerry_value_t err = jerry_create_error(
JERRY_ERROR_COMMON, (const jerry_char_t *)"reason"
);
scriptPromisePendReject(MY_PENDING, &MY_PENDING_COUNT, key, err);
jerry_release_value(err);
```
Both macros remove the slot from the pending array after settling.
---
## Guard against double-submit
```c
if(scriptPromisePendHas(MY_PENDING, MY_PENDING_COUNT, key)) {
// already waiting -- return the existing promise or an error
}
```
---
## Module teardown
Free all pending promises before cleaning up events or other state:
```c
scriptPromisePendFreeAll(MY_PENDING, &MY_PENDING_COUNT);
```
This rejects all still-pending promises and resets the count to 0.
Call it from the module's `Dispose` function, **before** any backing
data (asset entries, event subscriptions) is torn down.
---
## Design notes
- `MY_MODULE_PENDING_MAX` sets a hard cap on concurrent async ops.
Exceeding it is a runtime assertion -- size the array to the maximum
realistic concurrency for the module.
- The key is opaque (`void *`); the system does not dereference it.
A raw integer cast to `void *` is fine if no pointer is available.
- `scriptUpdate()` runs the JerryScript microjob queue each frame,
which is what processes `.then()` chains after a resolve/reject.
+167
View File
@@ -0,0 +1,167 @@
# Script System (JerryScript)
Source: `src/dusk/script/`, modules at `src/dusk/script/module/`
## Overview
The engine embeds **JerryScript** as its scripting runtime. Game scenes
and logic are authored in JavaScript (ES5 subset). The script system
initialises JerryScript, registers all built-in C modules as JS globals,
and runs the event loop each tick.
The full rules for writing JS asset scripts are in `CLAUDE.md` under
"JavaScript (asset scripts)". This doc covers the C-side module system.
## Script lifecycle
```c
errorret_t scriptInit(); // start JerryScript, register all modules
errorret_t scriptUpdate(); // run pending microjobs (call once per frame)
errorret_t scriptDispose(); // shut down JerryScript
errorret_t scriptExecString(const char_t *source);
// Evaluate a JS source string in global scope.
errorret_t scriptExecFile(const char_t *path);
// Load + eval a script from the asset archive. Result cached by asset
// system -- repeated calls with the same path do not re-execute.
```
## Module registration
All C modules are initialised in `src/dusk/script/module/modulelist.c`:
```c
void moduleListInit(void); // called by scriptInit
void moduleListDispose(void); // called by scriptDispose
```
Each module's `Init` is called once. The module registers its
properties and methods on `scriptproto_t` objects (see below), which
become JS globals.
## Writing a C module -- the `scriptproto_t` pattern
A `scriptproto_t` represents a JS class prototype backed by a C struct.
### 1. Declare in the header
```c
// moduleMything.h
extern scriptproto_t MODULE_MYTHING_PROTO;
// Init and dispose for the module itself:
void moduleMyThingInit(void);
void moduleMyThingDispose(void);
```
### 2. Implement
```c
// moduleMything.c
scriptproto_t MODULE_MYTHING_PROTO;
// JS-callable function using the convenience macro:
moduleBaseFunction(myThingDoSomething) {
moduleBaseRequireArgs(1);
moduleBaseRequireNumber(0);
float_t x = moduleBaseArgFloat(0);
// ... do work ...
return jerry_undefined();
}
void moduleMyThingInit(void) {
scriptProtoInit(
&MODULE_MYTHING_PROTO,
"MyThing", // JS global name; NULL to skip registration
sizeof(mything_t),
myThingCtor // constructor handler, or NULL
);
// Instance methods:
scriptProtoDefineFunc(
&MODULE_MYTHING_PROTO, "doSomething", myThingDoSomething
);
// Instance property (get/set):
scriptProtoDefineProp(
&MODULE_MYTHING_PROTO, "x", myThingGetX, myThingSetX
);
// Static method:
scriptProtoDefineStaticFunc(
&MODULE_MYTHING_PROTO, "create", myThingCreate
);
}
```
### 3. Register
In `modulelist.c`: `#include` the header and call `moduleMyThingInit()`
in `moduleListInit()` (and `Dispose` in `moduleListDispose()`).
## `moduleBaseFunction` macro
```c
moduleBaseFunction(myFn) {
// callInfo, args[], argc available
moduleBaseRequireArgs(2);
moduleBaseRequireNumber(0);
moduleBaseRequireString(1);
float_t x = moduleBaseArgFloat(0);
int32_t n = moduleBaseArgInt(0);
bool_t b = moduleBaseArgBool(0);
float_t opt = moduleBaseOptFloat(2, 0.0f); // optional with default
// Error propagation:
errorret_t ret = someCall();
if(errorIsNotOk(ret)) return moduleBaseThrowError(ret);
return jerry_undefined(); // or jerry_boolean(true) etc.
}
```
## Wrapping C values in JS objects
```c
// Create a JS object wrapping a copy of a C value:
jerry_value_t obj = scriptProtoCreateValue(&MY_PROTO, &myValue);
// Unwrap back to C pointer:
mything_t *ptr = scriptProtoGetValue(&MY_PROTO, jsObj);
// ptr is NULL if jsObj is not an instance of MY_PROTO.
```
## Utility helpers (`modulebase.h`)
| Helper | Purpose |
|--------|---------|
| `moduleBaseThrow(msg)` | Return a JS TypeError |
| `moduleBaseThrowError(ret)` | Convert `errorret_t` -> JS error |
| `moduleBaseToString(val, buf, len)` | Jerry value -> C string |
| `moduleBaseGetProp(obj, name)` | Get object property by name |
| `moduleBaseWrapPointer(ptr)` | Wrap a raw pointer in a JS object |
| `moduleBaseUnwrapPointer(val)` | Unwrap a raw pointer |
| `moduleBaseSetValue(name, val)` | Set a global JS variable |
| `moduleBaseSetNumber(name, n)` | Set a global JS number |
| `moduleBaseSetInt(name, n)` | Set a global JS integer |
| `moduleBaseDefineMethod(obj, name, fn)` | Add method to any JS object |
| `moduleBaseDefineGlobalMethod(name, fn)` | Add method to global scope |
## Async JS -- pending promises (`scriptpromisepend.h`)
When a C module needs to resolve a JS `Promise` from an asynchronous
C event, use `scriptpromisepend_t`. Each module declares a fixed-size
pending slot array; the helpers add/resolve/reject by an opaque key.
Full API and design notes: `.claude/script-promises.md`
## Type declarations (`.d.ts`)
Every module that is accessible from JS **must** have a corresponding
TypeScript declaration file in `types/`. The CLAUDE.md checklist
requires updating these whenever a `.c` module file changes.
- Add `types/<category>/mymod.d.ts`
- Add `/// <reference path="..." />` to `types/index.d.ts`
+111
View File
@@ -0,0 +1,111 @@
# Tests and Assertions
## Test infrastructure
Tests live in `test/` and mirror the `src/dusk/` directory structure.
Enable with `-DDUSK_BUILD_TESTS=ON`. The test runner is **cmocka**.
### Entry point
Every test file includes `dusktest.h`, which pulls in `dusk.h` and
`assert/assert.h`. When `DUSK_TEST_ASSERT` is defined, `assert.h`
includes `cmocka.h` and redirects assertion failures through
`mock_assert()` instead of calling `abort()`.
### Test function signature
```c
static void test_something(void **state) {
// ... setup ...
// ... exercise ...
// ... assert ...
assert_int_equal(memoryGetAllocatedCount(), 0); // leak check
}
```
### Registering and running tests
```c
int main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_errorThrow),
cmocka_unit_test(test_errorOk),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}
```
Use `cmocka_unit_test_setup_teardown()` when a test needs per-test
setup or teardown callbacks.
### Assertion mix
Tests use **two** sets of assertion macros:
| Origin | When to use |
|--------|-------------|
| cmocka: `assert_int_equal()`, `assert_non_null()` etc. | Validate results inside test functions |
| Dusk: `assertTrue()`, `assertNotNull()` etc. | Exercise the code under test (these may fire and need catching) |
To assert that a Dusk assertion fires, use cmocka's mock system:
```c
expect_assert_failure(assertTrueImpl(__FILE__, __LINE__, false, "msg"));
```
### Memory leak discipline
Every test function must end by asserting:
```c
assert_int_equal(memoryGetAllocatedCount(), 0);
```
This ensures all allocations from the code under test were freed.
---
## Assertion system
Source: `src/dusk/assert/`
### Runtime vs test mode
| Mode | Trigger | Effect on failure |
|------|---------|-------------------|
| Runtime (default) | Release / non-test builds | Logs the message + backtrace, then calls `abort()` |
| Test (`DUSK_TEST_ASSERT`) | `-DDUSK_TEST_ASSERT` build flag | Routes through cmocka `mock_assert()` for controlled catching |
| Faked (`DUSK_ASSERTIONS_FAKED`) | Defined by platform or test | All macros become no-ops (`((void)0)`) |
### Available macros
| Macro | Description |
|-------|-------------|
| `assertTrue(x, msg)` | Fails if `x` is false |
| `assertFalse(x, msg)` | Fails if `x` is true |
| `assertNotNull(ptr, msg)` | Fails if `ptr` is NULL |
| `assertNull(ptr, msg)` | Fails if `ptr` is not NULL |
| `assertUnreachable(msg)` | Unconditional failure; marks unreachable code |
| `assertDeprecated(msg)` | Marks a code path as deprecated |
| `assertStringEqual(a, b, msg)` | Fails if strings differ |
| `assertStrLenMax(str, len, msg)` | Fails if `strlen(str) >= len` |
| `assertStrLenMin(str, len, msg)` | Fails if `strlen(str) < len` |
| `assertIsMainThread(msg)` | Fails if called from a non-main thread |
| `assertNotMainThread(msg)` | Fails if called from the main thread |
| `assertStructSize(type, size)` | Compile-time size check via `_Static_assert` |
### Thread tracking
`assertInit()` records the main thread ID (pthreads). The main-thread
assertions compare against this stored ID. Call `assertInit()` once at
startup before spawning any threads.
### Usage guidelines
- Prefer the specific macro over a bare `assertTrue` for clarity
(e.g. use `assertNotNull` instead of `assertTrue(ptr != NULL, ...)`).
- Use `assertUnreachable` in `default:` cases of exhaustive switches.
- Use `assertStructSize` to guard struct layouts that must match
a known binary format or a platform ABI.
- Do not use asserts for expected error paths -- use `errorThrow`
instead. Asserts are for programmer mistakes, not runtime errors.
+100
View File
@@ -0,0 +1,100 @@
# Threading System
Source: `src/dusk/thread/`
## Platform support
Threading currently requires **pthreads** (`DUSK_THREAD_PTHREAD`). The
implementation lives in the core thread files and is guarded by that
compile-time flag -- there are no separate per-platform thread
directories.
Thread-local storage uses the `THREAD_LOCAL` macro, which maps to
`__thread` when pthreads is available. This is used by the error system
to give each thread its own `ERROR_STATE`.
## Thread lifecycle
Threads follow a strict state machine:
```
STOPPED -> STARTING -> RUNNING -> STOP_REQUESTED -> STOPPED
```
- `threadStart()` -- blocking: starts the thread and waits until it
reaches RUNNING.
- `threadStop()` -- blocking: requests stop and waits until STOPPED.
- `threadStartRequest()` -- non-blocking equivalent of `threadStart`.
- `threadStopRequest()` -- non-blocking equivalent of `threadStop`.
The thread callback polls `threadShouldStop()` to know when to exit.
Never kill a thread forcefully -- always let it stop cooperatively.
## Thread API
```c
void threadInit(thread_t *thread, errorret_t (*callback)(thread_t *t));
// Initialise; callback is the thread entry point.
errorret_t threadStart(thread_t *thread);
// Start and block until RUNNING.
errorret_t threadStop(thread_t *thread);
// Request stop, block until STOPPED.
void threadStartRequest(thread_t *thread);
void threadStopRequest(thread_t *thread);
// Non-blocking variants.
bool_t threadShouldStop(const thread_t *thread);
// Call from inside the thread callback to know when to exit.
```
## Mutex API (`threadmutex.h`)
Each `threadmutex_t` wraps a pthread mutex and a condition variable.
```c
void threadMutexInit(threadmutex_t *mutex);
void threadMutexDispose(threadmutex_t *mutex);
void threadMutexLock(threadmutex_t *mutex);
void threadMutexUnlock(threadmutex_t *mutex);
bool_t threadMutexTryLock(threadmutex_t *mutex);
// Returns true if the lock was acquired; false if already held.
void threadMutexWaitLock(threadmutex_t *mutex);
// Block until signalled (like pthread_cond_wait).
// Must be called while holding the lock.
void threadMutexSignal(threadmutex_t *mutex);
// Wake one waiter.
```
## Usage example
```c
static errorret_t workerCallback(thread_t *t) {
while(!threadShouldStop(t)) {
// do work
}
errorOk();
}
thread_t worker;
threadInit(&worker, workerCallback);
errorChain(threadStart(&worker));
// ... later ...
errorChain(threadStop(&worker));
```
## Thread safety rules
- The error system (`ERROR_STATE`) is thread-local -- each thread has
its own error state. Do not pass `errorret_t` across thread
boundaries without copying the message and lines strings first.
- Asset loading: the background thread calls `loadAsync`; the main
thread calls `loadSync`. Never call GPU or SDL functions from the
loader background thread.
- Use `assertIsMainThread()` / `assertNotMainThread()` to guard
functions that have thread affinity requirements.
+115
View File
@@ -0,0 +1,115 @@
# Time System
Source: `src/dusk/time/`, platform layers in `src/dusk<platform>/time/`
## Global state
```c
extern dusktime_t TIME;
```
```c
typedef struct {
float_t delta; // Fixed step size in seconds (DUSK_TIME_STEP)
float_t time; // Accumulated game time in seconds
// Only present when DUSK_TIME_DYNAMIC is defined:
float_t lastNonDynamic;
bool_t dynamicUpdate; // true on sub-step ticks
float_t dynamicDelta; // real elapsed seconds this frame
float_t dynamicTime; // accumulated real time
} dusktime_t;
```
## Fixed vs dynamic timestep
### Fixed timestep (default)
`DUSK_TIME_STEP` defaults to `16ms / 1000 = 0.016f` seconds (62.5 Hz).
Every call to `timeUpdate()` advances `TIME.time` by exactly
`DUSK_TIME_STEP` and sets `TIME.delta = DUSK_TIME_STEP`. This is the
safe, deterministic mode for physics and game logic.
### Dynamic timestep (`DUSK_TIME_DYNAMIC`)
When enabled, `timeUpdate()` calls the platform tick hook to measure
actual elapsed time. It fires a "non-dynamic" step (`dynamicUpdate =
false`, `delta = DUSK_TIME_STEP`) once per fixed interval, and
"dynamic" sub-steps (`dynamicUpdate = true`) in between. Systems that
must run on the fixed interval (physics, networking) skip the dynamic
sub-steps by checking:
```c
if(TIME.dynamicUpdate) return;
```
## Platform hooks
Each platform provides three macros in its `time/timeplatform.h`:
| Macro | Purpose |
|-------|---------|
| `timeTickPlatform()` | Sample the hardware timer |
| `timeGetDeltaPlatform()` | Return seconds since last tick |
| `timeGetRealPlatform()` | Return epoch seconds since 1970 |
| `timeGetRealTimeZonePlatform()` | Return local timezone offset (seconds) |
`timeTickPlatform` and `timeGetDeltaPlatform` are only required when
`DUSK_TIME_DYNAMIC` is defined.
## Platform implementations
| Platform | Tick source | Real time source |
|----------|------------|-----------------|
| Linux | `SDL_GetTicks64()` (via SDL2) | `clock_gettime(CLOCK_REALTIME)` |
| Knulli | `SDL_GetTicks64()` (via SDL2) | `clock_gettime(CLOCK_REALTIME)` |
| PSP | `SDL_GetTicks64()` (via SDL2) | `sceRtcGetCurrentTick()` (microseconds) |
| GameCube | none (fixed step only) | `ticks_to_microsecs(__SYS_GetSystemTime())` + 2000->1970 offset |
| Wii | none (fixed step only) | same as GameCube |
GameCube / Wii note: the hardware timer returns ticks since
2000-01-01, so an offset of 946684800 seconds is added to convert to
UNIX epoch. The timezone offset is always returned as 0.0 on Dolphin
(timezone is not available without network time).
## Epoch time (`timeepoch.h`)
```c
typedef struct {
double_t time; // raw UTC seconds since 1970
double_t timeZone; // timezone offset in seconds
double_t offsetTime; // time + timeZone
} dusktimeepoch_t;
dusktimeepoch_t timeGetEpoch(void);
// Returns current time in local timezone.
```
### Epoch helpers
```c
int32_t timeEpochGetYear(epoch);
int32_t timeEpochGetMonth(epoch); // 1-12
int32_t timeEpochGetDayOfMonth(epoch); // 1-31
int32_t timeEpochGetHours(epoch); // 0-23
int32_t timeEpochGetMinutes(epoch); // 0-59
int32_t timeEpochGetSeconds(epoch); // 0-59
bool_t timeEpochIsLeapYear(year);
size_t timeEpochFormat(
dusktimeepoch_t epoch,
const char_t *format, // %Y %m %d %H %M %S
char_t *buffer,
size_t bufferSize
);
```
## Adding a new platform time implementation
1. Create `src/dusk<platform>/time/time<platform>.h/.c`.
2. Implement `timeGetReal<Platform>()` and
`timeGetRealTimeZone<Platform>()`.
3. If `DUSK_TIME_DYNAMIC`: also implement `timeTick<Platform>()` and
`timeGetDelta<Platform>()`.
4. Create `src/dusk<platform>/time/timeplatform.h` with the `#define`
macros pointing to your functions.
+191
View File
@@ -0,0 +1,191 @@
# Utility Library
Source: `src/dusk/util/`
All C code in the project must use these utilities instead of their
standard library equivalents. Do not use `malloc`, `free`, `strcmp`,
`strcpy`, `memcpy`, `memset`, etc. directly.
---
## Memory (`memory.h`)
```c
void *memoryAllocate(size_t size);
void *memoryAlign(size_t alignment, size_t size); // aligned alloc
void memoryFree(void *ptr);
void memoryCopy(void *dest, const void *src, size_t size);
void memoryZero(void *dest, size_t size);
errorret_t memoryCompare(const void *a, const void *b, size_t size);
size_t memoryGetAllocatedCount(void);
// Returns the number of live allocations. Must be 0 at test teardown.
void memoryTrack(void *ptr);
// Register a pointer that was malloc'd outside the engine (e.g. by a
// third-party library) so it counts toward the allocation tracker.
```
`MEMORY_POINTERS_IN_USE` is a file-scope static tracking the live count.
It is incremented by `memoryAllocate` / `memoryTrack` and decremented by
`memoryFree`. Tests assert this is 0 at teardown to catch leaks.
---
## String (`string.h`)
Use these instead of `<string.h>` / `<ctype.h>` functions:
```c
void stringCopy(char_t *dest, const char_t *src, size_t destSize);
int stringCompare(const char_t *a, const char_t *b);
bool_t stringEquals(const char_t *a, const char_t *b);
int stringCompareInsensitive(const char_t *a, const char_t *b);
size_t stringLength(const char_t *str);
void stringTrim(char_t *str);
bool_t stringIsWhitespace(char_t c);
bool_t stringStartsWith(const char_t *str, const char_t *prefix);
bool_t stringEndsWith(const char_t *str, const char_t *suffix);
bool_t stringContains(const char_t *haystack, const char_t *needle);
char_t *stringFind(const char_t *haystack, const char_t *needle);
void stringFormat(char_t *dest, size_t destSize, const char_t *fmt, ...);
int32_t stringToInt(const char_t *str);
float_t stringToFloat(const char_t *str);
void stringFromInt(char_t *dest, size_t destSize, int32_t value);
void stringFromFloat(char_t *dest, size_t destSize, float_t value);
```
`destSize` in `stringCopy` / `stringFormat` is the buffer capacity
**excluding** the null terminator.
---
## Math (`math.h`)
```c
#define MATH_PI M_PI
#define mathMax(a, b)
#define mathMin(a, b)
#define mathClamp(x, lower, upper)
#define mathAbs(amt)
uint32_t mathNextPowTwo(uint32_t value);
float_t mathModFloat(float_t x, float_t y); // always non-negative
float_t mathLerp(float_t a, float_t b, float_t t);
// plus additional trig / remap helpers
```
The project uses **cglm** for vector and matrix math (`vec3`, `mat4`,
`glm_vec3_*`, `glm_mat4_*`, etc.). `math.h` provides scalar helpers
that complement cglm.
---
## Endian (`endian.h`)
GameCube and Wii are big-endian. Any binary data format (asset files,
network packets) must use the endian utilities when reading multi-byte
values.
```c
bool_t isHostLittleEndian(void);
uint16_t endianLittleToHost16(uint16_t value);
uint32_t endianLittleToHost32(uint32_t value);
uint64_t endianLittleToHost64(uint64_t value);
float_t endianLittleToHostFloat(float_t value);
```
If neither `DUSK_PLATFORM_ENDIAN_LITTLE` nor `DUSK_PLATFORM_ENDIAN_BIG`
is defined, the implementation falls back to a runtime check
(`ENDIAN_MAGIC` probe). Prefer setting the compile-time macro for new
platform targets.
---
## Reference counting (`ref.h`)
`ref_t` is a generic reference-counted handle with optional lock /
unlock / all-unlocked callbacks.
```c
void refInit(
ref_t *ref,
void *data,
refcallback_t onLock,
refcallback_t onUnlock,
refcallback_t onAllUnlocked // called when count -> 0; do cleanup here
);
void refLock(ref_t *ref); // increment count
bool_t refUnlock(ref_t *ref); // decrement; returns true if count == 0
```
The asset entry system uses `ref_t` internally to track how many
subsystems have locked a loaded asset.
---
## Array (`array.h`)
```c
void arrayReverse(void *array, size_t count, size_t elementSize);
```
Generic in-place reverse using the element stride.
---
## Sort (`sort.h`)
Use these instead of `qsort` for portability across all platforms.
```c
typedef int_t (*sortcompare_t)(const void *, const void *);
void sortBubble(
void *array,
const size_t count,
const size_t size,
const sortcompare_t compare
);
void sortQuick(
void *array,
const size_t count,
const size_t size,
const sortcompare_t compare
);
#define sort sortQuick // preferred; use this in new code
```
Typed convenience helpers for `uint8_t` arrays:
```c
int sortArrayU8Compare(const void *a, const void *b);
void sortArrayU8(uint8_t *array, const size_t count);
```
---
## Crypt (`crypt.h`)
CRC32 checksum for save file integrity. Not cryptographically secure --
do not use for security purposes.
```c
// One-shot checksum:
uint32_t cryptCRC32(const void *data, const size_t size);
// Streaming (incremental) CRC32:
uint32_t cryptCRC32Begin(void);
void cryptCRC32Update(
uint32_t *crc, const void *data, const size_t size
);
uint32_t cryptCRC32End(const uint32_t crc);
```
The streaming API allows computing a checksum across multiple buffers
or while interleaving other reads -- the save system uses this to
verify the whole save slot in a single pass.
+64 -7
View File
@@ -1,11 +1,8 @@
name: Build Dusk
on:
push:
branches:
- main
pull_request:
branches:
- main
tags:
- '*'
jobs:
run-tests:
runs-on: ubuntu-latest
@@ -53,6 +50,22 @@ jobs:
path: ./git-artifcats/Dusk
if-no-files-found: error
# build-vita:
# runs-on: ubuntu-latest
# steps:
# - name: Checkout repository
# uses: actions/checkout@v6
# - name: Set up Docker
# uses: docker/setup-docker-action@v5
# - name: Build Vita
# run: ./scripts/build-vita-docker.sh
# - name: Upload Vita binary
# uses: actions/upload-artifact@v6
# with:
# name: dusk-vita
# path: build-vita/Dusk.vpk
# if-no-files-found: error
build-knulli:
runs-on: ubuntu-latest
steps:
@@ -94,6 +107,28 @@ jobs:
path: ./git-artifcats/Dusk
if-no-files-found: error
build-gamecube-iso:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build GameCube ISO
run: ./scripts/build-gamecube-iso-docker.sh
- name: Copy output files.
run: |
mkdir -p ./git-artifcats/Dusk
cp build-gamecube-iso/Dusk-NTSC-J.iso ./git-artifcats/Dusk/Dusk-NTSC-J.iso
cp build-gamecube-iso/Dusk-NTSC-U.iso ./git-artifcats/Dusk/Dusk-NTSC-U.iso
cp build-gamecube-iso/Dusk-PAL.iso ./git-artifcats/Dusk/Dusk-PAL.iso
- name: Upload GameCube ISO
uses: actions/upload-artifact@v6
with:
name: dusk-gamecube-iso
path: ./git-artifcats/Dusk
if-no-files-found: error
build-wii:
runs-on: ubuntu-latest
steps:
@@ -106,12 +141,34 @@ jobs:
- name: Copy output files.
run: |
mkdir -p ./git-artifcats/Dusk/apps/Dusk
cp build-wii/Dusk.dol ./git-artifcats/Dusk/apps/Dusk/Dusk.dol
cp build-wii/boot.dol ./git-artifcats/Dusk/apps/Dusk/boot.dol
cp build-wii/dusk.dsk ./git-artifcats/Dusk/apps/Dusk/dusk.dsk
cp docker/dolphin/meta.xml ./git-artifcats/Dusk/apps/Dusk/meta.xml
cp build-wii/meta.xml ./git-artifcats/Dusk/apps/Dusk/meta.xml
- name: Upload Wii binary
uses: actions/upload-artifact@v6
with:
name: dusk-wii
path: ./git-artifcats/Dusk
if-no-files-found: error
build-wii-iso:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker
uses: docker/setup-docker-action@v5
- name: Build Wii ISO
run: ./scripts/build-wii-iso-docker.sh
- name: Copy output files.
run: |
mkdir -p ./git-artifcats/Dusk
cp build-wii-iso/Dusk-NTSC-J.iso ./git-artifcats/Dusk/Dusk-NTSC-J.iso
cp build-wii-iso/Dusk-NTSC-U.iso ./git-artifcats/Dusk/Dusk-NTSC-U.iso
cp build-wii-iso/Dusk-PAL.iso ./git-artifcats/Dusk/Dusk-PAL.iso
- name: Upload Wii ISO
uses: actions/upload-artifact@v6
with:
name: dusk-wii-iso
path: ./git-artifcats/Dusk
if-no-files-found: error
+4 -2
View File
@@ -83,7 +83,6 @@ assets/borrowed
.VSCode*
/vita
._*
*~
@@ -104,4 +103,7 @@ yarn.lock
.venv
/build2
/build*
/build*
/assets/test
/tools_old
/assets/test.png
+513
View File
@@ -0,0 +1,513 @@
# Dusk -- Claude Code rules
## About Dusk
Dusk is a pure C game and game engine. There is no C++ anywhere in the
codebase. The engine is built around a data-oriented Entity Component
System (ECS) and is designed for heavy optimization across a wide range
of hardware targets, including platforms with very limited RAM and CPU.
**Current and planned platforms:** Linux, Windows, macOS, Sony PSP,
PlayStation Vita, Nintendo GameCube, Nintendo Wii. Additional platforms
will be added over time. GameCube and Wii are collectively referred to
as the **Dolphin** targets throughout the codebase and docs.
**Build system:** CMake exclusively. Every source subdirectory owns its
own `CMakeLists.txt`.
**Architecture:** Entity Component System (ECS) as the primary pattern.
All game objects are entities; behaviour and state are attached via
components. No inheritance hierarchies -- favour composition.
**Optimization:** Performance is a first-class constraint, not an
afterthought. The engine must run well on hardware as constrained as
the GameCube (16 MB main RAM, 485 MHz PowerPC) and PSP (32 MB RAM,
333 MHz MIPS). Every design and implementation decision should consider
the most constrained target.
**Coding style:** All C code must strictly follow the project style
rules documented in the [Coding style](#coding-style) section below.
Deviations are not acceptable.
### Further reading
Detailed documentation on specific topics lives in `.claude/`:
| Topic | File |
|-------|------|
| Platform overview, capability macros, quick comparison | `.claude/platforms.md` |
| Platform -- Linux and Knulli | `.claude/platform-linux.md` |
| Platform -- Sony PSP | `.claude/platform-psp.md` |
| Platform -- PlayStation Vita | `.claude/platform-vita.md` |
| Platform -- GameCube and Wii (Dolphin) | `.claude/platform-dolphin.md` |
| Platform -- Windows (planned) | `.claude/platform-windows.md` |
| Platform -- macOS (planned) | `.claude/platform-macos.md` |
| ECS architecture and conventions | `.claude/ecs.md` |
| CMake build system and toolchain setup | `.claude/build.md` |
| Optimization guidelines and platform budgets | `.claude/optimization.md` |
| Test infrastructure and assertion macros | `.claude/tests.md` |
| Error handling system (`errorret_t`, macros) | `.claude/errors.md` |
| Asset system (loading, caching, loaders) | `.claude/assets.md` |
| Threading (`thread_t`, mutex, thread-local) | `.claude/threading.md` |
| Input system (actions, buttons, platforms) | `.claude/input.md` |
| Physics simulation and collision shapes | `.claude/physics.md` |
| Event system (pub/sub) | `.claude/events.md` |
| Locale / localisation system | `.claude/locale.md` |
| Time system (fixed/dynamic, epoch) | `.claude/time.md` |
| Network system (per-platform status) | `.claude/network.md` |
| Utility library (memory, string, math, endian, ref) | `.claude/util.md` |
| Script system (JerryScript, module proto API) | `.claude/script.md` |
| Script async promises (`scriptpromisepend_t`) | `.claude/script-promises.md` |
| Engine main loop, system platform API, log | `.claude/engine.md` |
| Save system (multi-slot, platform storage) | `.claude/save.md` |
| Animation (keyframes, easing functions) | `.claude/animation.md` |
| Display (index) | `.claude/display.md` |
| Display -- screen, framebuffer, size modes | `.claude/display-core.md` |
| Display -- texture, tileset, font | `.claude/display-texture.md` |
| Display -- shader, material, display state | `.claude/display-shader.md` |
| Scene system (lifecycle, render pipeline, JS hooks) | `.claude/scene.md` |
---
## File headers
Every C, H, and JS file starts with:
```c
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
```
JS files use `//` comment style instead.
---
## C conventions
### Types
Always use the project-defined aliases instead of bare C primitives:
| Use | Not |
|-----------|--------------|
| `bool_t` | `bool` |
| `int_t` | `int` |
| `float_t` | `float` |
| `char_t` | `char` |
Use `uint8_t`, `uint16_t`, `int32_t`, etc. for fixed-width integers.
All struct and enum types end in `_t` (`animation_t`, `errorret_t`, …).
### Naming
- **Functions** — snake_case, prefixed with their module:
`assetLock()`, `entityPositionInit()`, `moduleAssetBatchCtor()`
- **Struct fields** — camelCase: `keyframeCount`, `localPosition`
- **Macros / constants** — UPPER_SNAKE_CASE:
`ENTITY_ID_INVALID`, `ERROR_OK`, `COMPONENT_TYPE_COUNT`
- **Files** — snake_case matching the primary type: `entityposition.c`,
`moduleassetbatch.c`
### Header files (`.h`)
- Use `#pragma once` — no include guards.
- Declare every public function, `#define`, and `extern` global.
- Write a JSDoc block (`/** … */`) above every declaration explaining
purpose, `@param`s, and `@returns`.
- Only include headers that the `.h` file itself strictly requires for
the types it exposes. Move everything else to the `.c` file.
Do not use forward declarations as a workaround — use the real
include in the `.c` file instead.
### Implementation files (`.c`)
- Contain function bodies only; no declarations.
- Pull in whatever additional includes the implementation needs.
- Do not use `static` or `inline` on **functions**. Every function,
including internal helpers, must be declared in the matching `.h` and
defined in the `.c` file. Internal helpers belong near the bottom of
the `.c` file, not at the top with a `static` qualifier.
`static` and `inline` on functions are only appropriate when the
function body is written directly inside a `.h` file.
`static` on **variables** (file-scope state) is fine and expected.
### Formatting
- Hard-wrap all lines at **80 characters**.
### Error handling
Return `errorret_t` from fallible functions. Use these macros:
```c
errorOk(); // return success
errorThrow("msg %d", val); // return failure with message
errorChain(someCall()); // propagate failure, continue on success
errorIsOk(ret) / errorIsNotOk(ret) // test a result
errorCatch(ret); // handle + free an error
```
Never return raw error codes or use `errno` for in-engine errors.
### Memory
Use the project allocator — never raw `malloc`/`free`:
```c
memoryAllocate(size) // allocate
memoryFree(ptr) // free
memoryZero(dest, size) // zero a block
memoryCopy(dest, src, size) // copy
```
### Asserts
Prefer specific assert macros over bare `assert()`:
```c
assertNotNull(ptr, "msg");
assertTrue(cond, "msg");
assertFalse(cond, "msg");
assertUnreachable("msg");
assertIsMainThread("msg");
```
---
## Build system
See `.claude/build.md` for extended CMake conventions, platform
toolchain setup, and adding platform-conditional sources.
Each subdirectory has its own `CMakeLists.txt` that adds sources with:
```cmake
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
myfile.c
)
```
Never add source files to the root `CMakeLists.txt` directly.
---
## Platform support
See `.claude/platforms.md` for the full platform table (including
planned Windows / macOS targets), capability macros, toolchain setup,
and endianness notes.
### Targets
Set `DUSK_TARGET_SYSTEM` at CMake configure time to select a platform:
| `DUSK_TARGET_SYSTEM` | Macro defined | Platform |
|----------------------|-------------------|------------------|
| `linux` | `DUSK_LINUX` | Linux desktop |
| `knulli` | `DUSK_KNULLI` | Knulli (handheld)|
| `psp` | `DUSK_PSP` | Sony PSP |
| `vita` | `DUSK_VITA` | PlayStation Vita |
| `gamecube` | `DUSK_GAMECUBE` | Nintendo GameCube|
| `wii` | `DUSK_WII` | Nintendo Wii |
### Layer structure
```
src/dusk/ core, platform-agnostic game logic
src/duskgl/ OpenGL abstraction (Linux, Knulli, PSP, Vita)
src/dusksdl2/ SDL2 window + input (Linux, Knulli, PSP, Vita)
src/dusklinux/ Linux + Knulli platform impl
src/duskpsp/ PSP platform impl
src/duskvita/ Vita platform impl
src/duskdolphin/ GameCube / Wii platform impl (no SDL2/OpenGL)
```
Dolphin is the only target that bypasses SDL2 and OpenGL entirely —
it uses native GameCube/Wii rendering and input APIs.
### Platform guards
Use the compile-time macros for platform-specific code:
```c
#ifdef DUSK_PSP
// PSP-only path
#elif defined(DUSK_GAMECUBE) || defined(DUSK_WII)
// GameCube / Wii path
#else
// Generic / Linux fallback
#endif
```
Additional capability macros set per-target:
`DUSK_SDL2`, `DUSK_OPENGL`, `DUSK_OPENGL_ES`, `DUSK_OPENGL_LEGACY`,
`DUSK_INPUT_GAMEPAD`, `DUSK_INPUT_KEYBOARD`, `DUSK_INPUT_POINTER`,
`DUSK_PLATFORM_ENDIAN_BIG` / `DUSK_PLATFORM_ENDIAN_LITTLE`.
### Abstraction pattern
Platform-specific implementations are wired in via `#define` macros in
each platform's `displayplatform.h` / `inputplatform.h` etc., which
the core calls through. Functions that a platform does not support are
simply left undefined — the core guards calls with `#ifdef`.
### Adding platform-specific code
- Put it under `src/dusk<platform>/` in the matching subsystem folder.
- Gate any core call-site with the appropriate `#ifdef DUSK_<PLATFORM>`
or capability macro.
- Keep the `src/dusk/` core free of platform ifdefs — delegate through
the platform header macros instead.
---
## Adding a new entity component
See `.claude/ecs.md` for ECS design rules, component categories, and
entity lifecycle details.
1. Create `src/dusk/entity/component/<category>/entityMyComp.h/.c` with
struct `entityMyComp_t`, `entityMyCompInit()`, and optionally
`entityMyCompDispose()`.
2. Add the include to `src/dusk/entity/componentlist.h` header block.
3. Add a row to `src/dusk/entity/componentlist.h`:
```c
X(MYCOMP, entityMyComp_t, myComp, entityMyCompInit, NULL, NULL)
```
This auto-generates the enum, union field, and definition entry.
4. If JS-facing, create the script module and `.d.ts` (see below).
---
## Adding a new asset loader type
1. Add an enum value to `assetloadertype_t` (before `_COUNT`) in
`src/dusk/asset/loader/assetloader.h`.
2. Add fields to the input/loading/output unions in `assetloader.h`.
3. Implement `assetXxxLoaderSync`, `assetXxxLoaderAsync`, and
`assetXxxDispose` in a new `src/dusk/asset/loader/xxx/` directory.
4. Register the three callbacks in `ASSET_LOADER_CALLBACKS[]` in
`src/dusk/asset/loader/assetloader.c`.
5. If user-facing, create a JS module (see below) and a `.d.ts` file.
---
## Adding a new script (JS) module
1. Create `src/dusk/script/module/<category>/moduleMyMod.h/.c`.
- Declare `extern scriptproto_t MODULE_MYMOD_PROTO;` in the header.
- Use `moduleBaseFunction(name)` to define JS-callable functions.
- Register props/funcs in `moduleMyModInit()` with
`scriptProtoDefineProp` / `scriptProtoDefineFunc` /
`scriptProtoDefineStaticFunc`.
2. `#include` the header in
`src/dusk/script/module/modulelist.c` and call
`moduleMyModInit()` in `moduleListInit()` (and `Dispose` in
`moduleListDispose()`).
3. For component modules also register in
`src/dusk/script/module/entity/component/modulecomponentlist.c`
so `entity.add()` returns the typed wrapper.
4. Create `types/<category>/mymod.d.ts` and add a
`/// <reference path="..." />` line to `types/index.d.ts`.
---
## Script module type declarations
Whenever a `src/dusk/script/module/**/*.c` file is created or modified,
check whether the corresponding `types/**/*.d.ts` needs updating and
apply any changes before finishing the task.
---
## JavaScript (asset scripts)
- Use `var` for module-level state; `const` for values that never
change.
- Always use semicolons.
- Scene objects are plain objects (`var scene = {}`) with assigned
methods.
- Export via `module.exports = scene`.
- Async scene init should use `async function` and `await`.
---
## Coding style
### ASCII only
Source files (`.c`, `.h`, `.js`) must contain only ASCII characters (U+0000U+007F).
Non-ASCII characters are banned even in comments and string literals.
Use ASCII-only substitutes instead:
- `--` or `-` instead of `` (em dash)
- `->` instead of `` (arrow)
- `x` or `*` instead of `×` (multiplication)
Only non-script asset files (e.g. `.po` locale files) may contain non-ASCII text.
### Indentation
2 spaces. No tabs.
### Keyword and operator spacing
No space between a keyword or function name and its opening parenthesis:
```c
if(!ptr) return;
for(uint8_t i = 0; i < count; i++) {
while(entry->state != DONE) {
switch(type) {
sizeof(assetbatch_t)
memoryZero(ptr, size)
```
Spaces around all binary operators and after every comma:
```c
pos->flags |= ENTITY_POSITION_FLAG_WORLD_DIRTY;
(size_t)end - (size_t)start
foo(a, b, c)
```
### Braces
Opening brace on the **same line** as the statement (K&R style) for all
constructs — functions, `if`, `else`, `for`, `while`, `switch`:
```c
void assetEntryLock(assetentry_t *entry) {
...
}
if(dirty) {
...
} else {
...
}
```
### Guard returns
Short guards go on one line with no braces:
```c
if(!ptr) return;
if(!b || !b->batch) return jerry_undefined();
if(!(flags & DIRTY)) return;
```
### Blank lines
- One blank line between functions; no blank line at the start or end of
a function body.
- One blank line between logical blocks inside a function body.
- No trailing blank lines at the end of a file.
### Pointer placement
`*` is attached to the variable name, not the type:
```c
assetentry_t *entry
const char_t *name
void *ptr
uint8_t *d = (uint8_t *)dest;
```
### Casts
Space between cast and operand:
```c
(assetbatch_t *)user
(uint8_t *)dest
(textureformat_t)v
```
### Return
No parentheses around the return value:
```c
return ptr;
return MEMORY_POINTERS_IN_USE;
```
### switch / case
`case` indented 2 spaces from `switch`; body indented 2 more from `case`:
```c
switch(type) {
case ASSET_LOADER_TYPE_TEXTURE:
descs[i].input.texture = (textureformat_t)v;
break;
default:
break;
}
```
### Multi-line function signatures
When parameters don't fit on one line, put each on its own line indented
2 spaces; the closing `) {` (definition) or `);` (declaration) goes on
its own line at column 0:
```c
void assetEntryInit(
assetentry_t *entry,
const char_t *name,
const assetloadertype_t type,
assetloaderinput_t *input
) {
errorret_t memoryCompare(
const void *a,
const void *b,
const size_t size
);
```
### Structs and enums
Anonymous inner struct or enum with a `typedef`, `_t` suffix, closing
brace and name on the same line:
```c
typedef struct {
errorcode_t code;
char_t *message;
} errorstate_t;
typedef enum {
ASSET_LOADER_TYPE_NULL,
ASSET_LOADER_TYPE_COUNT
} assetloadertype_t;
```
### Designated initialisers
Spaces inside braces; `.field = value`:
```c
jsassetentry_t e = { .entry = entry };
assetbatchloadedpend_t init = { .batch = batch };
```
### Ternary operator
Spaces around `?` and `:`:
```c
const float val = psx > 0.0f ? pt[0][0] / psx : 0.0f;
```
### const placement
`const` before the type, `*` attached to the variable:
```c
const char_t *name
const void *src
const size_t size
```
### Comments in `.c` files
- Do not use section dividers (`/* ---- ... ---- */`). Just let the
functions follow one another with a single blank line between them.
- Multi-line explanatory comments inside function bodies use `//` lines:
```c
// Script modules are freed; orphaned JS wrapper objects now get GC'd
// so their finalizers fire before assetDispose() checks ref counts.
jerry_heap_gc(JERRY_GC_PRESSURE_HIGH);
```
- Do not use `/* */` for inline or inline-block comments inside `.c`
function bodies.
### Comments in `.h` files
Every public declaration gets a Javadoc block (`/** … */`) with
`@param` and `@returns` where relevant. Keep it on the lines immediately
above the declaration with no blank line in between.
---
## Tests
- Tests live in `test/` mirroring `src/dusk/` structure.
- Use cmocka; include `dusktest.h`.
- Test functions: `static void test_something(void **state)`.
- After each test, assert `memoryGetAllocatedCount() == 0` to catch
leaks.
- Build with `-DDUSK_BUILD_TESTS=ON`.
+18 -1
View File
@@ -4,14 +4,22 @@
# https://opensource.org/licenses/MIT
# Setup
cmake_minimum_required(VERSION 3.18)
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
cmake_policy(SET CMP0079 NEW)
# set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
option(DUSK_BUILD_TESTS "Enable tests" OFF)
# Game identity — override these per-project
set(DUSK_GAME_NAME "Dusk" CACHE STRING "Game display name")
set(DUSK_GAME_AUTHOR "YouWish" CACHE STRING "Game author / coder")
set(DUSK_GAME_SHORT_DESCRIPTION "Dusk game" CACHE STRING "One-line description")
set(DUSK_GAME_LONG_DESCRIPTION "No description yet." CACHE STRING "Full description")
# Prep cache
set(DUSK_CACHE_TARGET "dusk-target")
@@ -68,10 +76,19 @@ else()
set(DUSK_LIBRARY_TARGET_NAME "${DUSK_BINARY_TARGET_NAME}" CACHE INTERNAL ${DUSK_CACHE_TARGET})
endif()
if(NOT DEFINED DUSK_VERSION)
string(TIMESTAMP DUSK_VERSION "debug-%y%m%d%H%M%S")
endif()
# Definitions
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
DUSK_TARGET_SYSTEM="${DUSK_TARGET_SYSTEM}"
DUSK_GAME_NAME="${DUSK_GAME_NAME}"
DUSK_GAME_AUTHOR="${DUSK_GAME_AUTHOR}"
DUSK_GAME_SHORT_DESCRIPTION="${DUSK_GAME_SHORT_DESCRIPTION}"
DUSK_GAME_LONG_DESCRIPTION="${DUSK_GAME_LONG_DESCRIPTION}"
DUSK_VERSION="${DUSK_VERSION}"
)
# Toolchains
@@ -35,9 +35,9 @@ void cameraInitOrthographic(camera_t *camera) {
camera->projType = CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC;
camera->orthographic.left = 0.0f;
camera->orthographic.right = SCREEN.width;
camera->orthographic.top = 0.0f;
camera->orthographic.bottom = SCREEN.height;
camera->nearClip = -1.0f;
camera->orthographic.top = SCREEN.height;
camera->orthographic.bottom = 0.0f;
camera->nearClip = 0.1f;
camera->farClip = 1.0f;
camera->viewType = CAMERA_VIEW_TYPE_2D;
@@ -84,7 +84,7 @@ void cameraGetViewMatrix(camera_t *camera, mat4 dest) {
assertNotNull(dest, "Destination matrix must not be null");
if(camera->viewType == CAMERA_VIEW_TYPE_MATRIX) {
glm_mat4_copy(camera->view, dest);
glm_mat4_ucopy(camera->view, dest);
} else if(camera->viewType == CAMERA_VIEW_TYPE_LOOKAT) {
glm_mat4_identity(dest);
glm_lookat(
@@ -14,11 +14,6 @@
#error "cameraPushMatrixPlatform must be defined"
#endif
typedef enum {
CAMERA_PROJECTION_TYPE_PERSPECTIVE,
CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED,
CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC
} cameraprojectiontype_t;
typedef enum {
CAMERA_VIEW_TYPE_MATRIX,
@@ -10,6 +10,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
assettileset.c
assetlanguage.c
assetscript.c
assetmap.c
assetmapchunk.c
)
+78
View File
@@ -0,0 +1,78 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assettexture.h"
#include "asset/assettype.h"
#include "assert/assert.h"
#include "display/texture/texture.h"
#include "util/endian.h"
errorret_t assetTextureLoad(assetentire_t entire) {
assertNotNull(entire.data, "Data pointer cannot be NULL.");
assertNotNull(entire.output, "Output pointer cannot be NULL.");
assettexture_t *assetData = (assettexture_t *)entire.data;
texture_t *texture = (texture_t *)entire.output;
// Read header and version (first 4 bytes)
if(
assetData->header[0] != 'D' ||
assetData->header[1] != 'T' ||
assetData->header[2] != 'X'
) {
errorThrow("Invalid texture header");
}
// Version (can only be 1 atm)
if(assetData->version != 0x01) {
errorThrow("Unsupported texture version");
}
// Fix endian
assetData->width = endianLittleToHost32(assetData->width);
assetData->height = endianLittleToHost32(assetData->height);
// Check dimensions.
if(
assetData->width == 0 || assetData->width > ASSET_TEXTURE_WIDTH_MAX ||
assetData->height == 0 || assetData->height > ASSET_TEXTURE_HEIGHT_MAX
) {
errorThrow("Invalid texture dimensions");
}
// Validate format
textureformat_t format;
texturedata_t data;
switch(assetData->type) {
case 0x00: // RGBA8888
format = TEXTURE_FORMAT_RGBA;
data.rgbaColors = (color_t *)assetData->data;
break;
// case 0x01:
// format = TEXTURE_FORMAT_RGB;
// break;
// case 0x02:
// format = TEXTURE_FORMAT_RGB565;
// break;
// case 0x03:
// format = TEXTURE_FORMAT_RGB5A3;
// break;
default:
errorThrow("Unsupported texture format");
}
errorChain(textureInit(
texture, assetData->width, assetData->height, format, data
));
errorOk();
}
@@ -7,6 +7,7 @@
#pragma once
#include "error/error.h"
#include "display/color.h"
#define ASSET_TEXTURE_WIDTH_MAX 2048
#define ASSET_TEXTURE_HEIGHT_MAX 2048
@@ -20,9 +21,10 @@ typedef struct assetentire_s assetentire_t;
typedef struct {
char_t header[3];
uint8_t version;
uint8_t type;
uint32_t width;
uint32_t height;
uint8_t palette[ASSET_TEXTURE_SIZE_MAX];
uint8_t data[ASSET_TEXTURE_SIZE_MAX * sizeof(color4b_t)];
} assettexture_t;
#pragma pack(pop)
-13
View File
@@ -1,13 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct {
void *nothing;
} inventory_t;
+21
View File
@@ -0,0 +1,21 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
const platformNames = {
[System.PLATFORM_LINUX]: 'Linux',
[System.PLATFORM_KNULLI]: 'Knulli',
[System.PLATFORM_PSP]: 'PSP',
[System.PLATFORM_GAMECUBE]: 'GameCube',
[System.PLATFORM_WII]: 'Wii',
};
Console.print('Platform: ' + (platformNames[System.platform] || 'Unknown'));
UIFullboxOver.setColor(Color.BLACK);
requireAsync('testscene.js').then(Scene.set).catch(err => {
Console.print('Error loading scene: ' + err);
Engine.exit();
});
-77
View File
@@ -1,77 +0,0 @@
module('input')
module('platform')
module('scene')
module('locale')
-- Default Input bindings.
if PSP then
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT)
inputBind("circle", INPUT_ACTION_CANCEL)
inputBind("cross", INPUT_ACTION_ACCEPT)
inputBind("select", INPUT_ACTION_RAGEQUIT)
inputBind("lstick_up", INPUT_ACTION_UP)
inputBind("lstick_down", INPUT_ACTION_DOWN)
inputBind("lstick_left", INPUT_ACTION_LEFT)
inputBind("lstick_right", INPUT_ACTION_RIGHT)
elseif DOLPHIN then
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT)
inputBind("b", INPUT_ACTION_CANCEL)
inputBind("a", INPUT_ACTION_ACCEPT)
inputBind("z", INPUT_ACTION_RAGEQUIT)
inputBind("lstick_up", INPUT_ACTION_UP)
inputBind("lstick_down", INPUT_ACTION_DOWN)
inputBind("lstick_left", INPUT_ACTION_LEFT)
inputBind("lstick_right", INPUT_ACTION_RIGHT)
elseif LINUX then
if INPUT_KEYBOARD then
inputBind("w", INPUT_ACTION_UP)
inputBind("s", INPUT_ACTION_DOWN)
inputBind("a", INPUT_ACTION_LEFT)
inputBind("d", INPUT_ACTION_RIGHT)
inputBind("left", INPUT_ACTION_LEFT)
inputBind("right", INPUT_ACTION_RIGHT)
inputBind("up", INPUT_ACTION_UP)
inputBind("down", INPUT_ACTION_DOWN)
inputBind("enter", INPUT_ACTION_ACCEPT)
inputBind("e", INPUT_ACTION_ACCEPT)
inputBind("q", INPUT_ACTION_CANCEL)
inputBind("escape", INPUT_ACTION_RAGEQUIT)
end
if INPUT_GAMEPAD then
inputBind("gamepad_up", INPUT_ACTION_UP)
inputBind("gamepad_down", INPUT_ACTION_DOWN)
inputBind("gamepad_left", INPUT_ACTION_LEFT)
inputBind("gamepad_right", INPUT_ACTION_RIGHT)
inputBind("gamepad_a", INPUT_ACTION_ACCEPT)
inputBind("gamepad_b", INPUT_ACTION_CANCEL)
inputBind("gamepad_back", INPUT_ACTION_RAGEQUIT)
inputBind("gamepad_lstick_up", INPUT_ACTION_UP)
inputBind("gamepad_lstick_down", INPUT_ACTION_DOWN)
inputBind("gamepad_lstick_left", INPUT_ACTION_LEFT)
inputBind("gamepad_lstick_right", INPUT_ACTION_RIGHT)
end
if INPUT_POINTER then
inputBind("mouse_x", INPUT_ACTION_POINTERX)
inputBind("mouse_y", INPUT_ACTION_POINTERY)
end
else
print("Unknown platform, no default input bindings set.")
end
sceneSet('scene/minesweeper.lua')
+56 -5
View File
@@ -1,9 +1,60 @@
#
msgid ""
msgstr ""
"Language: en_US\n"
"Project-Id-Version: ExampleApp 1.0\n"
"Language: en\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : n==2 ? 1 : (n<7 ? 2 : 3));\n"
msgid "ui.test"
msgstr "Hello this is a test."
#: ui/menu.c:10
msgid "ui.title"
msgstr ""
"Welcome"
#: ui/user.c:22
msgid "ui.greeting"
msgstr "Hello, %s!"
#: ui/files.c:40
msgid "ui.file_status"
msgstr "%s has %d files."
#: ui/cart.c:55
msgid "cart.item_count"
msgid_plural "cart.item_count"
msgstr[0] "%d item"
msgstr[1] "%d items (dual)"
msgstr[2] "%d items (few)"
msgstr[3] "%d items (many)"
#: ui/notifications.c:71
msgid ""
"ui.multiline_help"
msgstr ""
"Line one of the help text.\n"
"Line two continues here.\n"
"Line three ends here."
#: ui/errors.c:90
msgid ""
"error.upload_failed.long"
msgstr ""
"Upload failed for file \"%s\".\n"
"Please try again later or contact support."
#: ui/messages.c:110
msgid ""
"user.invite_status"
msgid_plural ""
"user.invite_status"
msgstr[0] ""
"%s invited %d user.\n"
"Please review the request."
msgstr[1] ""
"%s invited %d users (dual).\n"
"Please review the requests."
msgstr[2] ""
"%s invited %d users (few).\n"
"Please review the requests."
msgstr[3] ""
"%s invited %d users (many).\n"
"Please review the requests."
Binary file not shown.
+63
View File
@@ -0,0 +1,63 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
const PLAYER_SPEED = 5.0;
// 1 world unit = 16 pixels.
const PIXEL_SCALE = 1.0 / 16.0;
// Player sprite is 32x32 px (test.png dimensions).
const PLAYER_W = 32 * PIXEL_SCALE;
const PLAYER_H = 32 * PIXEL_SCALE;
var player = {};
player.getAssets = () => {
return [
{ path: 'test.png', type: Asset.TYPE_TEXTURE, format: Texture.FORMAT_RGBA }
];
}
player.init = function(scene) {
var texture = scene.assets.getAssetByPath('test.png');
Console.print('Player init: got texture ' + texture);
_entity = Entity.create();
_position = _entity.add(Component.POSITION);
_physics = _entity.add(Component.PHYSICS);
_physics.bodyType = Physics.DYNAMIC;
_physics.shape = Physics.SHAPE_CUBE;
_physics.gravityScale = 1.0;
var r = _entity.add(Component.RENDERABLE);
r.texture = texture.texture;
r.type = Renderable.SPRITEBATCH;
r.color = new Color(220, 80, 80);
// Upright quad centered on X, bottom-aligned on Y.
r.sprites = [[-PLAYER_W/2, 0, 0, PLAYER_W/2, PLAYER_H, 0, 0, 1, 1, 0]];
_position.localPosition = new Vec3(0, PLAYER_H, 0);
};
player.getPosition = function() {
return _position;
};
player.update = function() {
if(!_physics) return;
var vx = Input.axis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT) * PLAYER_SPEED;
var vz = Input.axis(INPUT_ACTION_DOWN, INPUT_ACTION_UP) * PLAYER_SPEED;
// Preserve vertical velocity so gravity and landing work correctly.
var vy = _physics.velocity.y;
_physics.velocity = new Vec3(vx, vy, vz);
};
player.dispose = function() {
Entity.dispose(_entity);
_entity = null;
_position = null;
_physics = null;
};
module.exports = player;
-256
View File
@@ -1,256 +0,0 @@
module('spritebatch')
module('camera')
module('color')
module('ui')
module('screen')
module('time')
module('glm')
module('text')
module('tileset')
module('texture')
module('input')
CELL_STATE_DEFAULT = 0
CELL_STATE_HOVER = 1
CELL_STATE_DOWN = 2
CELL_STATE_DISABLED = 3
screenSetBackground(colorCornflowerBlue())
camera = cameraCreate(CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC)
-- tilesetUi = tilesetGetByName("ui")
-- textureUi = textureLoad(tilesetUi.texture)
-- tilesetBorder = tilesetGetByName("border")
-- textureBorder = textureLoad(tilesetBorder.texture)
-- textureGrid = textureLoad("minesweeper/grid_bg.dpi")
-- tilesetCell = tilesetGetByName("cell")
-- textureCell = textureLoad(tilesetCell.texture)
-- cellSliceDefault = tilesetPositionGetUV(tilesetCell, 3, 5)
-- cellSliceHover = tilesetPositionGetUV(tilesetCell, 3, 4)
-- cellSliceDown = tilesetPositionGetUV(tilesetCell, 3, 6)
-- cellSliceDisabled = tilesetPositionGetUV(tilesetCell, 3, 7)
-- sweepwerCols = 10
-- sweeperRows = 14
-- mouseX = -1
-- mouseY = -1
-- centerX = 0
-- centerY = 0
-- boardWidth = sweepwerCols * tilesetCell.tileWidth
-- boardHeight = sweeperRows * tilesetCell.tileHeight
-- i = 0
-- cells = {}
-- for y = 1, sweeperRows do
-- for x = 1, sweepwerCols do
-- cells[i] = CELL_STATE_DEFAULT
-- i = i + 1
-- end
-- end
function cellDraw(x, y, type)
local slice = cellSliceDefault
if type == CELL_STATE_HOVER then
slice = cellSliceHover
elseif type == CELL_STATE_DOWN then
slice = cellSliceDown
elseif type == CELL_STATE_DISABLED then
slice = cellSliceDisabled
end
spriteBatchPush(textureCell,
x, y,
x + tilesetCell.tileWidth, y + tilesetCell.tileHeight,
colorWhite(),
slice.u0, slice.v0,
slice.u1, slice.v1
)
end
function backgroundDraw()
local t = (TIME.time / 40) % 1
local scaleX = screenGetWidth() / textureGrid.width
local scaleY = screenGetHeight() / textureGrid.height
local u0 = t * scaleX
local v0 = t * scaleY
local u1 = scaleX + u0
local v1 = scaleY + v0
spriteBatchPush(textureGrid,
0, 0,
screenGetWidth(), screenGetHeight(),
colorWhite(),
u0, v0,
u1, v1
)
end
function borderDraw(x, y, innerWidth, innerHeight)
-- Top Left
local uv = tilesetPositionGetUV(tilesetBorder, 0, 0)
spriteBatchPush(textureBorder,
x - tilesetBorder.tileWidth, y - tilesetBorder.tileWidth,
x, y,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
-- Top Right
uv = tilesetPositionGetUV(tilesetBorder, 10, 0)
spriteBatchPush(textureBorder,
x + innerWidth, y - tilesetBorder.tileHeight,
x + innerWidth + tilesetBorder.tileWidth, y,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
-- Bottom Left
uv = tilesetPositionGetUV(tilesetBorder, 0, 10)
spriteBatchPush(textureBorder,
x - tilesetBorder.tileWidth, y + innerHeight,
x, y + innerHeight + tilesetBorder.tileHeight,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
-- Bottom Right
uv = tilesetPositionGetUV(tilesetBorder, 10, 10)
spriteBatchPush(textureBorder,
x + innerWidth, y + innerHeight,
x + innerWidth + tilesetBorder.tileWidth, y + innerHeight + tilesetBorder.tileHeight,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
-- Top
uv = tilesetPositionGetUV(tilesetBorder, 1, 0)
spriteBatchPush(textureBorder,
x, y - tilesetBorder.tileHeight,
x + innerWidth, y,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
-- Bottom
uv = tilesetPositionGetUV(tilesetBorder, 1, 10)
spriteBatchPush(textureBorder,
x, y + innerHeight,
x + innerWidth, y + innerHeight + tilesetBorder.tileHeight,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
-- Left
uv = tilesetPositionGetUV(tilesetBorder, 0, 1)
spriteBatchPush(textureBorder,
x - tilesetBorder.tileWidth, y,
x, y + innerHeight,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
-- Right
uv = tilesetPositionGetUV(tilesetBorder, 10, 1)
spriteBatchPush(textureBorder,
x + innerWidth, y,
x + innerWidth + tilesetBorder.tileWidth, y + innerHeight,
colorWhite(),
uv.u0, uv.v0,
uv.u1, uv.v1
)
end
x = 0
y = 0
function sceneDispose()
end
function sceneUpdate()
x = x + inputAxis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT)
y = y + inputAxis(INPUT_ACTION_UP, INPUT_ACTION_DOWN)
end
function sceneRender()
-- Update camera
cameraPushMatrix(camera)
camera.bottom = screenGetHeight()
camera.right = screenGetWidth()
spriteBatchPush(
nil,
x, y, x + 32, y + 32,
colorWhite()
)
-- Update mouse position
-- if INPUT_POINTER then
-- mouseX = inputGetValue(INPUT_ACTION_POINTERX) * screenGetWidth()
-- mouseY = inputGetValue(INPUT_ACTION_POINTERY) * screenGetHeight()
-- -- Draw cursor
-- spriteBatchPush(
-- nil,
-- mouseX - 2, mouseY - 2,
-- mouseX + 2, mouseY + 2,
-- colorRed(),
-- 0, 0,
-- 1, 1
-- )
-- end
-- textDraw(10, 10, "Hello World")
-- centerX = math.floor(screenGetWidth() / 2)
-- centerY = math.floor(screenGetHeight() / 2)
-- Draw elements
-- backgroundDraw()
-- borderDraw(
-- centerX - (boardWidth / 2), centerY - (boardHeight / 2),
-- boardWidth, boardHeight
-- )
-- i = 0
-- -- Foreach cell
-- local offX = centerX - (boardWidth / 2)
-- local offY = centerY - (boardHeight / 2)
-- for y = 0, sweeperRows - 1 do
-- for x = 0, sweepwerCols - 1 do
-- i = y * sweepwerCols + x
-- -- Hovered
-- if
-- cells[i] == CELL_STATE_DEFAULT and
-- mouseX >= x * tilesetCell.tileWidth + offX and mouseX < (x + 1) * tilesetCell.tileWidth + offX and
-- mouseY >= y * tilesetCell.tileHeight + offY and mouseY < (y + 1) * tilesetCell.tileHeight + offY
-- then
-- cells[i] = CELL_STATE_HOVER
-- else
-- cells[i] = CELL_STATE_DEFAULT
-- end
-- cellDraw(
-- x * tilesetCell.tileWidth + offX,
-- y * tilesetCell.tileHeight + offY,
-- cells[i]
-- )
-- end
-- end
spriteBatchFlush()
cameraPopMatrix()
end
+42
View File
@@ -0,0 +1,42 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
var scene = {};
// Pokemon DS-style camera: ~34 degrees elevation (atan(6/9)).
// CAM_HEIGHT / CAM_DIST ratio controls the tilt - keep it under 0.7 for
// the characteristically shallow DS angle.
const CAM_HEIGHT = 6;
const CAM_DIST = 9;
scene.init = async function() {
// Camera
scene.cam = Entity.create();
var camPos = scene.cam.add(Component.POSITION);
var cam = scene.cam.add(Component.CAMERA);
camPos.localPosition = new Vec3(3, 3, 3);
camPos.lookAt(new Vec3(0, 0, 0));
// Floor - large flat slab, no texture needed.
scene.floor = Entity.create();
var floorPos = scene.floor.add(Component.POSITION);
var floorR = scene.floor.add(Component.RENDERABLE);
floorR.type = Renderable.SHADER_MATERIAL;
floorR.color = Color.BLUE;
// floorPos.localScale = new Vec3(16, 0.2, 16);
// floorPos.localPosition = new Vec3(0, -0.1, 0);
await UIFullboxOver.transition(Color.BLACK, Color.TRANSPARENT, 1.0);
};
scene.update = function() {
};
scene.dispose = function() {
Entity.dispose(scene.floor);
Entity.dispose(scene.cam);
};
module.exports = scene;
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

+6
View File
@@ -0,0 +1,6 @@
module = {
render() {
Text.draw(0, 0, "Hello World");
SpriteBatch.flush();
}
};
+96
View File
@@ -0,0 +1,96 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Turn things off we don't need
set(JERRY_CMDLINE OFF CACHE BOOL "" FORCE)
set(JERRY_EXT ON CACHE BOOL "" FORCE)
set(JERRY_DEBUGGER OFF CACHE BOOL "" FORCE)
set(JERRY_BUILTIN_DATE OFF CACHE BOOL "" FORCE)
set(ENABLE_LTO OFF CACHE BOOL "" FORCE)
# Fetch Jerry
include(FetchContent)
FetchContent_Declare(
jerryscript
GIT_REPOSITORY https://git.wish.moe/YourWishes/jerryscript
GIT_TAG float32-fix
)
FetchContent_MakeAvailable(jerryscript)
# Mark found
set(jerryscript_FOUND ON)
# Define targets
if(TARGET jerryscript-core)
set(JERRY_CORE_TARGET jerryscript-core)
elseif(TARGET jerry-core)
set(JERRY_CORE_TARGET jerry-core)
endif()
if(TARGET jerryscript-ext)
set(JERRY_EXT_TARGET jerryscript-ext)
elseif(TARGET jerry-ext)
set(JERRY_EXT_TARGET jerry-ext)
endif()
if(TARGET jerryscript-port-default)
set(JERRY_PORT_TARGET jerryscript-port-default)
elseif(TARGET jerry-port-default)
set(JERRY_PORT_TARGET jerry-port-default)
elseif(TARGET jerryscript-port)
set(JERRY_PORT_TARGET jerryscript-port)
elseif(TARGET jerry-port)
set(JERRY_PORT_TARGET jerry-port)
endif()
if(NOT JERRY_CORE_TARGET)
message(FATAL_ERROR "JerryScript core target not found")
endif()
if(NOT JERRY_EXT_TARGET)
message(FATAL_ERROR "JerryScript ext target not found")
endif()
if(NOT JERRY_PORT_TARGET)
message(FATAL_ERROR "JerryScript port target not found")
endif()
foreach(tgt IN ITEMS
${JERRY_CORE_TARGET}
${JERRY_EXT_TARGET}
${JERRY_PORT_TARGET}
)
if(TARGET ${tgt})
set_property(TARGET ${tgt} PROPERTY INTERPROCEDURAL_OPTIMIZATION OFF)
target_compile_definitions(${JERRY_CORE_TARGET} PRIVATE
JERRY_NUMBER_TYPE_FLOAT64=0
JERRY_BUILTIN_DATE=0
)
endif()
endforeach()
# Export include dirs through the targets
target_include_directories(${JERRY_CORE_TARGET} INTERFACE
${jerryscript_SOURCE_DIR}/jerry-core/include
)
target_include_directories(${JERRY_EXT_TARGET} INTERFACE
${jerryscript_SOURCE_DIR}/jerry-ext/include
)
target_include_directories(${JERRY_PORT_TARGET} INTERFACE
${jerryscript_SOURCE_DIR}/jerry-port/default/include
)
# Suppress JerryScript-only warning
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(${JERRY_CORE_TARGET} PRIVATE
-Wno-error
)
endif()
add_library(jerryscript::core ALIAS ${JERRY_CORE_TARGET})
add_library(jerryscript::ext ALIAS ${JERRY_EXT_TARGET})
add_library(jerryscript::port ALIAS ${JERRY_PORT_TARGET})
+31
View File
@@ -0,0 +1,31 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
include(FetchContent)
FetchContent_Declare(
stb
GIT_REPOSITORY https://github.com/nothings/stb.git
)
# Fetch stb if not already done
FetchContent_MakeAvailable(stb)
# Find the stb_image.h header
set(STB_INCLUDE_DIR "${stb_SOURCE_DIR}")
set(STB_IMAGE_HEADER "${stb_SOURCE_DIR}/stb_image.h")
if(EXISTS "${STB_IMAGE_HEADER}")
add_library(stb_image INTERFACE)
target_include_directories(stb_image INTERFACE "${STB_INCLUDE_DIR}")
set(STB_IMAGE_FOUND TRUE)
else()
set(STB_IMAGE_FOUND FALSE)
endif()
# Standard CMake variables
set(STB_IMAGE_INCLUDE_DIRS "${STB_INCLUDE_DIR}")
set(STB_IMAGE_LIBRARIES stb_image)
mark_as_advanced(STB_IMAGE_INCLUDE_DIRS STB_IMAGE_LIBRARIES STB_IMAGE_FOUND)
+18
View File
@@ -0,0 +1,18 @@
include(FetchContent)
if(NOT TARGET yyjson)
FetchContent_Declare(
yyjson
GIT_REPOSITORY https://github.com/ibireme/yyjson.git
GIT_TAG 0.12.0
)
FetchContent_MakeAvailable(yyjson)
endif()
# Provide an imported target if not already available
if(NOT TARGET yyjson::yyjson)
add_library(yyjson::yyjson ALIAS yyjson)
endif()
# Mark yyjson as found for find_package compatibility
set(yyjson_FOUND TRUE)
+43
View File
@@ -0,0 +1,43 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# dusk_embed_js(TARGET JS_FILE [NAME identifier])
#
# Converts a JS file into a C string header in DUSK_GENERATED_HEADERS_DIR.
# The generated header defines:
# static const char <NAME>[] = "...";
# static const size_t <NAME>_SIZE = sizeof(<NAME>) - 1;
#
# NAME defaults to the uppercase stem + "_JS" (e.g. scene.js -> SCENE_JS).
function(dusk_embed_js TARGET JS_FILE)
cmake_parse_arguments(ARG "" "NAME" "" ${ARGN})
get_filename_component(JS_ABS "${JS_FILE}" ABSOLUTE)
get_filename_component(JS_STEM "${JS_FILE}" NAME_WE)
set(OUTPUT_HEADER "${DUSK_GENERATED_HEADERS_DIR}/${JS_STEM}_js.h")
set(NAME_ARG "")
if(ARG_NAME)
set(NAME_ARG "--name" "${ARG_NAME}")
endif()
add_custom_command(
OUTPUT "${OUTPUT_HEADER}"
COMMAND ${Python3_EXECUTABLE} -m tools.js2c
--input "${JS_ABS}"
--output "${OUTPUT_HEADER}"
${NAME_ARG}
WORKING_DIRECTORY "${DUSK_ROOT_DIR}"
DEPENDS "${JS_ABS}"
COMMENT "js2c: ${JS_STEM}.js -> ${JS_STEM}_js.h"
VERBATIM
)
file(RELATIVE_PATH JS_REL "${DUSK_ROOT_DIR}" "${JS_ABS}")
string(MAKE_C_IDENTIFIER "dusk_js2c_${JS_REL}" JS_TARGET)
add_custom_target(${JS_TARGET} DEPENDS "${OUTPUT_HEADER}")
add_dependencies(${TARGET} ${JS_TARGET})
endfunction()
+46 -34
View File
@@ -1,61 +1,73 @@
# Target definitions
# Build type: DOL (SD/USB via libfat) or ISO (DVD disc via libogc DVD driver)
set(DUSK_DOLPHIN_BUILD_TYPE "DOL" CACHE STRING "Dolphin asset source: DOL (SD/USB) or ISO (DVD disc)")
set_property(CACHE DUSK_DOLPHIN_BUILD_TYPE PROPERTY STRINGS "DOL" "ISO")
# Numeric tokens so #if DUSK_DOLPHIN_BUILD_TYPE == DOL works in C.
# DUSK_DOLPHIN_BUILD_TYPE is passed without quotes so it expands to the identifier.
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_DOLPHIN
DUSK_INPUT_GAMEPAD
DUSK_DISPLAY_WIDTH=640
DUSK_DISPLAY_HEIGHT=480
DUSK_THREAD_PTHREAD
DOL=1
ISO=2
DUSK_DOLPHIN_BUILD_TYPE=${DUSK_DOLPHIN_BUILD_TYPE}
)
# Custom compiler flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -fno-exceptions")
# Need PkgConfig
find_package(PkgConfig REQUIRED)
pkg_check_modules(zip IMPORTED_TARGET libzip)
# Disable all warnings
target_compile_options(${DUSK_LIBRARY_TARGET_NAME} PRIVATE -w)
# Custom flags for cglm
set(CGLM_SHARED OFF CACHE BOOL "Build cglm shared" FORCE)
set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE)
# cglm: fetched at source level via Findcglm.cmake (FetchContent, headers only)
find_package(cglm REQUIRED)
# Compile lua
include(FetchContent)
FetchContent_Declare(
liblua
URL https://www.lua.org/ftp/lua-5.5.0.tar.gz
)
FetchContent_MakeAvailable(liblua)
set(LUA_SRC_DIR "${liblua_SOURCE_DIR}/src")
set(LUA_C_FILES
lapi.c lauxlib.c lbaselib.c lcode.c lcorolib.c lctype.c ldblib.c ldebug.c
ldo.c ldump.c lfunc.c lgc.c linit.c liolib.c llex.c lmathlib.c lmem.c
loadlib.c lobject.c lopcodes.c loslib.c lparser.c lstate.c lstring.c
lstrlib.c ltable.c ltablib.c ltm.c lundump.c lutf8lib.c lvm.c lzio.c
)
list(TRANSFORM LUA_C_FILES PREPEND "${LUA_SRC_DIR}/")
add_library(liblua STATIC ${LUA_C_FILES})
target_include_directories(liblua PUBLIC "${LUA_SRC_DIR}")
target_compile_definitions(liblua PRIVATE LUA_USE_C89)
add_library(lua::lua ALIAS liblua)
set(Lua_FOUND TRUE CACHE BOOL "Lua found" FORCE)
# Pre-create ZLIB::ZLIB so any downstream cmake module that resolves it
# (FindZLIB, pkg-config IMPORTED_TARGET Requires processing) gets plain -lz
# rather than an unresolvable IMPORTED target that the PPC linker rejects.
if(NOT TARGET ZLIB::ZLIB)
add_library(ZLIB::ZLIB INTERFACE IMPORTED GLOBAL)
set_target_properties(ZLIB::ZLIB PROPERTIES INTERFACE_LINK_LIBRARIES "z")
endif()
# Link libraries
# Mark libzip as found so src/dusk/CMakeLists.txt skips Findlibzip.cmake.
# Findlibzip.cmake calls find_package(ZLIB) which can recreate a broken
# ZLIB::ZLIB IMPORTED target, bypassing the shim above.
set(libzip_FOUND TRUE CACHE BOOL "libzip found (devkitpro portlibs)" FORCE)
# Locate zip.h in the devkitpro sysroot (respects CMAKE_FIND_ROOT_PATH).
find_path(_dusk_zip_inc NAMES zip.h)
if(_dusk_zip_inc)
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE "${_dusk_zip_inc}")
endif()
# Link libraries.
# zip/z/lzma use target_link_options (raw flags) to bypass cmake target
# resolution — pkg-config-generated targets for these carry ZLIB::ZLIB in
# INTERFACE_LINK_LIBRARIES which breaks the PPC link step.
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
cglm
liblua
m
fat
PkgConfig::zip
zip
bz2
zstd
z
lzma
)
# Postbuild
if(DUSK_DOLPHIN_BUILD_TYPE STREQUAL "ISO")
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC DUSK_DOLPHIN_BUILD_ISO)
else()
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE fat)
endif()
# Postbuild: ELF -> DOL
set(DUSK_BINARY_TARGET_NAME_DOL "${DUSK_BUILD_DIR}/Dusk.dol")
add_custom_command(TARGET ${DUSK_BINARY_TARGET_NAME} POST_BUILD
COMMAND elf2dol
"$<TARGET_FILE:${DUSK_BINARY_TARGET_NAME}>"
"${DUSK_BINARY_TARGET_NAME_DOL}"
COMMENT "Generating ${DUSK_BINARY_TARGET_NAME_DOL} from ${DUSK_BINARY_TARGET_NAME}"
)
)
+20 -1
View File
@@ -2,4 +2,23 @@ include(cmake/targets/dolphin.cmake)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_GAMECUBE
)
)
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
# bba
)
# ISO post-build: produce NTSC-J, NTSC-U and PAL disc images
if(DUSK_DOLPHIN_BUILD_TYPE STREQUAL "ISO")
add_custom_command(TARGET ${DUSK_BINARY_TARGET_NAME} POST_BUILD
COMMAND ${Python3_EXECUTABLE}
"${CMAKE_SOURCE_DIR}/tools/makedolphiniso.py"
"GCN"
"${DUSK_BINARY_TARGET_NAME_DOL}"
"${DUSK_ASSETS_ZIP}"
"${DUSK_GAME_NAME}"
"${DUSK_BUILD_DIR}"
COMMENT "Building GameCube ISO images (NTSC-J, NTSC-U, PAL)"
)
endif()
+1
View File
@@ -34,6 +34,7 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_OPENGL
DUSK_OPENGL_ES
DUSK_LINUX
DUSK_KNULLI
DUSK_DISPLAY_SIZE_DYNAMIC
DUSK_DISPLAY_WIDTH_DEFAULT=640
DUSK_DISPLAY_HEIGHT_DEFAULT=480
+8 -1
View File
@@ -1,6 +1,7 @@
# Find link platform-specific libraries
find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
# find_package(CURL REQUIRED)
# Setup endianess at compile time to optimize.
include(TestBigEndian)
@@ -16,18 +17,22 @@ else()
endif()
# Link required libraries.
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
SDL2
pthread
OpenGL::GL
GL
m
# CURL::libcurl
)
set(DUSK_BACKTRACE ON CACHE BOOL "Enable backtrace support for assert failures.")
# Define platform-specific macros.
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_CONSOLE_POSIX
# DUSK_OPENGL_LEGACY
DUSK_LINUX
DUSK_DISPLAY_SIZE_DYNAMIC
@@ -38,4 +43,6 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_INPUT_POINTER
DUSK_INPUT_GAMEPAD
DUSK_TIME_DYNAMIC
DUSK_NETWORK_IPV6
DUSK_THREAD_PTHREAD
)
+19 -3
View File
@@ -1,6 +1,16 @@
set(CMAKE_AR "$ENV{PSPDEV}/bin/psp-ar" CACHE FILEPATH "" FORCE)
set(CMAKE_RANLIB "$ENV{PSPDEV}/bin/psp-ranlib" CACHE FILEPATH "" FORCE)
set(CMAKE_C_COMPILER_AR "$ENV{PSPDEV}/bin/psp-ar" CACHE FILEPATH "" FORCE)
set(CMAKE_C_COMPILER_RANLIB "$ENV{PSPDEV}/bin/psp-ranlib" CACHE FILEPATH "" FORCE)
set(CMAKE_C_ARCHIVE_CREATE "$ENV{PSPDEV}/bin/psp-ar qc <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_C_ARCHIVE_APPEND "$ENV{PSPDEV}/bin/psp-ar q <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_C_ARCHIVE_FINISH "$ENV{PSPDEV}/bin/psp-ranlib <TARGET>")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF CACHE BOOL "" FORCE)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_C OFF CACHE BOOL "" FORCE)
find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
target_link_libraries(${DUSK_BINARY_TARGET_NAME} PUBLIC
${SDL2_LIBRARIES}
SDL2
pthread
@@ -24,13 +34,18 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
pspvfpu
pspvram
psphprm
pspnet
pspnet_inet
pspnet_apctl
psphttp
pspssl
)
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
target_include_directories(${DUSK_BINARY_TARGET_NAME} PRIVATE
${SDL2_INCLUDE_DIRS}
)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_PSP
@@ -39,6 +54,7 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_OPENGL_LEGACY
DUSK_DISPLAY_WIDTH=480
DUSK_DISPLAY_HEIGHT=272
DUSK_THREAD_PTHREAD
)
# Postbuild, create .pbp file for PSP.
+88
View File
@@ -0,0 +1,88 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
if(NOT DEFINED ENV{VITASDK})
message(FATAL_ERROR "VITASDK environment variable is not set.")
endif()
include("$ENV{VITASDK}/share/vita.cmake" REQUIRED)
set(VITA_APP_NAME "Dusk")
set(VITA_TITLEID "DUSK00001")
set(VITA_VERSION "01.00")
find_package(SDL2 REQUIRED)
# Custom flags for cglm
set(CGLM_SHARED OFF CACHE BOOL "Build cglm shared" FORCE)
set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE)
find_package(cglm REQUIRED)
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
${SDL2_LIBRARIES}
cglm
SDL2
SDL2main
zip
bz2
z
zstd
crypto
lzma
m
pthread
stdc++
vitaGL
mathneon
vitashark
kubridge_stub
SceAppMgr_stub
SceAudio_stub
SceCtrl_stub
SceCommonDialog_stub
SceDisplay_stub
SceKernelDmacMgr_stub
SceGxm_stub
SceShaccCg_stub
SceSysmodule_stub
ScePower_stub
SceTouch_stub
SceVshBridge_stub
SceIofilemgr_stub
SceShaccCgExt
libtaihen_stub.a
# SceKernel_stub
SceAppUtil_stub
SceHid_stub
SceRtc_stub
)
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
${SDL2_INCLUDE_DIRS}
)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_VITA
DUSK_INPUT_GAMEPAD
DUSK_PLATFORM_ENDIAN_LITTLE
DUSK_OPENGL_LEGACY
DUSK_DISPLAY_WIDTH=960
DUSK_DISPLAY_HEIGHT=544
)
# Post-build: create SELF from the ELF binary (UNSAFE = homebrew, no signing)
vita_create_self(${DUSK_BINARY_TARGET_NAME}.self ${DUSK_BINARY_TARGET_NAME} UNSAFE)
# Post-build: package SELF + assets into a .vpk installable on the Vita
vita_create_vpk(${DUSK_BINARY_TARGET_NAME}.vpk ${VITA_TITLEID} ${DUSK_BINARY_TARGET_NAME}.self
VERSION ${VITA_VERSION}
NAME ${VITA_APP_NAME}
FILE ${DUSK_ASSETS_ZIP} dusk.dsk
)
+23 -1
View File
@@ -2,4 +2,26 @@ include(cmake/targets/dolphin.cmake)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_WII
)
)
# Generate Homebrew Channel meta.xml from project identity variables
string(TIMESTAMP DUSK_BUILD_DATE "%Y%m%d000000" UTC)
configure_file(
"${CMAKE_SOURCE_DIR}/docker/dolphin/meta.xml.in"
"${DUSK_BUILD_DIR}/meta.xml"
@ONLY
)
# ISO post-build: produce NTSC-J, NTSC-U and PAL disc images
if(DUSK_DOLPHIN_BUILD_TYPE STREQUAL "ISO")
add_custom_command(TARGET ${DUSK_BINARY_TARGET_NAME} POST_BUILD
COMMAND ${Python3_EXECUTABLE}
"${CMAKE_SOURCE_DIR}/tools/makedolphiniso.py"
"WII"
"${DUSK_BINARY_TARGET_NAME_DOL}"
"${DUSK_ASSETS_ZIP}"
"${DUSK_GAME_NAME}"
"${DUSK_BUILD_DIR}"
COMMENT "Building Wii ISO images (NTSC-J, NTSC-U, PAL)"
)
endif()
+4 -3
View File
@@ -1,6 +1,7 @@
FROM devkitpro/devkitppc
FROM ghcr.io/extremscorner/libogc2
WORKDIR /workdir
RUN apt update && \
apt install -y python3 python3-pip python3-polib python3-pil python3-dotenv python3-pyqt5 python3-opengl && \
dkp-pacman -S --needed --noconfirm gamecube-sdl2 ppc-liblzma ppc-libzip
dkp-pacman -Syu --noconfirm && \
apt install -y python3 python3-pip python3-polib python3-pil python3-dotenv python3-pyqt5 python3-opengl xorriso && \
dkp-pacman -S --needed --noconfirm gamecube-sdl2 ppc-liblzma ppc-libzip libogc2 gamecube-tools ppc-libmad ppc-zlib-ng ppc-liblzma ppc-bzip2 ppc-zstd
VOLUME ["/workdir"]
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<app version="1">
<name>Dusk</name>
<version>1.00</version>
<release_date></release_date>
<coder>YouWish</coder>
<short_description>Dusk game</short_description>
<long_description>No description yet.</long_description>
<ahb_access/>
</app>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<app version="1">
<name>@DUSK_GAME_NAME@</name>
<version>@PROJECT_VERSION@</version>
<release_date>@DUSK_BUILD_DATE@</release_date>
<coder>@DUSK_GAME_AUTHOR@</coder>
<short_description>@DUSK_GAME_SHORT_DESCRIPTION@</short_description>
<long_description>@DUSK_GAME_LONG_DESCRIPTION@</long_description>
<ahb_access/>
</app>
+1 -1
View File
@@ -14,8 +14,8 @@ RUN apt-get install -y \
python3-dotenv \
python3-pyqt5 \
python3-opengl \
liblua5.3-dev \
xz-utils \
liblzma-dev \
libbz2-dev \
zlib1g-dev \
libzip-dev \
+13
View File
@@ -0,0 +1,13 @@
FROM vitasdk/vitasdk:latest
WORKDIR /workdir
# Install vitaGL and its dependencies (vitashark, SceShaccCg) via vdpm
RUN which vdpm
# Install Python (needed for Dusk code generation tools)
RUN apk add --no-cache \
python3 \
py3-pip \
py3-dotenv
VOLUME ["/workdir"]
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
docker run --rm -v $(pwd):/workdir dusk-dolphin /bin/bash -c "./scripts/build-gamecube.sh"
docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-gamecube.sh"
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-gamecube-iso.sh"
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
if [ -z "$DEVKITPRO" ]; then
echo "DEVKITPRO environment variable is not set. Please set it to the path of your DEVKITPRO installation."
exit 1
fi
mkdir -p build-gamecube-iso
cmake -S. -Bbuild-gamecube-iso \
-DDUSK_TARGET_SYSTEM=gamecube \
-DDUSK_DOLPHIN_BUILD_TYPE=ISO \
-DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/GameCube.cmake"
cd build-gamecube-iso
make -j$(nproc) VERBOSE=1
+5 -1
View File
@@ -5,6 +5,10 @@ if [ -z "$DEVKITPRO" ]; then
fi
mkdir -p build-gamecube
cmake -S. -Bbuild-gamecube -DDUSK_TARGET_SYSTEM=gamecube -DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/GameCube.cmake"
cmake -S. -Bbuild-gamecube \
-DDUSK_TARGET_SYSTEM=gamecube \
-DDUSK_DOLPHIN_BUILD_TYPE=DOL \
-DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/GameCube.cmake" \
-DDKP_OGC_PLATFORM_LIBRARY=libogc2
cd build-gamecube
make -j$(nproc) VERBOSE=1
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
docker build -t dusk-knulli -f docker/knulli/Dockerfile .
docker run --rm -v $(pwd):/workdir dusk-knulli /bin/bash -c "./scripts/build-knulli.sh"
docker run --rm -v "$(pwd):/workdir" dusk-knulli /bin/bash -c "./scripts/build-knulli.sh"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
docker build -t dusk-linux -f docker/linux/Dockerfile .
docker run --rm -v $(pwd):/workdir dusk-linux /bin/bash -c "./scripts/build-linux.sh"
docker run --rm -v "$(pwd):/workdir" dusk-linux /bin/bash -c "./scripts/build-linux.sh"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
docker build -t dusk-psp -f docker/psp/Dockerfile .
docker run --rm -v $(pwd):/workdir dusk-psp /bin/bash -c "./scripts/build-psp.sh"
docker run --rm -v "$(pwd):/workdir" dusk-psp /bin/bash -c "./scripts/build-psp.sh"
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
docker build -t dusk-vita -f docker/vita/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-vita /bin/bash -c "./scripts/build-vita.sh"
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
if [ -z "$VITASDK" ]; then
echo "VITASDK environment variable is not set. Please set it to the path of your VitaSDK installation."
exit 1
fi
mkdir -p build-vita
cd build-vita
cmake \
-DCMAKE_TOOLCHAIN_FILE=$VITASDK/share/vita.toolchain.cmake \
-DDUSK_TARGET_SYSTEM=vita \
..
make -j$(nproc)
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/bash
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
docker run --rm -v $(pwd):/workdir dusk-dolphin /bin/bash -c "./scripts/build-wii.sh"
docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-wii.sh"
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
docker build -t dusk-dolphin -f docker/dolphin/Dockerfile .
docker run --rm -v "$(pwd):/workdir" dusk-dolphin /bin/bash -c "./scripts/build-wii-iso.sh"
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
if [ -z "$DEVKITPRO" ]; then
echo "DEVKITPRO environment variable is not set. Please set it to the path of your DEVKITPRO installation."
exit 1
fi
mkdir -p build-wii-iso
cmake -S. -Bbuild-wii-iso \
-DDUSK_TARGET_SYSTEM=wii \
-DDUSK_DOLPHIN_BUILD_TYPE=ISO \
-DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/Wii.cmake"
cd build-wii-iso
make -j$(nproc) VERBOSE=1
+6 -2
View File
@@ -5,6 +5,10 @@ if [ -z "$DEVKITPRO" ]; then
fi
mkdir -p build-wii
cmake -S. -Bbuild-wii -DDUSK_TARGET_SYSTEM=wii -DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/Wii.cmake"
cmake -S. -Bbuild-wii \
-DDUSK_TARGET_SYSTEM=wii \
-DCMAKE_TOOLCHAIN_FILE="$DEVKITPRO/cmake/Wii.cmake" \
-DDUSK_DOLPHIN_BUILD_TYPE=DOL
cd build-wii
make -j$(nproc) VERBOSE=1
make -j$(nproc) VERBOSE=1
mv Dusk.dol boot.dol
+5 -1
View File
@@ -1,3 +1,7 @@
#!/bin/bash
docker build -t dusk-linux -f docker/linux/Dockerfile .
docker run --rm -v $(pwd):/workdir dusk-linux /bin/bash -c "./scripts/test-linux.sh"
docker run \
--rm \
-v "${GITHUB_WORKSPACE}:/workdir" \
dusk-linux \
/bin/bash -c "./scripts/test-linux.sh"
+2
View File
@@ -1,4 +1,6 @@
#!/bin/bash
set -e
rm -rf build-tests
cmake -S . -B build-tests -DDUSK_BUILD_TESTS=ON -DDUSK_TARGET_SYSTEM=linux
cmake --build build-tests -- -j$(nproc)
ctest --output-on-failure --test-dir build-tests
+6 -1
View File
@@ -15,7 +15,12 @@ elseif(DUSK_TARGET_SYSTEM STREQUAL "psp")
add_subdirectory(dusksdl2)
add_subdirectory(duskgl)
elseif(DUSK_TARGET_SYSTEM STREQUAL "gamecube" OR DUSK_TARGET_SYSTEM STREQUAL "wii")
elseif(DUSK_TARGET_SYSTEM STREQUAL "vita")
add_subdirectory(duskvita)
add_subdirectory(dusksdl2)
add_subdirectory(duskgl)
elseif(DUSK_TARGET_SYSTEM STREQUAL "wii" OR DUSK_TARGET_SYSTEM STREQUAL "gamecube")
add_subdirectory(duskdolphin)
endif()
+48 -26
View File
@@ -14,18 +14,38 @@ if(NOT libzip_FOUND)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC zip)
endif()
if(NOT Lua_FOUND)
find_package(Lua REQUIRED)
if(Lua_FOUND AND NOT TARGET Lua::Lua)
add_library(Lua::Lua INTERFACE IMPORTED)
set_target_properties(
Lua::Lua
PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}"
)
if(NOT stb_image_FOUND)
find_package(stb REQUIRED)
if(STB_IMAGE_FOUND)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC stb_image)
else()
message(FATAL_ERROR "stb_image not found. Please ensure stb is correctly fetched.")
endif()
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC Lua::Lua)
endif()
if(NOT yyjson_FOUND)
find_package(yyjson REQUIRED)
if(yyjson_FOUND)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC yyjson::yyjson)
else()
message(FATAL_ERROR "yyjson not found. Please ensure yyjson is correctly fetched.")
endif()
endif()
if(NOT jerryscript_FOUND)
find_package(jerryscript REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
jerryscript::core
jerryscript::ext
jerryscript::port
)
endif()
if(DUSK_BACKTRACE)
target_link_options(${DUSK_LIBRARY_TARGET_NAME} PUBLIC -rdynamic)
target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
DUSK_BACKTRACE
)
endif()
# Includes
@@ -40,28 +60,30 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
main.c
)
# Defs
dusk_env_to_h(duskdefs.env duskdefs.h)
# Subdirs
add_subdirectory(animation)
add_subdirectory(event)
add_subdirectory(assert)
add_subdirectory(asset)
add_subdirectory(log)
add_subdirectory(display)
add_subdirectory(engine)
add_subdirectory(error)
add_subdirectory(event)
add_subdirectory(input)
add_subdirectory(cutscene)
add_subdirectory(item)
add_subdirectory(story)
add_subdirectory(console)
add_subdirectory(display)
add_subdirectory(log)
add_subdirectory(engine)
add_subdirectory(entity)
add_subdirectory(error)
add_subdirectory(input)
add_subdirectory(locale)
add_subdirectory(map)
add_subdirectory(physics)
add_subdirectory(scene)
add_subdirectory(script)
add_subdirectory(story)
add_subdirectory(system)
add_subdirectory(time)
add_subdirectory(ui)
add_subdirectory(network)
add_subdirectory(overworld)
add_subdirectory(save)
add_subdirectory(util)
# if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "psp")
# add_subdirectory(thread)
# endif()
add_subdirectory(thread)
+10
View File
@@ -0,0 +1,10 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
easing.c
animation.c
)
+52
View File
@@ -0,0 +1,52 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "animation.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/math.h"
void animationInit(
animation_t *anim,
keyframe_t *keyframes,
uint16_t keyframeCount
) {
assertNotNull(anim, "Animation pointer cannot be null.");
assertNotNull(keyframes, "Keyframes pointer cannot be null.");
assertTrue(keyframeCount > 0, "Keyframe count must be more than 0.");
anim->keyframes = keyframes;
anim->keyframeCount = keyframeCount;
}
float_t animationGetValue(animation_t *anim, const float_t time) {
assertNotNull(anim, "Animation pointer cannot be null.");
assertNotNull(anim->keyframes, "Keyframes pointer cannot be null.");
assertTrue(anim->keyframeCount > 0, "Keyframe count invalid.");
assertTrue(time >= 0, "Time must be non-negative.");
keyframe_t *start;
keyframe_t *end;
keyframe_t *last = anim->keyframes + anim->keyframeCount - 1;
keyframe_t *current = anim->keyframes;
start = current;
do {
if(current->time > time) {
end = current;
break;
}
start = current;
current++;
if(current > last) {
end = start;
break;
}
} while(true);
float_t t = (time - start->time) / (end->time - start->time);
return mathLerp(start->value, end->value, easingApply(start->easing, t));
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "keyframe.h"
typedef struct {
keyframe_t *keyframes;
uint16_t keyframeCount;
} animation_t;
/**
* Initializes an animation.
*
* @param anim The animation to initialize.
* @param keyframes The keyframes to use for the animation.
* @param keyframeCount The number of keyframes in the animation.
*/
void animationInit(
animation_t *anim,
keyframe_t *keyframes,
uint16_t keyframeCount
);
/**
* Gets the value of the animation at a given time.
*
* @param anim The animation to get the value from.
* @param time The time at which to get the value, in seconds.
* @return The value of the animation at the given time.
*/
float_t animationGetValue(animation_t *anim, const float_t time);
+111
View File
@@ -0,0 +1,111 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "easing.h"
#include "assert/assert.h"
#include "util/math.h"
const easingfn_t EASING_FUNCTIONS[EASING_COUNT] = {
easingLinear,
easingInSine,
easingOutSine,
easingInOutSine,
easingInQuad,
easingOutQuad,
easingInOutQuad,
easingInCubic,
easingOutCubic,
easingInOutCubic,
easingInQuart,
easingOutQuart,
easingInOutQuart,
easingInBack,
easingOutBack,
easingInOutBack,
};
float_t easingApply(const easingtype_t type, const float_t t) {
assertTrue(type < EASING_COUNT, "Invalid easing type");
return EASING_FUNCTIONS[type](t);
}
float_t easingLinear(const float_t t) {
return t;
}
float_t easingInSine(const float_t t) {
return 1.0f - cosf(t * MATH_PI * 0.5f);
}
float_t easingOutSine(const float_t t) {
return sinf(t * MATH_PI * 0.5f);
}
float_t easingInOutSine(const float_t t) {
return -(cosf(MATH_PI * t) - 1.0f) * 0.5f;
}
float_t easingInQuad(const float_t t) {
return t * t;
}
float_t easingOutQuad(const float_t t) {
float_t u = 1.0f - t;
return 1.0f - u * u;
}
float_t easingInOutQuad(const float_t t) {
if(t < 0.5f) return 2.0f * t * t;
float_t u = -2.0f * t + 2.0f;
return 1.0f - u * u * 0.5f;
}
float_t easingInCubic(const float_t t) {
return t * t * t;
}
float_t easingOutCubic(const float_t t) {
float_t u = 1.0f - t;
return 1.0f - u * u * u;
}
float_t easingInOutCubic(const float_t t) {
if(t < 0.5f) return 4.0f * t * t * t;
float_t u = -2.0f * t + 2.0f;
return 1.0f - u * u * u * 0.5f;
}
float_t easingInQuart(const float_t t) {
return t * t * t * t;
}
float_t easingOutQuart(const float_t t) {
float_t u = 1.0f - t;
return 1.0f - u * u * u * u;
}
float_t easingInOutQuart(const float_t t) {
if(t < 0.5f) return 8.0f * t * t * t * t;
float_t u = -2.0f * t + 2.0f;
return 1.0f - u * u * u * u * 0.5f;
}
float_t easingInBack(const float_t t) {
return EASING_C3 * t * t * t - EASING_C1 * t * t;
}
float_t easingOutBack(const float_t t) {
float_t u = t - 1.0f;
return 1.0f + EASING_C3 * u * u * u + EASING_C1 * u * u;
}
float_t easingInOutBack(const float_t t) {
if(t < 0.5f) {
float_t u = 2.0f * t;
return u * u * ((EASING_C2 + 1.0f) * u - EASING_C2) * 0.5f;
}
float_t u = 2.0f * t - 2.0f;
return (u * u * ((EASING_C2 + 1.0f) * u + EASING_C2) + 2.0f) * 0.5f;
}
+63
View File
@@ -0,0 +1,63 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "dusk.h"
#define EASING_PI 3.14159265358979323846f
#define EASING_C1 1.70158f
#define EASING_C2 (EASING_C1 * 1.525f)
#define EASING_C3 (EASING_C1 + 1.0f)
typedef enum {
EASING_LINEAR,
EASING_IN_SINE,
EASING_OUT_SINE,
EASING_IN_OUT_SINE,
EASING_IN_QUAD,
EASING_OUT_QUAD,
EASING_IN_OUT_QUAD,
EASING_IN_CUBIC,
EASING_OUT_CUBIC,
EASING_IN_OUT_CUBIC,
EASING_IN_QUART,
EASING_OUT_QUART,
EASING_IN_OUT_QUART,
EASING_IN_BACK,
EASING_OUT_BACK,
EASING_IN_OUT_BACK,
EASING_COUNT
} easingtype_t;
typedef float_t (*easingfn_t)(const float_t t);
extern const easingfn_t EASING_FUNCTIONS[EASING_COUNT];
/**
* Applies the specified easing function to t.
*
* @param type The easing type to apply.
* @param t The input time, in the range [0, 1].
* @return The eased value, in the range [0, 1].
*/
float_t easingApply(const easingtype_t type, const float_t t);
float_t easingLinear(const float_t t);
float_t easingInSine(const float_t t);
float_t easingOutSine(const float_t t);
float_t easingInOutSine(const float_t t);
float_t easingInQuad(const float_t t);
float_t easingOutQuad(const float_t t);
float_t easingInOutQuad(const float_t t);
float_t easingInCubic(const float_t t);
float_t easingOutCubic(const float_t t);
float_t easingInOutCubic(const float_t t);
float_t easingInQuart(const float_t t);
float_t easingOutQuart(const float_t t);
float_t easingInOutQuart(const float_t t);
float_t easingInBack(const float_t t);
float_t easingOutBack(const float_t t);
float_t easingInOutBack(const float_t t);
+13
View File
@@ -0,0 +1,13 @@
// Copyright (c) 2026 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "easing.h"
typedef struct {
float_t time;
float_t value;
easingtype_t easing;
} keyframe_t;
+61 -3
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023 Dominic Masters
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
@@ -8,8 +8,19 @@
#include "assert.h"
#include "log/log.h"
#include "util/string.h"
#include "util/memory.h"
#ifdef DUSK_THREAD_PTHREAD
pthread_t ASSERT_MAIN_THREAD_ID = 0;
#endif
#ifndef DUSK_ASSERTIONS_FAKED
void assertInit(void) {
#ifdef DUSK_THREAD_PTHREAD
ASSERT_MAIN_THREAD_ID = pthread_self();
#endif
}
#ifdef DUSK_TEST_ASSERT
void assertTrueImpl(
const char *file,
@@ -25,6 +36,25 @@
);
}
#else
#ifdef DUSK_BACKTRACE
#include <execinfo.h>
#include <stdlib.h>
static void assertLogBacktrace(void) {
void *frames[64];
int count = backtrace(frames, 64);
char **symbols = backtrace_symbols(frames, count);
memoryTrack(symbols);
logError("Stack trace:\n");
if(symbols) {
for(int i = 0; i < count; i++) {
logError(" %s\n", symbols[i]);
}
memoryFree(symbols);
}
}
#endif
void assertTrueImpl(
const char *file,
const int32_t line,
@@ -33,11 +63,14 @@
) {
if(x != true) {
logError(
"Assertion Failed in %s:%i\n\n%s\n",
"Assertion Failed in %s:%i\n\n%s\n\n",
file,
line,
message
);
#ifdef DUSK_BACKTRACE
assertLogBacktrace();
#endif
abort();
}
}
@@ -109,4 +142,29 @@
) {
assertTrueImpl(file, line, stringCompare(a, b) == 0, message);
}
#endif
void assertIsMainThreadImpl(
const char *file,
const int32_t line,
const char *message
) {
#ifdef DUSK_THREAD_PTHREAD
assertTrueImpl(
file, line, pthread_self() == ASSERT_MAIN_THREAD_ID, message
);
#endif
}
void assertNotMainThreadImpl(
const char *file,
const int32_t line,
const char *message
) {
#ifdef DUSK_THREAD_PTHREAD
assertTrueImpl(
file, line, pthread_self() != ASSERT_MAIN_THREAD_ID, message
);
#endif
}
#endif
+64 -2
View File
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023 Dominic Masters
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
@@ -16,7 +16,18 @@
#endif
#endif
#ifdef DUSK_THREAD_PTHREAD
#include "thread/thread.h"
extern pthread_t ASSERT_MAIN_THREAD_ID;
#endif
#ifndef DUSK_ASSERTIONS_FAKED
/**
* Initializes the assert system. Must be the very first call in engine
* startup.
*/
void assertInit(void);
/**
* Assert a given value to be true.
*
@@ -121,6 +132,28 @@
const char *message
);
/**
* Asserts that the current thread is the main thread.
*
* @param message Message to throw against assertion failure.
*/
void assertIsMainThreadImpl(
const char *file,
const int32_t line,
const char *message
);
/**
* Asserts that the current thread is NOT the main thread.
*
* @param message Message to throw against assertion failure.
*/
void assertNotMainThreadImpl(
const char *file,
const int32_t line,
const char *message
);
/**
* Asserts a given value to be true.
*
@@ -205,8 +238,28 @@
#define assertStringEqual(a, b, message) \
assertStringEqualImpl(__FILE__, __LINE__, a, b, message)
/**
* Asserts that the current thread is the main thread.
*
* @param message Message to throw against assertion failure.
*/
#define assertIsMainThread(message) \
assertIsMainThreadImpl(__FILE__, __LINE__, message)
/**
* Asserts that the current thread is NOT the main thread.
*
* @param message Message to throw against assertion failure.
*/
#define assertNotMainThread(message) \
assertNotMainThreadImpl(__FILE__, __LINE__, message)
#else
// If assertions are faked, we define the macros to do nothing.
#define assertInit() ((void)0)
#define assertMainThreadInit() ((void)0)
#define assertIsMainThread(message) ((void)0)
#define assertNotMainThread(message) ((void)0)
#define assertTrue(x, message) ((void)0)
#define assertFalse(x, message) ((void)0)
#define assertUnreachable(message) ((void)0)
@@ -215,5 +268,14 @@
#define assertDeprecated(message) ((void)0)
#define assertStrLenMax(str, len, message) ((void)0)
#define assertStrLenMin(str, len, message) ((void)0)
#define assertStringEqual(a, b, message) ((void)0)
#define assertIsMainThread(message) ((void)0)
#define assertNotMainThread(message) ((void)0)
#endif
#endif
// Static Assertions
#define assertStructSize(struct, size) \
_Static_assert(sizeof(struct) == size, "Size of " #struct " must be " #size)
// EOF

Some files were not shown because too many files have changed in this diff Show More